diff --git a/.ci.yaml b/.ci.yaml index 181c70b00f491..e84001fada595 100644 --- a/.ci.yaml +++ b/.ci.yaml @@ -89,29 +89,6 @@ targets: - testing/scenario_app/** - testing/skia_gold_client/** -# Task to run Linux linux_android_emulator_tests on AVDs running Android 33 -# instead of 34 for investigating https://github.com/flutter/flutter/issues/137947. - - name: Linux linux_android_emulator_tests_api_33 - bringup: true - enabled_branches: - - main - recipe: engine_v2/engine_v2 - properties: - config_name: linux_android_emulator_api_33 - dependencies: >- - [ - {"dependency": "goldctl", "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd"} - ] - timeout: 60 - runIf: - - .ci.yaml - - ci/builders/linux_android_emulator_api_33.json - - DEPS - - lib/ui/** - - shell/platform/android/** - - testing/scenario_app/** - - testing/skia_gold_client/** - - name: Linux builder_cache enabled_branches: - main @@ -300,6 +277,11 @@ targets: - ci/** - flutter_frontend_server/** + - name: Linux clangd + recipe: engine_v2/engine_v2 + properties: + config_name: linux_unopt_debug_no_rbe + - name: Linux linux_unopt recipe: engine_v2/engine_v2 timeout: 120 @@ -385,6 +367,14 @@ targets: drone_dimensions: - os=Mac-13 + - name: Linux mac_clangd + recipe: engine_v2/engine_v2 + # Avoid using a Mac orchestrator to save ~5 minutes of Mac host time. + drone_dimensions: + - os=Linux + properties: + config_name: mac_unopt_debug_no_rbe + - name: Mac mac_unopt recipe: engine_v2/engine_v2 properties: diff --git a/.clangd b/.clangd new file mode 100644 index 0000000000000..c36b4792d750c --- /dev/null +++ b/.clangd @@ -0,0 +1,10 @@ +# Used to allow clangd to run on CI platforms as part of //tools/clangd_check. +# +# Intended to have no effect elsewhere. +# +# See also: +# - https://clangd.llvm.org/config#compileflags +# - https://github.com/clangd/clangd/issues/662 +CompileFlags: + Add: -Wno-unknown-warning-option + Remove: [-m*, -f*] diff --git a/.engine-release.version b/.engine-release.version new file mode 100644 index 0000000000000..dd395bfd21045 --- /dev/null +++ b/.engine-release.version @@ -0,0 +1,8 @@ +# Used to branch and track golden-file rendering of release branches. +# +# See ./testing/skia_gold_client/README.md#release-testing for more information. + +# On the main branch, this should always read 'none'. +# On release branches, this should be the release version such as '3.21' +# (without the quotes). +none diff --git a/DEPS b/DEPS index 97852b4f7a042..1a1a19c3338fc 100644 --- a/DEPS +++ b/DEPS @@ -14,7 +14,7 @@ vars = { 'flutter_git': 'https://flutter.googlesource.com', 'skia_git': 'https://skia.googlesource.com', 'llvm_git': 'https://llvm.googlesource.com', - 'skia_revision': 'c0e0b76d6d519c4d4d1be59e4723d415dd01f24e', + 'skia_revision': 'c61843470d89de81c571d87ed2c810911edeb1a3', # WARNING: DO NOT EDIT canvaskit_cipd_instance MANUALLY # See `lib/web_ui/README.md` for how to roll CanvasKit to a new version. @@ -72,7 +72,7 @@ vars = { 'dart_browser_launcher_rev': '79562306c86e5c087359a31a60309c8d65a699d3', 'dart_clock_rev': 'daf0fadabc5b3dc8e6e71bf3fb27ef9c3b79df5c', 'dart_collection_rev': 'fc616ff8fd7b732c07b2b19e31b6601e59900ccf', - 'dart_devtools_rev': '7421a2d5d67cbcdec92f1a2ac993b8b64b28d7ca', + 'dart_devtools_rev': '8be1bc8d6862a3fcf8f43917f6e0c783cac06d51', 'dart_libprotobuf_rev': '24487dd1045c7f3d64a21f38a3f0c06cc4cf2edb', 'dart_perfetto_rev': '13ce0c9e13b0940d2476cd0cff2301708a9a2e2b', 'dart_protobuf_gn_rev': 'ca669f79945418f6229e4fef89b666b2a88cbb10', @@ -336,7 +336,7 @@ deps = { Var('flutter_git') + '/third_party/yapf' + '@' + '212c5b5ad8e172d2d914ae454c121c89cccbcb35', 'src/flutter/third_party/boringssl/src': - 'https://boringssl.googlesource.com/boringssl.git' + '@' + Var('dart_boringssl_rev'), + 'https://boringssl.googlesource.com/boringssl.git' + '@' + Var('dart_boringssl_rev'), 'src/flutter/third_party/perfetto': Var('flutter_git') + "/third_party/perfetto" + '@' + Var('dart_perfetto_rev'), @@ -358,7 +358,7 @@ deps = { Var('chromium_git') + '/external/github.com/WebAssembly/binaryen.git@9784f012848a7eb321c2037bdb363dfe0eab8bc9', 'src/third_party/dart/third_party/devtools': - {'dep_type': 'cipd', 'packages': [{'package': 'dart/third_party/flutter/devtools', 'version': 'git_revision:7421a2d5d67cbcdec92f1a2ac993b8b64b28d7ca'}]}, + {'dep_type': 'cipd', 'packages': [{'package': 'dart/third_party/flutter/devtools', 'version': 'git_revision:8be1bc8d6862a3fcf8f43917f6e0c783cac06d51'}]}, 'src/third_party/dart/third_party/pkg/args': Var('dart_git') + '/args.git@788d93541a578e49f066699e1584bc3ce591c376', @@ -397,7 +397,7 @@ deps = { Var('dart_git') + '/dart_style.git@a6ad7693555a9add6f98ad6fd94de80d35c89415', 'src/third_party/dart/third_party/pkg/dartdoc': - Var('dart_git') + '/dartdoc.git@79c1675956f74b4e3f44134879beeb230a84bcd1', + Var('dart_git') + '/dartdoc.git@bf6080c8a12bf3a2f29f517d152bbd5fa1bb0a37', 'src/third_party/dart/third_party/pkg/file': Var('dart_git') + '/external/github.com/google/file.dart@3aa06490bf34bddf04c7ea964a50c177a4ca0de7', @@ -412,7 +412,7 @@ deps = { Var('dart_git') + '/html.git@327e37a6a4dd46599737ee982f280d73a8f646f7', 'src/third_party/dart/third_party/pkg/http': - Var('dart_git') + '/http.git@7949d6f4a60bb5e9158da52910aa6bacda6b9286', + Var('dart_git') + '/http.git@280d3615a2d248d155e87766c9d9bcf586af2c3c', 'src/third_party/dart/third_party/pkg/http_multi_server': Var('dart_git') + '/http_multi_server.git@ba9d07f3596b24718ddf45c9e071d40879cca565', @@ -695,6 +695,9 @@ deps = { 'src/flutter/third_party/pkg/archive': Var('chromium_git') + '/external/github.com/brendan-duncan/archive.git' + '@' + '9de7a0544457c6aba755ccb65abb41b0dc1db70d', # 3.1.2 + 'src/flutter/third_party/pkg/coverage': + Var('flutter_git') + '/third_party/coverage.git' + '@' + 'bb0ab721ee4ceef1abfa413d8d6fd46013b583b9', # 1.7.2 + 'src/flutter/third_party/pkg/equatable': Var('flutter_git') + '/third_party/equatable.git' + '@' + '2117551ff3054f8edb1a58f63ffe1832a8d25623', # 2.0.5 @@ -707,6 +710,12 @@ deps = { 'src/flutter/third_party/pkg/googleapis': Var('flutter_git') + '/third_party/googleapis.dart.git' + '@' + '526011f56d98eab183cc6075ee1392e8303e43e2', # various + 'src/flutter/third_party/pkg/io': + Var('flutter_git') + '/third_party/io.git' + '@' + '997a6243aad20af4238147d9ec00bf638b9169af', # 1.0.5-wip + + 'src/flutter/third_party/pkg/node_preamble': + Var('flutter_git') + '/third_party/node_preamble.dart.git' + '@' + '47245865175929ec452d8058e563c267b64c3d64', # 2.0.2 + 'src/flutter/third_party/pkg/platform': Var('dart_git') + '/platform.dart' + '@' + '1ffad63428bbd1b3ecaa15926bacfb724023648c', # 3.1.0 @@ -1015,7 +1024,7 @@ deps = { 'packages': [ { 'package': 'fuchsia/sdk/core/linux-amd64', - 'version': 'uzI3wnbEGlZ_dtO0Zp5QFLl5WbvXoUiKu_gjI3PhSykC' + 'version': 'TXxMINUq7JduIRX8KEopRJ8DWvKc7M-QEoRRZAzxVp4C' } ], 'condition': 'download_fuchsia_deps and not download_fuchsia_sdk', diff --git a/build/copy_info_plist.py b/build/copy_info_plist.py index d812edacf06a1..feedab7b9a77e 100644 --- a/build/copy_info_plist.py +++ b/build/copy_info_plist.py @@ -8,8 +8,6 @@ Copies the Info.plist and adds extra fields to it like the git hash of the engine. -Precondition: $CWD/../../flutter is the path to the flutter engine repo. - usage: copy_info_plist.py --source --destination --minversion= """ @@ -20,10 +18,13 @@ import git_revision +_script_dir = os.path.abspath(os.path.join(os.path.realpath(__file__), '..')) +_src_root_dir = os.path.join(_script_dir, '..', '..') + def get_clang_version(): clang_executable = str( - os.path.join('..', '..', 'flutter', 'buildtools', 'mac-x64', 'clang', 'bin', 'clang++') + os.path.join(_src_root_dir, 'flutter', 'buildtools', 'mac-x64', 'clang', 'bin', 'clang++') ) version = subprocess.check_output([clang_executable, '--version']) return version.splitlines()[0] @@ -47,7 +48,7 @@ def main(): args = parser.parse_args() text = open(args.source).read() - engine_path = os.path.join(os.getcwd(), '..', '..', 'flutter') + engine_path = os.path.join(_src_root_dir, 'flutter') revision = git_revision.get_repository_version(engine_path) clang_version = get_clang_version() text = text.format(revision=revision, clang_version=clang_version, min_version=args.minversion) diff --git a/ci/analyze.sh b/ci/analyze.sh index 30946e7529f28..e92ad3930398a 100755 --- a/ci/analyze.sh +++ b/ci/analyze.sh @@ -35,9 +35,12 @@ FLUTTER_DIR="$SRC_DIR/flutter" # directory. If not specified, we default to the build output for # host_debug_unopt. if [[ $# -eq 0 ]] ; then -DART_BIN="$SRC_DIR/out/host_debug_unopt/dart-sdk/bin" + DART_BIN="$SRC_DIR/out/host_debug_unopt/dart-sdk/bin" + if [[ ! -d "$DART_BIN" ]]; then + DART_BIN="$SRC_DIR/out/ci/host_debug_unopt/dart-sdk/bin" + fi else -DART_BIN="$1" + DART_BIN="$1" fi DART="$DART_BIN/dart" diff --git a/ci/builders/linux_android_aot_engine.json b/ci/builders/linux_android_aot_engine.json index d242283c719e4..116e0fc4d4a75 100644 --- a/ci/builders/linux_android_aot_engine.json +++ b/ci/builders/linux_android_aot_engine.json @@ -3,14 +3,14 @@ { "archives": [ { - "name": "android_profile", + "name": "ci/android_profile", "type": "gcs", - "base_path": "out/android_profile/zip_archives/", + "base_path": "out/ci/android_profile/zip_archives/", "include_paths": [ - "out/android_profile/zip_archives/android-arm-profile/artifacts.zip", - "out/android_profile/zip_archives/android-arm-profile/linux-x64.zip", - "out/android_profile/zip_archives/android-arm-profile/symbols.zip", - "out/android_profile/zip_archives/download.flutter.io" + "out/ci/android_profile/zip_archives/android-arm-profile/artifacts.zip", + "out/ci/android_profile/zip_archives/android-arm-profile/linux-x64.zip", + "out/ci/android_profile/zip_archives/android-arm-profile/symbols.zip", + "out/ci/android_profile/zip_archives/download.flutter.io" ], "realm": "production" } @@ -23,6 +23,8 @@ "use_rbe": true }, "gn": [ + "--target-dir", + "ci/android_profile", "--runtime-mode", "profile", "--android", @@ -31,9 +33,9 @@ "--rbe", "--no-goma" ], - "name": "android_profile", + "name": "ci/android_profile", "ninja": { - "config": "android_profile", + "config": "ci/android_profile", "targets": [ "default", "clang_x64/gen_snapshot", @@ -45,14 +47,14 @@ { "archives": [ { - "name": "android_release", + "name": "ci/android_release", "type": "gcs", - "base_path": "out/android_release/zip_archives/", + "base_path": "out/ci/android_release/zip_archives/", "include_paths": [ - "out/android_release/zip_archives/android-arm-release/artifacts.zip", - "out/android_release/zip_archives/android-arm-release/linux-x64.zip", - "out/android_release/zip_archives/android-arm-release/symbols.zip", - "out/android_release/zip_archives/download.flutter.io" + "out/ci/android_release/zip_archives/android-arm-release/artifacts.zip", + "out/ci/android_release/zip_archives/android-arm-release/linux-x64.zip", + "out/ci/android_release/zip_archives/android-arm-release/symbols.zip", + "out/ci/android_release/zip_archives/download.flutter.io" ], "realm": "production" } @@ -65,6 +67,8 @@ "use_rbe": true }, "gn": [ + "--target-dir", + "ci/android_release", "--runtime-mode", "release", "--android", @@ -73,9 +77,9 @@ "--rbe", "--no-goma" ], - "name": "android_release", + "name": "ci/android_release", "ninja": { - "config": "android_release", + "config": "ci/android_release", "targets": [ "default", "clang_x64/gen_snapshot", @@ -87,15 +91,15 @@ { "archives": [ { - "name": "android_release_arm64", + "name": "ci/android_release_arm64", "type": "gcs", - "base_path": "out/android_release_arm64/zip_archives/", + "base_path": "out/ci/android_release_arm64/zip_archives/", "include_paths": [ - "out/android_release_arm64/zip_archives/android-arm64-release/artifacts.zip", - "out/android_release_arm64/zip_archives/android-arm64-release/linux-x64.zip", - "out/android_release_arm64/zip_archives/android-arm64-release/symbols.zip", - "out/android_release_arm64/zip_archives/android-arm64-release/analyze-snapshot-linux-x64.zip", - "out/android_release_arm64/zip_archives/download.flutter.io" + "out/ci/android_release_arm64/zip_archives/android-arm64-release/artifacts.zip", + "out/ci/android_release_arm64/zip_archives/android-arm64-release/linux-x64.zip", + "out/ci/android_release_arm64/zip_archives/android-arm64-release/symbols.zip", + "out/ci/android_release_arm64/zip_archives/android-arm64-release/analyze-snapshot-linux-x64.zip", + "out/ci/android_release_arm64/zip_archives/download.flutter.io" ], "realm": "production" } @@ -108,6 +112,8 @@ "use_rbe": true }, "gn": [ + "--target-dir", + "ci/android_release_arm64", "--runtime-mode", "release", "--android", @@ -116,9 +122,9 @@ "--rbe", "--no-goma" ], - "name": "android_release_arm64", + "name": "ci/android_release_arm64", "ninja": { - "config": "android_release_arm64", + "config": "ci/android_release_arm64", "targets": [ "default", "clang_x64/gen_snapshot", @@ -132,7 +138,7 @@ "language": "bash", "script": "flutter/ci/binary_size_treemap.sh", "parameters": [ - "../../src/out/android_release_arm64/libflutter.so", + "../../src/out/ci/android_release_arm64/libflutter.so", "${FLUTTER_LOGS_DIR}" ] } @@ -141,15 +147,15 @@ { "archives": [ { - "name": "android_profile_arm64", + "name": "ci/android_profile_arm64", "type": "gcs", - "base_path": "out/android_profile_arm64/zip_archives/", + "base_path": "out/ci/android_profile_arm64/zip_archives/", "include_paths": [ - "out/android_profile_arm64/zip_archives/android-arm64-profile/artifacts.zip", - "out/android_profile_arm64/zip_archives/android-arm64-profile/linux-x64.zip", - "out/android_profile_arm64/zip_archives/android-arm64-profile/symbols.zip", - "out/android_profile_arm64/zip_archives/android-arm64-profile/analyze-snapshot-linux-x64.zip", - "out/android_profile_arm64/zip_archives/download.flutter.io" + "out/ci/android_profile_arm64/zip_archives/android-arm64-profile/artifacts.zip", + "out/ci/android_profile_arm64/zip_archives/android-arm64-profile/linux-x64.zip", + "out/ci/android_profile_arm64/zip_archives/android-arm64-profile/symbols.zip", + "out/ci/android_profile_arm64/zip_archives/android-arm64-profile/analyze-snapshot-linux-x64.zip", + "out/ci/android_profile_arm64/zip_archives/download.flutter.io" ], "realm": "production" } @@ -162,6 +168,8 @@ "use_rbe": true }, "gn": [ + "--target-dir", + "ci/android_profile_arm64", "--android", "--runtime-mode", "profile", @@ -170,9 +178,9 @@ "--rbe", "--no-goma" ], - "name": "android_profile_arm64", + "name": "ci/android_profile_arm64", "ninja": { - "config": "android_profile_arm64", + "config": "ci/android_profile_arm64", "targets": [ "clang_x64/gen_snapshot", "default", @@ -192,7 +200,7 @@ "script": "flutter/ci/firebase_testlab.py", "parameters": [ "--variant", - "android_profile_arm64" + "ci/android_profile_arm64" ], "test_if": "main" } @@ -201,15 +209,15 @@ { "archives": [ { - "name": "android_profile_x64", + "name": "ci/android_profile_x64", "type": "gcs", - "base_path": "out/android_profile_x64/zip_archives/", + "base_path": "out/ci/android_profile_x64/zip_archives/", "include_paths": [ - "out/android_profile_x64/zip_archives/android-x64-profile/artifacts.zip", - "out/android_profile_x64/zip_archives/android-x64-profile/linux-x64.zip", - "out/android_profile_x64/zip_archives/android-x64-profile/symbols.zip", - "out/android_profile_x64/zip_archives/android-x64-profile/analyze-snapshot-linux-x64.zip", - "out/android_profile_x64/zip_archives/download.flutter.io" + "out/ci/android_profile_x64/zip_archives/android-x64-profile/artifacts.zip", + "out/ci/android_profile_x64/zip_archives/android-x64-profile/linux-x64.zip", + "out/ci/android_profile_x64/zip_archives/android-x64-profile/symbols.zip", + "out/ci/android_profile_x64/zip_archives/android-x64-profile/analyze-snapshot-linux-x64.zip", + "out/ci/android_profile_x64/zip_archives/download.flutter.io" ], "realm": "production" } @@ -222,6 +230,8 @@ "use_rbe": true }, "gn": [ + "--target-dir", + "ci/android_profile_x64", "--runtime-mode", "profile", "--android", @@ -230,9 +240,9 @@ "--rbe", "--no-goma" ], - "name": "android_profile_x64", + "name": "ci/android_profile_x64", "ninja": { - "config": "android_profile_x64", + "config": "ci/android_profile_x64", "targets": [ "default", "clang_x64/gen_snapshot", @@ -244,15 +254,15 @@ { "archives": [ { - "name": "android_release_x64", + "name": "ci/android_release_x64", "type": "gcs", - "base_path": "out/android_release_x64/zip_archives/", + "base_path": "out/ci/android_release_x64/zip_archives/", "include_paths": [ - "out/android_release_x64/zip_archives/android-x64-release/artifacts.zip", - "out/android_release_x64/zip_archives/android-x64-release/linux-x64.zip", - "out/android_release_x64/zip_archives/android-x64-release/symbols.zip", - "out/android_release_x64/zip_archives/android-x64-release/analyze-snapshot-linux-x64.zip", - "out/android_release_x64/zip_archives/download.flutter.io" + "out/ci/android_release_x64/zip_archives/android-x64-release/artifacts.zip", + "out/ci/android_release_x64/zip_archives/android-x64-release/linux-x64.zip", + "out/ci/android_release_x64/zip_archives/android-x64-release/symbols.zip", + "out/ci/android_release_x64/zip_archives/android-x64-release/analyze-snapshot-linux-x64.zip", + "out/ci/android_release_x64/zip_archives/download.flutter.io" ], "realm": "production" } @@ -265,6 +275,8 @@ "use_rbe": true }, "gn": [ + "--target-dir", + "ci/android_release_x64", "--runtime-mode", "release", "--android", @@ -273,9 +285,9 @@ "--rbe", "--no-goma" ], - "name": "android_release_x64", + "name": "ci/android_release_x64", "ninja": { - "config": "android_release_x64", + "config": "ci/android_release_x64", "targets": [ "default", "clang_x64/gen_snapshot", @@ -290,7 +302,8 @@ { "name": "Verify-export-symbols-release-binaries", "parameters": [ - "src/out" + "src/out/ci", + "src/flutter/buildtools" ], "script": "flutter/testing/symbols/verify_exported.dart", "language": "dart" diff --git a/ci/builders/linux_android_debug_engine.json b/ci/builders/linux_android_debug_engine.json index 1959f62b5cc20..c35c7fc59fc9b 100644 --- a/ci/builders/linux_android_debug_engine.json +++ b/ci/builders/linux_android_debug_engine.json @@ -3,12 +3,12 @@ { "archives": [ { - "name": "android_jit_release_x86", + "name": "ci/android_jit_release_x86", "type": "gcs", - "base_path": "out/android_jit_release_x86/zip_archives/", + "base_path": "out/ci/android_jit_release_x86/zip_archives/", "include_paths": [ - "out/android_jit_release_x86/zip_archives/android-x86-jit-release/artifacts.zip", - "out/android_jit_release_x86/zip_archives/download.flutter.io" + "out/ci/android_jit_release_x86/zip_archives/android-x86-jit-release/artifacts.zip", + "out/ci/android_jit_release_x86/zip_archives/download.flutter.io" ], "realm": "production" } @@ -21,15 +21,17 @@ "use_rbe": true }, "gn": [ + "--target-dir", + "ci/android_jit_release_x86", "--android", "--android-cpu=x86", "--runtime-mode=jit_release", "--rbe", "--no-goma" ], - "name": "android_jit_release_x86", + "name": "ci/android_jit_release_x86", "ninja": { - "config": "android_jit_release_x86", + "config": "ci/android_jit_release_x86", "targets": [ "flutter", "flutter/shell/platform/android:embedding_jars", @@ -44,12 +46,12 @@ "script": "flutter/testing/run_tests.py", "parameters": [ "--variant", - "android_jit_release_x86", + "ci/android_jit_release_x86", "--type", "java", "--engine-capture-core-dump", "--android-variant", - "android_jit_release_x86" + "ci/android_jit_release_x86" ] } ] @@ -57,15 +59,15 @@ { "archives": [ { - "name": "android_debug", + "name": "ci/android_debug", "type": "gcs", - "base_path": "out/android_debug/zip_archives/", + "base_path": "out/ci/android_debug/zip_archives/", "include_paths": [ - "out/android_debug/zip_archives/android-arm/artifacts.zip", - "out/android_debug/zip_archives/android-arm/symbols.zip", - "out/android_debug/zip_archives/download.flutter.io", - "out/android_debug/zip_archives/sky_engine.zip", - "out/android_debug/zip_archives/android-javadoc.zip" + "out/ci/android_debug/zip_archives/android-arm/artifacts.zip", + "out/ci/android_debug/zip_archives/android-arm/symbols.zip", + "out/ci/android_debug/zip_archives/download.flutter.io", + "out/ci/android_debug/zip_archives/sky_engine.zip", + "out/ci/android_debug/zip_archives/android-javadoc.zip" ], "realm": "production" } @@ -78,15 +80,17 @@ "use_rbe": true }, "gn": [ + "--target-dir", + "ci/android_debug", "--android", "--android-cpu=arm", "--no-lto", "--rbe", "--no-goma" ], - "name": "android_debug", + "name": "ci/android_debug", "ninja": { - "config": "android_debug", + "config": "ci/android_debug", "targets": [ "flutter", "flutter/sky/dist:zip_old_location", @@ -102,12 +106,12 @@ "script": "flutter/testing/run_tests.py", "parameters": [ "--variant", - "android_debug", + "ci/android_debug", "--type", "java", "--engine-capture-core-dump", "--android-variant", - "android_debug" + "ci/android_debug" ] } ] @@ -115,13 +119,13 @@ { "archives": [ { - "name": "android_debug_arm64", + "name": "ci/android_debug_arm64", "type": "gcs", - "base_path": "out/android_debug_arm64/zip_archives/", + "base_path": "out/ci/android_debug_arm64/zip_archives/", "include_paths": [ - "out/android_debug_arm64/zip_archives/android-arm64/artifacts.zip", - "out/android_debug_arm64/zip_archives/android-arm64/symbols.zip", - "out/android_debug_arm64/zip_archives/download.flutter.io" + "out/ci/android_debug_arm64/zip_archives/android-arm64/artifacts.zip", + "out/ci/android_debug_arm64/zip_archives/android-arm64/symbols.zip", + "out/ci/android_debug_arm64/zip_archives/download.flutter.io" ], "realm": "production" } @@ -134,15 +138,17 @@ "use_rbe": true }, "gn": [ + "--target-dir", + "ci/android_debug_arm64", "--android", "--android-cpu=arm64", "--no-lto", "--rbe", "--no-goma" ], - "name": "android_debug_arm64", + "name": "ci/android_debug_arm64", "ninja": { - "config": "android_debug_arm64", + "config": "ci/android_debug_arm64", "targets": [ "flutter", "flutter/shell/platform/android:abi_jars" @@ -152,13 +158,13 @@ { "archives": [ { - "name": "android_debug_x86", + "name": "ci/android_debug_x86", "type": "gcs", - "base_path": "out/android_debug_x86/zip_archives/", + "base_path": "out/ci/android_debug_x86/zip_archives/", "include_paths": [ - "out/android_debug_x86/zip_archives/android-x86/artifacts.zip", - "out/android_debug_x86/zip_archives/android-x86/symbols.zip", - "out/android_debug_x86/zip_archives/download.flutter.io" + "out/ci/android_debug_x86/zip_archives/android-x86/artifacts.zip", + "out/ci/android_debug_x86/zip_archives/android-x86/symbols.zip", + "out/ci/android_debug_x86/zip_archives/download.flutter.io" ], "realm": "production" } @@ -171,15 +177,17 @@ "use_rbe": true }, "gn": [ + "--target-dir", + "ci/android_debug_x86", "--android", "--android-cpu=x86", "--no-lto", "--rbe", "--no-goma" ], - "name": "android_debug_x86", + "name": "ci/android_debug_x86", "ninja": { - "config": "android_debug_x86", + "config": "ci/android_debug_x86", "targets": [ "flutter", "flutter/shell/platform/android:abi_jars" @@ -189,13 +197,13 @@ { "archives": [ { - "name": "android_debug_x64", + "name": "ci/android_debug_x64", "type": "gcs", - "base_path": "out/android_debug_x64/zip_archives/", + "base_path": "out/ci/android_debug_x64/zip_archives/", "include_paths": [ - "out/android_debug_x64/zip_archives/android-x64/artifacts.zip", - "out/android_debug_x64/zip_archives/android-x64/symbols.zip", - "out/android_debug_x64/zip_archives/download.flutter.io" + "out/ci/android_debug_x64/zip_archives/android-x64/artifacts.zip", + "out/ci/android_debug_x64/zip_archives/android-x64/symbols.zip", + "out/ci/android_debug_x64/zip_archives/download.flutter.io" ], "realm": "production" } @@ -208,15 +216,17 @@ "use_rbe": true }, "gn": [ + "--target-dir", + "ci/android_debug_x64", "--android", "--android-cpu=x64", "--no-lto", "--rbe", "--no-goma" ], - "name": "android_debug_x64", + "name": "ci/android_debug_x64", "ninja": { - "config": "android_debug_x64", + "config": "ci/android_debug_x64", "targets": [ "flutter", "flutter/shell/platform/android:abi_jars" @@ -229,7 +239,8 @@ { "name": "Verify-export-symbols-release-binaries", "parameters": [ - "src/out" + "src/out/ci", + "src/flutter/buildtools" ], "script": "flutter/testing/symbols/verify_exported.dart", "language": "dart" diff --git a/ci/builders/linux_android_emulator.json b/ci/builders/linux_android_emulator.json index 4d5bb00957997..56e27025182a3 100644 --- a/ci/builders/linux_android_emulator.json +++ b/ci/builders/linux_android_emulator.json @@ -17,7 +17,7 @@ "--rbe", "--no-goma", "--target-dir", - "android_emulator_debug_x64" + "ci/android_emulator_debug_x64" ], "dependencies": [ { @@ -25,9 +25,9 @@ "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd" } ], - "name": "android_emulator_debug_x64", + "name": "ci/android_emulator_debug_x64", "ninja": { - "config": "android_emulator_debug_x64", + "config": "ci/android_emulator_debug_x64", "targets": [ "flutter/impeller/toolkit/android:unittests", "flutter/shell/platform/android:flutter_shell_native_unittests", @@ -54,7 +54,7 @@ "script": "flutter/testing/run_tests.py", "parameters": [ "--android-variant", - "android_emulator_debug_x64", + "ci/android_emulator_debug_x64", "--type", "android" ] @@ -85,7 +85,7 @@ ], "script": "flutter/testing/scenario_app/bin/run_android_tests.dart", "parameters": [ - "--out-dir=../out/android_emulator_debug_x64", + "--out-dir=../out/ci/android_emulator_debug_x64", "--enable-impeller", "--impeller-backend=vulkan" ] @@ -109,7 +109,7 @@ "--rbe", "--no-goma", "--target-dir", - "android_emulator_debug_x86" + "ci/android_emulator_debug_x86" ], "dependencies": [ { @@ -117,9 +117,9 @@ "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd" } ], - "name": "android_emulator_debug_x86", + "name": "ci/android_emulator_debug_x86", "ninja": { - "config": "android_emulator_debug_x86", + "config": "ci/android_emulator_debug_x86", "targets": [ "flutter/impeller/toolkit/android:unittests", "flutter/shell/platform/android:flutter_shell_native_unittests", @@ -146,7 +146,7 @@ "script": "flutter/testing/run_tests.py", "parameters": [ "--android-variant", - "android_emulator_debug_x86", + "ci/android_emulator_debug_x86", "--type", "android" ] diff --git a/ci/builders/linux_android_emulator_api_33.json b/ci/builders/linux_android_emulator_api_33.json deleted file mode 100644 index 7f02655523cdd..0000000000000 --- a/ci/builders/linux_android_emulator_api_33.json +++ /dev/null @@ -1,88 +0,0 @@ -{ - "builds": [ - { - "drone_dimensions": [ - "device_type=none", - "os=Linux", - "kvm=1", - "cores=8" - ], - "gclient_variables": { - "use_rbe": true - }, - "gn": [ - "--android", - "--android-cpu=x64", - "--no-lto", - "--rbe", - "--no-goma", - "--target-dir", - "android_debug_api33_x64" - ], - "dependencies": [ - { - "dependency": "goldctl", - "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd" - } - ], - "name": "android_debug_api33_x64", - "ninja": { - "config": "android_debug_api33_x64", - "targets": [ - "flutter/impeller/toolkit/android:unittests", - "flutter/shell/platform/android:flutter_shell_native_unittests", - "flutter/testing/scenario_app" - ] - }, - "tests": [ - { - "language": "python3", - "name": "Android Unit Tests", - "test_dependencies": [ - { - "dependency": "android_virtual_device", - "version": "android_33_google_apis_x64.textpb" - }, - { - "dependency": "avd_cipd_version", - "version": "build_id:8759428741582061553" - } - ], - "contexts": [ - "android_virtual_device" - ], - "script": "flutter/testing/run_tests.py", - "parameters": [ - "--android-variant", - "android_debug_api33_x64", - "--type", - "android" - ] - }, - { - "language": "dart", - "name": "Scenario App Integration Tests", - "test_timeout_secs": 900, - "max_attempts": 1, - "test_dependencies": [ - { - "dependency": "android_virtual_device", - "version": "android_33_google_apis_x64.textpb" - }, - { - "dependency": "avd_cipd_version", - "version": "build_id:8759428741582061553" - } - ], - "contexts": [ - "android_virtual_device" - ], - "script": "flutter/testing/scenario_app/bin/run_android_tests.dart", - "parameters": [ - "--out-dir=../out/android_debug_api33_x64" - ] - } - ] - } - ] -} diff --git a/ci/builders/linux_android_emulator_skia.json b/ci/builders/linux_android_emulator_skia.json index a1a4d0321b133..a711c8c71ea1b 100644 --- a/ci/builders/linux_android_emulator_skia.json +++ b/ci/builders/linux_android_emulator_skia.json @@ -17,7 +17,7 @@ "--rbe", "--no-goma", "--target-dir", - "android_emulator_skia_debug_x64" + "ci/android_emulator_skia_debug_x64" ], "dependencies": [ { @@ -25,9 +25,9 @@ "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd" } ], - "name": "android_emulator_skia_debug_x64", + "name": "ci/android_emulator_skia_debug_x64", "ninja": { - "config": "android_emulator_skia_debug_x64", + "config": "ci/android_emulator_skia_debug_x64", "targets": [ "flutter/impeller/toolkit/android:unittests", "flutter/shell/platform/android:flutter_shell_native_unittests", @@ -55,7 +55,7 @@ ], "script": "flutter/testing/scenario_app/bin/run_android_tests.dart", "parameters": [ - "--out-dir=../out/android_emulator_skia_debug_x64", + "--out-dir=../out/ci/android_emulator_skia_debug_x64", "--no-enable-impeller" ] }, @@ -79,7 +79,7 @@ ], "script": "flutter/testing/scenario_app/bin/run_android_tests.dart", "parameters": [ - "--out-dir=../out/android_emulator_skia_debug_x64", + "--out-dir=../out/ci/android_emulator_skia_debug_x64", "--no-enable-impeller", "--force-surface-producer-surface-texture" ] diff --git a/ci/builders/linux_arm_host_engine.json b/ci/builders/linux_arm_host_engine.json index a8a71846bfca8..937c6f57dfd62 100644 --- a/ci/builders/linux_arm_host_engine.json +++ b/ci/builders/linux_arm_host_engine.json @@ -3,11 +3,11 @@ { "archives": [ { - "name": "linux_profile_arm64", + "name": "ci/linux_profile_arm64", "type": "gcs", - "base_path": "out/linux_profile_arm64/zip_archives/", + "base_path": "out/ci/linux_profile_arm64/zip_archives/", "include_paths": [ - "out/linux_profile_arm64/zip_archives/linux-arm64-profile/linux-arm64-flutter-gtk.zip" + "out/ci/linux_profile_arm64/zip_archives/linux-arm64-profile/linux-arm64-flutter-gtk.zip" ], "realm": "production" } @@ -21,6 +21,8 @@ "use_rbe": true }, "gn": [ + "--target-dir", + "ci/linux_profile_arm64", "--runtime-mode", "profile", "--no-lto", @@ -30,9 +32,9 @@ "--rbe", "--no-goma" ], - "name": "linux_profile_arm64", + "name": "ci/linux_profile_arm64", "ninja": { - "config": "linux_profile_arm64", + "config": "ci/linux_profile_arm64", "targets": [ "flutter/shell/platform/linux:flutter_gtk" ] @@ -41,14 +43,14 @@ { "archives": [ { - "name": "linux_debug_arm64", + "name": "ci/linux_debug_arm64", "type": "gcs", - "base_path": "out/linux_debug_arm64/zip_archives/", + "base_path": "out/ci/linux_debug_arm64/zip_archives/", "include_paths": [ - "out/linux_debug_arm64/zip_archives/linux-arm64/artifacts.zip", - "out/linux_debug_arm64/zip_archives/linux-arm64/font-subset.zip", - "out/linux_debug_arm64/zip_archives/linux-arm64-debug/linux-arm64-flutter-gtk.zip", - "out/linux_debug_arm64/zip_archives/dart-sdk-linux-arm64.zip" + "out/ci/linux_debug_arm64/zip_archives/linux-arm64/artifacts.zip", + "out/ci/linux_debug_arm64/zip_archives/linux-arm64/font-subset.zip", + "out/ci/linux_debug_arm64/zip_archives/linux-arm64-debug/linux-arm64-flutter-gtk.zip", + "out/ci/linux_debug_arm64/zip_archives/dart-sdk-linux-arm64.zip" ], "realm": "production" } @@ -62,6 +64,8 @@ "use_rbe": true }, "gn": [ + "--target-dir", + "ci/linux_debug_arm64", "--runtime-mode", "debug", "--target-os=linux", @@ -70,9 +74,9 @@ "--rbe", "--no-goma" ], - "name": "linux_debug_arm64", + "name": "ci/linux_debug_arm64", "ninja": { - "config": "linux_debug_arm64", + "config": "ci/linux_debug_arm64", "targets": [ "flutter/build/archives:artifacts", "flutter/build/archives:dart_sdk_archive", @@ -84,11 +88,11 @@ { "archives": [ { - "name": "linux_release_arm64", + "name": "ci/linux_release_arm64", "type": "gcs", - "base_path": "out/linux_release_arm64/zip_archives/", + "base_path": "out/ci/linux_release_arm64/zip_archives/", "include_paths": [ - "out/linux_release_arm64/zip_archives/linux-arm64-release/linux-arm64-flutter-gtk.zip" + "out/ci/linux_release_arm64/zip_archives/linux-arm64-release/linux-arm64-flutter-gtk.zip" ], "realm": "production" } @@ -102,6 +106,8 @@ "use_rbe": true }, "gn": [ + "--target-dir", + "ci/linux_release_arm64", "--runtime-mode", "release", "--target-os=linux", @@ -110,9 +116,9 @@ "--rbe", "--no-goma" ], - "name": "linux_release_arm64", + "name": "ci/linux_release_arm64", "ninja": { - "config": "linux_release_arm64", + "config": "ci/linux_release_arm64", "targets": [ "flutter/shell/platform/linux:flutter_gtk" ] diff --git a/ci/builders/linux_clang_tidy.json b/ci/builders/linux_clang_tidy.json index b233e117d67c2..0d81a7b8ac0c4 100644 --- a/ci/builders/linux_clang_tidy.json +++ b/ci/builders/linux_clang_tidy.json @@ -15,13 +15,13 @@ "arm64", "--no-lto", "--target-dir", - "android_debug_arm64_clang_tidy", + "ci/android_debug_arm64_clang_tidy", "--rbe", "--no-goma" ], - "name": "android_debug_arm64_clang_tidy", + "name": "ci/android_debug_arm64_clang_tidy", "ninja": { - "config": "android_debug_arm64_clang_tidy" + "config": "ci/android_debug_arm64_clang_tidy" } }, { @@ -39,13 +39,13 @@ "--prebuilt-dart-sdk", "--no-lto", "--target-dir", - "host_debug_clang_tidy", + "ci/host_debug_clang_tidy", "--rbe", "--no-goma" ], - "name": "host_debug_clang_tidy", + "name": "ci/host_debug_clang_tidy", "ninja": { - "config": "host_debug_clang_tidy" + "config": "ci/host_debug_clang_tidy" } } ], @@ -62,18 +62,17 @@ "download_android_deps": false }, "dependencies": [ - "host_debug_clang_tidy" + "ci/host_debug_clang_tidy" ], "tasks": [ { "name": "test: lint host_debug", "parameters": [ - "--verbose", "--variant", - "host_debug_clang_tidy", + "ci/host_debug_clang_tidy", "--lint-all", "--shard-id=0", - "--shard-variants=host_debug_clang_tidy,host_debug_clang_tidy,host_debug_clang_tidy" + "--shard-variants=ci/host_debug_clang_tidy,ci/host_debug_clang_tidy,ci/host_debug_clang_tidy" ], "max_attempts": 1, "script": "flutter/ci/clang_tidy.sh" @@ -92,18 +91,17 @@ "download_android_deps": false }, "dependencies": [ - "host_debug_clang_tidy" + "ci/host_debug_clang_tidy" ], "tasks": [ { "name": "test: lint host_debug", "parameters": [ - "--verbose", "--variant", - "host_debug_clang_tidy", + "ci/host_debug_clang_tidy", "--lint-all", "--shard-id=1", - "--shard-variants=host_debug_clang_tidy,host_debug_clang_tidy,host_debug_clang_tidy" + "--shard-variants=ci/host_debug_clang_tidy,ci/host_debug_clang_tidy,ci/host_debug_clang_tidy" ], "max_attempts": 1, "script": "flutter/ci/clang_tidy.sh" @@ -122,18 +120,17 @@ "download_android_deps": false }, "dependencies": [ - "host_debug_clang_tidy" + "ci/host_debug_clang_tidy" ], "tasks": [ { "name": "test: lint host_debug", "parameters": [ - "--verbose", "--variant", - "host_debug_clang_tidy", + "ci/host_debug_clang_tidy", "--lint-all", "--shard-id=2", - "--shard-variants=host_debug_clang_tidy,host_debug_clang_tidy,host_debug_clang_tidy" + "--shard-variants=ci/host_debug_clang_tidy,ci/host_debug_clang_tidy,ci/host_debug_clang_tidy" ], "max_attempts": 1, "script": "flutter/ci/clang_tidy.sh" @@ -152,18 +149,17 @@ "download_android_deps": false }, "dependencies": [ - "host_debug_clang_tidy" + "ci/host_debug_clang_tidy" ], "tasks": [ { "name": "test: lint host_debug", "parameters": [ - "--verbose", "--variant", - "host_debug_clang_tidy", + "ci/host_debug_clang_tidy", "--lint-all", "--shard-id=3", - "--shard-variants=host_debug_clang_tidy,host_debug_clang_tidy,host_debug_clang_tidy" + "--shard-variants=ci/host_debug_clang_tidy,ci/host_debug_clang_tidy,ci/host_debug_clang_tidy" ], "max_attempts": 1, "script": "flutter/ci/clang_tidy.sh" @@ -179,19 +175,18 @@ "cores=32" ], "dependencies": [ - "host_debug_clang_tidy", - "android_debug_arm64_clang_tidy" + "ci/host_debug_clang_tidy", + "ci/android_debug_arm64_clang_tidy" ], "tasks": [ { "name": "test: lint android_debug_arm64", "parameters": [ - "--verbose", "--variant", - "android_debug_arm64_clang_tidy", + "ci/android_debug_arm64_clang_tidy", "--lint-all", "--shard-id=0", - "--shard-variants=host_debug_clang_tidy,host_debug_clang_tidy,host_debug_clang_tidy,host_debug_clang_tidy" + "--shard-variants=ci/host_debug_clang_tidy,ci/host_debug_clang_tidy,ci/host_debug_clang_tidy,ci/host_debug_clang_tidy" ], "max_attempts": 1, "script": "flutter/ci/clang_tidy.sh" diff --git a/ci/builders/linux_fuchsia.json b/ci/builders/linux_fuchsia.json index ab96dfc260229..6212ee2b5ed02 100644 --- a/ci/builders/linux_fuchsia.json +++ b/ci/builders/linux_fuchsia.json @@ -10,6 +10,8 @@ "use_rbe": true }, "gn": [ + "--target-dir", + "ci/fuchsia_profile_arm64", "--fuchsia", "--fuchsia-cpu", "arm64", @@ -18,9 +20,9 @@ "--rbe", "--no-goma" ], - "name": "fuchsia_profile_arm64", + "name": "ci/fuchsia_profile_arm64", "ninja": { - "config": "fuchsia_profile_arm64", + "config": "ci/fuchsia_profile_arm64", "targets": [ "flutter/shell/platform/fuchsia:fuchsia" ] @@ -39,6 +41,8 @@ "use_rbe": true }, "gn": [ + "--target-dir", + "ci/fuchsia_profile_arm64_tester", "--fuchsia", "--fuchsia-cpu", "arm64", @@ -48,9 +52,9 @@ "--rbe", "--no-goma" ], - "name": "fuchsia_profile_arm64_tester", + "name": "ci/fuchsia_profile_arm64_tester", "ninja": { - "config": "fuchsia_profile_arm64", + "config": "ci/fuchsia_profile_arm64_tester", "targets": [ "flutter/shell/platform/fuchsia:fuchsia", "flutter/shell/platform/fuchsia/dart_runner:dart_runner_tests", @@ -64,7 +68,7 @@ "script": "flutter/tools/fuchsia/with_envs.py", "parameters": [ "testing/fuchsia/run_tests.py", - "fuchsia_profile_arm64" + "ci/fuchsia_profile_arm64_tester" ] } ] @@ -79,6 +83,8 @@ "use_rbe": true }, "gn": [ + "--target-dir", + "ci/fuchsia_release_arm64", "--fuchsia", "--fuchsia-cpu", "arm64", @@ -87,9 +93,9 @@ "--rbe", "--no-goma" ], - "name": "fuchsia_release_arm64", + "name": "ci/fuchsia_release_arm64", "ninja": { - "config": "fuchsia_release_arm64", + "config": "ci/fuchsia_release_arm64", "targets": [ "flutter/shell/platform/fuchsia:fuchsia" ] @@ -108,6 +114,8 @@ "use_rbe": true }, "gn": [ + "--target-dir", + "ci/fuchsia_release_arm64_tester", "--fuchsia", "--fuchsia-cpu", "arm64", @@ -117,9 +125,9 @@ "--rbe", "--no-goma" ], - "name": "fuchsia_release_arm64_tester", + "name": "ci/fuchsia_release_arm64_tester", "ninja": { - "config": "fuchsia_release_arm64", + "config": "ci/fuchsia_release_arm64_tester", "targets": [ "flutter/shell/platform/fuchsia:fuchsia", "flutter/shell/platform/fuchsia/dart_runner:dart_runner_tests", @@ -133,7 +141,7 @@ "script": "flutter/tools/fuchsia/with_envs.py", "parameters": [ "testing/fuchsia/run_tests.py", - "fuchsia_release_arm64" + "ci/fuchsia_release_arm64_tester" ] } ] @@ -148,6 +156,8 @@ "use_rbe": true }, "gn": [ + "--target-dir", + "ci/fuchsia_debug_arm64", "--fuchsia", "--fuchsia-cpu", "arm64", @@ -157,9 +167,9 @@ "--rbe", "--no-goma" ], - "name": "fuchsia_debug_arm64", + "name": "ci/fuchsia_debug_arm64", "ninja": { - "config": "fuchsia_debug_arm64", + "config": "ci/fuchsia_debug_arm64", "targets": [ "flutter/shell/platform/fuchsia:fuchsia" ] @@ -172,7 +182,7 @@ "script": "flutter/tools/fuchsia/upload_to_symbol_server.py", "parameters": [ "--symbol-dir", - "out/fuchsia_debug_arm64/.build-id", + "out/ci/fuchsia_debug_arm64/.build-id", "--engine-version", "${REVISION}", "--upload" @@ -193,6 +203,8 @@ "use_rbe": true }, "gn": [ + "--target-dir", + "ci/fuchsia_debug_arm64_tester", "--fuchsia", "--fuchsia-cpu", "arm64", @@ -202,9 +214,9 @@ "--rbe", "--no-goma" ], - "name": "fuchsia_debug_arm64_tester", + "name": "ci/fuchsia_debug_arm64_tester", "ninja": { - "config": "fuchsia_debug_arm64", + "config": "ci/fuchsia_debug_arm64_tester", "targets": [ "flutter/shell/platform/fuchsia:fuchsia", "flutter/shell/platform/fuchsia/dart_runner:dart_runner_tests", @@ -218,7 +230,7 @@ "script": "flutter/tools/fuchsia/with_envs.py", "parameters": [ "testing/fuchsia/run_tests.py", - "fuchsia_debug_arm64" + "ci/fuchsia_debug_arm64_tester" ] } ] @@ -233,6 +245,8 @@ "use_rbe": true }, "gn": [ + "--target-dir", + "ci/fuchsia_profile_x64", "--fuchsia", "--fuchsia-cpu", "x64", @@ -241,9 +255,9 @@ "--rbe", "--no-goma" ], - "name": "fuchsia_profile_x64", + "name": "ci/fuchsia_profile_x64", "ninja": { - "config": "fuchsia_profile_x64", + "config": "ci/fuchsia_profile_x64", "targets": [ "flutter/shell/platform/fuchsia:fuchsia" ] @@ -262,6 +276,8 @@ "use_rbe": true }, "gn": [ + "--target-dir", + "ci/fuchsia_profile_x64_tester", "--fuchsia", "--fuchsia-cpu", "x64", @@ -271,9 +287,9 @@ "--rbe", "--no-goma" ], - "name": "fuchsia_profile_x64_tester", + "name": "ci/fuchsia_profile_x64_tester", "ninja": { - "config": "fuchsia_profile_x64", + "config": "ci/fuchsia_profile_x64_tester", "targets": [ "flutter/shell/platform/fuchsia:fuchsia", "flutter/shell/platform/fuchsia/dart_runner:dart_runner_tests", @@ -287,7 +303,7 @@ "script": "flutter/tools/fuchsia/with_envs.py", "parameters": [ "testing/fuchsia/run_tests.py", - "fuchsia_profile_x64" + "ci/fuchsia_profile_x64_tester" ] } ] @@ -303,6 +319,8 @@ "use_rbe": true }, "gn": [ + "--target-dir", + "ci/fuchsia_release_x64", "--fuchsia", "--fuchsia-cpu", "x64", @@ -311,9 +329,9 @@ "--rbe", "--no-goma" ], - "name": "fuchsia_release_x64", + "name": "ci/fuchsia_release_x64", "ninja": { - "config": "fuchsia_release_x64", + "config": "ci/fuchsia_release_x64", "targets": [ "flutter/shell/platform/fuchsia:fuchsia" ] @@ -332,6 +350,8 @@ "use_rbe": true }, "gn": [ + "--target-dir", + "ci/fuchsia_release_x64_tester", "--fuchsia", "--fuchsia-cpu", "x64", @@ -341,9 +361,9 @@ "--rbe", "--no-goma" ], - "name": "fuchsia_release_x64_tester", + "name": "ci/fuchsia_release_x64_tester", "ninja": { - "config": "fuchsia_release_x64", + "config": "ci/fuchsia_release_x64_tester", "targets": [ "flutter/shell/platform/fuchsia:fuchsia", "flutter/shell/platform/fuchsia/dart_runner:dart_runner_tests", @@ -357,7 +377,7 @@ "script": "flutter/tools/fuchsia/with_envs.py", "parameters": [ "testing/fuchsia/run_tests.py", - "fuchsia_release_x64" + "ci/fuchsia_release_x64_tester" ] } ] @@ -373,6 +393,8 @@ "use_rbe": true }, "gn": [ + "--target-dir", + "ci/fuchsia_debug_x64", "--fuchsia", "--fuchsia-cpu", "x64", @@ -382,9 +404,9 @@ "--rbe", "--no-goma" ], - "name": "fuchsia_debug_x64", + "name": "ci/fuchsia_debug_x64", "ninja": { - "config": "fuchsia_debug_x64", + "config": "ci/fuchsia_debug_x64", "targets": [ "flutter/shell/platform/fuchsia:fuchsia" ] @@ -397,7 +419,7 @@ "script": "flutter/tools/fuchsia/upload_to_symbol_server.py", "parameters": [ "--symbol-dir", - "out/fuchsia_debug_x64/.build-id", + "out/ci/fuchsia_debug_x64/.build-id", "--engine-version", "${REVISION}", "--upload" @@ -418,6 +440,8 @@ "use_rbe": true }, "gn": [ + "--target-dir", + "ci/fuchsia_debug_x64_tester", "--fuchsia", "--fuchsia-cpu", "x64", @@ -427,9 +451,9 @@ "--rbe", "--no-goma" ], - "name": "fuchsia_debug_x64_tester", + "name": "ci/fuchsia_debug_x64_tester", "ninja": { - "config": "fuchsia_debug_x64", + "config": "ci/fuchsia_debug_x64_tester", "targets": [ "flutter/shell/platform/fuchsia:fuchsia", "flutter/shell/platform/fuchsia/dart_runner:dart_runner_tests", @@ -450,7 +474,8 @@ "language": "python3", "script": "flutter/tools/fuchsia/with_envs.py", "parameters": [ - "testing/fuchsia/run_tests.py" + "testing/fuchsia/run_tests.py", + "ci/fuchsia_debug_x64_tester" ] } ] @@ -480,9 +505,9 @@ "--out-dir", "${LUCI_CLEANUP}", "--symbol-dirs", - "out/fuchsia_debug_arm64/.build-id", - "out/fuchsia_profile_arm64/.build-id", - "out/fuchsia_release_arm64/.build-id" + "out/ci/fuchsia_debug_arm64/.build-id", + "out/ci/fuchsia_profile_arm64/.build-id", + "out/ci/fuchsia_release_arm64/.build-id" ], "script": "flutter/tools/fuchsia/merge_and_upload_debug_symbols.py", "language": "python3" @@ -498,9 +523,9 @@ "--out-dir", "${LUCI_CLEANUP}", "--symbol-dirs", - "out/fuchsia_debug_x64/.build-id", - "out/fuchsia_profile_x64/.build-id", - "out/fuchsia_release_x64/.build-id" + "out/ci/fuchsia_debug_x64/.build-id", + "out/ci/fuchsia_profile_x64/.build-id", + "out/ci/fuchsia_release_x64/.build-id" ], "script": "flutter/tools/fuchsia/merge_and_upload_debug_symbols.py", "language": "python3" @@ -508,7 +533,8 @@ { "name": "Verify-export-symbols-release-binaries", "parameters": [ - "src/out" + "src/out/ci", + "src/flutter/buildtools" ], "script": "flutter/testing/symbols/verify_exported.dart", "language": "dart" diff --git a/ci/builders/linux_host_desktop_engine.json b/ci/builders/linux_host_desktop_engine.json index 29393e3a6b976..773deb9b26a8d 100644 --- a/ci/builders/linux_host_desktop_engine.json +++ b/ci/builders/linux_host_desktop_engine.json @@ -3,11 +3,11 @@ { "archives": [ { - "name": "host_debug", - "base_path": "out/host_debug_desktop/zip_archives/", + "name": "ci/host_debug_desktop", + "base_path": "out/ci/host_debug_desktop/zip_archives/", "type": "gcs", "include_paths": [ - "out/host_debug_desktop/zip_archives/linux-x64-debug/linux-x64-flutter-gtk.zip" + "out/ci/host_debug_desktop/zip_archives/linux-x64-debug/linux-x64-flutter-gtk.zip" ], "realm": "production" } @@ -28,11 +28,11 @@ "--rbe", "--no-goma", "--target-dir", - "host_debug_desktop" + "ci/host_debug_desktop" ], - "name": "host_debug_desktop", + "name": "ci/host_debug_desktop", "ninja": { - "config": "host_debug_desktop", + "config": "ci/host_debug_desktop", "targets": [ "flutter/shell/platform/linux:flutter_gtk" ] @@ -41,11 +41,11 @@ { "archives": [ { - "name": "host_profile", - "base_path": "out/host_profile_desktop/zip_archives/", + "name": "ci/host_profile_desktop", + "base_path": "out/ci/host_profile_desktop/zip_archives/", "type": "gcs", "include_paths": [ - "out/host_profile_desktop/zip_archives/linux-x64-profile/linux-x64-flutter-gtk.zip" + "out/ci/host_profile_desktop/zip_archives/linux-x64-profile/linux-x64-flutter-gtk.zip" ], "realm": "production" } @@ -67,11 +67,11 @@ "--rbe", "--no-goma", "--target-dir", - "host_profile_desktop" + "ci/host_profile_desktop" ], - "name": "host_profile_desktop", + "name": "ci/host_profile_desktop", "ninja": { - "config": "host_profile_desktop", + "config": "ci/host_profile_desktop", "targets": [ "flutter/shell/platform/linux:flutter_gtk" ] @@ -80,11 +80,11 @@ { "archives": [ { - "name": "host_release", - "base_path": "out/host_release_desktop/zip_archives/", + "name": "ci/host_release_desktop", + "base_path": "out/ci/host_release_desktop/zip_archives/", "type": "gcs", "include_paths": [ - "out/host_release_desktop/zip_archives/linux-x64-release/linux-x64-flutter-gtk.zip" + "out/ci/host_release_desktop/zip_archives/linux-x64-release/linux-x64-flutter-gtk.zip" ], "realm": "production" } @@ -105,11 +105,11 @@ "--rbe", "--no-goma", "--target-dir", - "host_release_desktop" + "ci/host_release_desktop" ], - "name": "host_release_desktop", + "name": "ci/host_release_desktop", "ninja": { - "config": "host_release_desktop", + "config": "ci/host_release_desktop", "targets": [ "flutter/shell/platform/linux:flutter_gtk" ] diff --git a/ci/builders/linux_host_engine.json b/ci/builders/linux_host_engine.json index a9e14038fc590..904dbaa3b8b42 100644 --- a/ci/builders/linux_host_engine.json +++ b/ci/builders/linux_host_engine.json @@ -3,15 +3,15 @@ { "archives": [ { - "name": "host_debug", - "base_path": "out/host_debug/zip_archives/", + "name": "ci/host_debug", + "base_path": "out/ci/host_debug/zip_archives/", "type": "gcs", "include_paths": [ - "out/host_debug/zip_archives/linux-x64/artifacts.zip", - "out/host_debug/zip_archives/linux-x64/linux-x64-embedder.zip", - "out/host_debug/zip_archives/linux-x64/font-subset.zip", - "out/host_debug/zip_archives/flutter_patched_sdk.zip", - "out/host_debug/zip_archives/dart-sdk-linux-x64.zip" + "out/ci/host_debug/zip_archives/linux-x64/artifacts.zip", + "out/ci/host_debug/zip_archives/linux-x64/linux-x64-embedder.zip", + "out/ci/host_debug/zip_archives/linux-x64/font-subset.zip", + "out/ci/host_debug/zip_archives/flutter_patched_sdk.zip", + "out/ci/host_debug/zip_archives/dart-sdk-linux-x64.zip" ], "realm": "production" } @@ -25,6 +25,8 @@ "use_rbe": true }, "gn": [ + "--target-dir", + "ci/host_debug", "--runtime-mode", "debug", "--prebuilt-dart-sdk", @@ -32,9 +34,9 @@ "--rbe", "--no-goma" ], - "name": "host_debug", + "name": "ci/host_debug", "ninja": { - "config": "host_debug", + "config": "ci/host_debug", "targets": [ "flutter:unittests", "flutter/build/archives:artifacts", @@ -52,7 +54,7 @@ "script": "flutter/testing/run_tests.py", "parameters": [ "--variant", - "host_debug", + "ci/host_debug", "--type", "dart,dart-host", "--engine-capture-core-dump" @@ -63,8 +65,8 @@ { "archives": [ { - "name": "host_profile", - "base_path": "out/host_profile/zip_archives/", + "name": "ci/host_profile", + "base_path": "out/ci/host_profile/zip_archives/", "type": "gcs", "include_paths": [] } @@ -78,6 +80,8 @@ "use_rbe": true }, "gn": [ + "--target-dir", + "ci/host_profile", "--runtime-mode", "profile", "--no-lto", @@ -86,9 +90,9 @@ "--rbe", "--no-goma" ], - "name": "host_profile", + "name": "ci/host_profile", "ninja": { - "config": "host_profile", + "config": "ci/host_profile", "targets": [ "flutter/build/dart:copy_dart_sdk", "flutter/shell/testing", @@ -103,7 +107,7 @@ "script": "flutter/testing/run_tests.py", "parameters": [ "--variant", - "host_profile", + "ci/host_profile", "--type", "dart,dart-host,engine", "--engine-capture-core-dump" @@ -114,11 +118,11 @@ { "archives": [ { - "name": "host_release", - "base_path": "out/host_release/zip_archives/", + "name": "ci/host_release", + "base_path": "out/ci/host_release/zip_archives/", "type": "gcs", "include_paths": [ - "out/host_release/zip_archives/flutter_patched_sdk_product.zip" + "out/ci/host_release/zip_archives/flutter_patched_sdk_product.zip" ], "realm": "production" } @@ -138,6 +142,8 @@ "use_rbe": true }, "gn": [ + "--target-dir", + "ci/host_release", "--runtime-mode", "release", "--prebuilt-dart-sdk", @@ -145,9 +151,9 @@ "--rbe", "--no-goma" ], - "name": "host_release", + "name": "ci/host_release", "ninja": { - "config": "host_release", + "config": "ci/host_release", "targets": [ "flutter/build/dart:copy_dart_sdk", "flutter/display_list:display_list_benchmarks", @@ -173,7 +179,7 @@ "script": "flutter/testing/run_tests.py", "parameters": [ "--variant", - "host_release", + "ci/host_release", "--type", "dart,dart-host,engine,benchmarks", "--engine-capture-core-dump" @@ -182,13 +188,17 @@ { "language": "bash", "name": "Generate metrics test", - "script": "flutter/testing/benchmark/generate_metrics.sh" + "script": "flutter/testing/benchmark/generate_metrics.sh", + "parameters": [ + "ci/host_release" + ] }, { "language": "bash", "name": "Upload metrics dry-run", "script": "flutter/testing/benchmark/upload_metrics.sh", "parameters": [ + "ci/host_release", "--no-upload" ] } @@ -200,7 +210,8 @@ { "name": "Verify-export-symbols-release-binaries", "parameters": [ - "src/out" + "src/out/ci", + "src/flutter/buildtools" ], "script": "flutter/testing/symbols/verify_exported.dart", "language": "dart" diff --git a/ci/builders/linux_unopt.json b/ci/builders/linux_unopt.json index 4987afb3773ab..d80b870c944a0 100644 --- a/ci/builders/linux_unopt.json +++ b/ci/builders/linux_unopt.json @@ -10,6 +10,8 @@ "use_rbe": true }, "gn": [ + "--target-dir", + "ci/host_debug_unopt", "--runtime-mode", "debug", "--unoptimized", @@ -20,9 +22,9 @@ "--rbe", "--no-goma" ], - "name": "host_debug_unopt", + "name": "ci/host_debug_unopt", "ninja": { - "config": "host_debug_unopt", + "config": "ci/host_debug_unopt", "targets": [ "flutter/tools/font_subset", "flutter:unittests", @@ -62,7 +64,7 @@ "--logs-dir", "${FLUTTER_LOGS_DIR}", "--variant", - "host_debug_unopt", + "ci/host_debug_unopt", "--type", "dart,dart-host,engine", "--engine-capture-core-dump", @@ -82,7 +84,7 @@ "name": "test: observatory and service protocol", "script": "flutter/shell/testing/observatory/test.dart", "parameters": [ - "out/host_debug_unopt/flutter_tester", + "out/ci/host_debug_unopt/flutter_tester", "flutter/shell/testing/observatory/empty_main.dart" ] }, @@ -119,14 +121,14 @@ "--unoptimized", "--prebuilt-dart-sdk", "--target-dir", - "host_debug_unopt_impeller_tests", + "ci/host_debug_unopt_impeller_tests", "--rbe", "--no-goma", "--use-glfw-swiftshader" ], - "name": "host_debug_unopt_impeller_tests", + "name": "ci/host_debug_unopt_impeller_tests", "ninja": { - "config": "host_debug_unopt_impeller_tests", + "config": "ci/host_debug_unopt_impeller_tests", "targets": [ "flutter", "flutter/sky/packages" @@ -139,7 +141,7 @@ "script": "flutter/testing/run_tests.py", "parameters": [ "--variant", - "host_debug_unopt_impeller_tests", + "ci/host_debug_unopt_impeller_tests", "--type", "impeller", "--engine-capture-core-dump" @@ -161,14 +163,14 @@ "--android", "--embedder-for-target", "--target-dir", - "android_embedder_debug_unopt", + "ci/android_embedder_debug_unopt", "--unoptimized", "--rbe", "--no-goma" ], - "name": "android_embedder_debug_unopt", + "name": "ci/android_embedder_debug_unopt", "ninja": { - "config": "android_embedder_debug_unopt", + "config": "ci/android_embedder_debug_unopt", "targets": [ "flutter/shell/platform/embedder:flutter_engine" ] @@ -184,6 +186,8 @@ "use_rbe": true }, "gn": [ + "--target-dir", + "ci/android_debug_arm64_validation_layers", "--android", "--android-cpu=arm64", "--no-lto", @@ -191,9 +195,9 @@ "--rbe", "--no-goma" ], - "name": "android_debug_arm64_validation_layers", + "name": "ci/android_debug_arm64_validation_layers", "ninja": { - "config": "android_debug_arm64", + "config": "ci/android_debug_arm64_validation_layers", "targets": [ "flutter", "flutter/shell/platform/android:abi_jars" @@ -215,14 +219,16 @@ "use_rbe": true }, "gn": [ + "--target-dir", + "ci/android_debug_unopt", "--android", "--unoptimized", "--rbe", "--no-goma" ], - "name": "android_debug_unopt", + "name": "ci/android_debug_unopt", "ninja": { - "config": "android_debug_unopt", + "config": "ci/android_debug_unopt", "targets": [ "flutter/impeller", "flutter/shell/platform/android:android_jar", @@ -236,12 +242,12 @@ "script": "flutter/testing/run_tests.py", "parameters": [ "--variant", - "android_debug_unopt", + "ci/android_debug_unopt", "--type", "java", "--engine-capture-core-dump", "--android-variant", - "android_debug_unopt" + "ci/android_debug_unopt" ] }, { @@ -252,7 +258,7 @@ "--before-relative-to-src", "flutter/impeller/tools/malioc.json", "--after-relative-to-src", - "out/android_debug_unopt/gen/malioc", + "out/ci/android_debug_unopt/gen/malioc", "--print-diff" ] } diff --git a/ci/builders/linux_unopt_debug_no_rbe.json b/ci/builders/linux_unopt_debug_no_rbe.json new file mode 100644 index 0000000000000..f9c200917f429 --- /dev/null +++ b/ci/builders/linux_unopt_debug_no_rbe.json @@ -0,0 +1,42 @@ +{ + "_comment": [ + "This build is only used to generate compile_commands.json for clangd ", + "and should not be used for any other purpose.", + "", + "Note the `flutter/tools/font_subset` target is only used because if no ", + "targets are specified, the build will fail, and if an empty list of ", + "targets are specified, all targets are built (wasteful/slow)" + ], + "builds": [ + { + "drone_dimensions": ["device_type=none", "os=Linux", "cores=32"], + "gn": [ + "--runtime-mode", + "debug", + "--unoptimized", + "--prebuilt-dart-sdk", + "--no-lto", + "--no-rbe", + "--no-goma", + "--target-dir", + "ci/linux_unopt_debug_no_rbe" + ], + "name": "ci/linux_unopt_debug_no_rbe", + "ninja": { + "config": "ci/linux_unopt_debug_no_rbe", + "targets": ["flutter/tools/font_subset"] + }, + "tests": [ + { + "language": "dart", + "name": "clangd", + "script": "flutter/tools/clangd_check/bin/main.dart", + "parameters": [ + "--clangd=buildtools/linux-x64/clang/bin/clangd", + "--compile-commands-dir=../out/ci/linux_unopt_debug_no_rbe" + ] + } + ] + } + ] +} diff --git a/ci/builders/mac_android_aot_engine.json b/ci/builders/mac_android_aot_engine.json index b6889f57efdfe..58463ae05d00c 100644 --- a/ci/builders/mac_android_aot_engine.json +++ b/ci/builders/mac_android_aot_engine.json @@ -3,12 +3,12 @@ { "archives": [ { - "base_path": "out/android_profile/zip_archives/", + "base_path": "out/ci/android_profile/zip_archives/", "type": "gcs", "include_paths": [ - "out/android_profile/zip_archives/android-arm-profile/darwin-x64.zip" + "out/ci/android_profile/zip_archives/android-arm-profile/darwin-x64.zip" ], - "name": "android_profile", + "name": "ci/android_profile", "realm": "production" } ], @@ -21,13 +21,15 @@ "download_android_deps": false }, "gn": [ + "--target-dir", + "ci/android_profile", "--runtime-mode", "profile", "--android" ], - "name": "android_profile", + "name": "ci/android_profile", "ninja": { - "config": "android_profile", + "config": "ci/android_profile", "targets": [ "flutter/lib/snapshot", "flutter/shell/platform/android:gen_snapshot" @@ -42,12 +44,12 @@ { "archives": [ { - "base_path": "out/android_profile_arm64/zip_archives/", + "base_path": "out/ci/android_profile_arm64/zip_archives/", "type": "gcs", "include_paths": [ - "out/android_profile_arm64/zip_archives/android-arm64-profile/darwin-x64.zip" + "out/ci/android_profile_arm64/zip_archives/android-arm64-profile/darwin-x64.zip" ], - "name": "android_profile_arm64", + "name": "ci/android_profile_arm64", "realm": "production" } ], @@ -60,14 +62,16 @@ "download_android_deps": false }, "gn": [ + "--target-dir", + "ci/android_profile_arm64", "--runtime-mode", "profile", "--android", "--android-cpu=arm64" ], - "name": "android_profile_arm64", + "name": "ci/android_profile_arm64", "ninja": { - "config": "android_profile_arm64", + "config": "ci/android_profile_arm64", "targets": [ "flutter/lib/snapshot", "flutter/shell/platform/android:gen_snapshot" @@ -82,12 +86,12 @@ { "archives": [ { - "base_path": "out/android_profile_x64/zip_archives/", + "base_path": "out/ci/android_profile_x64/zip_archives/", "type": "gcs", "include_paths": [ - "out/android_profile_x64/zip_archives/android-x64-profile/darwin-x64.zip" + "out/ci/android_profile_x64/zip_archives/android-x64-profile/darwin-x64.zip" ], - "name": "android_profile_x64", + "name": "ci/android_profile_x64", "realm": "production" } ], @@ -100,14 +104,16 @@ "download_android_deps": false }, "gn": [ + "--target-dir", + "ci/android_profile_x64", "--runtime-mode", "profile", "--android", "--android-cpu=x64" ], - "name": "android_profile_x64", + "name": "ci/android_profile_x64", "ninja": { - "config": "android_profile_x64", + "config": "ci/android_profile_x64", "targets": [ "flutter/lib/snapshot", "flutter/shell/platform/android:gen_snapshot" @@ -122,12 +128,12 @@ { "archives": [ { - "base_path": "out/android_release/zip_archives/", + "base_path": "out/ci/android_release/zip_archives/", "type": "gcs", "include_paths": [ - "out/android_release/zip_archives/android-arm-release/darwin-x64.zip" + "out/ci/android_release/zip_archives/android-arm-release/darwin-x64.zip" ], - "name": "android_release", + "name": "ci/android_release", "realm": "production" } ], @@ -140,13 +146,15 @@ "download_android_deps": false }, "gn": [ + "--target-dir", + "ci/android_release", "--runtime-mode", "release", "--android" ], - "name": "android_release", + "name": "ci/android_release", "ninja": { - "config": "android_release", + "config": "ci/android_release", "targets": [ "flutter/lib/snapshot", "flutter/shell/platform/android:gen_snapshot" @@ -161,12 +169,12 @@ { "archives": [ { - "base_path": "out/android_release_arm64/zip_archives/", + "base_path": "out/ci/android_release_arm64/zip_archives/", "type": "gcs", "include_paths": [ - "out/android_release_arm64/zip_archives/android-arm64-release/darwin-x64.zip" + "out/ci/android_release_arm64/zip_archives/android-arm64-release/darwin-x64.zip" ], - "name": "android_release_arm64", + "name": "ci/android_release_arm64", "realm": "production" } ], @@ -179,14 +187,16 @@ "download_android_deps": false }, "gn": [ + "--target-dir", + "ci/android_release_arm64", "--runtime-mode", "release", "--android", "--android-cpu=arm64" ], - "name": "android_release_arm64", + "name": "ci/android_release_arm64", "ninja": { - "config": "android_release_arm64", + "config": "ci/android_release_arm64", "targets": [ "flutter/lib/snapshot", "flutter/shell/platform/android:gen_snapshot" @@ -201,12 +211,12 @@ { "archives": [ { - "base_path": "out/android_release_x64/zip_archives/", + "base_path": "out/ci/android_release_x64/zip_archives/", "type": "gcs", "include_paths": [ - "out/android_release_x64/zip_archives/android-x64-release/darwin-x64.zip" + "out/ci/android_release_x64/zip_archives/android-x64-release/darwin-x64.zip" ], - "name": "android_release_x64", + "name": "ci/android_release_x64", "realm": "production" } ], @@ -219,14 +229,16 @@ "download_android_deps": false }, "gn": [ + "--target-dir", + "ci/android_release_x64", "--runtime-mode", "release", "--android", "--android-cpu=x64" ], - "name": "android_release_x64", + "name": "ci/android_release_x64", "ninja": { - "config": "android_release_x64", + "config": "ci/android_release_x64", "targets": [ "flutter/lib/snapshot", "flutter/shell/platform/android:gen_snapshot" diff --git a/ci/builders/mac_clang_tidy.json b/ci/builders/mac_clang_tidy.json index 6f50c27f0e350..789ee39feabbb 100644 --- a/ci/builders/mac_clang_tidy.json +++ b/ci/builders/mac_clang_tidy.json @@ -17,14 +17,14 @@ "--no-lto", "--force-mac-arm64", "--target-dir", - "host_debug_clang_tidy", + "ci/host_debug_clang_tidy", "--rbe", "--no-goma", "--xcode-symlinks" ], - "name": "host_debug_clang_tidy", + "name": "ci/host_debug_clang_tidy", "ninja": { - "config": "host_debug_clang_tidy" + "config": "ci/host_debug_clang_tidy" } }, { @@ -45,14 +45,14 @@ "--no-lto", "--force-mac-arm64", "--target-dir", - "ios_debug_sim_clang_tidy", + "ci/ios_debug_sim_clang_tidy", "--rbe", "--no-goma", "--xcode-symlinks" ], - "name": "ios_debug_sim_clang_tidy", + "name": "ci/ios_debug_sim_clang_tidy", "ninja": { - "config": "ios_debug_sim_clang_tidy" + "config": "ci/ios_debug_sim_clang_tidy" } } ], @@ -70,7 +70,7 @@ "use_rbe": true }, "dependencies": [ - "host_debug_clang_tidy" + "ci/host_debug_clang_tidy" ], "contexts": [ "osx_sdk" @@ -87,7 +87,7 @@ "--no-lto", "--force-mac-arm64", "--target-dir", - "host_debug_clang_tidy", + "ci/host_debug_clang_tidy", "--rbe", "--no-goma", "--xcode-symlinks" @@ -97,10 +97,10 @@ "name": "test: lint host_debug", "parameters": [ "--variant", - "host_debug_clang_tidy", + "ci/host_debug_clang_tidy", "--lint-all", "--shard-id=0", - "--shard-variants=host_debug_clang_tidy,host_debug_clang_tidy,host_debug_clang_tidy" + "--shard-variants=ci/host_debug_clang_tidy,ci/host_debug_clang_tidy,ci/host_debug_clang_tidy" ], "max_attempts": 1, "script": "flutter/ci/clang_tidy.sh" @@ -120,7 +120,7 @@ "use_rbe": true }, "dependencies": [ - "host_debug_clang_tidy" + "ci/host_debug_clang_tidy" ], "contexts": [ "osx_sdk" @@ -137,7 +137,7 @@ "--no-lto", "--force-mac-arm64", "--target-dir", - "host_debug_clang_tidy", + "ci/host_debug_clang_tidy", "--rbe", "--no-goma", "--xcode-symlinks" @@ -147,10 +147,10 @@ "name": "test: lint host_debug", "parameters": [ "--variant", - "host_debug_clang_tidy", + "ci/host_debug_clang_tidy", "--lint-all", "--shard-id=1", - "--shard-variants=host_debug_clang_tidy,host_debug_clang_tidy,host_debug_clang_tidy" + "--shard-variants=ci/host_debug_clang_tidy,ci/host_debug_clang_tidy,ci/host_debug_clang_tidy" ], "max_attempts": 1, "script": "flutter/ci/clang_tidy.sh" @@ -170,7 +170,7 @@ "use_rbe": true }, "dependencies": [ - "host_debug_clang_tidy" + "ci/host_debug_clang_tidy" ], "contexts": [ "osx_sdk" @@ -187,7 +187,7 @@ "--no-lto", "--force-mac-arm64", "--target-dir", - "host_debug_clang_tidy", + "ci/host_debug_clang_tidy", "--rbe", "--no-goma", "--xcode-symlinks" @@ -197,10 +197,10 @@ "name": "test: lint host_debug", "parameters": [ "--variant", - "host_debug_clang_tidy", + "ci/host_debug_clang_tidy", "--lint-all", "--shard-id=2", - "--shard-variants=host_debug_clang_tidy,host_debug_clang_tidy,host_debug_clang_tidy" + "--shard-variants=ci/host_debug_clang_tidy,ci/host_debug_clang_tidy,ci/host_debug_clang_tidy" ], "max_attempts": 1, "script": "flutter/ci/clang_tidy.sh" @@ -220,7 +220,7 @@ "use_rbe": true }, "dependencies": [ - "host_debug_clang_tidy" + "ci/host_debug_clang_tidy" ], "contexts": [ "osx_sdk" @@ -237,7 +237,7 @@ "--no-lto", "--force-mac-arm64", "--target-dir", - "host_debug_clang_tidy", + "ci/host_debug_clang_tidy", "--rbe", "--no-goma", "--xcode-symlinks" @@ -247,10 +247,10 @@ "name": "test: lint host_debug", "parameters": [ "--variant", - "host_debug_clang_tidy", + "ci/host_debug_clang_tidy", "--lint-all", "--shard-id=3", - "--shard-variants=host_debug_clang_tidy,host_debug_clang_tidy,host_debug_clang_tidy" + "--shard-variants=ci/host_debug_clang_tidy,ci/host_debug_clang_tidy,ci/host_debug_clang_tidy" ], "max_attempts": 1, "script": "flutter/ci/clang_tidy.sh" @@ -270,8 +270,8 @@ "use_rbe": true }, "dependencies": [ - "host_debug_clang_tidy", - "ios_debug_sim_clang_tidy" + "ci/host_debug_clang_tidy", + "ci/ios_debug_sim_clang_tidy" ], "contexts": [ "osx_sdk" @@ -288,7 +288,7 @@ "--no-lto", "--force-mac-arm64", "--target-dir", - "host_debug_clang_tidy", + "ci/host_debug_clang_tidy", "--rbe", "--no-goma", "--xcode-symlinks" @@ -306,7 +306,7 @@ "--no-lto", "--force-mac-arm64", "--target-dir", - "ios_debug_sim_clang_tidy", + "ci/ios_debug_sim_clang_tidy", "--rbe", "--no-goma", "--xcode-symlinks" @@ -316,10 +316,10 @@ "name": "test: lint ios_debug_sim", "parameters": [ "--variant", - "ios_debug_sim_clang_tidy", + "ci/ios_debug_sim_clang_tidy", "--lint-all", "--shard-id=0", - "--shard-variants=host_debug_clang_tidy" + "--shard-variants=ci/host_debug_clang_tidy" ], "max_attempts": 1, "script": "flutter/ci/clang_tidy.sh" diff --git a/ci/builders/mac_host_engine.json b/ci/builders/mac_host_engine.json index c12a0632a134d..7d116dd65e54b 100644 --- a/ci/builders/mac_host_engine.json +++ b/ci/builders/mac_host_engine.json @@ -3,13 +3,13 @@ { "archives": [ { - "base_path": "out/host_debug/zip_archives/", + "base_path": "out/ci/host_debug/zip_archives/", "type": "gcs", "include_paths": [ - "out/host_debug/zip_archives/darwin-x64/artifacts.zip", - "out/host_debug/zip_archives/dart-sdk-darwin-x64.zip" + "out/ci/host_debug/zip_archives/darwin-x64/artifacts.zip", + "out/ci/host_debug/zip_archives/dart-sdk-darwin-x64.zip" ], - "name": "host_debug", + "name": "ci/host_debug", "realm": "production" } ], @@ -23,6 +23,8 @@ "download_android_deps": false }, "gn": [ + "--target-dir", + "ci/host_debug", "--runtime-mode", "debug", "--no-lto", @@ -30,9 +32,9 @@ "--build-embedder-examples", "--use-glfw-swiftshader" ], - "name": "host_debug", + "name": "ci/host_debug", "ninja": { - "config": "host_debug", + "config": "ci/host_debug", "targets": [ "flutter/build/archives:archive_gen_snapshot", "flutter/build/archives:artifacts", @@ -56,7 +58,7 @@ "script": "flutter/testing/run_tests.py", "parameters": [ "--variant", - "host_debug", + "ci/host_debug", "--type", "dart,dart-host,engine", "--engine-capture-core-dump" @@ -67,12 +69,12 @@ { "archives": [ { - "base_path": "out/host_profile/zip_archives/", + "base_path": "out/ci/host_profile/zip_archives/", "type": "gcs", "include_paths": [ - "out/host_profile/zip_archives/darwin-x64-profile/artifacts.zip" + "out/ci/host_profile/zip_archives/darwin-x64-profile/artifacts.zip" ], - "name": "host_profile", + "name": "ci/host_profile", "realm": "production" } ], @@ -86,15 +88,17 @@ "download_android_deps": false }, "gn": [ + "--target-dir", + "ci/host_profile", "--runtime-mode", "profile", "--no-lto", "--prebuilt-dart-sdk", "--build-embedder-examples" ], - "name": "host_profile", + "name": "ci/host_profile", "ninja": { - "config": "host_profile", + "config": "ci/host_profile", "targets": [ "flutter/build/dart:copy_dart_sdk", "flutter/build/archives:archive_gen_snapshot", @@ -115,7 +119,7 @@ "script": "flutter/testing/run_tests.py", "parameters": [ "--variant", - "host_profile", + "ci/host_profile", "--type", "dart,dart-host,engine", "--engine-capture-core-dump" @@ -126,13 +130,13 @@ { "archives": [ { - "base_path": "out/host_release/zip_archives/", + "base_path": "out/ci/host_release/zip_archives/", "type": "gcs", "include_paths": [ - "out/host_release/zip_archives/darwin-x64-release/artifacts.zip", - "out/host_release/zip_archives/darwin-x64/font-subset.zip" + "out/ci/host_release/zip_archives/darwin-x64-release/artifacts.zip", + "out/ci/host_release/zip_archives/darwin-x64/font-subset.zip" ], - "name": "host_release", + "name": "ci/host_release", "realm": "production" } ], @@ -152,6 +156,8 @@ "download_android_deps": false }, "gn": [ + "--target-dir", + "ci/host_release", "--runtime-mode", "release", "--no-lto", @@ -159,9 +165,9 @@ "--build-embedder-examples", "--use-glfw-swiftshader" ], - "name": "host_release", + "name": "ci/host_release", "ninja": { - "config": "host_release", + "config": "ci/host_release", "targets": [ "flutter/build/archives:archive_gen_snapshot", "flutter/build/archives:artifacts", @@ -184,7 +190,7 @@ "script": "flutter/testing/run_tests.py", "parameters": [ "--variant", - "host_release", + "ci/host_release", "--type", "dart,dart-host,engine,impeller-golden" ] @@ -194,13 +200,13 @@ { "archives": [ { - "base_path": "out/mac_debug_arm64/zip_archives/", + "base_path": "out/ci/mac_debug_arm64/zip_archives/", "type": "gcs", "include_paths": [ - "out/mac_debug_arm64/zip_archives/darwin-arm64/artifacts.zip", - "out/mac_debug_arm64/zip_archives/dart-sdk-darwin-arm64.zip" + "out/ci/mac_debug_arm64/zip_archives/darwin-arm64/artifacts.zip", + "out/ci/mac_debug_arm64/zip_archives/dart-sdk-darwin-arm64.zip" ], - "name": "mac_debug_arm64", + "name": "ci/mac_debug_arm64", "realm": "production" } ], @@ -213,6 +219,8 @@ "download_android_deps": false }, "gn": [ + "--target-dir", + "ci/mac_debug_arm64", "--mac", "--mac-cpu", "arm64", @@ -221,9 +229,9 @@ "--no-lto", "--prebuilt-dart-sdk" ], - "name": "mac_debug_arm64", + "name": "ci/mac_debug_arm64", "ninja": { - "config": "mac_debug_arm64", + "config": "ci/mac_debug_arm64", "targets": [ "flutter/tools/font_subset", "flutter/build/archives:archive_gen_snapshot", @@ -242,12 +250,12 @@ { "archives": [ { - "base_path": "out/mac_profile_arm64/zip_archives/", + "base_path": "out/ci/mac_profile_arm64/zip_archives/", "type": "gcs", "include_paths": [ - "out/mac_profile_arm64/zip_archives/darwin-arm64-profile/artifacts.zip" + "out/ci/mac_profile_arm64/zip_archives/darwin-arm64-profile/artifacts.zip" ], - "name": "mac_profile_arm64", + "name": "ci/mac_profile_arm64", "realm": "production" } ], @@ -260,6 +268,8 @@ "download_android_deps": false }, "gn": [ + "--target-dir", + "ci/mac_profile_arm64", "--mac", "--mac-cpu", "arm64", @@ -268,9 +278,9 @@ "--no-lto", "--prebuilt-dart-sdk" ], - "name": "mac_profile_arm64", + "name": "ci/mac_profile_arm64", "ninja": { - "config": "mac_profile_arm64", + "config": "ci/mac_profile_arm64", "targets": [ "flutter/build/archives:artifacts", "flutter/shell/platform/darwin/macos:zip_macos_flutter_framework" @@ -285,11 +295,11 @@ { "archives": [ { - "base_path": "out/mac_release_arm64/zip_archives/", + "base_path": "out/ci/mac_release_arm64/zip_archives/", "type": "gcs", "include_paths": [ - "out/mac_release_arm64/zip_archives/darwin-arm64/font-subset.zip", - "out/mac_release_arm64/zip_archives/darwin-arm64-release/artifacts.zip" + "out/ci/mac_release_arm64/zip_archives/darwin-arm64/font-subset.zip", + "out/ci/mac_release_arm64/zip_archives/darwin-arm64-release/artifacts.zip" ], "name": "mac_release_arm64", "realm": "production" @@ -304,6 +314,8 @@ "download_android_deps": false }, "gn": [ + "--target-dir", + "ci/mac_release_arm64", "--mac", "--mac-cpu", "arm64", @@ -312,9 +324,9 @@ "--no-lto", "--prebuilt-dart-sdk" ], - "name": "mac_release_arm64", + "name": "ci/mac_release_arm64", "ninja": { - "config": "mac_release_arm64", + "config": "ci/mac_release_arm64", "targets": [ "flutter/tools/font_subset", "flutter/build/archives:artifacts", @@ -336,9 +348,9 @@ "--dst", "out/debug/framework", "--arm64-out-dir", - "out/mac_debug_arm64", + "out/ci/mac_debug_arm64", "--x64-out-dir", - "out/host_debug", + "out/ci/host_debug", "--zip" ], "script": "flutter/sky/tools/create_embedder_framework.py" @@ -349,9 +361,9 @@ "--dst", "out/release/framework", "--arm64-out-dir", - "out/mac_release_arm64", + "out/ci/mac_release_arm64", "--x64-out-dir", - "out/host_release", + "out/ci/host_release", "--dsym", "--strip", "--zip" @@ -364,9 +376,9 @@ "--dst", "out/debug/framework", "--arm64-out-dir", - "out/mac_debug_arm64", + "out/ci/mac_debug_arm64", "--x64-out-dir", - "out/host_debug", + "out/ci/host_debug", "--zip" ], "script": "flutter/sky/tools/create_macos_framework.py" @@ -377,9 +389,9 @@ "--dst", "out/profile/framework", "--arm64-out-dir", - "out/mac_profile_arm64", + "out/ci/mac_profile_arm64", "--x64-out-dir", - "out/host_profile", + "out/ci/host_profile", "--zip" ], "script": "flutter/sky/tools/create_macos_framework.py" @@ -387,7 +399,8 @@ { "name": "Verify-export-symbols", "parameters": [ - "src/out" + "src/out/ci", + "src/flutter/buildtools" ], "script": "flutter/testing/symbols/verify_exported.dart", "language": "dart" @@ -398,9 +411,9 @@ "--dst", "out/debug/snapshot", "--arm64-out-dir", - "out/mac_debug_arm64", + "out/ci/mac_debug_arm64", "--x64-out-dir", - "out/host_debug", + "out/ci/host_debug", "--zip" ], "script": "flutter/sky/tools/create_macos_gen_snapshots.py" @@ -411,9 +424,9 @@ "--dst", "out/profile/snapshot", "--arm64-out-dir", - "out/mac_profile_arm64", + "out/ci/mac_profile_arm64", "--x64-out-dir", - "out/host_profile", + "out/ci/host_profile", "--zip" ], "script": "flutter/sky/tools/create_macos_gen_snapshots.py" @@ -424,9 +437,9 @@ "--dst", "out/release/snapshot", "--arm64-out-dir", - "out/mac_release_arm64", + "out/ci/mac_release_arm64", "--x64-out-dir", - "out/host_release", + "out/ci/host_release", "--zip" ], "script": "flutter/sky/tools/create_macos_gen_snapshots.py" diff --git a/ci/builders/mac_impeller_cmake_example.json b/ci/builders/mac_impeller_cmake_example.json index 476697f283457..9bc57f27b745e 100644 --- a/ci/builders/mac_impeller_cmake_example.json +++ b/ci/builders/mac_impeller_cmake_example.json @@ -1,7 +1,8 @@ { "builds": [ { - "name": "impeller-cmake-example", + "cas_archive": false, + "name": "ci/impeller-cmake-example", "archives": [], "drone_dimensions": [ "device_type=none", diff --git a/ci/builders/mac_ios_engine.json b/ci/builders/mac_ios_engine.json index a182ae332c041..013a0c5461dfd 100644 --- a/ci/builders/mac_ios_engine.json +++ b/ci/builders/mac_ios_engine.json @@ -7,15 +7,17 @@ "cpu=x86" ], "gn": [ + "--target-dir", + "ci/ios_debug_sim", "--ios", "--runtime-mode", "debug", "--simulator", "--no-lto" ], - "name": "ios_debug_sim", + "name": "ci/ios_debug_sim", "ninja": { - "config": "ios_debug_sim" + "config": "ci/ios_debug_sim" }, "properties": { "$flutter/osx_sdk": { @@ -29,6 +31,8 @@ "os=Mac-13" ], "gn": [ + "--target-dir", + "ci/ios_debug_sim_arm64", "--ios", "--runtime-mode", "debug", @@ -36,9 +40,9 @@ "--simulator-cpu=arm64", "--no-lto" ], - "name": "ios_debug_sim_arm64", + "name": "ci/ios_debug_sim_arm64", "ninja": { - "config": "ios_debug_sim_arm64" + "config": "ci/ios_debug_sim_arm64" }, "properties": { "$flutter/osx_sdk": { @@ -52,13 +56,15 @@ "os=Mac-13" ], "gn": [ + "--target-dir", + "ci/ios_debug", "--ios", "--runtime-mode", "debug" ], - "name": "ios_debug", + "name": "ci/ios_debug", "ninja": { - "config": "ios_debug", + "config": "ci/ios_debug", "targets": [ "flutter/shell/platform/darwin/ios:flutter_framework" ] @@ -75,13 +81,15 @@ "os=Mac-13" ], "gn": [ + "--target-dir", + "ci/ios_profile", "--ios", "--runtime-mode", "profile" ], - "name": "ios_profile", + "name": "ci/ios_profile", "ninja": { - "config": "ios_profile", + "config": "ci/ios_profile", "targets": [ "flutter/shell/platform/darwin/ios:flutter_framework", "flutter/lib/snapshot:generate_snapshot_bin" @@ -99,13 +107,15 @@ "os=Mac-13" ], "gn": [ + "--target-dir", + "ci/ios_release", "--ios", "--runtime-mode", "release" ], - "name": "ios_release", + "name": "ci/ios_release", "ninja": { - "config": "ios_release", + "config": "ci/ios_release", "targets": [ "flutter/shell/platform/darwin/ios:flutter_framework", "flutter/lib/snapshot:generate_snapshot_bin" @@ -124,6 +134,8 @@ "cpu=x86" ], "gn": [ + "--target-dir", + "ci/ios_debug_sim_extension_safe", "--ios", "--runtime-mode", "debug", @@ -131,9 +143,9 @@ "--no-lto", "--darwin-extension-safe" ], - "name": "ios_debug_sim_extension_safe", + "name": "ci/ios_debug_sim_extension_safe", "ninja": { - "config": "ios_debug_sim_extension_safe" + "config": "ci/ios_debug_sim_extension_safe" }, "properties": { "$flutter/osx_sdk": { @@ -147,6 +159,8 @@ "os=Mac-13" ], "gn": [ + "--target-dir", + "ci/ios_debug_sim_arm64_extension_safe", "--ios", "--runtime-mode", "debug", @@ -155,9 +169,9 @@ "--no-lto", "--darwin-extension-safe" ], - "name": "ios_debug_sim_arm64_extension_safe", + "name": "ci/ios_debug_sim_arm64_extension_safe", "ninja": { - "config": "ios_debug_sim_arm64_extension_safe" + "config": "ci/ios_debug_sim_arm64_extension_safe" }, "properties": { "$flutter/osx_sdk": { @@ -171,14 +185,16 @@ "os=Mac-13" ], "gn": [ + "--target-dir", + "ci/ios_debug_extension_safe", "--ios", "--runtime-mode", "debug", "--darwin-extension-safe" ], - "name": "ios_debug_extension_safe", + "name": "ci/ios_debug_extension_safe", "ninja": { - "config": "ios_debug_extension_safe", + "config": "ci/ios_debug_extension_safe", "targets": [ "flutter/shell/platform/darwin/ios:flutter_framework" ] @@ -195,14 +211,16 @@ "os=Mac-13" ], "gn": [ + "--target-dir", + "ci/ios_profile_extension_safe", "--ios", "--runtime-mode", "profile", "--darwin-extension-safe" ], - "name": "ios_profile_extension_safe", + "name": "ci/ios_profile_extension_safe", "ninja": { - "config": "ios_profile_extension_safe", + "config": "ci/ios_profile_extension_safe", "targets": [ "flutter/shell/platform/darwin/ios:flutter_framework", "flutter/lib/snapshot:generate_snapshot_bin" @@ -220,14 +238,16 @@ "os=Mac-13" ], "gn": [ + "--target-dir", + "ci/ios_release_extension_safe", "--ios", "--runtime-mode", "release", "--darwin-extension-safe" ], - "name": "ios_release_extension_safe", + "name": "ci/ios_release_extension_safe", "ninja": { - "config": "ios_release_extension_safe", + "config": "ci/ios_release_extension_safe", "targets": [ "flutter/shell/platform/darwin/ios:flutter_framework", "flutter/lib/snapshot:generate_snapshot_bin" @@ -248,11 +268,11 @@ "--dst", "out/debug", "--arm64-out-dir", - "out/ios_debug", + "out/ci/ios_debug", "--simulator-x64-out-dir", - "out/ios_debug_sim", + "out/ci/ios_debug_sim", "--simulator-arm64-out-dir", - "out/ios_debug_sim_arm64" + "out/ci/ios_debug_sim_arm64" ], "script": "flutter/sky/tools/create_full_ios_framework.py", "language": "python3" @@ -263,11 +283,11 @@ "--dst", "out/profile", "--arm64-out-dir", - "out/ios_profile", + "out/ci/ios_profile", "--simulator-x64-out-dir", - "out/ios_debug_sim", + "out/ci/ios_debug_sim", "--simulator-arm64-out-dir", - "out/ios_debug_sim_arm64" + "out/ci/ios_debug_sim_arm64" ], "script": "flutter/sky/tools/create_full_ios_framework.py", "language": "python3" @@ -278,11 +298,11 @@ "--dst", "out/release", "--arm64-out-dir", - "out/ios_release", + "out/ci/ios_release", "--simulator-x64-out-dir", - "out/ios_debug_sim", + "out/ci/ios_debug_sim", "--simulator-arm64-out-dir", - "out/ios_debug_sim_arm64", + "out/ci/ios_debug_sim_arm64", "--dsym", "--strip" ], @@ -295,7 +315,7 @@ "--dst", "out/release", "--arm64-out-dir", - "out/ios_release" + "out/ci/ios_release" ], "script": "flutter/sky/tools/create_macos_gen_snapshots.py", "language": "python3" @@ -303,7 +323,8 @@ { "name": "Verify-export-symbols-release-binaries", "parameters": [ - "src/out" + "src/out/ci", + "src/flutter/buildtools" ], "script": "flutter/testing/symbols/verify_exported.dart", "language": "dart" diff --git a/ci/builders/mac_unopt.json b/ci/builders/mac_unopt.json index ed7a8da7d7408..e7acb1e75496f 100644 --- a/ci/builders/mac_unopt.json +++ b/ci/builders/mac_unopt.json @@ -12,6 +12,8 @@ "use_rbe": true }, "gn": [ + "--target-dir", + "ci/host_debug_unopt", "--runtime-mode", "debug", "--unoptimized", @@ -22,9 +24,9 @@ "--no-goma", "--xcode-symlinks" ], - "name": "host_debug_unopt", + "name": "ci/host_debug_unopt", "ninja": { - "config": "host_debug_unopt", + "config": "ci/host_debug_unopt", "targets": [] }, "properties": { @@ -39,7 +41,7 @@ "script": "flutter/testing/run_tests.py", "parameters": [ "--variant", - "host_debug_unopt", + "ci/host_debug_unopt", "--type", "dart,dart-host,engine", "--engine-capture-core-dump" @@ -80,11 +82,11 @@ "--no-goma", "--xcode-symlinks", "--target-dir", - "ios_debug_unopt_sim" + "ci/ios_debug_unopt_sim" ], - "name": "ios_debug_unopt_sim", + "name": "ci/ios_debug_unopt_sim", "ninja": { - "config": "ios_debug_unopt_sim", + "config": "ci/ios_debug_unopt_sim", "targets": [ "flutter/testing/scenario_app", "flutter/shell/platform/darwin/ios:ios_test_flutter" @@ -97,18 +99,18 @@ "script": "flutter/testing/run_tests.py", "parameters": [ "--variant", - "ios_debug_unopt_sim", + "ci/ios_debug_unopt_sim", "--type", "objc", "--engine-capture-core-dump", "--ios-variant", - "ios_debug_unopt_sim" + "ci/ios_debug_unopt_sim" ] }, { "name": "Scenario App Integration Tests", "parameters": [ - "ios_debug_unopt_sim" + "ci/ios_debug_unopt_sim" ], "script": "flutter/testing/scenario_app/run_ios_tests.sh" } @@ -125,22 +127,24 @@ "use_rbe": true }, "gn": [ - "--runtime-mode", - "debug", - "--unoptimized", - "--no-lto", - "--prebuilt-dart-sdk", - "--force-mac-arm64", - "--mac-cpu", - "arm64", - "--rbe", - "--no-goma", - "--xcode-symlinks", - "--use-glfw-swiftshader" + "--target-dir", + "ci/host_debug_unopt_arm64", + "--runtime-mode", + "debug", + "--unoptimized", + "--no-lto", + "--prebuilt-dart-sdk", + "--force-mac-arm64", + "--mac-cpu", + "arm64", + "--rbe", + "--no-goma", + "--xcode-symlinks", + "--use-glfw-swiftshader" ], - "name": "host_debug_unopt_arm64", + "name": "ci/host_debug_unopt_arm64", "ninja": { - "config": "host_debug_unopt_arm64", + "config": "ci/host_debug_unopt_arm64", "targets": [ ] }, @@ -156,7 +160,7 @@ "script": "flutter/testing/run_tests.py", "parameters": [ "--variant", - "host_debug_unopt_arm64", + "ci/host_debug_unopt_arm64", "--type", "dart,dart-host,engine,impeller-golden", "--engine-capture-core-dump" @@ -195,11 +199,11 @@ "--no-goma", "--xcode-symlinks", "--target-dir", - "ios_debug_unopt_sim_arm64" + "ci/ios_debug_unopt_sim_arm64" ], - "name": "ios_debug_unopt_sim_arm64", + "name": "ci/ios_debug_unopt_sim_arm64", "ninja": { - "config": "ios_debug_unopt_sim_arm64", + "config": "ci/ios_debug_unopt_sim_arm64", "targets": [ "flutter/testing/scenario_app", "flutter/shell/platform/darwin/ios:ios_test_flutter" @@ -212,18 +216,18 @@ "script": "flutter/testing/run_tests.py", "parameters": [ "--variant", - "ios_debug_unopt_sim_arm64", + "ci/ios_debug_unopt_sim_arm64", "--type", "objc", "--engine-capture-core-dump", "--ios-variant", - "ios_debug_unopt_sim_arm64" + "ci/ios_debug_unopt_sim_arm64" ] }, { "name": "Scenario App Integration Tests", "parameters": [ - "ios_debug_unopt_sim_arm64" + "ci/ios_debug_unopt_sim_arm64" ], "script": "flutter/testing/scenario_app/run_ios_tests.sh" } @@ -262,11 +266,11 @@ "--no-goma", "--xcode-symlinks", "--target-dir", - "ios_debug_unopt_sim_arm64_extension_safe" + "ci/ios_debug_unopt_sim_arm64_extension_safe" ], - "name": "ios_debug_unopt_sim_arm64_extension_safe", + "name": "ci/ios_debug_unopt_sim_arm64_extension_safe", "ninja": { - "config": "ios_debug_unopt_sim_arm64_extension_safe", + "config": "ci/ios_debug_unopt_sim_arm64_extension_safe", "targets": [ "flutter/testing/scenario_app", "flutter/shell/platform/darwin/ios:ios_test_flutter" @@ -279,18 +283,18 @@ "script": "flutter/testing/run_tests.py", "parameters": [ "--variant", - "ios_debug_unopt_sim_arm64_extension_safe", + "ci/ios_debug_unopt_sim_arm64_extension_safe", "--type", "objc", "--engine-capture-core-dump", "--ios-variant", - "ios_debug_unopt_sim_arm64_extension_safe" + "ci/ios_debug_unopt_sim_arm64_extension_safe" ] }, { "name": "Scenario App Integration Tests", "parameters": [ - "ios_debug_unopt_sim_arm64_extension_safe" + "ci/ios_debug_unopt_sim_arm64_extension_safe" ], "script": "flutter/testing/scenario_app/run_ios_tests.sh" } diff --git a/ci/builders/mac_unopt_debug_no_rbe.json b/ci/builders/mac_unopt_debug_no_rbe.json new file mode 100644 index 0000000000000..7287e8990df66 --- /dev/null +++ b/ci/builders/mac_unopt_debug_no_rbe.json @@ -0,0 +1,53 @@ +{ + "_comment": [ + "This build is only used to generate compile_commands.json for clangd ", + "and should not be used for any other purpose.", + "", + "Note the `flutter/tools/font_subset` target is only used because if no ", + "targets are specified, the build will fail, and if an empty list of ", + "targets are specified, all targets are built (wasteful/slow)" + ], + "builds": [ + { + "drone_dimensions": [ + "device_type=none", + "os=Mac-13", + "cpu=x86", + "mac_model=Macmini8,1" + ], + "gn": [ + "--runtime-mode", + "debug", + "--unoptimized", + "--prebuilt-dart-sdk", + "--no-lto", + "--no-rbe", + "--no-goma", + "--xcode-symlinks", + "--target-dir", + "ci/mac_unopt_debug_no_rbe" + ], + "name": "ci/mac_unopt_debug_no_rbe", + "ninja": { + "config": "ci/mac_unopt_debug_no_rbe", + "targets": ["flutter/tools/font_subset"] + }, + "properties": { + "$flutter/osx_sdk": { + "sdk_version": "15a240d" + } + }, + "tests": [ + { + "language": "dart", + "name": "clangd", + "script": "flutter/tools/clangd_check/bin/main.dart", + "parameters": [ + "--clangd=buildtools/mac-x64/clang/bin/clangd", + "--compile-commands-dir=../out/ci/mac_unopt_debug_no_rbe" + ] + } + ] + } + ] +} diff --git a/ci/builders/standalone/linux_benchmarks.json b/ci/builders/standalone/linux_benchmarks.json index 1f09c3c131a48..36ebfc464e91a 100644 --- a/ci/builders/standalone/linux_benchmarks.json +++ b/ci/builders/standalone/linux_benchmarks.json @@ -8,6 +8,8 @@ "use_rbe": true }, "gn": [ + "--target-dir", + "ci/host_release_benchmarks", "--runtime-mode", "release", "--prebuilt-dart-sdk", @@ -15,9 +17,9 @@ "--rbe", "--no-goma" ], - "name": "host_release", + "name": "ci/host_release_benchmarks", "ninja": { - "config": "host_release", + "config": "ci/host_release_benchmarks", "targets": [ "flutter/build/dart:copy_dart_sdk", "flutter/display_list:display_list_benchmarks", @@ -40,7 +42,10 @@ { "language": "bash", "name": "Generate metrics test", - "script": "flutter/testing/benchmark/generate_metrics.sh" + "script": "flutter/testing/benchmark/generate_metrics.sh", + "parameters": [ + "ci/host_release_benchmarks" + ] }, { "contexts": [ @@ -48,7 +53,10 @@ ], "language": "bash", "name": "Upload metrics", - "script": "flutter/testing/benchmark/upload_metrics.sh" + "script": "flutter/testing/benchmark/upload_metrics.sh", + "parameters": [ + "ci/host_release_benchmarks" + ] } ] } diff --git a/ci/builders/standalone/windows_unopt.json b/ci/builders/standalone/windows_unopt.json index 3af977a0ef960..317d7ecc1e9a9 100644 --- a/ci/builders/standalone/windows_unopt.json +++ b/ci/builders/standalone/windows_unopt.json @@ -14,6 +14,8 @@ } ], "gn": [ + "--target-dir", + "ci/host_debug_unopt", "--runtime-mode", "debug", "--unoptimized", @@ -21,9 +23,9 @@ "--rbe", "--no-goma" ], - "name": "host_debug_unopt", + "name": "ci\\host_debug_unopt", "ninja": { - "config": "host_debug_unopt" + "config": "ci/host_debug_unopt" }, "tests": [ { @@ -32,7 +34,7 @@ "script": "flutter/testing/run_tests.py", "parameters": [ "--variant", - "host_debug_unopt", + "ci/host_debug_unopt", "--type", "engine", "--engine-capture-core-dump" diff --git a/ci/builders/windows_android_aot_engine.json b/ci/builders/windows_android_aot_engine.json index a35dad5ea2777..b1bc13d4438b4 100644 --- a/ci/builders/windows_android_aot_engine.json +++ b/ci/builders/windows_android_aot_engine.json @@ -3,12 +3,12 @@ { "archives": [ { - "base_path": "out/android_profile/zip_archives/", + "base_path": "out/ci/android_profile/zip_archives/", "type": "gcs", "include_paths": [ - "out/android_profile/zip_archives/android-arm-profile/windows-x64.zip" + "out/ci/android_profile/zip_archives/android-arm-profile/windows-x64.zip" ], - "name": "android_profile", + "name": "ci/android_profile", "realm": "production" } ], @@ -20,15 +20,17 @@ "use_rbe": true }, "gn": [ + "--target-dir", + "ci/android_profile", "--runtime-mode", "profile", "--android", "--no-goma", "--rbe" ], - "name": "android_profile", + "name": "ci\\android_profile", "ninja": { - "config": "android_profile", + "config": "ci/android_profile", "targets": [ "flutter/build/archives:archive_win_gen_snapshot" ] @@ -37,12 +39,12 @@ { "archives": [ { - "base_path": "out/android_profile_arm64/zip_archives/", + "base_path": "out/ci/android_profile_arm64/zip_archives/", "type": "gcs", "include_paths": [ - "out/android_profile_arm64/zip_archives/android-arm64-profile/windows-x64.zip" + "out/ci/android_profile_arm64/zip_archives/android-arm64-profile/windows-x64.zip" ], - "name": "android_profile_arm64", + "name": "ci/android_profile_arm64", "realm": "production" } ], @@ -54,6 +56,8 @@ "use_rbe": true }, "gn": [ + "--target-dir", + "ci/android_profile_arm64", "--runtime-mode", "profile", "--android", @@ -61,9 +65,9 @@ "--no-goma", "--rbe" ], - "name": "android_profile_arm64", + "name": "ci\\android_profile_arm64", "ninja": { - "config": "android_profile_arm64", + "config": "ci/android_profile_arm64", "targets": [ "flutter/build/archives:archive_win_gen_snapshot" ] @@ -72,12 +76,12 @@ { "archives": [ { - "base_path": "out/android_profile_x64/zip_archives/", + "base_path": "out/ci/android_profile_x64/zip_archives/", "type": "gcs", "include_paths": [ - "out/android_profile_x64/zip_archives/android-x64-profile/windows-x64.zip" + "out/ci/android_profile_x64/zip_archives/android-x64-profile/windows-x64.zip" ], - "name": "android_profile_x64", + "name": "ci/android_profile_x64", "realm": "production" } ], @@ -89,6 +93,8 @@ "use_rbe": true }, "gn": [ + "--target-dir", + "ci/android_profile_x64", "--runtime-mode", "profile", "--android", @@ -96,9 +102,9 @@ "--no-goma", "--rbe" ], - "name": "android_profile_x64", + "name": "ci\\android_profile_x64", "ninja": { - "config": "android_profile_x64", + "config": "ci/android_profile_x64", "targets": [ "flutter/build/archives:archive_win_gen_snapshot" ] @@ -107,12 +113,12 @@ { "archives": [ { - "base_path": "out/android_release/zip_archives/", + "base_path": "out/ci/android_release/zip_archives/", "type": "gcs", "include_paths": [ - "out/android_release/zip_archives/android-arm-release/windows-x64.zip" + "out/ci/android_release/zip_archives/android-arm-release/windows-x64.zip" ], - "name": "android_release", + "name": "ci/android_release", "realm": "production" } ], @@ -124,15 +130,17 @@ "use_rbe": true }, "gn": [ + "--target-dir", + "ci/android_release", "--runtime-mode", "release", "--android", "--no-goma", "--rbe" ], - "name": "android_release", + "name": "ci\\android_release", "ninja": { - "config": "android_release", + "config": "ci/android_release", "targets": [ "flutter/build/archives:archive_win_gen_snapshot" ] @@ -141,12 +149,12 @@ { "archives": [ { - "base_path": "out/android_release_arm64/zip_archives/", + "base_path": "out/ci/android_release_arm64/zip_archives/", "type": "gcs", "include_paths": [ - "out/android_release_arm64/zip_archives/android-arm64-release/windows-x64.zip" + "out/ci/android_release_arm64/zip_archives/android-arm64-release/windows-x64.zip" ], - "name": "android_release_arm64", + "name": "ci/android_release_arm64", "realm": "production" } ], @@ -158,6 +166,8 @@ "use_rbe": true }, "gn": [ + "--target-dir", + "ci/android_release_arm64", "--runtime-mode", "release", "--android", @@ -165,9 +175,9 @@ "--no-goma", "--rbe" ], - "name": "android_release_arm64", + "name": "ci\\android_release_arm64", "ninja": { - "config": "android_release_arm64", + "config": "ci/android_release_arm64", "targets": [ "flutter/build/archives:archive_win_gen_snapshot" ] @@ -176,12 +186,12 @@ { "archives": [ { - "base_path": "out/android_release_x64/zip_archives/", + "base_path": "out/ci/android_release_x64/zip_archives/", "type": "gcs", "include_paths": [ - "out/android_release_x64/zip_archives/android-x64-release/windows-x64.zip" + "out/ci/android_release_x64/zip_archives/android-x64-release/windows-x64.zip" ], - "name": "android_release_x64", + "name": "ci/android_release_x64", "realm": "production" } ], @@ -193,6 +203,8 @@ "use_rbe": true }, "gn": [ + "--target-dir", + "ci/android_release_x64", "--runtime-mode", "release", "--android", @@ -200,9 +212,9 @@ "--no-goma", "--rbe" ], - "name": "android_release_x64", + "name": "ci\\android_release_x64", "ninja": { - "config": "android_release_x64", + "config": "ci/android_release_x64", "targets": [ "flutter/build/archives:archive_win_gen_snapshot" ] diff --git a/ci/builders/windows_arm_host_engine.json b/ci/builders/windows_arm_host_engine.json index 8912dade7ef74..e2f316e7fb66f 100644 --- a/ci/builders/windows_arm_host_engine.json +++ b/ci/builders/windows_arm_host_engine.json @@ -3,17 +3,17 @@ { "archives": [ { - "base_path": "out/host_debug_arm64/zip_archives/", + "base_path": "out/ci/host_debug_arm64/zip_archives/", "type": "gcs", "include_paths": [ - "out/host_debug_arm64/zip_archives/windows-arm64/artifacts.zip", - "out/host_debug_arm64/zip_archives/windows-arm64/windows-arm64-embedder.zip", - "out/host_debug_arm64/zip_archives/windows-arm64/font-subset.zip", - "out/host_debug_arm64/zip_archives/dart-sdk-windows-arm64.zip", - "out/host_debug_arm64/zip_archives/windows-arm64-debug/windows-arm64-flutter.zip", - "out/host_debug_arm64/zip_archives/windows-arm64/flutter-cpp-client-wrapper.zip" + "out/ci/host_debug_arm64/zip_archives/windows-arm64/artifacts.zip", + "out/ci/host_debug_arm64/zip_archives/windows-arm64/windows-arm64-embedder.zip", + "out/ci/host_debug_arm64/zip_archives/windows-arm64/font-subset.zip", + "out/ci/host_debug_arm64/zip_archives/dart-sdk-windows-arm64.zip", + "out/ci/host_debug_arm64/zip_archives/windows-arm64-debug/windows-arm64-flutter.zip", + "out/ci/host_debug_arm64/zip_archives/windows-arm64/flutter-cpp-client-wrapper.zip" ], - "name": "host_debug_arm64", + "name": "ci/host_debug_arm64", "realm": "production" } ], @@ -26,6 +26,8 @@ "use_rbe": true }, "gn": [ + "--target-dir", + "ci/host_debug_arm64", "--runtime-mode", "debug", "--no-lto", @@ -34,9 +36,9 @@ "--no-goma", "--rbe" ], - "name": "host_debug_arm64", + "name": "ci\\host_debug_arm64", "ninja": { - "config": "host_debug_arm64", + "config": "ci/host_debug_arm64", "targets": [ "flutter/build/archives:artifacts", "flutter/build/archives:embedder", @@ -50,12 +52,12 @@ { "archives": [ { - "base_path": "out/host_profile_arm64/zip_archives/", + "base_path": "out/ci/host_profile_arm64/zip_archives/", "type": "gcs", "include_paths": [ - "out/host_profile_arm64/zip_archives/windows-arm64-profile/windows-arm64-flutter.zip" + "out/ci/host_profile_arm64/zip_archives/windows-arm64-profile/windows-arm64-flutter.zip" ], - "name": "host_profile_arm64", + "name": "ci/host_profile_arm64", "realm": "production" } ], @@ -68,6 +70,8 @@ "use_rbe": true }, "gn": [ + "--target-dir", + "ci/host_profile_arm64", "--runtime-mode", "profile", "--no-lto", @@ -76,9 +80,9 @@ "--no-goma", "--rbe" ], - "name": "host_profile_arm64", + "name": "ci\\host_profile_arm64", "ninja": { - "config": "host_profile_arm64", + "config": "ci/host_profile_arm64", "targets": [ "windows", "gen_snapshot", @@ -89,12 +93,12 @@ { "archives": [ { - "base_path": "out/host_release_arm64/zip_archives/", + "base_path": "out/ci/host_release_arm64/zip_archives/", "type": "gcs", "include_paths": [ - "out/host_release_arm64/zip_archives/windows-arm64-release/windows-arm64-flutter.zip" + "out/ci/host_release_arm64/zip_archives/windows-arm64-release/windows-arm64-flutter.zip" ], - "name": "host_profile_arm64", + "name": "ci/host_profile_arm64", "realm": "production" } ], @@ -108,6 +112,8 @@ }, "generators": {}, "gn": [ + "--target-dir", + "ci/host_release_arm64", "--runtime-mode", "release", "--no-lto", @@ -116,9 +122,9 @@ "--no-goma", "--rbe" ], - "name": "host_release_arm64", + "name": "ci\\host_release_arm64", "ninja": { - "config": "host_release_arm64", + "config": "ci/host_release_arm64", "targets": [ "windows", "gen_snapshot", diff --git a/ci/builders/windows_host_engine.json b/ci/builders/windows_host_engine.json index bca555a9f4499..a8f1f99c89e76 100644 --- a/ci/builders/windows_host_engine.json +++ b/ci/builders/windows_host_engine.json @@ -3,17 +3,17 @@ { "archives": [ { - "base_path": "out/host_debug/zip_archives/", + "base_path": "out/ci/host_debug/zip_archives/", "type": "gcs", "include_paths": [ - "out/host_debug/zip_archives/windows-x64/artifacts.zip", - "out/host_debug/zip_archives/windows-x64/windows-x64-embedder.zip", - "out/host_debug/zip_archives/windows-x64/font-subset.zip", - "out/host_debug/zip_archives/dart-sdk-windows-x64.zip", - "out/host_debug/zip_archives/windows-x64-debug/windows-x64-flutter.zip", - "out/host_debug/zip_archives/windows-x64/flutter-cpp-client-wrapper.zip" + "out/ci/host_debug/zip_archives/windows-x64/artifacts.zip", + "out/ci/host_debug/zip_archives/windows-x64/windows-x64-embedder.zip", + "out/ci/host_debug/zip_archives/windows-x64/font-subset.zip", + "out/ci/host_debug/zip_archives/dart-sdk-windows-x64.zip", + "out/ci/host_debug/zip_archives/windows-x64-debug/windows-x64-flutter.zip", + "out/ci/host_debug/zip_archives/windows-x64/flutter-cpp-client-wrapper.zip" ], - "name": "host_debug", + "name": "ci/host_debug", "realm": "production" } ], @@ -26,15 +26,17 @@ "use_rbe": true }, "gn": [ + "--target-dir", + "ci/host_debug", "--runtime-mode", "debug", "--no-lto", "--no-goma", "--rbe" ], - "name": "host_debug", + "name": "ci\\host_debug", "ninja": { - "config": "host_debug", + "config": "ci/host_debug", "targets": [ "flutter:unittests", "flutter/build/archives:artifacts", @@ -52,7 +54,7 @@ "script": "flutter/testing/run_tests.py", "parameters": [ "--variant", - "host_debug", + "ci/host_debug", "--type", "engine" ] @@ -62,12 +64,12 @@ { "archives": [ { - "base_path": "out/host_profile/zip_archives/", + "base_path": "out/ci/host_profile/zip_archives/", "type": "gcs", "include_paths": [ - "out/host_profile/zip_archives/windows-x64-profile/windows-x64-flutter.zip" + "out/ci/host_profile/zip_archives/windows-x64-profile/windows-x64-flutter.zip" ], - "name": "host_profile", + "name": "ci/host_profile", "realm": "production" } ], @@ -80,15 +82,17 @@ "use_rbe": true }, "gn": [ + "--target-dir", + "ci/host_profile", "--runtime-mode", "profile", "--no-lto", "--no-goma", "--rbe" ], - "name": "host_profile", + "name": "ci\\host_profile", "ninja": { - "config": "host_profile", + "config": "ci/host_profile", "targets": [ "windows", "gen_snapshot", @@ -99,12 +103,12 @@ { "archives": [ { - "base_path": "out/host_release/zip_archives/", + "base_path": "out/ci/host_release/zip_archives/", "type": "gcs", "include_paths": [ - "out/host_release/zip_archives/windows-x64-release/windows-x64-flutter.zip" + "out/ci/host_release/zip_archives/windows-x64-release/windows-x64-flutter.zip" ], - "name": "host_release", + "name": "ci/host_release", "realm": "production" } ], @@ -118,15 +122,17 @@ }, "generators": {}, "gn": [ + "--target-dir", + "ci/host_release", "--runtime-mode", "release", "--no-lto", "--no-goma", "--rbe" ], - "name": "host_release", + "name": "ci\\host_release", "ninja": { - "config": "host_release", + "config": "ci/host_release", "targets": [ "windows", "gen_snapshot", diff --git a/ci/licenses.sh b/ci/licenses.sh index c971050b67cf4..1f59876fe6b94 100755 --- a/ci/licenses.sh +++ b/ci/licenses.sh @@ -174,7 +174,7 @@ function verify_licenses() ( local actualLicenseCount actualLicenseCount="$(tail -n 1 flutter/ci/licenses_golden/licenses_flutter | tr -dc '0-9')" - local expectedLicenseCount=879 # When changing this number: Update the error message below as well describing the newly expected license types. + local expectedLicenseCount=888 # When changing this number: Update the error message below as well describing the newly expected license types. if [[ $actualLicenseCount -ne $expectedLicenseCount ]]; then echo "=============================== ERROR ===============================" diff --git a/ci/licenses_golden/excluded_files b/ci/licenses_golden/excluded_files index 212e9095ac440..8b9f6e7ad663f 100644 --- a/ci/licenses_golden/excluded_files +++ b/ci/licenses_golden/excluded_files @@ -124,6 +124,7 @@ ../../../flutter/impeller/.clang-format ../../../flutter/impeller/.gitignore ../../../flutter/impeller/README.md +../../../flutter/impeller/aiks/aiks_blend_unittests.cc ../../../flutter/impeller/aiks/aiks_blur_unittests.cc ../../../flutter/impeller/aiks/aiks_gradient_unittests.cc ../../../flutter/impeller/aiks/aiks_path_unittests.cc @@ -144,6 +145,7 @@ ../../../flutter/impeller/display_list/skia_conversions_unittests.cc ../../../flutter/impeller/docs ../../../flutter/impeller/entity/contents/checkerboard_contents_unittests.cc +../../../flutter/impeller/entity/contents/clip_contents_unittests.cc ../../../flutter/impeller/entity/contents/content_context_unittests.cc ../../../flutter/impeller/entity/contents/filters/gaussian_blur_filter_contents_unittests.cc ../../../flutter/impeller/entity/contents/filters/inputs/filter_input_unittests.cc @@ -2154,10 +2156,38 @@ ../../../flutter/third_party/perfetto/ui/src/plugins/dev.perfetto.LargeScreensPerf/OWNERS ../../../flutter/third_party/perfetto/ui/src/test ../../../flutter/third_party/pkg/archive +../../../flutter/third_party/pkg/coverage/.git +../../../flutter/third_party/pkg/coverage/.github +../../../flutter/third_party/pkg/coverage/.gitignore +../../../flutter/third_party/pkg/coverage/AUTHORS +../../../flutter/third_party/pkg/coverage/CHANGELOG.md +../../../flutter/third_party/pkg/coverage/PATENTS +../../../flutter/third_party/pkg/coverage/README.md +../../../flutter/third_party/pkg/coverage/analysis_options.yaml +../../../flutter/third_party/pkg/coverage/benchmark/.gitignore +../../../flutter/third_party/pkg/coverage/pubspec.yaml +../../../flutter/third_party/pkg/coverage/test ../../../flutter/third_party/pkg/equatable ../../../flutter/third_party/pkg/flutter_packages ../../../flutter/third_party/pkg/gcloud ../../../flutter/third_party/pkg/googleapis +../../../flutter/third_party/pkg/io/.git +../../../flutter/third_party/pkg/io/.github +../../../flutter/third_party/pkg/io/.gitignore +../../../flutter/third_party/pkg/io/AUTHORS +../../../flutter/third_party/pkg/io/CHANGELOG.md +../../../flutter/third_party/pkg/io/README.md +../../../flutter/third_party/pkg/io/analysis_options.yaml +../../../flutter/third_party/pkg/io/example +../../../flutter/third_party/pkg/io/pubspec.yaml +../../../flutter/third_party/pkg/io/test +../../../flutter/third_party/pkg/node_preamble/.git +../../../flutter/third_party/pkg/node_preamble/.gitignore +../../../flutter/third_party/pkg/node_preamble/CHANGELOG.md +../../../flutter/third_party/pkg/node_preamble/README.md +../../../flutter/third_party/pkg/node_preamble/example +../../../flutter/third_party/pkg/node_preamble/package.json +../../../flutter/third_party/pkg/node_preamble/pubspec.yaml ../../../flutter/third_party/pkg/platform ../../../flutter/third_party/pkg/process ../../../flutter/third_party/pkg/process_runner diff --git a/ci/licenses_golden/licenses_dart b/ci/licenses_golden/licenses_dart index ece337df138dc..86e7aaf4e30a9 100644 --- a/ci/licenses_golden/licenses_dart +++ b/ci/licenses_golden/licenses_dart @@ -4737,7 +4737,7 @@ Exhibit B - "Incompatible With Secondary Licenses" Notice This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0. -You may obtain a copy of this library's Source Code Form from: https://dart.googlesource.com/sdk/+/bb65648e20e29abc47acf3dd984518d29fd625c3 +You may obtain a copy of this library's Source Code Form from: https://dart.googlesource.com/sdk/+/b735974580e7ad7461abf8175fec2fb27fc8b8e7 /third_party/fallback_root_certificates/ ==================================================================================================== diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index feb7ecc75b01c..5ee86025aa619 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -35030,6 +35030,42 @@ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================================================== +==================================================================================================== +LIBRARY: pkg +ORIGIN: ../../../flutter/third_party/pkg/coverage/bin/format_coverage.dart + ../../../flutter/third_party/pkg/coverage/LICENSE +TYPE: LicenseType.bsd +FILE: ../../../flutter/third_party/pkg/coverage/bin/format_coverage.dart +---------------------------------------------------------------------------------------------------- +Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file +for details. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +==================================================================================================== + ==================================================================================================== LIBRARY: khronos ORIGIN: ../../../flutter/third_party/angle/src/third_party/khronos/GL/wglext.h @@ -35447,6 +35483,54 @@ use or other dealings in these Data Files or Software without prior written authorization of the copyright holder. ==================================================================================================== +==================================================================================================== +LIBRARY: pkg +ORIGIN: ../../../flutter/third_party/pkg/coverage/bin/collect_coverage.dart + ../../../flutter/third_party/pkg/coverage/LICENSE +ORIGIN: ../../../flutter/third_party/pkg/coverage/lib/coverage.dart + ../../../flutter/third_party/pkg/coverage/LICENSE +ORIGIN: ../../../flutter/third_party/pkg/coverage/lib/src/collect.dart + ../../../flutter/third_party/pkg/coverage/LICENSE +ORIGIN: ../../../flutter/third_party/pkg/coverage/lib/src/formatter.dart + ../../../flutter/third_party/pkg/coverage/LICENSE +ORIGIN: ../../../flutter/third_party/pkg/coverage/lib/src/hitmap.dart + ../../../flutter/third_party/pkg/coverage/LICENSE +ORIGIN: ../../../flutter/third_party/pkg/coverage/lib/src/resolver.dart + ../../../flutter/third_party/pkg/coverage/LICENSE +ORIGIN: ../../../flutter/third_party/pkg/coverage/lib/src/util.dart + ../../../flutter/third_party/pkg/coverage/LICENSE +TYPE: LicenseType.bsd +FILE: ../../../flutter/third_party/pkg/coverage/bin/collect_coverage.dart +FILE: ../../../flutter/third_party/pkg/coverage/lib/coverage.dart +FILE: ../../../flutter/third_party/pkg/coverage/lib/src/collect.dart +FILE: ../../../flutter/third_party/pkg/coverage/lib/src/formatter.dart +FILE: ../../../flutter/third_party/pkg/coverage/lib/src/hitmap.dart +FILE: ../../../flutter/third_party/pkg/coverage/lib/src/resolver.dart +FILE: ../../../flutter/third_party/pkg/coverage/lib/src/util.dart +---------------------------------------------------------------------------------------------------- +Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file +for details. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +==================================================================================================== + ==================================================================================================== LIBRARY: glslang ORIGIN: ../../../flutter/third_party/vulkan-deps/glslang/src/SPIRV/GLSL.ext.AMD.h @@ -35621,6 +35705,64 @@ FROM,OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. ==================================================================================================== +==================================================================================================== +LIBRARY: pkg +ORIGIN: ../../../flutter/third_party/pkg/node_preamble/LICENSE +TYPE: LicenseType.bsd +FILE: ../../../flutter/third_party/pkg/node_preamble/lib/preamble.dart +FILE: ../../../flutter/third_party/pkg/node_preamble/lib/preamble.js +FILE: ../../../flutter/third_party/pkg/node_preamble/lib/preamble.min.js +FILE: ../../../flutter/third_party/pkg/node_preamble/tool/minify.js +FILE: ../../../flutter/third_party/pkg/node_preamble/yarn.lock +---------------------------------------------------------------------------------------------------- +Copyright (c) 2015 Michael Bullington + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + + +Copyright 2012, the Dart project authors. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +==================================================================================================== + ==================================================================================================== LIBRARY: boringssl ORIGIN: ../../../flutter/third_party/boringssl/err_data.c @@ -35725,6 +35867,42 @@ use or other dealings in these Data Files or Software without prior written authorization of the copyright holder. ==================================================================================================== +==================================================================================================== +LIBRARY: pkg +ORIGIN: ../../../flutter/third_party/pkg/coverage/lib/src/run_and_collect.dart + ../../../flutter/third_party/pkg/coverage/LICENSE +TYPE: LicenseType.bsd +FILE: ../../../flutter/third_party/pkg/coverage/lib/src/run_and_collect.dart +---------------------------------------------------------------------------------------------------- +Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +for details. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +==================================================================================================== + ==================================================================================================== LIBRARY: fiat ORIGIN: ../../../flutter/third_party/boringssl/src/third_party/fiat/LICENSE @@ -35978,6 +36156,42 @@ OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ==================================================================================================== +==================================================================================================== +LIBRARY: pkg +ORIGIN: ../../../flutter/third_party/pkg/coverage/bin/run_and_collect.dart + ../../../flutter/third_party/pkg/coverage/LICENSE +TYPE: LicenseType.bsd +FILE: ../../../flutter/third_party/pkg/coverage/bin/run_and_collect.dart +---------------------------------------------------------------------------------------------------- +Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file +for details. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +==================================================================================================== + ==================================================================================================== LIBRARY: boringssl ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/hrss/asm/poly_rq_mul.S @@ -36491,6 +36705,42 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================================================== +==================================================================================================== +LIBRARY: pkg +ORIGIN: ../../../flutter/third_party/pkg/coverage/lib/src/chrome.dart + ../../../flutter/third_party/pkg/coverage/LICENSE +TYPE: LicenseType.bsd +FILE: ../../../flutter/third_party/pkg/coverage/lib/src/chrome.dart +---------------------------------------------------------------------------------------------------- +Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file +for details. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +==================================================================================================== + ==================================================================================================== LIBRARY: etc_decoder ORIGIN: ../../../flutter/third_party/angle/src/libANGLE/renderer/vulkan/shaders/src/third_party/etc_decoder/etc_decoder.h @@ -36791,6 +37041,46 @@ OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ==================================================================================================== +==================================================================================================== +LIBRARY: pkg +ORIGIN: ../../../flutter/third_party/pkg/coverage/benchmark/many_isolates.dart + ../../../flutter/third_party/pkg/coverage/LICENSE +ORIGIN: ../../../flutter/third_party/pkg/coverage/benchmark/run_benchmarks.dart + ../../../flutter/third_party/pkg/coverage/LICENSE +ORIGIN: ../../../flutter/third_party/pkg/coverage/bin/test_with_coverage.dart + ../../../flutter/third_party/pkg/coverage/LICENSE +TYPE: LicenseType.bsd +FILE: ../../../flutter/third_party/pkg/coverage/benchmark/many_isolates.dart +FILE: ../../../flutter/third_party/pkg/coverage/benchmark/run_benchmarks.dart +FILE: ../../../flutter/third_party/pkg/coverage/bin/test_with_coverage.dart +---------------------------------------------------------------------------------------------------- +Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file +for details. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +==================================================================================================== + ==================================================================================================== LIBRARY: boringssl ORIGIN: ../../../flutter/third_party/boringssl/src/crypto/cpu_aarch64_sysreg.c @@ -39747,11 +40037,8 @@ ORIGIN: ../../../flutter/impeller/entity/shaders/conical_gradient_fill.frag + .. ORIGIN: ../../../flutter/impeller/entity/shaders/conical_gradient_ssbo_fill.frag + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/shaders/debug/checkerboard.frag + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/shaders/debug/checkerboard.vert + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/impeller/entity/shaders/gaussian_blur/gaussian_blur.glsl + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/impeller/entity/shaders/gaussian_blur/gaussian_blur.vert + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/impeller/entity/shaders/gaussian_blur/gaussian_blur_noalpha_decal.frag + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/impeller/entity/shaders/gaussian_blur/gaussian_blur_noalpha_nodecal.frag + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/shaders/gaussian_blur/kernel.glsl + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/entity/shaders/gaussian_blur/kernel.vert + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/shaders/gaussian_blur/kernel_decal.frag + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/shaders/gaussian_blur/kernel_nodecal.frag + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/shaders/geometry/points.comp + ../../../flutter/LICENSE @@ -41822,6 +42109,7 @@ ORIGIN: ../../../flutter/shell/platform/windows/flutter_windows_texture_registra ORIGIN: ../../../flutter/shell/platform/windows/flutter_windows_texture_registrar.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/windows/flutter_windows_view.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/windows/flutter_windows_view.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/windows/flutter_windows_view_controller.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/windows/flutter_windows_view_controller.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/windows/keyboard_handler_base.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/windows/keyboard_key_channel_handler.cc + ../../../flutter/LICENSE @@ -41993,6 +42281,8 @@ ORIGIN: ../../../flutter/vulkan/vulkan_utilities.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/vulkan/vulkan_window.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/vulkan/vulkan_window.h + ../../../flutter/LICENSE TYPE: LicenseType.bsd +FILE: ../../../flutter/.clangd +FILE: ../../../flutter/.engine-release.version FILE: ../../../flutter/.pylintrc FILE: ../../../flutter/assets/asset_manager.cc FILE: ../../../flutter/assets/asset_manager.h @@ -42623,11 +42913,8 @@ FILE: ../../../flutter/impeller/entity/shaders/conical_gradient_fill.frag FILE: ../../../flutter/impeller/entity/shaders/conical_gradient_ssbo_fill.frag FILE: ../../../flutter/impeller/entity/shaders/debug/checkerboard.frag FILE: ../../../flutter/impeller/entity/shaders/debug/checkerboard.vert -FILE: ../../../flutter/impeller/entity/shaders/gaussian_blur/gaussian_blur.glsl -FILE: ../../../flutter/impeller/entity/shaders/gaussian_blur/gaussian_blur.vert -FILE: ../../../flutter/impeller/entity/shaders/gaussian_blur/gaussian_blur_noalpha_decal.frag -FILE: ../../../flutter/impeller/entity/shaders/gaussian_blur/gaussian_blur_noalpha_nodecal.frag FILE: ../../../flutter/impeller/entity/shaders/gaussian_blur/kernel.glsl +FILE: ../../../flutter/impeller/entity/shaders/gaussian_blur/kernel.vert FILE: ../../../flutter/impeller/entity/shaders/gaussian_blur/kernel_decal.frag FILE: ../../../flutter/impeller/entity/shaders/gaussian_blur/kernel_nodecal.frag FILE: ../../../flutter/impeller/entity/shaders/geometry/points.comp @@ -44729,6 +45016,7 @@ FILE: ../../../flutter/shell/platform/windows/flutter_windows_texture_registrar. FILE: ../../../flutter/shell/platform/windows/flutter_windows_texture_registrar.h FILE: ../../../flutter/shell/platform/windows/flutter_windows_view.cc FILE: ../../../flutter/shell/platform/windows/flutter_windows_view.h +FILE: ../../../flutter/shell/platform/windows/flutter_windows_view_controller.cc FILE: ../../../flutter/shell/platform/windows/flutter_windows_view_controller.h FILE: ../../../flutter/shell/platform/windows/keyboard_handler_base.h FILE: ../../../flutter/shell/platform/windows/keyboard_key_channel_handler.cc @@ -47018,6 +47306,58 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================================================== +==================================================================================================== +LIBRARY: pkg +ORIGIN: ../../../flutter/third_party/pkg/io/lib/ansi.dart + ../../../flutter/third_party/pkg/io/LICENSE +ORIGIN: ../../../flutter/third_party/pkg/io/lib/io.dart + ../../../flutter/third_party/pkg/io/LICENSE +ORIGIN: ../../../flutter/third_party/pkg/io/lib/src/ansi_code.dart + ../../../flutter/third_party/pkg/io/LICENSE +ORIGIN: ../../../flutter/third_party/pkg/io/lib/src/copy_path.dart + ../../../flutter/third_party/pkg/io/LICENSE +ORIGIN: ../../../flutter/third_party/pkg/io/lib/src/exit_code.dart + ../../../flutter/third_party/pkg/io/LICENSE +ORIGIN: ../../../flutter/third_party/pkg/io/lib/src/permissions.dart + ../../../flutter/third_party/pkg/io/LICENSE +ORIGIN: ../../../flutter/third_party/pkg/io/lib/src/process_manager.dart + ../../../flutter/third_party/pkg/io/LICENSE +ORIGIN: ../../../flutter/third_party/pkg/io/lib/src/shared_stdin.dart + ../../../flutter/third_party/pkg/io/LICENSE +ORIGIN: ../../../flutter/third_party/pkg/io/lib/src/shell_words.dart + ../../../flutter/third_party/pkg/io/LICENSE +TYPE: LicenseType.bsd +FILE: ../../../flutter/third_party/pkg/io/lib/ansi.dart +FILE: ../../../flutter/third_party/pkg/io/lib/io.dart +FILE: ../../../flutter/third_party/pkg/io/lib/src/ansi_code.dart +FILE: ../../../flutter/third_party/pkg/io/lib/src/copy_path.dart +FILE: ../../../flutter/third_party/pkg/io/lib/src/exit_code.dart +FILE: ../../../flutter/third_party/pkg/io/lib/src/permissions.dart +FILE: ../../../flutter/third_party/pkg/io/lib/src/process_manager.dart +FILE: ../../../flutter/third_party/pkg/io/lib/src/shared_stdin.dart +FILE: ../../../flutter/third_party/pkg/io/lib/src/shell_words.dart +---------------------------------------------------------------------------------------------------- +Copyright 2017, the Dart project authors. Please see the AUTHORS file +for details. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +==================================================================================================== + ==================================================================================================== LIBRARY: libwebp ORIGIN: ../../../flutter/third_party/libwebp/src/dsp/cost_neon.c + ../../../flutter/third_party/libwebp/COPYING @@ -49651,6 +49991,42 @@ ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================================================== +==================================================================================================== +LIBRARY: pkg +ORIGIN: ../../../flutter/third_party/pkg/io/lib/src/charcodes.dart + ../../../flutter/third_party/pkg/io/LICENSE +TYPE: LicenseType.bsd +FILE: ../../../flutter/third_party/pkg/io/lib/src/charcodes.dart +---------------------------------------------------------------------------------------------------- +Copyright 2021, the Dart project authors. Please see the AUTHORS file +for details. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +==================================================================================================== + ==================================================================================================== LIBRARY: angle ORIGIN: ../../../flutter/third_party/angle/src/libANGLE/renderer/vulkan/linux/wayland/DisplayVkWayland.cpp + ../../../flutter/third_party/angle/LICENSE @@ -67150,4 +67526,4 @@ Disclaimer and license similar terms. ==================================================================================================== -Total license count: 879 +Total license count: 888 diff --git a/ci/licenses_golden/licenses_fuchsia b/ci/licenses_golden/licenses_fuchsia index 3d6812f74af6d..8a88f493c2451 100644 --- a/ci/licenses_golden/licenses_fuchsia +++ b/ci/licenses_golden/licenses_fuchsia @@ -1,4 +1,4 @@ -Signature: ed807d849045c39a1539be23d04d0e93 +Signature: a70195cba68c034d88ad6943cbcbd497 ==================================================================================================== LIBRARY: fuchsia_sdk diff --git a/ci/licenses_golden/licenses_skia b/ci/licenses_golden/licenses_skia index 0174f11d0237f..fb93ea6185333 100644 --- a/ci/licenses_golden/licenses_skia +++ b/ci/licenses_golden/licenses_skia @@ -1,4 +1,4 @@ -Signature: b49ce7cb754a2cffbb08ac9ebfebb98b +Signature: a5548041dd7d90f3a435c91bd478cbe6 ==================================================================================================== LIBRARY: etc1 diff --git a/display_list/testing/dl_test_surface_metal.cc b/display_list/testing/dl_test_surface_metal.cc index b7d7f7047bd41..def57908b4671 100644 --- a/display_list/testing/dl_test_surface_metal.cc +++ b/display_list/testing/dl_test_surface_metal.cc @@ -115,7 +115,7 @@ sk_sp DlMetalSurfaceProvider::MakeImpellerImage( void DlMetalSurfaceProvider::InitScreenShotter() const { if (!snapshotter_) { - snapshotter_.reset(new MetalScreenshotter()); + snapshotter_.reset(new MetalScreenshotter(/*enable_wide_gamut=*/false)); auto typographer = impeller::TypographerContextSkia::Make(); aiks_context_.reset(new impeller::AiksContext( snapshotter_->GetPlayground().GetContext(), typographer)); diff --git a/impeller/aiks/BUILD.gn b/impeller/aiks/BUILD.gn index 10b0a57c79f5e..0fc2354a1dd2f 100644 --- a/impeller/aiks/BUILD.gn +++ b/impeller/aiks/BUILD.gn @@ -85,6 +85,7 @@ impeller_component("context_spy") { template("aiks_unittests_component") { target_name = invoker.target_name predefined_sources = [ + "aiks_blend_unittests.cc", "aiks_blur_unittests.cc", "aiks_gradient_unittests.cc", "aiks_path_unittests.cc", diff --git a/impeller/aiks/aiks_blend_unittests.cc b/impeller/aiks/aiks_blend_unittests.cc new file mode 100644 index 0000000000000..b94bd5be6e453 --- /dev/null +++ b/impeller/aiks/aiks_blend_unittests.cc @@ -0,0 +1,525 @@ +// Copyright 2013 The Flutter 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 "flutter/impeller/aiks/aiks_unittests.h" + +#include "flutter/testing/testing.h" +#include "impeller/aiks/canvas.h" + +//////////////////////////////////////////////////////////////////////////////// +// This is for tests of Canvas that are interested the results of rendering +// blends. +//////////////////////////////////////////////////////////////////////////////// + +namespace impeller { +namespace testing { + +TEST_P(AiksTest, CanRenderAdvancedBlendColorFilterWithSaveLayer) { + Canvas canvas; + + Rect layer_rect = Rect::MakeXYWH(0, 0, 500, 500); + canvas.ClipRect(layer_rect); + + canvas.SaveLayer( + { + .color_filter = ColorFilter::MakeBlend(BlendMode::kDifference, + Color(0, 1, 0, 0.5)), + }, + layer_rect); + + Paint paint; + canvas.DrawPaint({.color = Color::Black()}); + canvas.DrawRect(Rect::MakeXYWH(100, 100, 300, 300), + {.color = Color::White()}); + canvas.Restore(); + + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + +TEST_P(AiksTest, BlendModeShouldCoverWholeScreen) { + Canvas canvas; + Paint paint; + + paint.color = Color::Red(); + canvas.DrawPaint(paint); + + paint.blend_mode = BlendMode::kSourceOver; + canvas.SaveLayer(paint); + + paint.color = Color::White(); + canvas.DrawRect(Rect::MakeXYWH(100, 100, 400, 400), paint); + + paint.blend_mode = BlendMode::kSource; + canvas.SaveLayer(paint); + + paint.color = Color::Blue(); + canvas.DrawRect(Rect::MakeXYWH(200, 200, 200, 200), paint); + + canvas.Restore(); + canvas.Restore(); + + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + +TEST_P(AiksTest, CanDrawPaintWithAdvancedBlend) { + Canvas canvas; + canvas.Scale(Vector2(0.2, 0.2)); + canvas.DrawPaint({.color = Color::MediumTurquoise()}); + canvas.DrawPaint({.color = Color::Color::OrangeRed().WithAlpha(0.5), + .blend_mode = BlendMode::kHue}); + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + +TEST_P(AiksTest, DrawPaintWithAdvancedBlendOverFilter) { + Paint filtered = { + .color = Color::Black(), + .mask_blur_descriptor = + Paint::MaskBlurDescriptor{ + .style = FilterContents::BlurStyle::kNormal, + .sigma = Sigma(60), + }, + }; + + Canvas canvas; + canvas.DrawPaint({.color = Color::White()}); + canvas.DrawCircle({300, 300}, 200, filtered); + canvas.DrawPaint({.color = Color::Green(), .blend_mode = BlendMode::kScreen}); + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + +TEST_P(AiksTest, DrawAdvancedBlendPartlyOffscreen) { + std::vector colors = {Color{0.9568, 0.2627, 0.2118, 1.0}, + Color{0.1294, 0.5882, 0.9529, 1.0}}; + std::vector stops = {0.0, 1.0}; + + Paint paint = { + .color_source = ColorSource::MakeLinearGradient( + {0, 0}, {100, 100}, std::move(colors), std::move(stops), + Entity::TileMode::kRepeat, Matrix::MakeScale(Vector3(0.3, 0.3, 0.3))), + .blend_mode = BlendMode::kLighten, + }; + + Canvas canvas; + canvas.DrawPaint({.color = Color::Blue()}); + canvas.Scale(Vector2(2, 2)); + canvas.ClipRect(Rect::MakeLTRB(0, 0, 200, 200)); + canvas.DrawCircle({100, 100}, 100, paint); + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + +TEST_P(AiksTest, PaintBlendModeIsRespected) { + Paint paint; + Canvas canvas; + // Default is kSourceOver. + paint.color = Color(1, 0, 0, 0.5); + canvas.DrawCircle(Point(150, 200), 100, paint); + paint.color = Color(0, 1, 0, 0.5); + canvas.DrawCircle(Point(250, 200), 100, paint); + + paint.blend_mode = BlendMode::kPlus; + paint.color = Color::Red(); + canvas.DrawCircle(Point(450, 250), 100, paint); + paint.color = Color::Green(); + canvas.DrawCircle(Point(550, 250), 100, paint); + paint.color = Color::Blue(); + canvas.DrawCircle(Point(500, 150), 100, paint); + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + +// Bug: https://github.com/flutter/flutter/issues/142549 +TEST_P(AiksTest, BlendModePlusAlphaWideGamut) { + if (GetParam() != PlaygroundBackend::kMetal) { + GTEST_SKIP_("This backend doesn't yet support wide gamut."); + } + EXPECT_EQ(GetContext()->GetCapabilities()->GetDefaultColorFormat(), + PixelFormat::kR16G16B16A16Float); + auto texture = CreateTextureForFixture("airplane.jpg", + /*enable_mipmapping=*/true); + + Canvas canvas; + canvas.Scale(GetContentScale()); + canvas.DrawPaint({.color = Color(0.9, 1.0, 0.9, 1.0)}); + canvas.SaveLayer({}); + Paint paint; + paint.blend_mode = BlendMode::kPlus; + paint.color = Color::Red(); + canvas.DrawRect(Rect::MakeXYWH(100, 100, 400, 400), paint); + paint.color = Color::White(); + canvas.DrawImageRect( + std::make_shared(texture), Rect::MakeSize(texture->GetSize()), + Rect::MakeXYWH(100, 100, 400, 400).Expand(-100, -100), paint); + canvas.Restore(); + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + +// Bug: https://github.com/flutter/flutter/issues/142549 +TEST_P(AiksTest, BlendModePlusAlphaColorFilterWideGamut) { + if (GetParam() != PlaygroundBackend::kMetal) { + GTEST_SKIP_("This backend doesn't yet support wide gamut."); + } + EXPECT_EQ(GetContext()->GetCapabilities()->GetDefaultColorFormat(), + PixelFormat::kR16G16B16A16Float); + auto texture = CreateTextureForFixture("airplane.jpg", + /*enable_mipmapping=*/true); + + Canvas canvas; + canvas.Scale(GetContentScale()); + canvas.DrawPaint({.color = Color(0.1, 0.2, 0.1, 1.0)}); + canvas.SaveLayer({ + .color_filter = + ColorFilter::MakeBlend(BlendMode::kPlus, Color(Vector4{1, 0, 0, 1})), + }); + Paint paint; + paint.color = Color::Red(); + canvas.DrawRect(Rect::MakeXYWH(100, 100, 400, 400), paint); + paint.color = Color::White(); + canvas.DrawImageRect( + std::make_shared(texture), Rect::MakeSize(texture->GetSize()), + Rect::MakeXYWH(100, 100, 400, 400).Expand(-100, -100), paint); + canvas.Restore(); + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + +#define BLEND_MODE_TUPLE(blend_mode) {#blend_mode, BlendMode::k##blend_mode}, + +struct BlendModeSelection { + std::vector blend_mode_names; + std::vector blend_mode_values; +}; + +static BlendModeSelection GetBlendModeSelection() { + std::vector blend_mode_names; + std::vector blend_mode_values; + { + const std::vector> blends = { + IMPELLER_FOR_EACH_BLEND_MODE(BLEND_MODE_TUPLE)}; + assert(blends.size() == + static_cast(Entity::kLastAdvancedBlendMode) + 1); + for (const auto& [name, mode] : blends) { + blend_mode_names.push_back(name); + blend_mode_values.push_back(mode); + } + } + + return {blend_mode_names, blend_mode_values}; +} + +TEST_P(AiksTest, ColorWheel) { + // Compare with https://fiddle.skia.org/c/@BlendModes + + BlendModeSelection blend_modes = GetBlendModeSelection(); + + auto draw_color_wheel = [](Canvas& canvas) { + /// color_wheel_sampler: r=0 -> fuchsia, r=2pi/3 -> yellow, r=4pi/3 -> + /// cyan domain: r >= 0 (because modulo used is non euclidean) + auto color_wheel_sampler = [](Radians r) { + Scalar x = r.radians / k2Pi + 1; + + // https://www.desmos.com/calculator/6nhjelyoaj + auto color_cycle = [](Scalar x) { + Scalar cycle = std::fmod(x, 6.0f); + return std::max(0.0f, std::min(1.0f, 2 - std::abs(2 - cycle))); + }; + return Color(color_cycle(6 * x + 1), // + color_cycle(6 * x - 1), // + color_cycle(6 * x - 3), // + 1); + }; + + Paint paint; + paint.blend_mode = BlendMode::kSourceOver; + + // Draw a fancy color wheel for the backdrop. + // https://www.desmos.com/calculator/xw7kafthwd + const int max_dist = 900; + for (int i = 0; i <= 900; i++) { + Radians r(kPhi / k2Pi * i); + Scalar distance = r.radians / std::powf(4.12, 0.0026 * r.radians); + Scalar normalized_distance = static_cast(i) / max_dist; + + paint.color = + color_wheel_sampler(r).WithAlpha(1.0f - normalized_distance); + Point position(distance * std::sin(r.radians), + -distance * std::cos(r.radians)); + + canvas.DrawCircle(position, 9 + normalized_distance * 3, paint); + } + }; + + std::shared_ptr color_wheel_image; + Matrix color_wheel_transform; + + auto callback = [&](AiksContext& renderer) -> std::optional { + // UI state. + static bool cache_the_wheel = true; + static int current_blend_index = 3; + static float dst_alpha = 1; + static float src_alpha = 1; + static Color color0 = Color::Red(); + static Color color1 = Color::Green(); + static Color color2 = Color::Blue(); + + if (AiksTest::ImGuiBegin("Controls", nullptr, + ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::Checkbox("Cache the wheel", &cache_the_wheel); + ImGui::ListBox("Blending mode", ¤t_blend_index, + blend_modes.blend_mode_names.data(), + blend_modes.blend_mode_names.size()); + ImGui::SliderFloat("Source alpha", &src_alpha, 0, 1); + ImGui::ColorEdit4("Color A", reinterpret_cast(&color0)); + ImGui::ColorEdit4("Color B", reinterpret_cast(&color1)); + ImGui::ColorEdit4("Color C", reinterpret_cast(&color2)); + ImGui::SliderFloat("Destination alpha", &dst_alpha, 0, 1); + ImGui::End(); + } + + static Point content_scale; + Point new_content_scale = GetContentScale(); + + if (!cache_the_wheel || new_content_scale != content_scale) { + content_scale = new_content_scale; + + // Render the color wheel to an image. + + Canvas canvas; + canvas.Scale(content_scale); + + canvas.Translate(Vector2(500, 400)); + canvas.Scale(Vector2(3, 3)); + + draw_color_wheel(canvas); + auto color_wheel_picture = canvas.EndRecordingAsPicture(); + auto snapshot = color_wheel_picture.Snapshot(renderer); + if (!snapshot.has_value() || !snapshot->texture) { + return std::nullopt; + } + color_wheel_image = std::make_shared(snapshot->texture); + color_wheel_transform = snapshot->transform; + } + + Canvas canvas; + + // Blit the color wheel backdrop to the screen with managed alpha. + canvas.SaveLayer({.color = Color::White().WithAlpha(dst_alpha), + .blend_mode = BlendMode::kSource}); + { + canvas.DrawPaint({.color = Color::White()}); + + canvas.Save(); + canvas.Transform(color_wheel_transform); + canvas.DrawImage(color_wheel_image, Point(), Paint()); + canvas.Restore(); + } + canvas.Restore(); + + canvas.Scale(content_scale); + canvas.Translate(Vector2(500, 400)); + canvas.Scale(Vector2(3, 3)); + + // Draw 3 circles to a subpass and blend it in. + canvas.SaveLayer( + {.color = Color::White().WithAlpha(src_alpha), + .blend_mode = blend_modes.blend_mode_values[current_blend_index]}); + { + Paint paint; + paint.blend_mode = BlendMode::kPlus; + const Scalar x = std::sin(k2Pi / 3); + const Scalar y = -std::cos(k2Pi / 3); + paint.color = color0; + canvas.DrawCircle(Point(-x, y) * 45, 65, paint); + paint.color = color1; + canvas.DrawCircle(Point(0, -1) * 45, 65, paint); + paint.color = color2; + canvas.DrawCircle(Point(x, y) * 45, 65, paint); + } + canvas.Restore(); + + return canvas.EndRecordingAsPicture(); + }; + + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + +TEST_P(AiksTest, ForegroundBlendSubpassCollapseOptimization) { + Canvas canvas; + + canvas.SaveLayer({ + .color_filter = + ColorFilter::MakeBlend(BlendMode::kColorDodge, Color::Red()), + }); + + canvas.Translate({500, 300, 0}); + canvas.Rotate(Radians(2 * kPi / 3)); + canvas.DrawRect(Rect::MakeXYWH(100, 100, 200, 200), {.color = Color::Blue()}); + + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + +TEST_P(AiksTest, ClearBlend) { + Canvas canvas; + Paint white; + white.color = Color::Blue(); + canvas.DrawRect(Rect::MakeXYWH(0, 0, 600.0, 600.0), white); + + Paint clear; + clear.blend_mode = BlendMode::kClear; + + canvas.DrawCircle(Point::MakeXY(300.0, 300.0), 200.0, clear); +} + +static Picture BlendModeTest(Vector2 content_scale, + BlendMode blend_mode, + const std::shared_ptr& src_image, + const std::shared_ptr& dst_image) { + Color destination_color = Color::CornflowerBlue().WithAlpha(0.75); + auto source_colors = std::vector({Color::White().WithAlpha(0.75), + Color::LimeGreen().WithAlpha(0.75), + Color::Black().WithAlpha(0.75)}); + + Canvas canvas; + canvas.DrawPaint({.color = Color::Black()}); + // TODO(bdero): Why does this cause the left image to double scale on high DPI + // displays. + // canvas.Scale(content_scale); + + //---------------------------------------------------------------------------- + /// 1. Save layer blending (top squares). + /// + + canvas.Save(); + for (const auto& color : source_colors) { + canvas.Save(); + { + canvas.ClipRect(Rect::MakeXYWH(25, 25, 100, 100)); + // Perform the blend in a SaveLayer so that the initial backdrop color is + // fully transparent black. SourceOver blend the result onto the parent + // pass. + canvas.SaveLayer({}); + { + canvas.DrawPaint({.color = destination_color}); + // Draw the source color in an offscreen pass and blend it to the parent + // pass. + canvas.SaveLayer({.blend_mode = blend_mode}); + { // + canvas.DrawRect(Rect::MakeXYWH(25, 25, 100, 100), {.color = color}); + } + canvas.Restore(); + } + canvas.Restore(); + } + canvas.Restore(); + canvas.Translate(Vector2(100, 0)); + } + canvas.RestoreToCount(0); + + //---------------------------------------------------------------------------- + /// 2. CPU blend modes (bottom squares). + /// + + canvas.Save(); + canvas.Translate({0, 100}); + // Perform the blend in a SaveLayer so that the initial backdrop color is + // fully transparent black. SourceOver blend the result onto the parent pass. + canvas.SaveLayer({}); + for (const auto& color : source_colors) { + // Simply write the CPU blended color to the pass. + canvas.DrawRect(Rect::MakeXYWH(25, 25, 100, 100), + {.color = destination_color.Blend(color, blend_mode), + .blend_mode = BlendMode::kSourceOver}); + canvas.Translate(Vector2(100, 0)); + } + canvas.Restore(); + canvas.Restore(); + + //---------------------------------------------------------------------------- + /// 3. Image blending (bottom images). + /// + /// Compare these results with the images in the Flutter blend mode + /// documentation: https://api.flutter.dev/flutter/dart-ui/BlendMode.html + /// + + canvas.Translate({0, 250}); + + // Draw grid behind the images. + canvas.DrawRect(Rect::MakeLTRB(0, 0, 800, 400), + {.color = Color::MakeRGBA8(41, 41, 41, 255)}); + Paint square_paint = {.color = Color::MakeRGBA8(15, 15, 15, 255)}; + for (int y = 0; y < 400 / 8; y++) { + for (int x = 0; x < 800 / 16; x++) { + canvas.DrawRect(Rect::MakeXYWH(x * 16 + (y % 2) * 8, y * 8, 8, 8), + square_paint); + } + } + + // Uploaded image source (left image). + canvas.Save(); + canvas.SaveLayer({.blend_mode = BlendMode::kSourceOver}); + { + canvas.DrawImage(dst_image, {0, 0}, {.blend_mode = BlendMode::kSourceOver}); + canvas.DrawImage(src_image, {0, 0}, {.blend_mode = blend_mode}); + } + canvas.Restore(); + canvas.Restore(); + + // Rendered image source (right image). + canvas.Save(); + canvas.SaveLayer({.blend_mode = BlendMode::kSourceOver}); + { + canvas.DrawImage(dst_image, {400, 0}, + {.blend_mode = BlendMode::kSourceOver}); + canvas.SaveLayer({.blend_mode = blend_mode}); + { + canvas.DrawImage(src_image, {400, 0}, + {.blend_mode = BlendMode::kSourceOver}); + } + canvas.Restore(); + } + canvas.Restore(); + canvas.Restore(); + + return canvas.EndRecordingAsPicture(); +} + +#define BLEND_MODE_TEST(blend_mode) \ + TEST_P(AiksTest, BlendMode##blend_mode) { \ + auto src_image = std::make_shared( \ + CreateTextureForFixture("blend_mode_src.png")); \ + auto dst_image = std::make_shared( \ + CreateTextureForFixture("blend_mode_dst.png")); \ + OpenPlaygroundHere(BlendModeTest( \ + GetContentScale(), BlendMode::k##blend_mode, src_image, dst_image)); \ + } +IMPELLER_FOR_EACH_BLEND_MODE(BLEND_MODE_TEST) + +TEST_P(AiksTest, CanDrawPaintMultipleTimesInteractive) { + auto modes = GetBlendModeSelection(); + + auto callback = [&](AiksContext& renderer) -> std::optional { + static Color background = Color::MediumTurquoise(); + static Color foreground = Color::Color::OrangeRed().WithAlpha(0.5); + static int current_blend_index = 3; + + if (AiksTest::ImGuiBegin("Controls", nullptr, + ImGuiWindowFlags_AlwaysAutoResize)) { + ImGui::ColorEdit4("Background", reinterpret_cast(&background)); + ImGui::ColorEdit4("Foreground", reinterpret_cast(&foreground)); + ImGui::ListBox("Blend mode", ¤t_blend_index, + modes.blend_mode_names.data(), + modes.blend_mode_names.size()); + ImGui::End(); + } + + Canvas canvas; + canvas.Scale(Vector2(0.2, 0.2)); + canvas.DrawPaint({.color = background}); + canvas.DrawPaint( + {.color = foreground, + .blend_mode = static_cast(current_blend_index)}); + return canvas.EndRecordingAsPicture(); + }; + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + +} // namespace testing +} // namespace impeller diff --git a/impeller/aiks/aiks_unittests.cc b/impeller/aiks/aiks_unittests.cc index 7df6ebd554cb6..96354f78d14c8 100644 --- a/impeller/aiks/aiks_unittests.cc +++ b/impeller/aiks/aiks_unittests.cc @@ -130,28 +130,6 @@ TEST_P(AiksTest, CanRenderColorFilterWithInvertColorsDrawPaint) { ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); } -TEST_P(AiksTest, CanRenderAdvancedBlendColorFilterWithSaveLayer) { - Canvas canvas; - - Rect layer_rect = Rect::MakeXYWH(0, 0, 500, 500); - canvas.ClipRect(layer_rect); - - canvas.SaveLayer( - { - .color_filter = ColorFilter::MakeBlend(BlendMode::kDifference, - Color(0, 1, 0, 0.5)), - }, - layer_rect); - - Paint paint; - canvas.DrawPaint({.color = Color::Black()}); - canvas.DrawRect(Rect::MakeXYWH(100, 100, 300, 300), - {.color = Color::White()}); - canvas.Restore(); - - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - namespace { bool GenerateMipmap(const std::shared_ptr& context, std::shared_ptr texture, @@ -517,31 +495,6 @@ TEST_P(AiksTest, CanEmptyPictureConvertToImage) { ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); } -TEST_P(AiksTest, BlendModeShouldCoverWholeScreen) { - Canvas canvas; - Paint paint; - - paint.color = Color::Red(); - canvas.DrawPaint(paint); - - paint.blend_mode = BlendMode::kSourceOver; - canvas.SaveLayer(paint); - - paint.color = Color::White(); - canvas.DrawRect(Rect::MakeXYWH(100, 100, 400, 400), paint); - - paint.blend_mode = BlendMode::kSource; - canvas.SaveLayer(paint); - - paint.color = Color::Blue(); - canvas.DrawRect(Rect::MakeXYWH(200, 200, 200, 200), paint); - - canvas.Restore(); - canvas.Restore(); - - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - TEST_P(AiksTest, CanRenderGroupOpacity) { Canvas canvas; @@ -984,258 +937,20 @@ TEST_P(AiksTest, CanDrawPaintMultipleTimes) { ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); } -TEST_P(AiksTest, CanDrawPaintWithAdvancedBlend) { - Canvas canvas; - canvas.Scale(Vector2(0.2, 0.2)); - canvas.DrawPaint({.color = Color::MediumTurquoise()}); - canvas.DrawPaint({.color = Color::Color::OrangeRed().WithAlpha(0.5), - .blend_mode = BlendMode::kHue}); - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -TEST_P(AiksTest, DrawPaintWithAdvancedBlendOverFilter) { - Paint filtered = { - .color = Color::Black(), - .mask_blur_descriptor = - Paint::MaskBlurDescriptor{ - .style = FilterContents::BlurStyle::kNormal, - .sigma = Sigma(60), - }, - }; - - Canvas canvas; - canvas.DrawPaint({.color = Color::White()}); - canvas.DrawCircle({300, 300}, 200, filtered); - canvas.DrawPaint({.color = Color::Green(), .blend_mode = BlendMode::kScreen}); - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -TEST_P(AiksTest, DrawAdvancedBlendPartlyOffscreen) { - std::vector colors = {Color{0.9568, 0.2627, 0.2118, 1.0}, - Color{0.1294, 0.5882, 0.9529, 1.0}}; - std::vector stops = {0.0, 1.0}; - - Paint paint = { - .color_source = ColorSource::MakeLinearGradient( - {0, 0}, {100, 100}, std::move(colors), std::move(stops), - Entity::TileMode::kRepeat, Matrix::MakeScale(Vector3(0.3, 0.3, 0.3))), - .blend_mode = BlendMode::kLighten, - }; - - Canvas canvas; - canvas.DrawPaint({.color = Color::Blue()}); - canvas.Scale(Vector2(2, 2)); - canvas.ClipRect(Rect::MakeLTRB(0, 0, 200, 200)); - canvas.DrawCircle({100, 100}, 100, paint); - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -#define BLEND_MODE_TUPLE(blend_mode) {#blend_mode, BlendMode::k##blend_mode}, - -struct BlendModeSelection { - std::vector blend_mode_names; - std::vector blend_mode_values; -}; - -static BlendModeSelection GetBlendModeSelection() { - std::vector blend_mode_names; - std::vector blend_mode_values; - { - const std::vector> blends = { - IMPELLER_FOR_EACH_BLEND_MODE(BLEND_MODE_TUPLE)}; - assert(blends.size() == - static_cast(Entity::kLastAdvancedBlendMode) + 1); - for (const auto& [name, mode] : blends) { - blend_mode_names.push_back(name); - blend_mode_values.push_back(mode); - } +// This makes sure the WideGamut named tests use 16bit float pixel format. +TEST_P(AiksTest, F16WideGamut) { + if (GetParam() != PlaygroundBackend::kMetal) { + GTEST_SKIP_("This backend doesn't yet support wide gamut."); } - - return {blend_mode_names, blend_mode_values}; -} - -TEST_P(AiksTest, CanDrawPaintMultipleTimesInteractive) { - auto modes = GetBlendModeSelection(); - - auto callback = [&](AiksContext& renderer) -> std::optional { - static Color background = Color::MediumTurquoise(); - static Color foreground = Color::Color::OrangeRed().WithAlpha(0.5); - static int current_blend_index = 3; - - if (AiksTest::ImGuiBegin("Controls", nullptr, - ImGuiWindowFlags_AlwaysAutoResize)) { - ImGui::ColorEdit4("Background", reinterpret_cast(&background)); - ImGui::ColorEdit4("Foreground", reinterpret_cast(&foreground)); - ImGui::ListBox("Blend mode", ¤t_blend_index, - modes.blend_mode_names.data(), - modes.blend_mode_names.size()); - ImGui::End(); - } - - Canvas canvas; - canvas.Scale(Vector2(0.2, 0.2)); - canvas.DrawPaint({.color = background}); - canvas.DrawPaint( - {.color = foreground, - .blend_mode = static_cast(current_blend_index)}); - return canvas.EndRecordingAsPicture(); - }; - ASSERT_TRUE(OpenPlaygroundHere(callback)); -} - -TEST_P(AiksTest, PaintBlendModeIsRespected) { - Paint paint; - Canvas canvas; - // Default is kSourceOver. - paint.color = Color(1, 0, 0, 0.5); - canvas.DrawCircle(Point(150, 200), 100, paint); - paint.color = Color(0, 1, 0, 0.5); - canvas.DrawCircle(Point(250, 200), 100, paint); - - paint.blend_mode = BlendMode::kPlus; - paint.color = Color::Red(); - canvas.DrawCircle(Point(450, 250), 100, paint); - paint.color = Color::Green(); - canvas.DrawCircle(Point(550, 250), 100, paint); - paint.color = Color::Blue(); - canvas.DrawCircle(Point(500, 150), 100, paint); - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); + EXPECT_EQ(GetContext()->GetCapabilities()->GetDefaultColorFormat(), + PixelFormat::kR16G16B16A16Float); + EXPECT_FALSE(IsAlphaClampedToOne( + GetContext()->GetCapabilities()->GetDefaultColorFormat())); } -TEST_P(AiksTest, ColorWheel) { - // Compare with https://fiddle.skia.org/c/@BlendModes - - BlendModeSelection blend_modes = GetBlendModeSelection(); - - auto draw_color_wheel = [](Canvas& canvas) { - /// color_wheel_sampler: r=0 -> fuchsia, r=2pi/3 -> yellow, r=4pi/3 -> - /// cyan domain: r >= 0 (because modulo used is non euclidean) - auto color_wheel_sampler = [](Radians r) { - Scalar x = r.radians / k2Pi + 1; - - // https://www.desmos.com/calculator/6nhjelyoaj - auto color_cycle = [](Scalar x) { - Scalar cycle = std::fmod(x, 6.0f); - return std::max(0.0f, std::min(1.0f, 2 - std::abs(2 - cycle))); - }; - return Color(color_cycle(6 * x + 1), // - color_cycle(6 * x - 1), // - color_cycle(6 * x - 3), // - 1); - }; - - Paint paint; - paint.blend_mode = BlendMode::kSourceOver; - - // Draw a fancy color wheel for the backdrop. - // https://www.desmos.com/calculator/xw7kafthwd - const int max_dist = 900; - for (int i = 0; i <= 900; i++) { - Radians r(kPhi / k2Pi * i); - Scalar distance = r.radians / std::powf(4.12, 0.0026 * r.radians); - Scalar normalized_distance = static_cast(i) / max_dist; - - paint.color = - color_wheel_sampler(r).WithAlpha(1.0f - normalized_distance); - Point position(distance * std::sin(r.radians), - -distance * std::cos(r.radians)); - - canvas.DrawCircle(position, 9 + normalized_distance * 3, paint); - } - }; - - std::shared_ptr color_wheel_image; - Matrix color_wheel_transform; - - auto callback = [&](AiksContext& renderer) -> std::optional { - // UI state. - static bool cache_the_wheel = true; - static int current_blend_index = 3; - static float dst_alpha = 1; - static float src_alpha = 1; - static Color color0 = Color::Red(); - static Color color1 = Color::Green(); - static Color color2 = Color::Blue(); - - if (AiksTest::ImGuiBegin("Controls", nullptr, - ImGuiWindowFlags_AlwaysAutoResize)) { - ImGui::Checkbox("Cache the wheel", &cache_the_wheel); - ImGui::ListBox("Blending mode", ¤t_blend_index, - blend_modes.blend_mode_names.data(), - blend_modes.blend_mode_names.size()); - ImGui::SliderFloat("Source alpha", &src_alpha, 0, 1); - ImGui::ColorEdit4("Color A", reinterpret_cast(&color0)); - ImGui::ColorEdit4("Color B", reinterpret_cast(&color1)); - ImGui::ColorEdit4("Color C", reinterpret_cast(&color2)); - ImGui::SliderFloat("Destination alpha", &dst_alpha, 0, 1); - ImGui::End(); - } - - static Point content_scale; - Point new_content_scale = GetContentScale(); - - if (!cache_the_wheel || new_content_scale != content_scale) { - content_scale = new_content_scale; - - // Render the color wheel to an image. - - Canvas canvas; - canvas.Scale(content_scale); - - canvas.Translate(Vector2(500, 400)); - canvas.Scale(Vector2(3, 3)); - - draw_color_wheel(canvas); - auto color_wheel_picture = canvas.EndRecordingAsPicture(); - auto snapshot = color_wheel_picture.Snapshot(renderer); - if (!snapshot.has_value() || !snapshot->texture) { - return std::nullopt; - } - color_wheel_image = std::make_shared(snapshot->texture); - color_wheel_transform = snapshot->transform; - } - - Canvas canvas; - - // Blit the color wheel backdrop to the screen with managed alpha. - canvas.SaveLayer({.color = Color::White().WithAlpha(dst_alpha), - .blend_mode = BlendMode::kSource}); - { - canvas.DrawPaint({.color = Color::White()}); - - canvas.Save(); - canvas.Transform(color_wheel_transform); - canvas.DrawImage(color_wheel_image, Point(), Paint()); - canvas.Restore(); - } - canvas.Restore(); - - canvas.Scale(content_scale); - canvas.Translate(Vector2(500, 400)); - canvas.Scale(Vector2(3, 3)); - - // Draw 3 circles to a subpass and blend it in. - canvas.SaveLayer( - {.color = Color::White().WithAlpha(src_alpha), - .blend_mode = blend_modes.blend_mode_values[current_blend_index]}); - { - Paint paint; - paint.blend_mode = BlendMode::kPlus; - const Scalar x = std::sin(k2Pi / 3); - const Scalar y = -std::cos(k2Pi / 3); - paint.color = color0; - canvas.DrawCircle(Point(-x, y) * 45, 65, paint); - paint.color = color1; - canvas.DrawCircle(Point(0, -1) * 45, 65, paint); - paint.color = color2; - canvas.DrawCircle(Point(x, y) * 45, 65, paint); - } - canvas.Restore(); - - return canvas.EndRecordingAsPicture(); - }; - - ASSERT_TRUE(OpenPlaygroundHere(callback)); +TEST_P(AiksTest, NotF16) { + EXPECT_TRUE(IsAlphaClampedToOne( + GetContext()->GetCapabilities()->GetDefaultColorFormat())); } TEST_P(AiksTest, TransformMultipliesCorrectly) { @@ -2166,21 +1881,6 @@ TEST_P(AiksTest, CollapsedDrawPaintInSubpassBackdropFilter) { ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); } -TEST_P(AiksTest, ForegroundBlendSubpassCollapseOptimization) { - Canvas canvas; - - canvas.SaveLayer({ - .color_filter = - ColorFilter::MakeBlend(BlendMode::kColorDodge, Color::Red()), - }); - - canvas.Translate({500, 300, 0}); - canvas.Rotate(Radians(2 * kPi / 3)); - canvas.DrawRect(Rect::MakeXYWH(100, 100, 200, 200), {.color = Color::Blue()}); - - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - TEST_P(AiksTest, ColorMatrixFilterSubpassCollapseOptimization) { Canvas canvas; @@ -2230,130 +1930,6 @@ TEST_P(AiksTest, SrgbToLinearFilterSubpassCollapseOptimization) { ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); } -static Picture BlendModeTest(Vector2 content_scale, - BlendMode blend_mode, - const std::shared_ptr& src_image, - const std::shared_ptr& dst_image) { - Color destination_color = Color::CornflowerBlue().WithAlpha(0.75); - auto source_colors = std::vector({Color::White().WithAlpha(0.75), - Color::LimeGreen().WithAlpha(0.75), - Color::Black().WithAlpha(0.75)}); - - Canvas canvas; - canvas.DrawPaint({.color = Color::Black()}); - // TODO(bdero): Why does this cause the left image to double scale on high DPI - // displays. - // canvas.Scale(content_scale); - - //---------------------------------------------------------------------------- - /// 1. Save layer blending (top squares). - /// - - canvas.Save(); - for (const auto& color : source_colors) { - canvas.Save(); - { - canvas.ClipRect(Rect::MakeXYWH(25, 25, 100, 100)); - // Perform the blend in a SaveLayer so that the initial backdrop color is - // fully transparent black. SourceOver blend the result onto the parent - // pass. - canvas.SaveLayer({}); - { - canvas.DrawPaint({.color = destination_color}); - // Draw the source color in an offscreen pass and blend it to the parent - // pass. - canvas.SaveLayer({.blend_mode = blend_mode}); - { // - canvas.DrawRect(Rect::MakeXYWH(25, 25, 100, 100), {.color = color}); - } - canvas.Restore(); - } - canvas.Restore(); - } - canvas.Restore(); - canvas.Translate(Vector2(100, 0)); - } - canvas.RestoreToCount(0); - - //---------------------------------------------------------------------------- - /// 2. CPU blend modes (bottom squares). - /// - - canvas.Save(); - canvas.Translate({0, 100}); - // Perform the blend in a SaveLayer so that the initial backdrop color is - // fully transparent black. SourceOver blend the result onto the parent pass. - canvas.SaveLayer({}); - for (const auto& color : source_colors) { - // Simply write the CPU blended color to the pass. - canvas.DrawRect(Rect::MakeXYWH(25, 25, 100, 100), - {.color = destination_color.Blend(color, blend_mode), - .blend_mode = BlendMode::kSourceOver}); - canvas.Translate(Vector2(100, 0)); - } - canvas.Restore(); - canvas.Restore(); - - //---------------------------------------------------------------------------- - /// 3. Image blending (bottom images). - /// - /// Compare these results with the images in the Flutter blend mode - /// documentation: https://api.flutter.dev/flutter/dart-ui/BlendMode.html - /// - - canvas.Translate({0, 250}); - - // Draw grid behind the images. - canvas.DrawRect(Rect::MakeLTRB(0, 0, 800, 400), - {.color = Color::MakeRGBA8(41, 41, 41, 255)}); - Paint square_paint = {.color = Color::MakeRGBA8(15, 15, 15, 255)}; - for (int y = 0; y < 400 / 8; y++) { - for (int x = 0; x < 800 / 16; x++) { - canvas.DrawRect(Rect::MakeXYWH(x * 16 + (y % 2) * 8, y * 8, 8, 8), - square_paint); - } - } - - // Uploaded image source (left image). - canvas.Save(); - canvas.SaveLayer({.blend_mode = BlendMode::kSourceOver}); - { - canvas.DrawImage(dst_image, {0, 0}, {.blend_mode = BlendMode::kSourceOver}); - canvas.DrawImage(src_image, {0, 0}, {.blend_mode = blend_mode}); - } - canvas.Restore(); - canvas.Restore(); - - // Rendered image source (right image). - canvas.Save(); - canvas.SaveLayer({.blend_mode = BlendMode::kSourceOver}); - { - canvas.DrawImage(dst_image, {400, 0}, - {.blend_mode = BlendMode::kSourceOver}); - canvas.SaveLayer({.blend_mode = blend_mode}); - { - canvas.DrawImage(src_image, {400, 0}, - {.blend_mode = BlendMode::kSourceOver}); - } - canvas.Restore(); - } - canvas.Restore(); - canvas.Restore(); - - return canvas.EndRecordingAsPicture(); -} - -#define BLEND_MODE_TEST(blend_mode) \ - TEST_P(AiksTest, BlendMode##blend_mode) { \ - auto src_image = std::make_shared( \ - CreateTextureForFixture("blend_mode_src.png")); \ - auto dst_image = std::make_shared( \ - CreateTextureForFixture("blend_mode_dst.png")); \ - OpenPlaygroundHere(BlendModeTest( \ - GetContentScale(), BlendMode::k##blend_mode, src_image, dst_image)); \ - } -IMPELLER_FOR_EACH_BLEND_MODE(BLEND_MODE_TEST) - TEST_P(AiksTest, TranslucentSaveLayerDrawsCorrectly) { Canvas canvas; @@ -3157,18 +2733,6 @@ TEST_P(AiksTest, VerticesGeometryUVPositionDataWithTranslate) { ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); } -TEST_P(AiksTest, ClearBlend) { - Canvas canvas; - Paint white; - white.color = Color::Blue(); - canvas.DrawRect(Rect::MakeXYWH(0, 0, 600.0, 600.0), white); - - Paint clear; - clear.blend_mode = BlendMode::kClear; - - canvas.DrawCircle(Point::MakeXY(300.0, 300.0), 200.0, clear); -} - TEST_P(AiksTest, MatrixImageFilterMagnify) { Canvas canvas; canvas.Scale(GetContentScale()); @@ -3458,6 +3022,57 @@ TEST_P(AiksTest, CanRenderClippedBackdropFilter) { ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); } +TEST_P(AiksTest, MipmapGenerationWorksCorrectly) { + TextureDescriptor texture_descriptor; + texture_descriptor.size = ISize{1024, 1024}; + texture_descriptor.format = PixelFormat::kR8G8B8A8UNormInt; + texture_descriptor.storage_mode = StorageMode::kHostVisible; + texture_descriptor.mip_count = texture_descriptor.size.MipCount(); + + std::vector bytes(4194304); + bool alternate = false; + for (auto i = 0u; i < 4194304; i += 4) { + if (alternate) { + bytes[i] = 255; + bytes[i + 1] = 0; + bytes[i + 2] = 0; + bytes[i + 3] = 255; + } else { + bytes[i] = 0; + bytes[i + 1] = 255; + bytes[i + 2] = 0; + bytes[i + 3] = 255; + } + alternate = !alternate; + } + + ASSERT_EQ(texture_descriptor.GetByteSizeOfBaseMipLevel(), bytes.size()); + auto mapping = std::make_shared( + bytes.data(), // data + texture_descriptor.GetByteSizeOfBaseMipLevel() // size + ); + auto texture = + GetContext()->GetResourceAllocator()->CreateTexture(texture_descriptor); + + ASSERT_TRUE(!!texture); + ASSERT_TRUE(texture->SetContents(mapping)); + + auto command_buffer = GetContext()->CreateCommandBuffer(); + auto blit_pass = command_buffer->CreateBlitPass(); + + blit_pass->GenerateMipmap(texture); + EXPECT_TRUE(blit_pass->EncodeCommands(GetContext()->GetResourceAllocator())); + EXPECT_TRUE(GetContext()->GetCommandQueue()->Submit({command_buffer}).ok()); + + auto image = std::make_shared(texture); + + Canvas canvas; + canvas.DrawImageRect(image, Rect::MakeSize(texture->GetSize()), + Rect::MakeLTRB(0, 0, 100, 100), {}); + + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + } // namespace testing } // namespace impeller @@ -3466,6 +3081,7 @@ TEST_P(AiksTest, CanRenderClippedBackdropFilter) { // â–ˆ the subdivisions of AiksTest to avoid having one massive file. // â–ˆ // â–ˆ Subdivisions: +// â–ˆ - aiks_blend_unittests.cc // â–ˆ - aiks_blur_unittests.cc // â–ˆ - aiks_gradient_unittests.cc // â–ˆ - aiks_path_unittests.cc diff --git a/impeller/compiler/shader_lib/impeller/color.glsl b/impeller/compiler/shader_lib/impeller/color.glsl index 310e22e779f76..c497f379fd550 100644 --- a/impeller/compiler/shader_lib/impeller/color.glsl +++ b/impeller/compiler/shader_lib/impeller/color.glsl @@ -46,4 +46,12 @@ f16vec4 IPHalfPremultiply(f16vec4 color) { return f16vec4(color.rgb * color.a, color.a); } +/// Performs the plus blend on `src` and `dst` which are premultiplied colors. +//`max` determines the values the results are clamped to. +f16vec4 IPHalfPlusBlend(f16vec4 src, f16vec4 dst) { + float16_t min = 0.0hf; + float16_t max = 1.0hf; + return clamp(dst + src, min, max); +} + #endif diff --git a/impeller/core/formats.h b/impeller/core/formats.h index aa859b45f6818..108cd454cd2fa 100644 --- a/impeller/core/formats.h +++ b/impeller/core/formats.h @@ -138,6 +138,14 @@ constexpr bool IsStencilWritable(PixelFormat format) { } } +/// Returns `true` if the pixel format has an implicit `clamp(x, 0, 1)` in the +/// pixel format. This is important for example when performing the `Plus` blend +/// where we don't want alpha values over 1.0. +constexpr bool IsAlphaClampedToOne(PixelFormat pixel_format) { + return !(pixel_format == PixelFormat::kR32G32B32A32Float || + pixel_format == PixelFormat::kR16G16B16A16Float); +} + constexpr const char* PixelFormatToString(PixelFormat format) { switch (format) { case PixelFormat::kUnknown: diff --git a/impeller/entity/BUILD.gn b/impeller/entity/BUILD.gn index 9e1a56a21a3c5..82b208479f146 100644 --- a/impeller/entity/BUILD.gn +++ b/impeller/entity/BUILD.gn @@ -25,11 +25,9 @@ impeller_shaders("entity_shaders") { "shaders/color_matrix_color_filter.frag", "shaders/color_matrix_color_filter.vert", "shaders/conical_gradient_fill.frag", - "shaders/gaussian_blur/gaussian_blur.vert", - "shaders/gaussian_blur/gaussian_blur_noalpha_decal.frag", - "shaders/gaussian_blur/gaussian_blur_noalpha_nodecal.frag", "shaders/gaussian_blur/kernel_decal.frag", "shaders/gaussian_blur/kernel_nodecal.frag", + "shaders/gaussian_blur/kernel.vert", "shaders/glyph_atlas.frag", "shaders/glyph_atlas_color.frag", "shaders/glyph_atlas.vert", @@ -267,6 +265,7 @@ impeller_component("entity_unittests") { sources = [ "contents/checkerboard_contents_unittests.cc", + "contents/clip_contents_unittests.cc", "contents/content_context_unittests.cc", "contents/filters/gaussian_blur_filter_contents_unittests.cc", "contents/filters/inputs/filter_input_unittests.cc", diff --git a/impeller/entity/contents/clip_contents.cc b/impeller/entity/contents/clip_contents.cc index 4897be855815a..c964a196d9852 100644 --- a/impeller/entity/contents/clip_contents.cc +++ b/impeller/entity/contents/clip_contents.cc @@ -89,6 +89,18 @@ bool ClipContents::RenderDepthClip(const ContentContext& renderer, const Geometry& geometry) const { using VS = ClipPipeline::VertexShader; + if (clip_op == Entity::ClipOperation::kIntersect && + geometry.IsAxisAlignedRect() && + entity.GetTransform().IsTranslationScaleOnly()) { + std::optional coverage = geometry.GetCoverage(entity.GetTransform()); + if (coverage.has_value() && + coverage->Contains(Rect::MakeSize(pass.GetRenderTargetSize()))) { + // Skip axis-aligned intersect clips that cover the whole render target + // since they won't draw anything to the depth buffer. + return true; + } + } + VS::FrameInfo info; info.depth = GetShaderClipDepth(entity); diff --git a/impeller/entity/contents/clip_contents_unittests.cc b/impeller/entity/contents/clip_contents_unittests.cc new file mode 100644 index 0000000000000..c7fc1dd9d92c1 --- /dev/null +++ b/impeller/entity/contents/clip_contents_unittests.cc @@ -0,0 +1,59 @@ +// Copyright 2013 The Flutter 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 +#include + +#include "gtest/gtest.h" + +#include "impeller/entity/contents/checkerboard_contents.h" +#include "impeller/entity/contents/clip_contents.h" +#include "impeller/entity/contents/content_context.h" +#include "impeller/entity/contents/contents.h" +#include "impeller/entity/contents/test/recording_render_pass.h" +#include "impeller/entity/entity.h" +#include "impeller/entity/entity_playground.h" +#include "impeller/renderer/render_target.h" + +namespace impeller { +namespace testing { + +using EntityTest = EntityPlayground; + +TEST_P(EntityTest, ClipContentsOptimizesFullScreenIntersectClips) { + if (!ContentContext::kEnableStencilThenCover) { + GTEST_SKIP(); + return; + } + + // Set up mock environment. + + auto content_context = GetContentContext(); + auto buffer = content_context->GetContext()->CreateCommandBuffer(); + auto render_target = + GetContentContext()->GetRenderTargetCache()->CreateOffscreenMSAA( + *content_context->GetContext(), {100, 100}, + /*mip_count=*/1); + auto render_pass = buffer->CreateRenderPass(render_target); + auto recording_pass = std::make_shared( + render_pass, GetContext(), render_target); + + // Set up clip contents. + + auto contents = std::make_shared(); + contents->SetClipOperation(Entity::ClipOperation::kIntersect); + contents->SetGeometry(Geometry::MakeCover()); + + Entity entity; + entity.SetContents(std::move(contents)); + + // Render the clip contents. + + ASSERT_TRUE(recording_pass->GetCommands().empty()); + ASSERT_TRUE(entity.Render(*content_context, *recording_pass)); + ASSERT_FALSE(recording_pass->GetCommands().empty()); +} + +} // namespace testing +} // namespace impeller diff --git a/impeller/entity/contents/content_context.cc b/impeller/entity/contents/content_context.cc index f500d1b75c14c..a127be44076c9 100644 --- a/impeller/entity/contents/content_context.cc +++ b/impeller/entity/contents/content_context.cc @@ -124,6 +124,8 @@ void ContentContextOptions::ApplyToPipelineDescriptor( color0.src_color_blend_factor = BlendFactor::kOneMinusDestinationAlpha; break; case BlendMode::kPlus: + // The kPlusAdvanced should be used instead. + FML_DCHECK(IsAlphaClampedToOne(color_attachment_pixel_format)); color0.dst_alpha_blend_factor = BlendFactor::kOne; color0.dst_color_blend_factor = BlendFactor::kOne; color0.src_alpha_blend_factor = BlendFactor::kOne; @@ -324,6 +326,10 @@ ContentContext::ContentContext( framebuffer_blend_lighten_pipelines_.CreateDefault( *context_, options_trianglestrip, {static_cast(BlendSelectValues::kLighten), supports_decal}); + framebuffer_blend_plus_advanced_pipelines_.CreateDefault( + *context_, options_trianglestrip, + {static_cast(BlendSelectValues::kPlusAdvanced), + supports_decal}); framebuffer_blend_luminosity_pipelines_.CreateDefault( *context_, options_trianglestrip, {static_cast(BlendSelectValues::kLuminosity), supports_decal}); @@ -371,6 +377,9 @@ ContentContext::ContentContext( blend_lighten_pipelines_.CreateDefault( *context_, options_trianglestrip, {static_cast(BlendSelectValues::kLighten), supports_decal}); + blend_plus_advanced_pipelines_.CreateDefault( + *context_, options_trianglestrip, + {static_cast(BlendSelectValues::kPlusAdvanced), supports_decal}); blend_luminosity_pipelines_.CreateDefault( *context_, options_trianglestrip, {static_cast(BlendSelectValues::kLuminosity), supports_decal}); @@ -396,10 +405,6 @@ ContentContext::ContentContext( texture_strict_src_pipelines_.CreateDefault(*context_, options); position_uv_pipelines_.CreateDefault(*context_, options); tiled_texture_pipelines_.CreateDefault(*context_, options); - gaussian_blur_noalpha_decal_pipelines_.CreateDefault(*context_, - options_trianglestrip); - gaussian_blur_noalpha_nodecal_pipelines_.CreateDefault(*context_, - options_trianglestrip); kernel_decal_pipelines_.CreateDefault(*context_, options_trianglestrip); kernel_nodecal_pipelines_.CreateDefault(*context_, options_trianglestrip); border_mask_blur_pipelines_.CreateDefault(*context_, options_trianglestrip); diff --git a/impeller/entity/contents/content_context.h b/impeller/entity/contents/content_context.h index c1eed6ea2fada..ab84526ba5827 100644 --- a/impeller/entity/contents/content_context.h +++ b/impeller/entity/contents/content_context.h @@ -67,9 +67,7 @@ #include "impeller/entity/yuv_to_rgb_filter.frag.h" #include "impeller/entity/yuv_to_rgb_filter.vert.h" -#include "impeller/entity/gaussian_blur.vert.h" -#include "impeller/entity/gaussian_blur_noalpha_decal.frag.h" -#include "impeller/entity/gaussian_blur_noalpha_nodecal.frag.h" +#include "impeller/entity/kernel.vert.h" #include "impeller/entity/kernel_decal.frag.h" #include "impeller/entity/kernel_nodecal.frag.h" @@ -139,16 +137,10 @@ using PositionUVPipeline = RenderPipelineT; using TiledTexturePipeline = RenderPipelineT; -using GaussianBlurDecalPipeline = - RenderPipelineT; -using GaussianBlurPipeline = - RenderPipelineT; using KernelDecalPipeline = - RenderPipelineT; + RenderPipelineT; using KernelPipeline = - RenderPipelineT; + RenderPipelineT; using BorderMaskBlurPipeline = RenderPipelineT; using MorphologyFilterPipeline = @@ -197,6 +189,8 @@ using BlendHuePipeline = RenderPipelineT; using BlendLightenPipeline = RenderPipelineT; +using BlendPlusAdvancedPipeline = + RenderPipelineT; using BlendLuminosityPipeline = RenderPipelineT; using BlendMultiplyPipeline = @@ -237,6 +231,9 @@ using FramebufferBlendHuePipeline = using FramebufferBlendLightenPipeline = RenderPipelineT; +using FramebufferBlendPlusAdvancedPipeline = + RenderPipelineT; using FramebufferBlendLuminosityPipeline = RenderPipelineT; @@ -518,16 +515,6 @@ class ContentContext { return GetPipeline(tiled_texture_pipelines_, opts); } - std::shared_ptr> GetGaussianBlurDecalPipeline( - ContentContextOptions opts) const { - return GetPipeline(gaussian_blur_noalpha_decal_pipelines_, opts); - } - - std::shared_ptr> GetGaussianBlurPipeline( - ContentContextOptions opts) const { - return GetPipeline(gaussian_blur_noalpha_nodecal_pipelines_, opts); - } - std::shared_ptr> GetKernelDecalPipeline( ContentContextOptions opts) const { return GetPipeline(kernel_decal_pipelines_, opts); @@ -640,6 +627,11 @@ class ContentContext { return GetPipeline(blend_lighten_pipelines_, opts); } + std::shared_ptr> GetBlendPlusAdvancedPipeline( + ContentContextOptions opts) const { + return GetPipeline(blend_plus_advanced_pipelines_, opts); + } + std::shared_ptr> GetBlendLuminosityPipeline( ContentContextOptions opts) const { return GetPipeline(blend_luminosity_pipelines_, opts); @@ -725,6 +717,12 @@ class ContentContext { return GetPipeline(framebuffer_blend_lighten_pipelines_, opts); } + std::shared_ptr> + GetFramebufferBlendPlusAdvancedPipeline(ContentContextOptions opts) const { + FML_DCHECK(GetDeviceCapabilities().SupportsFramebufferFetch()); + return GetPipeline(framebuffer_blend_plus_advanced_pipelines_, opts); + } + std::shared_ptr> GetFramebufferBlendLuminosityPipeline(ContentContextOptions opts) const { FML_DCHECK(GetDeviceCapabilities().SupportsFramebufferFetch()); @@ -961,10 +959,6 @@ class ContentContext { #endif // IMPELLER_ENABLE_OPENGLES mutable Variants position_uv_pipelines_; mutable Variants tiled_texture_pipelines_; - mutable Variants - gaussian_blur_noalpha_decal_pipelines_; - mutable Variants - gaussian_blur_noalpha_nodecal_pipelines_; mutable Variants kernel_decal_pipelines_; mutable Variants kernel_nodecal_pipelines_; mutable Variants border_mask_blur_pipelines_; @@ -989,6 +983,7 @@ class ContentContext { mutable Variants blend_hardlight_pipelines_; mutable Variants blend_hue_pipelines_; mutable Variants blend_lighten_pipelines_; + mutable Variants blend_plus_advanced_pipelines_; mutable Variants blend_luminosity_pipelines_; mutable Variants blend_multiply_pipelines_; mutable Variants blend_overlay_pipelines_; @@ -1014,6 +1009,8 @@ class ContentContext { framebuffer_blend_hue_pipelines_; mutable Variants framebuffer_blend_lighten_pipelines_; + mutable Variants + framebuffer_blend_plus_advanced_pipelines_; mutable Variants framebuffer_blend_luminosity_pipelines_; mutable Variants diff --git a/impeller/entity/contents/filters/blend_filter_contents.cc b/impeller/entity/contents/filters/blend_filter_contents.cc index 4aadfa3f5ba8f..968fddbf7082d 100644 --- a/impeller/entity/contents/filters/blend_filter_contents.cc +++ b/impeller/entity/contents/filters/blend_filter_contents.cc @@ -338,6 +338,9 @@ std::optional BlendFilterContents::CreateForegroundAdvancedBlend( case BlendMode::kColor: pass.SetPipeline(renderer.GetBlendColorPipeline(options)); break; + case BlendMode::kPlusAdvanced: + pass.SetPipeline(renderer.GetBlendPlusAdvancedPipeline(options)); + break; case BlendMode::kLuminosity: pass.SetPipeline(renderer.GetBlendLuminosityPipeline(options)); break; @@ -546,7 +549,7 @@ static std::optional PipelineBlend( #define BLEND_CASE(mode) \ case BlendMode::k##mode: \ - advanced_blend_proc_ = \ + return \ [](const FilterInput::Vector& inputs, const ContentContext& renderer, \ const Entity& entity, const Rect& coverage, BlendMode blend_mode, \ std::optional fg_color, \ @@ -559,6 +562,32 @@ static std::optional PipelineBlend( }; \ break; +namespace { +BlendFilterContents::AdvancedBlendProc GetAdvancedBlendProc( + BlendMode blend_mode) { + switch (blend_mode) { + BLEND_CASE(Screen) + BLEND_CASE(Overlay) + BLEND_CASE(Darken) + BLEND_CASE(Lighten) + BLEND_CASE(ColorDodge) + BLEND_CASE(ColorBurn) + BLEND_CASE(HardLight) + BLEND_CASE(SoftLight) + BLEND_CASE(Difference) + BLEND_CASE(Exclusion) + BLEND_CASE(Multiply) + BLEND_CASE(Hue) + BLEND_CASE(Saturation) + BLEND_CASE(Color) + BLEND_CASE(PlusAdvanced) + BLEND_CASE(Luminosity) + default: + FML_UNREACHABLE(); + } +} +} // namespace + void BlendFilterContents::SetBlendMode(BlendMode blend_mode) { if (blend_mode > Entity::kLastAdvancedBlendMode) { VALIDATION_LOG << "Invalid blend mode " << static_cast(blend_mode) @@ -566,28 +595,6 @@ void BlendFilterContents::SetBlendMode(BlendMode blend_mode) { } blend_mode_ = blend_mode; - - if (blend_mode > Entity::kLastPipelineBlendMode) { - switch (blend_mode) { - BLEND_CASE(Screen) - BLEND_CASE(Overlay) - BLEND_CASE(Darken) - BLEND_CASE(Lighten) - BLEND_CASE(ColorDodge) - BLEND_CASE(ColorBurn) - BLEND_CASE(HardLight) - BLEND_CASE(SoftLight) - BLEND_CASE(Difference) - BLEND_CASE(Exclusion) - BLEND_CASE(Multiply) - BLEND_CASE(Hue) - BLEND_CASE(Saturation) - BLEND_CASE(Color) - BLEND_CASE(Luminosity) - default: - FML_UNREACHABLE(); - } - } } void BlendFilterContents::SetForegroundColor(std::optional color) { @@ -611,21 +618,29 @@ std::optional BlendFilterContents::RenderFilter( std::nullopt, GetAbsorbOpacity(), GetAlpha()); } - if (blend_mode_ <= Entity::kLastPipelineBlendMode) { - return PipelineBlend(inputs, renderer, entity, coverage, blend_mode_, + BlendMode blend_mode = blend_mode_; + if (blend_mode == BlendMode::kPlus && + !IsAlphaClampedToOne( + renderer.GetContext()->GetCapabilities()->GetDefaultColorFormat())) { + blend_mode = BlendMode::kPlusAdvanced; + } + + if (blend_mode <= Entity::kLastPipelineBlendMode) { + return PipelineBlend(inputs, renderer, entity, coverage, blend_mode, foreground_color_, GetAbsorbOpacity(), GetAlpha()); } - if (blend_mode_ <= Entity::kLastAdvancedBlendMode) { + if (blend_mode <= Entity::kLastAdvancedBlendMode) { if (inputs.size() == 1 && foreground_color_.has_value() && GetAbsorbOpacity() == ColorFilterContents::AbsorbOpacity::kYes) { return CreateForegroundAdvancedBlend( inputs[0], renderer, entity, coverage, foreground_color_.value(), - blend_mode_, GetAlpha(), GetAbsorbOpacity()); + blend_mode, GetAlpha(), GetAbsorbOpacity()); } - return advanced_blend_proc_(inputs, renderer, entity, coverage, blend_mode_, - foreground_color_, GetAbsorbOpacity(), - GetAlpha()); + AdvancedBlendProc advanced_blend_proc = GetAdvancedBlendProc(blend_mode); + return advanced_blend_proc(inputs, renderer, entity, coverage, blend_mode, + foreground_color_, GetAbsorbOpacity(), + GetAlpha()); } FML_UNREACHABLE(); diff --git a/impeller/entity/contents/filters/blend_filter_contents.h b/impeller/entity/contents/filters/blend_filter_contents.h index 75f575f5b2aaf..5ecdd780c53d2 100644 --- a/impeller/entity/contents/filters/blend_filter_contents.h +++ b/impeller/entity/contents/filters/blend_filter_contents.h @@ -79,7 +79,6 @@ class BlendFilterContents : public ColorFilterContents { ColorFilterContents::AbsorbOpacity absorb_opacity) const; BlendMode blend_mode_ = BlendMode::kSourceOver; - AdvancedBlendProc advanced_blend_proc_; std::optional foreground_color_; BlendFilterContents(const BlendFilterContents&) = delete; diff --git a/impeller/entity/contents/framebuffer_blend_contents.cc b/impeller/entity/contents/framebuffer_blend_contents.cc index c4a75ddd03ab4..cee9b5a904e4c 100644 --- a/impeller/entity/contents/framebuffer_blend_contents.cc +++ b/impeller/entity/contents/framebuffer_blend_contents.cc @@ -118,6 +118,10 @@ bool FramebufferBlendContents::Render(const ContentContext& renderer, case BlendMode::kColor: pass.SetPipeline(renderer.GetFramebufferBlendColorPipeline(options)); break; + case BlendMode::kPlusAdvanced: + pass.SetPipeline( + renderer.GetFramebufferBlendPlusAdvancedPipeline(options)); + break; case BlendMode::kLuminosity: pass.SetPipeline(renderer.GetFramebufferBlendLuminosityPipeline(options)); break; diff --git a/impeller/entity/contents/framebuffer_blend_contents.h b/impeller/entity/contents/framebuffer_blend_contents.h index 5335055bd3f61..c8deee6450b25 100644 --- a/impeller/entity/contents/framebuffer_blend_contents.h +++ b/impeller/entity/contents/framebuffer_blend_contents.h @@ -27,6 +27,7 @@ enum class BlendSelectValues { kHue, kSaturation, kColor, + kPlusAdvanced, kLuminosity, }; diff --git a/impeller/entity/entity_pass.cc b/impeller/entity/entity_pass.cc index 5637fd8943313..89016da4e5209 100644 --- a/impeller/entity/entity_pass.cc +++ b/impeller/entity/entity_pass.cc @@ -970,6 +970,13 @@ bool EntityPass::OnRender( /// Setup advanced blends. /// + if (result.entity.GetBlendMode() == BlendMode::kPlus && + !IsAlphaClampedToOne(pass_context.GetPassTarget() + .GetRenderTarget() + .GetRenderTargetPixelFormat())) { + result.entity.SetBlendMode(BlendMode::kPlusAdvanced); + } + if (result.entity.GetBlendMode() > Entity::kLastPipelineBlendMode) { if (renderer.GetDeviceCapabilities().SupportsFramebufferFetch()) { auto src_contents = result.entity.GetContents(); diff --git a/impeller/entity/shaders/blending/advanced_blend.frag b/impeller/entity/shaders/blending/advanced_blend.frag index db444f522a092..07f72dab63af7 100644 --- a/impeller/entity/shaders/blending/advanced_blend.frag +++ b/impeller/entity/shaders/blending/advanced_blend.frag @@ -36,22 +36,33 @@ f16vec4 Sample(f16sampler2D texture_sampler, vec2 texture_coords) { } void main() { - f16vec4 dst = - IPHalfUnpremultiply(Sample(texture_sampler_dst, // sampler - v_dst_texture_coords // texture coordinates - )); - dst *= blend_info.dst_input_alpha; - f16vec4 src = blend_info.color_factor > 0.0hf - ? blend_info.color - : IPHalfUnpremultiply(Sample( - texture_sampler_src, // sampler - v_src_texture_coords // texture coordinates - )); - if (blend_info.color_factor == 0.0hf) { - src.a *= blend_info.src_input_alpha; - } + f16vec4 premultiplied_dst = + Sample(texture_sampler_dst, // sampler + v_dst_texture_coords // texture coordinates + ); + int nblend_type = int(blend_type); + + if (nblend_type == /*BlendSelectValues::kPlusAdvanced*/ 14) { + f16vec4 premultiplied_src = + Sample(texture_sampler_src, // sampler + v_src_texture_coords // texture coordinates + ); + frag_color = IPHalfPlusBlend(premultiplied_src, premultiplied_dst); + } else { + f16vec4 dst = IPHalfUnpremultiply(premultiplied_dst); + dst *= blend_info.dst_input_alpha; - f16vec3 blend_result = AdvancedBlend(dst.rgb, src.rgb, int(blend_type)); + f16vec4 src = blend_info.color_factor > 0.0hf + ? blend_info.color + : IPHalfUnpremultiply(Sample( + texture_sampler_src, // sampler + v_src_texture_coords // texture coordinates + )); + if (blend_info.color_factor == 0.0hf) { + src.a *= blend_info.src_input_alpha; + } - frag_color = IPApplyBlendedColor(dst, src, blend_result); + f16vec3 blend_result = AdvancedBlend(dst.rgb, src.rgb, nblend_type); + frag_color = IPApplyBlendedColor(dst, src, blend_result); + } } diff --git a/impeller/entity/shaders/blending/blend_select.glsl b/impeller/entity/shaders/blending/blend_select.glsl index 17c45c16fdcf4..b03e6eb520abd 100644 --- a/impeller/entity/shaders/blending/blend_select.glsl +++ b/impeller/entity/shaders/blending/blend_select.glsl @@ -20,6 +20,7 @@ // kHue, // kSaturation, // kColor, +// kPlusAdvanced, // kLuminosity, // Note, this isn't a switch as GLSL ES 1.0 does not support them. f16vec3 AdvancedBlend(f16vec3 dst, f16vec3 src, int blend_type) { @@ -65,7 +66,9 @@ f16vec3 AdvancedBlend(f16vec3 dst, f16vec3 src, int blend_type) { if (blend_type == 13) { return IPBlendColor(dst, src); } - if (blend_type == 14) { + // 14 is `PlusAdvanced`, it's handled in advanced_blend.frag and + // framebuffer_blend.frag. + if (blend_type == 15) { return IPBlendLuminosity(dst, src); } return f16vec3(0.0hf); diff --git a/impeller/entity/shaders/blending/framebuffer_blend.frag b/impeller/entity/shaders/blending/framebuffer_blend.frag index 6d03856b4a824..1eba2de9a9b3a 100644 --- a/impeller/entity/shaders/blending/framebuffer_blend.frag +++ b/impeller/entity/shaders/blending/framebuffer_blend.frag @@ -43,14 +43,21 @@ vec4 Sample(sampler2D texture_sampler, vec2 texture_coords) { } void main() { - f16vec4 dst = IPHalfUnpremultiply(f16vec4(ReadDestination())); - f16vec4 src = IPHalfUnpremultiply( + f16vec4 premultiplied_dst = f16vec4(ReadDestination()); + f16vec4 premultiplied_src = f16vec4(Sample(texture_sampler_src, // sampler v_src_texture_coords // texture coordinates - ))); - src.a *= frag_info.src_input_alpha; - - f16vec3 blend_result = AdvancedBlend(dst.rgb, src.rgb, int(blend_type)); - - frag_color = IPApplyBlendedColor(dst, src, blend_result); + )); + int nblend_type = int(blend_type); + + if (nblend_type == /*BlendSelectValues::kPlusAdvanced*/ 14) { + frag_color = IPHalfPlusBlend(premultiplied_src, premultiplied_dst); + } else { + f16vec4 dst = IPHalfUnpremultiply(premultiplied_dst); + f16vec4 src = IPHalfUnpremultiply(premultiplied_src); + src.a *= frag_info.src_input_alpha; + + f16vec3 blend_result = AdvancedBlend(dst.rgb, src.rgb, nblend_type); + frag_color = IPApplyBlendedColor(dst, src, blend_result); + } } diff --git a/impeller/entity/shaders/gaussian_blur/gaussian_blur.glsl b/impeller/entity/shaders/gaussian_blur/gaussian_blur.glsl deleted file mode 100644 index ebf135a482328..0000000000000 --- a/impeller/entity/shaders/gaussian_blur/gaussian_blur.glsl +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// 1D (directional) gaussian blur. -// -// Paths for future optimization: -// * Remove the uv bounds multiplier in SampleColor by adding optional -// support for SamplerAddressMode::ClampToBorder in the texture sampler. -// * Render both blur passes into a smaller texture than the source image -// (~1/radius size). -// * If doing the small texture render optimization, cache misses can be -// reduced in the first pass by sampling the source textures with a mip -// level of log2(min_radius). - -#include -#include -#include -#include - -uniform f16sampler2D texture_sampler; - -uniform BlurInfo { - f16vec2 blur_uv_offset; - - // The blur sigma and radius have a linear relationship which is defined - // host-side, but both are useful controls here. Sigma (pixels per standard - // deviation) is used to define the gaussian function itself, whereas the - // radius is used to limit how much of the function is integrated. - float blur_sigma; - float16_t blur_radius; - float16_t step_size; -} -blur_info; - -f16vec4 Sample(f16sampler2D tex, vec2 coords) { -#if ENABLE_DECAL_SPECIALIZATION - return IPHalfSampleDecal(tex, coords); -#else - return texture(tex, coords); -#endif -} - -in vec2 v_texture_coords; - -out f16vec4 frag_color; - -void main() { - f16vec4 total_color = f16vec4(0.0hf); - float16_t gaussian_integral = 0.0hf; - - // Step by 2.0 as a performance optimization, relying on bilinear filtering in - // the sampler to blend the texels. Typically the space between pixels is - // calculated so their blended amounts match the gaussian coefficients. This - // just uses 0.5 as an optimization until the gaussian coefficients are - // calculated and passed in from the cpu. - for (float16_t i = -blur_info.blur_radius; i <= blur_info.blur_radius; - i += blur_info.step_size) { - // Use the 32 bit Gaussian function because the 16 bit variation results in - // quality loss/visible banding. Also, 16 bit variation internally breaks - // down at a moderately high (but still reasonable) blur sigma of >255 when - // computing sigma^2 due to the exponent only having 5 bits. - float16_t gaussian = float16_t(IPGaussian(float(i), blur_info.blur_sigma)); - gaussian_integral += gaussian; - total_color += - gaussian * Sample(texture_sampler, // sampler - v_texture_coords + blur_info.blur_uv_offset * - i // texture coordinates - ); - } - - frag_color = total_color / gaussian_integral; -} diff --git a/impeller/entity/shaders/gaussian_blur/gaussian_blur_noalpha_decal.frag b/impeller/entity/shaders/gaussian_blur/gaussian_blur_noalpha_decal.frag deleted file mode 100644 index 7bb1045e19dbc..0000000000000 --- a/impeller/entity/shaders/gaussian_blur/gaussian_blur_noalpha_decal.frag +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -precision mediump float; - -#define ENABLE_DECAL_SPECIALIZATION 1 - -#include "gaussian_blur.glsl" diff --git a/impeller/entity/shaders/gaussian_blur/gaussian_blur_noalpha_nodecal.frag b/impeller/entity/shaders/gaussian_blur/gaussian_blur_noalpha_nodecal.frag deleted file mode 100644 index 79d708e1dae36..0000000000000 --- a/impeller/entity/shaders/gaussian_blur/gaussian_blur_noalpha_nodecal.frag +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -precision mediump float; - -#define ENABLE_DECAL_SPECIALIZATION 0 - -#include "gaussian_blur.glsl" diff --git a/impeller/entity/shaders/gaussian_blur/gaussian_blur.vert b/impeller/entity/shaders/gaussian_blur/kernel.vert similarity index 100% rename from impeller/entity/shaders/gaussian_blur/gaussian_blur.vert rename to impeller/entity/shaders/gaussian_blur/kernel.vert diff --git a/impeller/geometry/color.cc b/impeller/geometry/color.cc index d2565e6e6fd1d..a8c70e1862359 100644 --- a/impeller/geometry/color.cc +++ b/impeller/geometry/color.cc @@ -276,6 +276,8 @@ Color Color::Blend(Color src, BlendMode blend_mode) const { return (src.Premultiply() * (1 - dst.alpha) + dst.Premultiply() * (1 - src.alpha)) .Unpremultiply(); + case BlendMode::kPlusAdvanced: + [[fallthrough]]; case BlendMode::kPlus: // r = min(s + d, 1) return (Min(src.Premultiply() + dst.Premultiply(), 1)).Unpremultiply(); diff --git a/impeller/geometry/color.h b/impeller/geometry/color.h index c90c696066ed6..59805e952ec02 100644 --- a/impeller/geometry/color.h +++ b/impeller/geometry/color.h @@ -45,6 +45,7 @@ V(Hue) \ V(Saturation) \ V(Color) \ + V(PlusAdvanced) \ V(Luminosity) namespace impeller { @@ -91,6 +92,7 @@ enum class BlendMode : uint8_t { kHue, kSaturation, kColor, + kPlusAdvanced, kLuminosity, kLast = kLuminosity, diff --git a/impeller/geometry/geometry_unittests.cc b/impeller/geometry/geometry_unittests.cc index aa1e5910b77eb..af9a2a1cfe9df 100644 --- a/impeller/geometry/geometry_unittests.cc +++ b/impeller/geometry/geometry_unittests.cc @@ -6,6 +6,7 @@ #include "impeller/geometry/geometry_asserts.h" #include +#include #include #include @@ -1450,107 +1451,112 @@ struct ColorBlendTestData { Color::LimeGreen().WithAlpha(0.75), Color::Black().WithAlpha(0.75)}; - // THIS RESULT TABLE IS GENERATED! - // - // Uncomment the `GenerateColorBlendResults` test below to print a new table - // after making changes to `Color::Blend`. - static constexpr Color kExpectedResults - [sizeof(kSourceColors)] - [static_cast>(BlendMode::kLast) + 1] = { - { - {0, 0, 0, 0}, // Clear - {1, 1, 1, 0.75}, // Source - {0.392157, 0.584314, 0.929412, 0.75}, // Destination - {0.878431, 0.916863, 0.985882, 0.9375}, // SourceOver - {0.513726, 0.667451, 0.943529, 0.9375}, // DestinationOver - {1, 1, 1, 0.5625}, // SourceIn - {0.392157, 0.584314, 0.929412, 0.5625}, // DestinationIn - {1, 1, 1, 0.1875}, // SourceOut - {0.392157, 0.584314, 0.929412, 0.1875}, // DestinationOut - {0.848039, 0.896078, 0.982353, 0.75}, // SourceATop - {0.544118, 0.688235, 0.947059, 0.75}, // DestinationATop - {0.696078, 0.792157, 0.964706, 0.375}, // Xor - {1, 1, 1, 1}, // Plus - {0.392157, 0.584314, 0.929412, 0.5625}, // Modulate - {0.878431, 0.916863, 0.985882, 0.9375}, // Screen - {0.74902, 0.916863, 0.985882, 0.9375}, // Overlay - {0.513726, 0.667451, 0.943529, 0.9375}, // Darken - {0.878431, 0.916863, 0.985882, 0.9375}, // Lighten - {0.878431, 0.916863, 0.985882, 0.9375}, // ColorDodge - {0.513725, 0.667451, 0.943529, 0.9375}, // ColorBurn - {0.878431, 0.916863, 0.985882, 0.9375}, // HardLight - {0.654166, 0.775505, 0.964318, 0.9375}, // SoftLight - {0.643137, 0.566275, 0.428235, 0.9375}, // Difference - {0.643137, 0.566275, 0.428235, 0.9375}, // Exclusion - {0.513726, 0.667451, 0.943529, 0.9375}, // Multiply - {0.617208, 0.655639, 0.724659, 0.9375}, // Hue - {0.617208, 0.655639, 0.724659, 0.9375}, // Saturation - {0.617208, 0.655639, 0.724659, 0.9375}, // Color - {0.878431, 0.916863, 0.985882, 0.9375}, // Luminosity - }, - { - {0, 0, 0, 0}, // Clear - {0.196078, 0.803922, 0.196078, 0.75}, // Source - {0.392157, 0.584314, 0.929412, 0.75}, // Destination - {0.235294, 0.76, 0.342745, 0.9375}, // SourceOver - {0.352941, 0.628235, 0.782745, 0.9375}, // DestinationOver - {0.196078, 0.803922, 0.196078, 0.5625}, // SourceIn - {0.392157, 0.584314, 0.929412, 0.5625}, // DestinationIn - {0.196078, 0.803922, 0.196078, 0.1875}, // SourceOut - {0.392157, 0.584314, 0.929412, 0.1875}, // DestinationOut - {0.245098, 0.74902, 0.379412, 0.75}, // SourceATop - {0.343137, 0.639216, 0.746078, 0.75}, // DestinationATop - {0.294118, 0.694118, 0.562745, 0.375}, // Xor - {0.441176, 1, 0.844118, 1}, // Plus - {0.0768935, 0.469742, 0.182238, 0.5625}, // Modulate - {0.424452, 0.828743, 0.79105, 0.9375}, // Screen - {0.209919, 0.779839, 0.757001, 0.9375}, // Overlay - {0.235294, 0.628235, 0.342745, 0.9375}, // Darken - {0.352941, 0.76, 0.782745, 0.9375}, // Lighten - {0.41033, 0.877647, 0.825098, 0.9375}, // ColorDodge - {0.117647, 0.567403, 0.609098, 0.9375}, // ColorBurn - {0.209919, 0.779839, 0.443783, 0.9375}, // HardLight - {0.266006, 0.693915, 0.758818, 0.9375}, // SoftLight - {0.235294, 0.409412, 0.665098, 0.9375}, // Difference - {0.378316, 0.546897, 0.681707, 0.9375}, // Exclusion - {0.163783, 0.559493, 0.334441, 0.9375}, // Multiply - {0.266235, 0.748588, 0.373686, 0.9375}, // Hue - {0.339345, 0.629787, 0.811502, 0.9375}, // Saturation - {0.241247, 0.765953, 0.348698, 0.9375}, // Color - {0.346988, 0.622282, 0.776792, 0.9375}, // Luminosity - }, - { - {0, 0, 0, 0}, // Clear - {0, 0, 0, 0.75}, // Source - {0.392157, 0.584314, 0.929412, 0.75}, // Destination - {0.0784314, 0.116863, 0.185882, 0.9375}, // SourceOver - {0.313726, 0.467451, 0.743529, 0.9375}, // DestinationOver - {0, 0, 0, 0.5625}, // SourceIn - {0.392157, 0.584314, 0.929412, 0.5625}, // DestinationIn - {0, 0, 0, 0.1875}, // SourceOut - {0.392157, 0.584314, 0.929412, 0.1875}, // DestinationOut - {0.0980392, 0.146078, 0.232353, 0.75}, // SourceATop - {0.294118, 0.438235, 0.697059, 0.75}, // DestinationATop - {0.196078, 0.292157, 0.464706, 0.375}, // Xor - {0.294118, 0.438235, 0.697059, 1}, // Plus - {0, 0, 0, 0.5625}, // Modulate - {0.313726, 0.467451, 0.743529, 0.9375}, // Screen - {0.0784314, 0.218039, 0.701176, 0.9375}, // Overlay - {0.0784314, 0.116863, 0.185882, 0.9375}, // Darken - {0.313726, 0.467451, 0.743529, 0.9375}, // Lighten - {0.313726, 0.467451, 0.743529, 0.9375}, // ColorDodge - {0.0784314, 0.116863, 0.185882, 0.9375}, // ColorBurn - {0.0784314, 0.116863, 0.185882, 0.9375}, // HardLight - {0.170704, 0.321716, 0.704166, 0.9375}, // SoftLight - {0.313726, 0.467451, 0.743529, 0.9375}, // Difference - {0.313726, 0.467451, 0.743529, 0.9375}, // Exclusion - {0.0784314, 0.116863, 0.185882, 0.9375}, // Multiply - {0.417208, 0.455639, 0.524659, 0.9375}, // Hue - {0.417208, 0.455639, 0.524659, 0.9375}, // Saturation - {0.417208, 0.455639, 0.524659, 0.9375}, // Color - {0.0784314, 0.116863, 0.185882, 0.9375}, // Luminosity - }, - }; + static const std::map + kExpectedResults[sizeof(kSourceColors)]; +}; + +// THIS RESULT TABLE IS GENERATED! +// +// Uncomment the `GenerateColorBlendResults` test below to print a new table +// after making changes to `Color::Blend`. +const std::map ColorBlendTestData::kExpectedResults[sizeof( + ColorBlendTestData::kSourceColors)] = { + { + {BlendMode::kClear, {0, 0, 0, 0}}, + {BlendMode::kSource, {1, 1, 1, 0.75}}, + {BlendMode::kDestination, {0.392157, 0.584314, 0.929412, 0.75}}, + {BlendMode::kSourceOver, {0.878431, 0.916863, 0.985882, 0.9375}}, + {BlendMode::kDestinationOver, {0.513726, 0.667451, 0.943529, 0.9375}}, + {BlendMode::kSourceIn, {1, 1, 1, 0.5625}}, + {BlendMode::kDestinationIn, {0.392157, 0.584314, 0.929412, 0.5625}}, + {BlendMode::kSourceOut, {1, 1, 1, 0.1875}}, + {BlendMode::kDestinationOut, {0.392157, 0.584314, 0.929412, 0.1875}}, + {BlendMode::kSourceATop, {0.848039, 0.896078, 0.982353, 0.75}}, + {BlendMode::kDestinationATop, {0.544118, 0.688235, 0.947059, 0.75}}, + {BlendMode::kXor, {0.696078, 0.792157, 0.964706, 0.375}}, + {BlendMode::kPlus, {1, 1, 1, 1}}, + {BlendMode::kModulate, {0.392157, 0.584314, 0.929412, 0.5625}}, + {BlendMode::kScreen, {0.878431, 0.916863, 0.985882, 0.9375}}, + {BlendMode::kOverlay, {0.74902, 0.916863, 0.985882, 0.9375}}, + {BlendMode::kDarken, {0.513726, 0.667451, 0.943529, 0.9375}}, + {BlendMode::kLighten, {0.878431, 0.916863, 0.985882, 0.9375}}, + {BlendMode::kColorDodge, {0.878431, 0.916863, 0.985882, 0.9375}}, + {BlendMode::kColorBurn, {0.513725, 0.667451, 0.943529, 0.9375}}, + {BlendMode::kHardLight, {0.878431, 0.916863, 0.985882, 0.9375}}, + {BlendMode::kSoftLight, {0.654166, 0.775505, 0.964318, 0.9375}}, + {BlendMode::kDifference, {0.643137, 0.566275, 0.428235, 0.9375}}, + {BlendMode::kExclusion, {0.643137, 0.566275, 0.428235, 0.9375}}, + {BlendMode::kMultiply, {0.513726, 0.667451, 0.943529, 0.9375}}, + {BlendMode::kHue, {0.617208, 0.655639, 0.724659, 0.9375}}, + {BlendMode::kSaturation, {0.617208, 0.655639, 0.724659, 0.9375}}, + {BlendMode::kColor, {0.617208, 0.655639, 0.724659, 0.9375}}, + {BlendMode::kPlusAdvanced, {1, 1, 1, 1}}, + {BlendMode::kLuminosity, {0.878431, 0.916863, 0.985882, 0.9375}}, + }, + { + {BlendMode::kClear, {0, 0, 0, 0}}, + {BlendMode::kSource, {0.196078, 0.803922, 0.196078, 0.75}}, + {BlendMode::kDestination, {0.392157, 0.584314, 0.929412, 0.75}}, + {BlendMode::kSourceOver, {0.235294, 0.76, 0.342745, 0.9375}}, + {BlendMode::kDestinationOver, {0.352941, 0.628235, 0.782745, 0.9375}}, + {BlendMode::kSourceIn, {0.196078, 0.803922, 0.196078, 0.5625}}, + {BlendMode::kDestinationIn, {0.392157, 0.584314, 0.929412, 0.5625}}, + {BlendMode::kSourceOut, {0.196078, 0.803922, 0.196078, 0.1875}}, + {BlendMode::kDestinationOut, {0.392157, 0.584314, 0.929412, 0.1875}}, + {BlendMode::kSourceATop, {0.245098, 0.74902, 0.379412, 0.75}}, + {BlendMode::kDestinationATop, {0.343137, 0.639216, 0.746078, 0.75}}, + {BlendMode::kXor, {0.294118, 0.694118, 0.562745, 0.375}}, + {BlendMode::kPlus, {0.441176, 1, 0.844118, 1}}, + {BlendMode::kModulate, {0.0768935, 0.469742, 0.182238, 0.5625}}, + {BlendMode::kScreen, {0.424452, 0.828743, 0.79105, 0.9375}}, + {BlendMode::kOverlay, {0.209919, 0.779839, 0.757001, 0.9375}}, + {BlendMode::kDarken, {0.235294, 0.628235, 0.342745, 0.9375}}, + {BlendMode::kLighten, {0.352941, 0.76, 0.782745, 0.9375}}, + {BlendMode::kColorDodge, {0.41033, 0.877647, 0.825098, 0.9375}}, + {BlendMode::kColorBurn, {0.117647, 0.567403, 0.609098, 0.9375}}, + {BlendMode::kHardLight, {0.209919, 0.779839, 0.443783, 0.9375}}, + {BlendMode::kSoftLight, {0.266006, 0.693915, 0.758818, 0.9375}}, + {BlendMode::kDifference, {0.235294, 0.409412, 0.665098, 0.9375}}, + {BlendMode::kExclusion, {0.378316, 0.546897, 0.681707, 0.9375}}, + {BlendMode::kMultiply, {0.163783, 0.559493, 0.334441, 0.9375}}, + {BlendMode::kHue, {0.266235, 0.748588, 0.373686, 0.9375}}, + {BlendMode::kSaturation, {0.339345, 0.629787, 0.811502, 0.9375}}, + {BlendMode::kColor, {0.241247, 0.765953, 0.348698, 0.9375}}, + {BlendMode::kPlusAdvanced, {0.441176, 1, 0.844118, 1}}, + {BlendMode::kLuminosity, {0.346988, 0.622282, 0.776792, 0.9375}}, + }, + { + {BlendMode::kClear, {0, 0, 0, 0}}, + {BlendMode::kSource, {0, 0, 0, 0.75}}, + {BlendMode::kDestination, {0.392157, 0.584314, 0.929412, 0.75}}, + {BlendMode::kSourceOver, {0.0784314, 0.116863, 0.185882, 0.9375}}, + {BlendMode::kDestinationOver, {0.313726, 0.467451, 0.743529, 0.9375}}, + {BlendMode::kSourceIn, {0, 0, 0, 0.5625}}, + {BlendMode::kDestinationIn, {0.392157, 0.584314, 0.929412, 0.5625}}, + {BlendMode::kSourceOut, {0, 0, 0, 0.1875}}, + {BlendMode::kDestinationOut, {0.392157, 0.584314, 0.929412, 0.1875}}, + {BlendMode::kSourceATop, {0.0980392, 0.146078, 0.232353, 0.75}}, + {BlendMode::kDestinationATop, {0.294118, 0.438235, 0.697059, 0.75}}, + {BlendMode::kXor, {0.196078, 0.292157, 0.464706, 0.375}}, + {BlendMode::kPlus, {0.294118, 0.438235, 0.697059, 1}}, + {BlendMode::kModulate, {0, 0, 0, 0.5625}}, + {BlendMode::kScreen, {0.313726, 0.467451, 0.743529, 0.9375}}, + {BlendMode::kOverlay, {0.0784314, 0.218039, 0.701176, 0.9375}}, + {BlendMode::kDarken, {0.0784314, 0.116863, 0.185882, 0.9375}}, + {BlendMode::kLighten, {0.313726, 0.467451, 0.743529, 0.9375}}, + {BlendMode::kColorDodge, {0.313726, 0.467451, 0.743529, 0.9375}}, + {BlendMode::kColorBurn, {0.0784314, 0.116863, 0.185882, 0.9375}}, + {BlendMode::kHardLight, {0.0784314, 0.116863, 0.185882, 0.9375}}, + {BlendMode::kSoftLight, {0.170704, 0.321716, 0.704166, 0.9375}}, + {BlendMode::kDifference, {0.313726, 0.467451, 0.743529, 0.9375}}, + {BlendMode::kExclusion, {0.313726, 0.467451, 0.743529, 0.9375}}, + {BlendMode::kMultiply, {0.0784314, 0.116863, 0.185882, 0.9375}}, + {BlendMode::kHue, {0.417208, 0.455639, 0.524659, 0.9375}}, + {BlendMode::kSaturation, {0.417208, 0.455639, 0.524659, 0.9375}}, + {BlendMode::kColor, {0.417208, 0.455639, 0.524659, 0.9375}}, + {BlendMode::kPlusAdvanced, {0.294118, 0.438235, 0.697059, 1}}, + {BlendMode::kLuminosity, {0.0784314, 0.116863, 0.185882, 0.9375}}, + }, }; /// To print a new ColorBlendTestData::kExpectedResults table, uncomment this @@ -1567,8 +1573,10 @@ TEST(GeometryTest, GenerateColorBlendResults) { blend_i < static_cast(BlendMode::kLast) + 1; blend_i++) { auto blend = static_cast(blend_i); Color c = ColorBlendTestData::kDestinationColor.Blend(source, blend); + o << "{ BlendMode::k" << BlendModeToString(blend) << ", "; o << "{" << c.red << "," << c.green << "," << c.blue << "," << c.alpha - << "}, // " << BlendModeToString(blend) << std::endl; + << "}"; + o << "}," << std::endl; } o << "},"; } @@ -1576,20 +1584,19 @@ TEST(GeometryTest, GenerateColorBlendResults) { } */ -#define _BLEND_MODE_RESULT_CHECK(blend_mode) \ - blend_i = static_cast(BlendMode::k##blend_mode); \ - expected = ColorBlendTestData::kExpectedResults[source_i][blend_i]; \ +#define _BLEND_MODE_RESULT_CHECK(blend_mode) \ + expected = ColorBlendTestData::kExpectedResults[source_i] \ + .find(BlendMode::k##blend_mode) \ + ->second; \ EXPECT_COLOR_NEAR(dst.Blend(src, BlendMode::k##blend_mode), expected); TEST(GeometryTest, ColorBlendReturnsExpectedResults) { - using BlendT = std::underlying_type_t; Color dst = ColorBlendTestData::kDestinationColor; for (size_t source_i = 0; source_i < sizeof(ColorBlendTestData::kSourceColors) / sizeof(Color); source_i++) { Color src = ColorBlendTestData::kSourceColors[source_i]; - size_t blend_i; Color expected; IMPELLER_FOR_EACH_BLEND_MODE(_BLEND_MODE_RESULT_CHECK) } diff --git a/impeller/golden_tests/golden_playground_test_mac.cc b/impeller/golden_tests/golden_playground_test_mac.cc index 75fea0f34072a..833a62bfbf0a1 100644 --- a/impeller/golden_tests/golden_playground_test_mac.cc +++ b/impeller/golden_tests/golden_playground_test_mac.cc @@ -138,9 +138,12 @@ void GoldenPlaygroundTest::SetUp() { std::filesystem::path icd_path = target_path / "vk_swiftshader_icd.json"; setenv("VK_ICD_FILENAMES", icd_path.c_str(), 1); + std::string test_name = GetTestName(); + bool enable_wide_gamut = test_name.find("WideGamut_") != std::string::npos; switch (GetParam()) { case PlaygroundBackend::kMetal: - pimpl_->screenshotter = std::make_unique(); + pimpl_->screenshotter = + std::make_unique(enable_wide_gamut); break; case PlaygroundBackend::kVulkan: { const std::unique_ptr& playground = @@ -160,16 +163,7 @@ void GoldenPlaygroundTest::SetUp() { break; } } - if (GetParam() == PlaygroundBackend::kMetal) { - pimpl_->screenshotter = std::make_unique(); - } else if (GetParam() == PlaygroundBackend::kVulkan) { - const std::unique_ptr& playground = - GetSharedVulkanPlayground(/*enable_validations=*/true); - pimpl_->screenshotter = - std::make_unique(playground); - } - std::string test_name = GetTestName(); if (std::find(kSkipTests.begin(), kSkipTests.end(), test_name) != kSkipTests.end()) { GTEST_SKIP_( diff --git a/impeller/golden_tests/golden_tests.cc b/impeller/golden_tests/golden_tests.cc index c519f367a1ddb..40a79cd173703 100644 --- a/impeller/golden_tests/golden_tests.cc +++ b/impeller/golden_tests/golden_tests.cc @@ -50,7 +50,8 @@ bool SaveScreenshot(std::unique_ptr screenshot) { class GoldenTests : public ::testing::Test { public: - GoldenTests() : screenshotter_(new MetalScreenshotter()) {} + GoldenTests() + : screenshotter_(new MetalScreenshotter(/*enable_wide_gamut=*/false)) {} MetalScreenshotter& Screenshotter() { return *screenshotter_; } diff --git a/impeller/golden_tests/metal_screenshotter.h b/impeller/golden_tests/metal_screenshotter.h index 582462a73f425..8104396bd8be5 100644 --- a/impeller/golden_tests/metal_screenshotter.h +++ b/impeller/golden_tests/metal_screenshotter.h @@ -18,7 +18,7 @@ namespace testing { /// playground backend. class MetalScreenshotter : public Screenshotter { public: - MetalScreenshotter(); + explicit MetalScreenshotter(bool enable_wide_gamut); std::unique_ptr MakeScreenshot( AiksContext& aiks_context, diff --git a/impeller/golden_tests/metal_screenshotter.mm b/impeller/golden_tests/metal_screenshotter.mm index 534884078a17f..f09cb10e03849 100644 --- a/impeller/golden_tests/metal_screenshotter.mm +++ b/impeller/golden_tests/metal_screenshotter.mm @@ -13,10 +13,11 @@ namespace impeller { namespace testing { -MetalScreenshotter::MetalScreenshotter() { +MetalScreenshotter::MetalScreenshotter(bool enable_wide_gamut) { FML_CHECK(::glfwInit() == GLFW_TRUE); - playground_ = - PlaygroundImpl::Create(PlaygroundBackend::kMetal, PlaygroundSwitches{}); + PlaygroundSwitches switches; + switches.enable_wide_gamut = enable_wide_gamut; + playground_ = PlaygroundImpl::Create(PlaygroundBackend::kMetal, switches); } std::unique_ptr MetalScreenshotter::MakeScreenshot( @@ -33,10 +34,6 @@ id metal_texture = std::static_pointer_cast(texture)->GetMTLTexture(); - if (metal_texture.pixelFormat != MTLPixelFormatBGRA8Unorm) { - return {}; - } - CGColorSpaceRef color_space = CGColorSpaceCreateDeviceRGB(); CIImage* ciImage = [[CIImage alloc] initWithMTLTexture:metal_texture diff --git a/impeller/playground/backend/metal/playground_impl_mtl.mm b/impeller/playground/backend/metal/playground_impl_mtl.mm index 421d6eba3c46b..00babefd4c047 100644 --- a/impeller/playground/backend/metal/playground_impl_mtl.mm +++ b/impeller/playground/backend/metal/playground_impl_mtl.mm @@ -73,9 +73,12 @@ if (!window) { return; } - auto context = - ContextMTL::Create(ShaderLibraryMappingsForPlayground(), - is_gpu_disabled_sync_switch_, "Playground Library"); + auto context = ContextMTL::Create( + ShaderLibraryMappingsForPlayground(), is_gpu_disabled_sync_switch_, + "Playground Library", + switches.enable_wide_gamut + ? std::optional(PixelFormat::kR16G16B16A16Float) + : std::nullopt); if (!context) { return; } diff --git a/impeller/playground/compute_playground_test.cc b/impeller/playground/compute_playground_test.cc index 3df0e62a7260e..1abea913ebea4 100644 --- a/impeller/playground/compute_playground_test.cc +++ b/impeller/playground/compute_playground_test.cc @@ -25,7 +25,7 @@ void ComputePlaygroundTest::SetUp() { return; } - SetupContext(GetParam()); + SetupContext(GetParam(), switches_); SetupWindow(); start_time_ = fml::TimePoint::Now().ToEpochDelta(); diff --git a/impeller/playground/playground.cc b/impeller/playground/playground.cc index 4ed9e5af7efcb..ae4d7cd4dd3fb 100644 --- a/impeller/playground/playground.cc +++ b/impeller/playground/playground.cc @@ -120,10 +120,11 @@ bool Playground::SupportsBackend(PlaygroundBackend backend) { FML_UNREACHABLE(); } -void Playground::SetupContext(PlaygroundBackend backend) { +void Playground::SetupContext(PlaygroundBackend backend, + const PlaygroundSwitches& switches) { FML_CHECK(SupportsBackend(backend)); - impl_ = PlaygroundImpl::Create(backend, switches_); + impl_ = PlaygroundImpl::Create(backend, switches); if (!impl_) { FML_LOG(WARNING) << "PlaygroundImpl::Create failed."; return; diff --git a/impeller/playground/playground.h b/impeller/playground/playground.h index 9c6cde6467d6a..b15461272f3d5 100644 --- a/impeller/playground/playground.h +++ b/impeller/playground/playground.h @@ -57,7 +57,8 @@ class Playground { static bool ShouldOpenNewPlaygrounds(); - void SetupContext(PlaygroundBackend backend); + void SetupContext(PlaygroundBackend backend, + const PlaygroundSwitches& switches); void SetupWindow(); diff --git a/impeller/playground/playground_test.cc b/impeller/playground/playground_test.cc index 676c3b0412992..00ee371ce2fbb 100644 --- a/impeller/playground/playground_test.cc +++ b/impeller/playground/playground_test.cc @@ -28,7 +28,13 @@ void PlaygroundTest::SetUp() { ImpellerValidationErrorsSetFatal(true); - SetupContext(GetParam()); + // Test names that end with "WideGamut" will render with wide gamut support. + std::string test_name = flutter::testing::GetCurrentTestName(); + PlaygroundSwitches switches = switches_; + switches.enable_wide_gamut = + test_name.find("WideGamut/") != std::string::npos; + + SetupContext(GetParam(), switches); SetupWindow(); } diff --git a/impeller/playground/switches.h b/impeller/playground/switches.h index be49fb0191a9b..6b9df55eb7642 100644 --- a/impeller/playground/switches.h +++ b/impeller/playground/switches.h @@ -33,6 +33,8 @@ struct PlaygroundSwitches { /// bool use_angle = false; + bool enable_wide_gamut = false; + PlaygroundSwitches(); explicit PlaygroundSwitches(const fml::CommandLine& args); diff --git a/impeller/renderer/backend/metal/context_mtl.h b/impeller/renderer/backend/metal/context_mtl.h index 6497871b28b25..dd11641665128 100644 --- a/impeller/renderer/backend/metal/context_mtl.h +++ b/impeller/renderer/backend/metal/context_mtl.h @@ -43,7 +43,8 @@ class ContextMTL final : public Context, static std::shared_ptr Create( const std::vector>& shader_libraries_data, std::shared_ptr is_gpu_disabled_sync_switch, - const std::string& label); + const std::string& label, + std::optional pixel_format_override = std::nullopt); static std::shared_ptr Create( id device, @@ -133,11 +134,11 @@ class ContextMTL final : public Context, std::shared_ptr command_queue_ip_; bool is_valid_ = false; - ContextMTL( - id device, - id command_queue, - NSArray>* shader_libraries, - std::shared_ptr is_gpu_disabled_sync_switch); + ContextMTL(id device, + id command_queue, + NSArray>* shader_libraries, + std::shared_ptr is_gpu_disabled_sync_switch, + std::optional pixel_format_override = std::nullopt); std::shared_ptr CreateCommandBufferInQueue( id queue) const; diff --git a/impeller/renderer/backend/metal/context_mtl.mm b/impeller/renderer/backend/metal/context_mtl.mm index 623f3c8335f4c..6248368687451 100644 --- a/impeller/renderer/backend/metal/context_mtl.mm +++ b/impeller/renderer/backend/metal/context_mtl.mm @@ -75,7 +75,8 @@ static bool DeviceSupportsComputeSubgroups(id device) { id device, id command_queue, NSArray>* shader_libraries, - std::shared_ptr is_gpu_disabled_sync_switch) + std::shared_ptr is_gpu_disabled_sync_switch, + std::optional pixel_format_override) : device_(device), command_queue_(command_queue), is_gpu_disabled_sync_switch_(std::move(is_gpu_disabled_sync_switch)) { @@ -128,7 +129,9 @@ static bool DeviceSupportsComputeSubgroups(id device) { } device_capabilities_ = - InferMetalCapabilities(device_, PixelFormat::kB8G8R8A8UNormInt); + InferMetalCapabilities(device_, pixel_format_override.has_value() + ? pixel_format_override.value() + : PixelFormat::kB8G8R8A8UNormInt); command_queue_ip_ = std::make_shared(); #ifdef IMPELLER_DEBUG gpu_tracer_ = std::make_shared(); @@ -238,17 +241,18 @@ static bool DeviceSupportsComputeSubgroups(id device) { std::shared_ptr ContextMTL::Create( const std::vector>& shader_libraries_data, std::shared_ptr is_gpu_disabled_sync_switch, - const std::string& library_label) { + const std::string& library_label, + std::optional pixel_format_override) { auto device = CreateMetalDevice(); auto command_queue = CreateMetalCommandQueue(device); if (!command_queue) { return nullptr; } - auto context = std::shared_ptr( - new ContextMTL(device, command_queue, - MTLShaderLibraryFromFileData(device, shader_libraries_data, - library_label), - std::move(is_gpu_disabled_sync_switch))); + auto context = std::shared_ptr(new ContextMTL( + device, command_queue, + MTLShaderLibraryFromFileData(device, shader_libraries_data, + library_label), + std::move(is_gpu_disabled_sync_switch), pixel_format_override)); if (!context->IsValid()) { FML_LOG(ERROR) << "Could not create Metal context."; return nullptr; diff --git a/impeller/renderer/backend/vulkan/blit_command_vk.cc b/impeller/renderer/backend/vulkan/blit_command_vk.cc index c3c2ebcf88bf1..b5c3d6a147eda 100644 --- a/impeller/renderer/backend/vulkan/blit_command_vk.cc +++ b/impeller/renderer/backend/vulkan/blit_command_vk.cc @@ -6,11 +6,46 @@ #include +#include "impeller/renderer/backend/vulkan/barrier_vk.h" #include "impeller/renderer/backend/vulkan/command_encoder_vk.h" #include "impeller/renderer/backend/vulkan/texture_vk.h" +#include "vulkan/vulkan_core.h" +#include "vulkan/vulkan_enums.hpp" +#include "vulkan/vulkan_structs.hpp" namespace impeller { +static void InsertImageMemoryBarrier(const vk::CommandBuffer& cmd, + const vk::Image& image, + vk::AccessFlags src_access_mask, + vk::AccessFlags dst_access_mask, + vk::ImageLayout old_layout, + vk::ImageLayout new_layout, + vk::PipelineStageFlags src_stage, + vk::PipelineStageFlags dst_stage, + uint32_t base_mip_level, + uint32_t mip_level_count = 1u) { + if (old_layout == new_layout) { + return; + } + + vk::ImageMemoryBarrier barrier; + barrier.srcAccessMask = src_access_mask; + barrier.dstAccessMask = dst_access_mask; + barrier.oldLayout = old_layout; + barrier.newLayout = new_layout; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = image; + barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; + barrier.subresourceRange.baseMipLevel = base_mip_level; + barrier.subresourceRange.levelCount = mip_level_count; + barrier.subresourceRange.baseArrayLayer = 0u; + barrier.subresourceRange.layerCount = 1u; + + cmd.pipelineBarrier(src_stage, dst_stage, {}, nullptr, nullptr, barrier); +} + BlitEncodeVK::~BlitEncodeVK() = default; //------------------------------------------------------------------------------ @@ -240,37 +275,6 @@ std::string BlitGenerateMipmapCommandVK::GetLabel() const { return label; } -static void InsertImageMemoryBarrier(const vk::CommandBuffer& cmd, - const vk::Image& image, - vk::AccessFlags src_access_mask, - vk::AccessFlags dst_access_mask, - vk::ImageLayout old_layout, - vk::ImageLayout new_layout, - vk::PipelineStageFlags src_stage, - vk::PipelineStageFlags dst_stage, - uint32_t base_mip_level, - uint32_t mip_level_count = 1u) { - if (old_layout == new_layout) { - return; - } - - vk::ImageMemoryBarrier barrier; - barrier.srcAccessMask = src_access_mask; - barrier.dstAccessMask = dst_access_mask; - barrier.oldLayout = old_layout; - barrier.newLayout = new_layout; - barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.image = image; - barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; - barrier.subresourceRange.baseMipLevel = base_mip_level; - barrier.subresourceRange.levelCount = mip_level_count; - barrier.subresourceRange.baseArrayLayer = 0u; - barrier.subresourceRange.layerCount = 1u; - - cmd.pipelineBarrier(src_stage, dst_stage, {}, nullptr, nullptr, barrier); -} - bool BlitGenerateMipmapCommandVK::Encode(CommandEncoderVK& encoder) const { auto& src = TextureVK::Cast(*texture); @@ -288,41 +292,56 @@ bool BlitGenerateMipmapCommandVK::Encode(CommandEncoderVK& encoder) const { return false; } - // Transition the base mip level to transfer-src layout so we can read from - // it and transition the rest to dst-optimal since they are going to be - // written to. + // Initialize all mip levels to be in TransferDst mode. Later, in a loop, + // after writing to that mip level, we'll first switch its layout to + // TransferSrc to prepare the mip level after it, use the image as the source + // of the blit, before finally switching it to ShaderReadOnly so its available + // for sampling in a shader. InsertImageMemoryBarrier( cmd, // command buffer image, // image vk::AccessFlagBits::eTransferWrite, // src access mask vk::AccessFlagBits::eTransferRead, // dst access mask src.GetLayout(), // old layout - vk::ImageLayout::eTransferSrcOptimal, // new layout - vk::PipelineStageFlagBits::eTransfer, // src stage - vk::PipelineStageFlagBits::eTransfer, // dst stage - 0u // mip level - ); - InsertImageMemoryBarrier( - cmd, // command buffer - image, // image - {}, // src access mask - vk::AccessFlagBits::eTransferWrite, // dst access mask - vk::ImageLayout::eUndefined, // old layout vk::ImageLayout::eTransferDstOptimal, // new layout vk::PipelineStageFlagBits::eTransfer, // src stage vk::PipelineStageFlagBits::eTransfer, // dst stage - 1u, // mip level - mip_count - 1 // mip level count + 0u, // mip level + mip_count // mip level count ); - // Blit from the base mip level to all other levels. + vk::ImageMemoryBarrier barrier; + barrier.image = image; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; + barrier.subresourceRange.baseArrayLayer = 0; + barrier.subresourceRange.layerCount = 1; + barrier.subresourceRange.levelCount = 1; + + // Blit from the mip level N - 1 to mip level N. + size_t width = size.width; + size_t height = size.height; for (size_t mip_level = 1u; mip_level < mip_count; mip_level++) { - vk::ImageBlit blit; + barrier.subresourceRange.baseMipLevel = mip_level - 1; + barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal; + barrier.newLayout = vk::ImageLayout::eTransferSrcOptimal; + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eTransferRead; + + // We just finished writing to the previous (N-1) mip level or it was the + // base mip level. These were initialized to TransferDst earler. We are now + // going to read from it to write to the current level (N) . So it must be + // converted to TransferSrc. + cmd.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, + vk::PipelineStageFlagBits::eTransfer, {}, {}, {}, + {barrier}); + vk::ImageBlit blit; blit.srcSubresource.aspectMask = vk::ImageAspectFlagBits::eColor; blit.srcSubresource.baseArrayLayer = 0u; blit.srcSubresource.layerCount = 1u; - blit.srcSubresource.mipLevel = 0u; + blit.srcSubresource.mipLevel = mip_level - 1; blit.dstSubresource.aspectMask = vk::ImageAspectFlagBits::eColor; blit.dstSubresource.baseArrayLayer = 0u; @@ -330,13 +349,16 @@ bool BlitGenerateMipmapCommandVK::Encode(CommandEncoderVK& encoder) const { blit.dstSubresource.mipLevel = mip_level; // offsets[0] is origin. - blit.srcOffsets[1].x = size.width; - blit.srcOffsets[1].y = size.height; + blit.srcOffsets[1].x = std::max(width, 1u); + blit.srcOffsets[1].y = std::max(height, 1u); blit.srcOffsets[1].z = 1u; + width = width / 2; + height = height / 2; + // offsets[0] is origin. - blit.dstOffsets[1].x = std::max(size.width >> mip_level, 1u); - blit.dstOffsets[1].y = std::max(size.height >> mip_level, 1u); + blit.dstOffsets[1].x = std::max(width, 1u); + blit.dstOffsets[1].y = std::max(height, 1u); blit.dstOffsets[1].z = 1u; cmd.blitImage(image, // src image @@ -347,33 +369,29 @@ bool BlitGenerateMipmapCommandVK::Encode(CommandEncoderVK& encoder) const { &blit, // regions vk::Filter::eLinear // filter ); + + barrier.oldLayout = vk::ImageLayout::eTransferSrcOptimal; + barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal; + barrier.srcAccessMask = vk::AccessFlagBits::eTransferRead; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + + // Now that the blit is done, the image at the previous level (N-1) + // is done reading from (TransferSrc)/ Now we must prepare it to be read + // from a shader (ShaderReadOnly). + cmd.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, + vk::PipelineStageFlagBits::eFragmentShader, {}, {}, {}, + {barrier}); } - // Transition all mip levels to shader read. The base mip level has a - // different "old" layout than the rest now. - InsertImageMemoryBarrier( - cmd, // command buffer - image, // image - vk::AccessFlagBits::eTransferWrite, // src access mask - vk::AccessFlagBits::eShaderRead, // dst access mask - vk::ImageLayout::eTransferSrcOptimal, // old layout - vk::ImageLayout::eShaderReadOnlyOptimal, // new layout - vk::PipelineStageFlagBits::eTransfer, // src stage - vk::PipelineStageFlagBits::eFragmentShader, // dst stage - 0u // mip level - ); - InsertImageMemoryBarrier( - cmd, // command buffer - image, // image - vk::AccessFlagBits::eTransferWrite, // src access mask - vk::AccessFlagBits::eShaderRead, // dst access mask - vk::ImageLayout::eTransferDstOptimal, // old layout - vk::ImageLayout::eShaderReadOnlyOptimal, // new layout - vk::PipelineStageFlagBits::eTransfer, // src stage - vk::PipelineStageFlagBits::eFragmentShader, // dst stage - 1u, // mip level - mip_count - 1 // mip level count - ); + barrier.subresourceRange.baseMipLevel = mip_count - 1; + barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal; + barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal; + barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite; + barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead; + + cmd.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, + vk::PipelineStageFlagBits::eFragmentShader, {}, {}, {}, + {barrier}); // We modified the layouts of this image from underneath it. Tell it its new // state so it doesn't try to perform redundant transitions under the hood. diff --git a/impeller/renderer/backend/vulkan/sampler_vk.cc b/impeller/renderer/backend/vulkan/sampler_vk.cc index 175507d1ab9bf..2e0946302d5cf 100644 --- a/impeller/renderer/backend/vulkan/sampler_vk.cc +++ b/impeller/renderer/backend/vulkan/sampler_vk.cc @@ -37,6 +37,7 @@ static vk::UniqueSampler CreateSampler( sampler_info.addressModeW = address_mode_w; sampler_info.borderColor = vk::BorderColor::eFloatTransparentBlack; sampler_info.mipmapMode = mip_map; + sampler_info.maxLod = VK_LOD_CLAMP_NONE; if (yuv_conversion && yuv_conversion->IsValid()) { sampler_chain.get().conversion = diff --git a/impeller/runtime_stage/runtime_stage_unittests.cc b/impeller/runtime_stage/runtime_stage_unittests.cc index 8315c6af7c455..2dc646d3a640b 100644 --- a/impeller/runtime_stage/runtime_stage_unittests.cc +++ b/impeller/runtime_stage/runtime_stage_unittests.cc @@ -62,6 +62,7 @@ TEST_P(RuntimeStageTest, CanReadUniforms) { ASSERT_TRUE(stage->IsValid()); switch (GetBackend()) { case PlaygroundBackend::kMetal: + [[fallthrough]]; case PlaygroundBackend::kOpenGLES: { ASSERT_EQ(stage->GetUniforms().size(), 17u); { diff --git a/impeller/tools/malioc.json b/impeller/tools/malioc.json index faaffd82a987d..ae4d808c54a69 100644 --- a/impeller/tools/malioc.json +++ b/impeller/tools/malioc.json @@ -1153,263 +1153,6 @@ } } }, - "flutter/impeller/entity/gaussian_blur.vert.vkspv": { - "Mali-G78": { - "core": "Mali-G78", - "filename": "flutter/impeller/entity/gaussian_blur.vert.vkspv", - "has_uniform_computation": true, - "type": "Vertex", - "variants": { - "Position": { - "fp16_arithmetic": 0, - "has_stack_spilling": false, - "performance": { - "longest_path_bound_pipelines": [ - "load_store" - ], - "longest_path_cycles": [ - 0.125, - 0.125, - 0.0, - 0.0, - 2.0, - 0.0 - ], - "pipelines": [ - "arith_total", - "arith_fma", - "arith_cvt", - "arith_sfu", - "load_store", - "texture" - ], - "shortest_path_bound_pipelines": [ - "load_store" - ], - "shortest_path_cycles": [ - 0.125, - 0.125, - 0.0, - 0.0, - 2.0, - 0.0 - ], - "total_bound_pipelines": [ - "load_store" - ], - "total_cycles": [ - 0.125, - 0.125, - 0.0, - 0.0, - 2.0, - 0.0 - ] - }, - "stack_spill_bytes": 0, - "thread_occupancy": 100, - "uniform_registers_used": 30, - "work_registers_used": 32 - }, - "Varying": { - "fp16_arithmetic": 0, - "has_stack_spilling": false, - "performance": { - "longest_path_bound_pipelines": [ - "load_store" - ], - "longest_path_cycles": [ - 0.015625, - 0.015625, - 0.015625, - 0.0, - 3.0, - 0.0 - ], - "pipelines": [ - "arith_total", - "arith_fma", - "arith_cvt", - "arith_sfu", - "load_store", - "texture" - ], - "shortest_path_bound_pipelines": [ - "load_store" - ], - "shortest_path_cycles": [ - 0.015625, - 0.015625, - 0.015625, - 0.0, - 3.0, - 0.0 - ], - "total_bound_pipelines": [ - "load_store" - ], - "total_cycles": [ - 0.015625, - 0.015625, - 0.015625, - 0.0, - 3.0, - 0.0 - ] - }, - "stack_spill_bytes": 0, - "thread_occupancy": 100, - "uniform_registers_used": 22, - "work_registers_used": 8 - } - } - } - }, - "flutter/impeller/entity/gaussian_blur_noalpha_decal.frag.vkspv": { - "Mali-G78": { - "core": "Mali-G78", - "filename": "flutter/impeller/entity/gaussian_blur_noalpha_decal.frag.vkspv", - "has_side_effects": false, - "has_uniform_computation": true, - "modifies_coverage": false, - "reads_color_buffer": false, - "type": "Fragment", - "uses_late_zs_test": false, - "uses_late_zs_update": false, - "variants": { - "Main": { - "fp16_arithmetic": 42, - "has_stack_spilling": false, - "performance": { - "longest_path_bound_pipelines": [ - null - ], - "longest_path_cycles": [ - null, - null, - null, - null, - null, - null, - null - ], - "pipelines": [ - "arith_total", - "arith_fma", - "arith_cvt", - "arith_sfu", - "load_store", - "varying", - "texture" - ], - "shortest_path_bound_pipelines": [ - "arith_total", - "arith_cvt" - ], - "shortest_path_cycles": [ - 0.109375, - 0.03125, - 0.109375, - 0.0625, - 0.0, - 0.0, - 0.0 - ], - "total_bound_pipelines": [ - "arith_total", - "arith_cvt", - "arith_sfu" - ], - "total_cycles": [ - 0.3125, - 0.203125, - 0.3125, - 0.3125, - 0.0, - 0.25, - 0.25 - ] - }, - "stack_spill_bytes": 0, - "thread_occupancy": 100, - "uniform_registers_used": 10, - "work_registers_used": 16 - } - } - } - }, - "flutter/impeller/entity/gaussian_blur_noalpha_nodecal.frag.vkspv": { - "Mali-G78": { - "core": "Mali-G78", - "filename": "flutter/impeller/entity/gaussian_blur_noalpha_nodecal.frag.vkspv", - "has_side_effects": false, - "has_uniform_computation": true, - "modifies_coverage": false, - "reads_color_buffer": false, - "type": "Fragment", - "uses_late_zs_test": false, - "uses_late_zs_update": false, - "variants": { - "Main": { - "fp16_arithmetic": 35, - "has_stack_spilling": false, - "performance": { - "longest_path_bound_pipelines": [ - null - ], - "longest_path_cycles": [ - null, - null, - null, - null, - null, - null, - null - ], - "pipelines": [ - "arith_total", - "arith_fma", - "arith_cvt", - "arith_sfu", - "load_store", - "varying", - "texture" - ], - "shortest_path_bound_pipelines": [ - "arith_total", - "arith_cvt" - ], - "shortest_path_cycles": [ - 0.109375, - 0.03125, - 0.109375, - 0.0625, - 0.0, - 0.0, - 0.0 - ], - "total_bound_pipelines": [ - "varying", - "texture" - ], - "total_cycles": [ - 0.203125, - 0.203125, - 0.203125, - 0.125, - 0.0, - 0.25, - 0.25 - ] - }, - "stack_spill_bytes": 0, - "thread_occupancy": 100, - "uniform_registers_used": 10, - "work_registers_used": 13 - } - } - } - }, "flutter/impeller/entity/gles/advanced_blend.frag.gles": { "Mali-G78": { "core": "Mali-G78", @@ -3083,27 +2826,33 @@ } } }, - "flutter/impeller/entity/gles/gaussian_blur.vert.gles": { + "flutter/impeller/entity/gles/glyph_atlas.frag.gles": { "Mali-G78": { "core": "Mali-G78", - "filename": "flutter/impeller/entity/gles/gaussian_blur.vert.gles", - "has_uniform_computation": true, - "type": "Vertex", - "variants": { - "Position": { - "fp16_arithmetic": 0, + "filename": "flutter/impeller/entity/gles/glyph_atlas.frag.gles", + "has_side_effects": false, + "has_uniform_computation": false, + "modifies_coverage": false, + "reads_color_buffer": false, + "type": "Fragment", + "uses_late_zs_test": false, + "uses_late_zs_update": false, + "variants": { + "Main": { + "fp16_arithmetic": 100, "has_stack_spilling": false, "performance": { "longest_path_bound_pipelines": [ - "load_store" + "varying" ], "longest_path_cycles": [ - 0.140625, - 0.140625, + 0.03125, + 0.03125, + 0.03125, 0.0, 0.0, - 2.0, - 0.0 + 0.5, + 0.25 ], "pipelines": [ "arith_total", @@ -3111,105 +2860,59 @@ "arith_cvt", "arith_sfu", "load_store", + "varying", "texture" ], "shortest_path_bound_pipelines": [ - "load_store" + "varying" ], "shortest_path_cycles": [ - 0.140625, - 0.140625, - 0.0, - 0.0, - 2.0, - 0.0 - ], - "total_bound_pipelines": [ - "load_store" - ], - "total_cycles": [ - 0.140625, - 0.140625, - 0.0, + 0.03125, + 0.03125, 0.0, - 2.0, - 0.0 - ] - }, - "stack_spill_bytes": 0, - "thread_occupancy": 100, - "uniform_registers_used": 22, - "work_registers_used": 32 - }, - "Varying": { - "fp16_arithmetic": 0, - "has_stack_spilling": false, - "performance": { - "longest_path_bound_pipelines": [ - "load_store" - ], - "longest_path_cycles": [ - 0.015625, - 0.015625, - 0.015625, 0.0, - 3.0, - 0.0 - ], - "pipelines": [ - "arith_total", - "arith_fma", - "arith_cvt", - "arith_sfu", - "load_store", - "texture" - ], - "shortest_path_bound_pipelines": [ - "load_store" - ], - "shortest_path_cycles": [ - 0.015625, - 0.015625, - 0.015625, 0.0, - 3.0, - 0.0 + 0.5, + 0.25 ], "total_bound_pipelines": [ - "load_store" + "varying" ], "total_cycles": [ - 0.015625, - 0.015625, - 0.015625, + 0.03125, + 0.03125, + 0.03125, 0.0, - 3.0, - 0.0 + 0.0, + 0.5, + 0.25 ] }, "stack_spill_bytes": 0, "thread_occupancy": 100, - "uniform_registers_used": 10, - "work_registers_used": 8 + "uniform_registers_used": 4, + "work_registers_used": 19 } } }, "Mali-T880": { "core": "Mali-T880", - "filename": "flutter/impeller/entity/gles/gaussian_blur.vert.gles", + "filename": "flutter/impeller/entity/gles/glyph_atlas.frag.gles", "has_uniform_computation": false, - "type": "Vertex", + "type": "Fragment", "variants": { "Main": { "has_stack_spilling": false, "performance": { "longest_path_bound_pipelines": [ - "load_store" + "arithmetic", + "load_store", + "texture" ], "longest_path_cycles": [ - 2.9700000286102295, - 5.0, - 0.0 + 1.0, + 1.0, + 1.0 ], "pipelines": [ "arithmetic", @@ -3217,56 +2920,53 @@ "texture" ], "shortest_path_bound_pipelines": [ - "load_store" + "arithmetic", + "load_store", + "texture" ], "shortest_path_cycles": [ - 2.9700000286102295, - 5.0, - 0.0 + 1.0, + 1.0, + 1.0 ], "total_bound_pipelines": [ - "load_store" + "load_store", + "texture" ], "total_cycles": [ - 3.0, - 5.0, - 0.0 + 0.6666666865348816, + 1.0, + 1.0 ] }, "thread_occupancy": 100, - "uniform_registers_used": 6, + "uniform_registers_used": 0, "work_registers_used": 2 } } } }, - "flutter/impeller/entity/gles/gaussian_blur_noalpha_decal.frag.gles": { + "flutter/impeller/entity/gles/glyph_atlas.vert.gles": { "Mali-G78": { "core": "Mali-G78", - "filename": "flutter/impeller/entity/gles/gaussian_blur_noalpha_decal.frag.gles", - "has_side_effects": false, + "filename": "flutter/impeller/entity/gles/glyph_atlas.vert.gles", "has_uniform_computation": true, - "modifies_coverage": false, - "reads_color_buffer": false, - "type": "Fragment", - "uses_late_zs_test": false, - "uses_late_zs_update": false, + "type": "Vertex", "variants": { - "Main": { - "fp16_arithmetic": 70, + "Position": { + "fp16_arithmetic": 0, "has_stack_spilling": false, "performance": { "longest_path_bound_pipelines": [ - null + "load_store" ], "longest_path_cycles": [ - null, - null, - null, - null, - null, - null, - null + 0.515625, + 0.515625, + 0.140625, + 0.0, + 4.0, + 0.0 ], "pipelines": [ "arith_total", @@ -3274,178 +2974,105 @@ "arith_cvt", "arith_sfu", "load_store", - "varying", "texture" ], "shortest_path_bound_pipelines": [ - "arith_total", - "arith_cvt", - "arith_sfu" + "load_store" ], "shortest_path_cycles": [ - 0.0625, + 0.484375, + 0.484375, 0.03125, - 0.0625, - 0.0625, - 0.0, 0.0, + 4.0, 0.0 ], "total_bound_pipelines": [ - "arith_total", - "arith_sfu" + "load_store" ], "total_cycles": [ - 0.3125, - 0.234375, - 0.296875, - 0.3125, + 0.737500011920929, + 0.737500011920929, + 0.15625, 0.0, - 0.25, - 0.25 + 4.0, + 0.0 ] }, "stack_spill_bytes": 0, "thread_occupancy": 100, - "uniform_registers_used": 8, - "work_registers_used": 19 - } - } - }, - "Mali-T880": { - "core": "Mali-T880", - "filename": "flutter/impeller/entity/gles/gaussian_blur_noalpha_decal.frag.gles", - "has_uniform_computation": false, - "type": "Fragment", - "variants": { - "Main": { + "uniform_registers_used": 48, + "work_registers_used": 32 + }, + "Varying": { + "fp16_arithmetic": 0, "has_stack_spilling": false, "performance": { "longest_path_bound_pipelines": [ - null + "load_store" ], "longest_path_cycles": [ - null, - null, - null - ], - "pipelines": [ - "arithmetic", - "load_store", - "texture" - ], - "shortest_path_bound_pipelines": [ - "arithmetic" - ], - "shortest_path_cycles": [ - 1.3200000524520874, - 1.0, + 0.15625, + 0.15625, + 0.0625, + 0.0, + 5.0, 0.0 ], - "total_bound_pipelines": [ - "arithmetic" - ], - "total_cycles": [ - 4.666666507720947, - 1.0, - 1.0 - ] - }, - "thread_occupancy": 100, - "uniform_registers_used": 1, - "work_registers_used": 3 - } - } - } - }, - "flutter/impeller/entity/gles/gaussian_blur_noalpha_nodecal.frag.gles": { - "Mali-G78": { - "core": "Mali-G78", - "filename": "flutter/impeller/entity/gles/gaussian_blur_noalpha_nodecal.frag.gles", - "has_side_effects": false, - "has_uniform_computation": true, - "modifies_coverage": false, - "reads_color_buffer": false, - "type": "Fragment", - "uses_late_zs_test": false, - "uses_late_zs_update": false, - "variants": { - "Main": { - "fp16_arithmetic": 66, - "has_stack_spilling": false, - "performance": { - "longest_path_bound_pipelines": [ - null - ], - "longest_path_cycles": [ - null, - null, - null, - null, - null, - null, - null - ], "pipelines": [ "arith_total", "arith_fma", "arith_cvt", "arith_sfu", "load_store", - "varying", "texture" ], "shortest_path_bound_pipelines": [ - "arith_total", - "arith_cvt", - "arith_sfu" + "load_store" ], "shortest_path_cycles": [ - 0.0625, - 0.03125, - 0.0625, + 0.15625, + 0.15625, 0.0625, 0.0, - 0.0, + 5.0, 0.0 ], "total_bound_pipelines": [ - "varying", - "texture" + "load_store" ], "total_cycles": [ - 0.234375, - 0.234375, - 0.1875, - 0.125, + 0.15625, + 0.15625, + 0.0625, 0.0, - 0.25, - 0.25 + 5.0, + 0.0 ] }, "stack_spill_bytes": 0, "thread_occupancy": 100, - "uniform_registers_used": 8, - "work_registers_used": 19 + "uniform_registers_used": 18, + "work_registers_used": 16 } } }, "Mali-T880": { "core": "Mali-T880", - "filename": "flutter/impeller/entity/gles/gaussian_blur_noalpha_nodecal.frag.gles", - "has_uniform_computation": false, - "type": "Fragment", + "filename": "flutter/impeller/entity/gles/glyph_atlas.vert.gles", + "has_uniform_computation": true, + "type": "Vertex", "variants": { "Main": { "has_stack_spilling": false, "performance": { "longest_path_bound_pipelines": [ - null + "load_store" ], "longest_path_cycles": [ - null, - null, - null + 7.260000228881836, + 8.0, + 0.0 ], "pipelines": [ "arithmetic", @@ -3453,35 +3080,35 @@ "texture" ], "shortest_path_bound_pipelines": [ - "arithmetic" + "load_store" ], "shortest_path_cycles": [ - 1.3200000524520874, - 1.0, + 6.269999980926514, + 8.0, 0.0 ], "total_bound_pipelines": [ "arithmetic" ], "total_cycles": [ - 3.3333332538604736, - 1.0, - 1.0 + 9.333333015441895, + 8.0, + 0.0 ] }, "thread_occupancy": 100, - "uniform_registers_used": 1, - "work_registers_used": 2 + "uniform_registers_used": 13, + "work_registers_used": 3 } } } }, - "flutter/impeller/entity/gles/glyph_atlas.frag.gles": { + "flutter/impeller/entity/gles/glyph_atlas_color.frag.gles": { "Mali-G78": { "core": "Mali-G78", - "filename": "flutter/impeller/entity/gles/glyph_atlas.frag.gles", + "filename": "flutter/impeller/entity/gles/glyph_atlas_color.frag.gles", "has_side_effects": false, - "has_uniform_computation": false, + "has_uniform_computation": true, "modifies_coverage": false, "reads_color_buffer": false, "type": "Fragment", @@ -3496,9 +3123,9 @@ "varying" ], "longest_path_cycles": [ - 0.03125, - 0.03125, - 0.03125, + 0.0625, + 0.0625, + 0.0625, 0.0, 0.0, 0.5, @@ -3517,11 +3144,11 @@ "varying" ], "shortest_path_cycles": [ - 0.03125, + 0.0625, + 0.0625, 0.03125, 0.0, 0.0, - 0.0, 0.5, 0.25 ], @@ -3529,9 +3156,9 @@ "varying" ], "total_cycles": [ - 0.03125, - 0.03125, - 0.03125, + 0.0625, + 0.0625, + 0.0625, 0.0, 0.0, 0.5, @@ -3547,7 +3174,7 @@ }, "Mali-T880": { "core": "Mali-T880", - "filename": "flutter/impeller/entity/gles/glyph_atlas.frag.gles", + "filename": "flutter/impeller/entity/gles/glyph_atlas_color.frag.gles", "has_uniform_computation": false, "type": "Fragment", "variants": { @@ -3580,27 +3207,26 @@ 1.0 ], "total_bound_pipelines": [ - "load_store", - "texture" + "arithmetic" ], "total_cycles": [ - 0.6666666865348816, + 1.3333333730697632, 1.0, 1.0 ] }, "thread_occupancy": 100, - "uniform_registers_used": 0, + "uniform_registers_used": 1, "work_registers_used": 2 } } } }, - "flutter/impeller/entity/gles/glyph_atlas.vert.gles": { + "flutter/impeller/entity/gles/gradient_fill.vert.gles": { "Mali-G78": { "core": "Mali-G78", - "filename": "flutter/impeller/entity/gles/glyph_atlas.vert.gles", - "has_uniform_computation": true, + "filename": "flutter/impeller/entity/gles/gradient_fill.vert.gles", + "has_uniform_computation": false, "type": "Vertex", "variants": { "Position": { @@ -3611,62 +3237,11 @@ "load_store" ], "longest_path_cycles": [ - 0.515625, - 0.515625, + 0.140625, 0.140625, 0.0, - 4.0, - 0.0 - ], - "pipelines": [ - "arith_total", - "arith_fma", - "arith_cvt", - "arith_sfu", - "load_store", - "texture" - ], - "shortest_path_bound_pipelines": [ - "load_store" - ], - "shortest_path_cycles": [ - 0.484375, - 0.484375, - 0.03125, - 0.0, - 4.0, - 0.0 - ], - "total_bound_pipelines": [ - "load_store" - ], - "total_cycles": [ - 0.737500011920929, - 0.737500011920929, - 0.15625, - 0.0, - 4.0, - 0.0 - ] - }, - "stack_spill_bytes": 0, - "thread_occupancy": 100, - "uniform_registers_used": 48, - "work_registers_used": 32 - }, - "Varying": { - "fp16_arithmetic": 0, - "has_stack_spilling": false, - "performance": { - "longest_path_bound_pipelines": [ - "load_store" - ], - "longest_path_cycles": [ - 0.15625, - 0.15625, - 0.0625, 0.0, - 5.0, + 2.0, 0.0 ], "pipelines": [ @@ -3681,105 +3256,44 @@ "load_store" ], "shortest_path_cycles": [ - 0.15625, - 0.15625, - 0.0625, + 0.140625, + 0.140625, 0.0, - 5.0, + 0.0, + 2.0, 0.0 ], "total_bound_pipelines": [ "load_store" ], "total_cycles": [ - 0.15625, - 0.15625, - 0.0625, + 0.140625, + 0.140625, 0.0, - 5.0, + 0.0, + 2.0, 0.0 ] }, "stack_spill_bytes": 0, "thread_occupancy": 100, - "uniform_registers_used": 18, - "work_registers_used": 16 - } - } - }, - "Mali-T880": { - "core": "Mali-T880", - "filename": "flutter/impeller/entity/gles/glyph_atlas.vert.gles", - "has_uniform_computation": true, - "type": "Vertex", - "variants": { - "Main": { - "has_stack_spilling": false, - "performance": { - "longest_path_bound_pipelines": [ - "load_store" - ], - "longest_path_cycles": [ - 7.260000228881836, - 8.0, - 0.0 - ], - "pipelines": [ - "arithmetic", - "load_store", - "texture" - ], - "shortest_path_bound_pipelines": [ - "load_store" - ], - "shortest_path_cycles": [ - 6.269999980926514, - 8.0, - 0.0 - ], - "total_bound_pipelines": [ - "arithmetic" - ], - "total_cycles": [ - 9.333333015441895, - 8.0, - 0.0 - ] - }, - "thread_occupancy": 100, - "uniform_registers_used": 13, - "work_registers_used": 3 - } - } - } - }, - "flutter/impeller/entity/gles/glyph_atlas_color.frag.gles": { - "Mali-G78": { - "core": "Mali-G78", - "filename": "flutter/impeller/entity/gles/glyph_atlas_color.frag.gles", - "has_side_effects": false, - "has_uniform_computation": true, - "modifies_coverage": false, - "reads_color_buffer": false, - "type": "Fragment", - "uses_late_zs_test": false, - "uses_late_zs_update": false, - "variants": { - "Main": { - "fp16_arithmetic": 100, + "uniform_registers_used": 30, + "work_registers_used": 32 + }, + "Varying": { + "fp16_arithmetic": 0, "has_stack_spilling": false, "performance": { "longest_path_bound_pipelines": [ - "varying" + "load_store" ], "longest_path_cycles": [ - 0.0625, - 0.0625, - 0.0625, - 0.0, + 0.125, + 0.125, 0.0, - 0.5, - 0.25 + 0.0625, + 3.0, + 0.0 ], "pipelines": [ "arith_total", @@ -3787,59 +3301,54 @@ "arith_cvt", "arith_sfu", "load_store", - "varying", "texture" ], "shortest_path_bound_pipelines": [ - "varying" + "load_store" ], "shortest_path_cycles": [ - 0.0625, - 0.0625, - 0.03125, - 0.0, + 0.125, + 0.125, 0.0, - 0.5, - 0.25 + 0.0625, + 3.0, + 0.0 ], "total_bound_pipelines": [ - "varying" + "load_store" ], "total_cycles": [ - 0.0625, - 0.0625, - 0.0625, - 0.0, + 0.125, + 0.125, 0.0, - 0.5, - 0.25 + 0.0625, + 3.0, + 0.0 ] }, "stack_spill_bytes": 0, "thread_occupancy": 100, - "uniform_registers_used": 4, - "work_registers_used": 19 + "uniform_registers_used": 18, + "work_registers_used": 11 } } }, "Mali-T880": { "core": "Mali-T880", - "filename": "flutter/impeller/entity/gles/glyph_atlas_color.frag.gles", + "filename": "flutter/impeller/entity/gles/gradient_fill.vert.gles", "has_uniform_computation": false, - "type": "Fragment", + "type": "Vertex", "variants": { "Main": { "has_stack_spilling": false, "performance": { "longest_path_bound_pipelines": [ - "arithmetic", - "load_store", - "texture" + "load_store" ], "longest_path_cycles": [ - 1.0, - 1.0, - 1.0 + 3.299999952316284, + 4.0, + 0.0 ], "pipelines": [ "arithmetic", @@ -3847,36 +3356,34 @@ "texture" ], "shortest_path_bound_pipelines": [ - "arithmetic", - "load_store", - "texture" + "load_store" ], "shortest_path_cycles": [ - 1.0, - 1.0, - 1.0 + 3.299999952316284, + 4.0, + 0.0 ], "total_bound_pipelines": [ - "arithmetic" + "load_store" ], "total_cycles": [ - 1.3333333730697632, - 1.0, - 1.0 + 3.3333332538604736, + 4.0, + 0.0 ] }, "thread_occupancy": 100, - "uniform_registers_used": 1, + "uniform_registers_used": 8, "work_registers_used": 2 } } } }, - "flutter/impeller/entity/gles/gradient_fill.vert.gles": { + "flutter/impeller/entity/gles/kernel.vert.gles": { "Mali-G78": { "core": "Mali-G78", - "filename": "flutter/impeller/entity/gles/gradient_fill.vert.gles", - "has_uniform_computation": false, + "filename": "flutter/impeller/entity/gles/kernel.vert.gles", + "has_uniform_computation": true, "type": "Vertex", "variants": { "Position": { @@ -3927,7 +3434,7 @@ }, "stack_spill_bytes": 0, "thread_occupancy": 100, - "uniform_registers_used": 30, + "uniform_registers_used": 22, "work_registers_used": 32 }, "Varying": { @@ -3938,10 +3445,10 @@ "load_store" ], "longest_path_cycles": [ - 0.125, - 0.125, + 0.015625, + 0.015625, + 0.015625, 0.0, - 0.0625, 3.0, 0.0 ], @@ -3957,10 +3464,10 @@ "load_store" ], "shortest_path_cycles": [ - 0.125, - 0.125, + 0.015625, + 0.015625, + 0.015625, 0.0, - 0.0625, 3.0, 0.0 ], @@ -3968,24 +3475,24 @@ "load_store" ], "total_cycles": [ - 0.125, - 0.125, + 0.015625, + 0.015625, + 0.015625, 0.0, - 0.0625, 3.0, 0.0 ] }, "stack_spill_bytes": 0, "thread_occupancy": 100, - "uniform_registers_used": 18, - "work_registers_used": 11 + "uniform_registers_used": 10, + "work_registers_used": 8 } } }, "Mali-T880": { "core": "Mali-T880", - "filename": "flutter/impeller/entity/gles/gradient_fill.vert.gles", + "filename": "flutter/impeller/entity/gles/kernel.vert.gles", "has_uniform_computation": false, "type": "Vertex", "variants": { @@ -3996,8 +3503,8 @@ "load_store" ], "longest_path_cycles": [ - 3.299999952316284, - 4.0, + 2.9700000286102295, + 5.0, 0.0 ], "pipelines": [ @@ -4009,21 +3516,21 @@ "load_store" ], "shortest_path_cycles": [ - 3.299999952316284, - 4.0, + 2.9700000286102295, + 5.0, 0.0 ], "total_bound_pipelines": [ "load_store" ], "total_cycles": [ - 3.3333332538604736, - 4.0, + 3.0, + 5.0, 0.0 ] }, "thread_occupancy": 100, - "uniform_registers_used": 8, + "uniform_registers_used": 6, "work_registers_used": 2 } } @@ -8076,6 +7583,118 @@ } } }, + "flutter/impeller/entity/kernel.vert.vkspv": { + "Mali-G78": { + "core": "Mali-G78", + "filename": "flutter/impeller/entity/kernel.vert.vkspv", + "has_uniform_computation": true, + "type": "Vertex", + "variants": { + "Position": { + "fp16_arithmetic": 0, + "has_stack_spilling": false, + "performance": { + "longest_path_bound_pipelines": [ + "load_store" + ], + "longest_path_cycles": [ + 0.125, + 0.125, + 0.0, + 0.0, + 2.0, + 0.0 + ], + "pipelines": [ + "arith_total", + "arith_fma", + "arith_cvt", + "arith_sfu", + "load_store", + "texture" + ], + "shortest_path_bound_pipelines": [ + "load_store" + ], + "shortest_path_cycles": [ + 0.125, + 0.125, + 0.0, + 0.0, + 2.0, + 0.0 + ], + "total_bound_pipelines": [ + "load_store" + ], + "total_cycles": [ + 0.125, + 0.125, + 0.0, + 0.0, + 2.0, + 0.0 + ] + }, + "stack_spill_bytes": 0, + "thread_occupancy": 100, + "uniform_registers_used": 30, + "work_registers_used": 32 + }, + "Varying": { + "fp16_arithmetic": 0, + "has_stack_spilling": false, + "performance": { + "longest_path_bound_pipelines": [ + "load_store" + ], + "longest_path_cycles": [ + 0.015625, + 0.015625, + 0.015625, + 0.0, + 3.0, + 0.0 + ], + "pipelines": [ + "arith_total", + "arith_fma", + "arith_cvt", + "arith_sfu", + "load_store", + "texture" + ], + "shortest_path_bound_pipelines": [ + "load_store" + ], + "shortest_path_cycles": [ + 0.015625, + 0.015625, + 0.015625, + 0.0, + 3.0, + 0.0 + ], + "total_bound_pipelines": [ + "load_store" + ], + "total_cycles": [ + 0.015625, + 0.015625, + 0.015625, + 0.0, + 3.0, + 0.0 + ] + }, + "stack_spill_bytes": 0, + "thread_occupancy": 100, + "uniform_registers_used": 22, + "work_registers_used": 8 + } + } + } + }, "flutter/impeller/entity/kernel_decal.frag.vkspv": { "Mali-G78": { "core": "Mali-G78", diff --git a/lib/ui/painting.dart b/lib/ui/painting.dart index a2757b7359ce8..bfe9de5a854bb 100644 --- a/lib/ui/painting.dart +++ b/lib/ui/painting.dart @@ -1092,6 +1092,22 @@ final class Paint { /// their defaults. Paint(); + /// Constructs a new [Paint] object with the same fields as [other]. + /// + /// Any changes made to the object returned will not affect [other], and + /// changes to [other] will not affect the object returned. + /// + /// Backends (for example web versus native) may have different performance + /// characteristics. If the code is performance-sensitive, consider profiling + /// and falling back to reusing a single [Paint] object if necessary. + Paint.from(Paint other) { + // Every field on Paint is deeply immutable, so to create a copy of a Paint + // object, we copy the underlying data buffer and the list of objects (which + // are also deeply immutable). + _data.buffer.asUint32List().setAll(0, other._data.buffer.asUint32List()); + _objects = other._objects?.toList(); + } + // Paint objects are encoded in two buffers: // // * _data is binary data in four-byte fields, each of which is either a diff --git a/lib/ui/window/platform_configuration.cc b/lib/ui/window/platform_configuration.cc index 574b9e27f33ca..e6a934a63c69b 100644 --- a/lib/ui/window/platform_configuration.cc +++ b/lib/ui/window/platform_configuration.cc @@ -83,15 +83,18 @@ void PlatformConfiguration::DidCreateIsolate() { Dart_GetField(library, tonic::ToDart("_reportTimings"))); } -void PlatformConfiguration::AddView(int64_t view_id, +bool PlatformConfiguration::AddView(int64_t view_id, const ViewportMetrics& view_metrics) { auto [view_iterator, insertion_happened] = metrics_.emplace(view_id, view_metrics); - FML_DCHECK(insertion_happened); + if (!insertion_happened) { + FML_LOG(ERROR) << "View #" << view_id << " already exists."; + return false; + } std::shared_ptr dart_state = add_view_.dart_state().lock(); if (!dart_state) { - return; + return false; } tonic::DartState::Scope scope(dart_state); tonic::CheckAndHandleError(tonic::DartInvoke( @@ -119,6 +122,7 @@ void PlatformConfiguration::AddView(int64_t view_id, tonic::ToDart(view_metrics.physical_display_features_state), tonic::ToDart(view_metrics.display_id), })); + return true; } bool PlatformConfiguration::RemoveView(int64_t view_id) { diff --git a/lib/ui/window/platform_configuration.h b/lib/ui/window/platform_configuration.h index 6a583765d962f..b668b85cfd603 100644 --- a/lib/ui/window/platform_configuration.h +++ b/lib/ui/window/platform_configuration.h @@ -262,8 +262,7 @@ class PlatformConfigurationClient { /// @brief A class for holding and distributing platform-level information /// to and from the Dart code in Flutter's framework. /// -/// It handles communication between the engine and the framework, -/// and owns the main window. +/// It handles communication between the engine and the framework. /// /// It communicates with the RuntimeController through the use of a /// PlatformConfigurationClient interface, which the @@ -315,7 +314,9 @@ class PlatformConfiguration final { /// @param[in] view_id The ID of the new view. /// @param[in] viewport_metrics The initial viewport metrics for the view. /// - void AddView(int64_t view_id, const ViewportMetrics& view_metrics); + /// @return Whether the view was added. + /// + bool AddView(int64_t view_id, const ViewportMetrics& view_metrics); //---------------------------------------------------------------------------- /// @brief Notify the framework that a view is no longer available. diff --git a/lib/web_ui/lib/painting.dart b/lib/web_ui/lib/painting.dart index c050e43dec611..c7e1ff62c270a 100644 --- a/lib/web_ui/lib/painting.dart +++ b/lib/web_ui/lib/painting.dart @@ -217,6 +217,35 @@ enum Clip { abstract class Paint { factory Paint() => engine.renderer.createPaint(); + + factory Paint.from(Paint other) { + // This is less efficient than copying the underlying buffer or object but + // it's a reasonable default, as if a user wanted to implement a copy of a + // paint object themselves they are unable to do much better than this. + // + // TODO(matanlurey): Web team, if important to optimize, could: + // 1. Add a `engine.renderer.copyPaint` method. + // 2. Use the below code as the default implementation. + // 3. Have renderer-specific implementations override with optimized code. + final Paint paint = Paint(); + paint + ..blendMode = other.blendMode + ..color = other.color + ..colorFilter = other.colorFilter + ..filterQuality = other.filterQuality + ..imageFilter = other.imageFilter + ..invertColors = other.invertColors + ..isAntiAlias = other.isAntiAlias + ..maskFilter = other.maskFilter + ..shader = other.shader + ..strokeCap = other.strokeCap + ..strokeJoin = other.strokeJoin + ..strokeMiterLimit = other.strokeMiterLimit + ..strokeWidth = other.strokeWidth + ..style = other.style; + return paint; + } + BlendMode get blendMode; set blendMode(BlendMode value); PaintingStyle get style; diff --git a/lib/web_ui/lib/src/engine/platform_dispatcher/view_focus_binding.dart b/lib/web_ui/lib/src/engine/platform_dispatcher/view_focus_binding.dart index 1dc10b9fef846..4ec158546337b 100644 --- a/lib/web_ui/lib/src/engine/platform_dispatcher/view_focus_binding.dart +++ b/lib/web_ui/lib/src/engine/platform_dispatcher/view_focus_binding.dart @@ -95,11 +95,8 @@ final class ViewFocusBinding { } int? _viewId(DomElement? element) { - final DomElement? rootElement = element?.closest(DomManager.flutterViewTagName); - if (rootElement == null) { - return null; - } - return _viewManager.viewIdForRootElement(rootElement); + final FlutterViewManager viewManager = EnginePlatformDispatcher.instance.viewManager; + return viewManager.findViewForElement(element)?.viewId; } void _handleViewCreated(int viewId) { diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/filters.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/filters.dart index adc8026ef1d5b..d85668e61f374 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/filters.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/filters.dart @@ -2,42 +2,38 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:ffi'; import 'dart:typed_data'; import 'package:ui/src/engine.dart'; import 'package:ui/src/engine/skwasm/skwasm_impl.dart'; import 'package:ui/ui.dart' as ui; -class SkwasmImageFilter extends SkwasmObjectWrapper implements SceneImageFilter { - SkwasmImageFilter._(ImageFilterHandle handle) : super(handle, _registry); +abstract class SkwasmImageFilter extends SkwasmObjectWrapper implements SceneImageFilter { + SkwasmImageFilter(ImageFilterHandle handle) : super(handle, _registry); factory SkwasmImageFilter.blur({ double sigmaX = 0.0, double sigmaY = 0.0, ui.TileMode tileMode = ui.TileMode.clamp, - }) => SkwasmImageFilter._(imageFilterCreateBlur(sigmaX, sigmaY, tileMode.index)); + }) => SkwasmBlurFilter(sigmaX, sigmaY, tileMode); factory SkwasmImageFilter.dilate({ double radiusX = 0.0, double radiusY = 0.0, - }) => SkwasmImageFilter._(imageFilterCreateDilate(radiusX, radiusY)); + }) => SkwasmDilateFilter(radiusX, radiusY); factory SkwasmImageFilter.erode({ double radiusX = 0.0, double radiusY = 0.0, - }) => SkwasmImageFilter._(imageFilterCreateErode(radiusX, radiusY)); + }) => SkwasmErodeFilter(radiusX, radiusY); factory SkwasmImageFilter.matrix( Float64List matrix4, { ui.FilterQuality filterQuality = ui.FilterQuality.low - }) => withStackScope((StackScope scope) => SkwasmImageFilter._(imageFilterCreateMatrix( - scope.convertMatrix4toSkMatrix(matrix4), - filterQuality.index - ))); + }) => SkwasmMatrixFilter(matrix4, filterQuality); factory SkwasmImageFilter.fromColorFilter(SkwasmColorFilter filter) => - SkwasmImageFilter._(imageFilterCreateFromColorFilter(filter.handle)); + SkwasmColorImageFilter(filter); factory SkwasmImageFilter.fromUiFilter(ui.ImageFilter filter) { if (filter is ui.ColorFilter) { @@ -54,11 +50,10 @@ class SkwasmImageFilter extends SkwasmObjectWrapper implements S factory SkwasmImageFilter.compose( ui.ImageFilter outer, ui.ImageFilter inner, - ) { - final SkwasmImageFilter nativeOuter = SkwasmImageFilter.fromUiFilter(outer); - final SkwasmImageFilter nativeInner = SkwasmImageFilter.fromUiFilter(inner); - return SkwasmImageFilter._(imageFilterCompose(nativeOuter.handle, nativeInner.handle)); - } + ) => SkwasmComposedImageFilter( + SkwasmImageFilter.fromUiFilter(outer), + SkwasmImageFilter.fromUiFilter(inner), + ); static final SkwasmFinalizationRegistry _registry = SkwasmFinalizationRegistry(imageFilterDispose); @@ -71,32 +66,161 @@ class SkwasmImageFilter extends SkwasmObjectWrapper implements S }); } -class SkwasmColorFilter extends SkwasmObjectWrapper { - SkwasmColorFilter._(ColorFilterHandle handle) : super(handle, _registry); +class SkwasmBlurFilter extends SkwasmImageFilter { + SkwasmBlurFilter( + this.sigmaX, + this.sigmaY, + this.tileMode, + ) : super(imageFilterCreateBlur(sigmaX, sigmaY, tileMode.index)); + + final double sigmaX; + final double sigmaY; + ui.TileMode tileMode; + + @override + String toString() => 'ImageFilter.blur($sigmaX, $sigmaY, ${tileModeString(tileMode)})'; +} + +class SkwasmDilateFilter extends SkwasmImageFilter { + SkwasmDilateFilter( + this.radiusX, + this.radiusY, + ) : super(imageFilterCreateDilate(radiusX, radiusY)); + + final double radiusX; + final double radiusY; + + @override + String toString() => 'ImageFilter.dilate($radiusX, $radiusY)'; +} + +class SkwasmErodeFilter extends SkwasmImageFilter { + SkwasmErodeFilter( + this.radiusX, + this.radiusY, + ) : super(imageFilterCreateErode(radiusX, radiusY)); + + final double radiusX; + final double radiusY; + + @override + String toString() => 'ImageFilter.erode($radiusX, $radiusY)'; +} + +class SkwasmMatrixFilter extends SkwasmImageFilter { + SkwasmMatrixFilter( + this.matrix4, + this.filterQuality, + ) : super(withStackScope((StackScope scope) => imageFilterCreateMatrix( + scope.convertMatrix4toSkMatrix(matrix4), + filterQuality.index, + ))); + + final Float64List matrix4; + final ui.FilterQuality filterQuality; + + @override + String toString() => 'ImageFilter.matrix($matrix4, $filterQuality)'; +} + +class SkwasmColorImageFilter extends SkwasmImageFilter { + SkwasmColorImageFilter( + this.filter, + ) : super(imageFilterCreateFromColorFilter(filter.handle)); + + final SkwasmColorFilter filter; + + @override + String toString() => filter.toString(); +} + +class SkwasmComposedImageFilter extends SkwasmImageFilter { + SkwasmComposedImageFilter( + this.outer, + this.inner, + ) : super(imageFilterCompose(outer.handle, inner.handle)); + + final SkwasmImageFilter outer; + final SkwasmImageFilter inner; + + @override + String toString() => 'ImageFilter.compose($outer, $inner)'; +} + +abstract class SkwasmColorFilter extends SkwasmObjectWrapper { + SkwasmColorFilter(ColorFilterHandle handle) : super(handle, _registry); factory SkwasmColorFilter.fromEngineColorFilter(EngineColorFilter colorFilter) => switch (colorFilter.type) { - ColorFilterType.mode => SkwasmColorFilter._(colorFilterCreateMode( - colorFilter.color!.value, - colorFilter.blendMode!.index, - )), - ColorFilterType.linearToSrgbGamma => SkwasmColorFilter._(colorFilterCreateLinearToSRGBGamma()), - ColorFilterType.srgbToLinearGamma => SkwasmColorFilter._(colorFilterCreateSRGBToLinearGamma()), - ColorFilterType.matrix => withStackScope((StackScope scope) { - final Pointer nativeMatrix = scope.convertDoublesToNative(colorFilter.matrix!); - return SkwasmColorFilter._(colorFilterCreateMatrix(nativeMatrix)); - }), + ColorFilterType.mode => SkwasmModeColorFilter(colorFilter.color!, colorFilter.blendMode!), + ColorFilterType.linearToSrgbGamma => SkwasmLinearToSrgbGammaColorFilter(), + ColorFilterType.srgbToLinearGamma => SkwasmSrgbToLinearGammaColorFilter(), + ColorFilterType.matrix => SkwasmMatrixColorFilter(colorFilter.matrix!), }; factory SkwasmColorFilter.composed( SkwasmColorFilter outer, SkwasmColorFilter inner, - ) => SkwasmColorFilter._(colorFilterCompose(outer.handle, inner.handle)); + ) => SkwasmComposedColorFilter(outer, inner); static final SkwasmFinalizationRegistry _registry = SkwasmFinalizationRegistry(colorFilterDispose); } +class SkwasmModeColorFilter extends SkwasmColorFilter { + SkwasmModeColorFilter( + this.color, + this.blendMode, + ) : super(colorFilterCreateMode( + color.value, + blendMode.index, + )); + + final ui.Color color; + final ui.BlendMode blendMode; + + @override + String toString() => 'ColorFilter.mode($color, $blendMode)'; +} + +class SkwasmLinearToSrgbGammaColorFilter extends SkwasmColorFilter { + SkwasmLinearToSrgbGammaColorFilter() : super(colorFilterCreateLinearToSRGBGamma()); + + @override + String toString() => 'ColorFilter.linearToSrgbGamma()'; +} + +class SkwasmSrgbToLinearGammaColorFilter extends SkwasmColorFilter { + SkwasmSrgbToLinearGammaColorFilter() : super(colorFilterCreateSRGBToLinearGamma()); + + @override + String toString() => 'ColorFilter.srgbToLinearGamma()'; +} + +class SkwasmMatrixColorFilter extends SkwasmColorFilter { + SkwasmMatrixColorFilter(this.matrix) : super(withStackScope((StackScope scope) => + colorFilterCreateMatrix(scope.convertDoublesToNative(matrix)) + )); + + final List matrix; + + @override + String toString() => 'ColorFilter.matrix($matrix)'; +} + +class SkwasmComposedColorFilter extends SkwasmColorFilter { + SkwasmComposedColorFilter( + this.outer, + this.inner, + ) : super(colorFilterCompose(outer.handle, inner.handle)); + + final SkwasmColorFilter outer; + final SkwasmColorFilter inner; + + @override + String toString() => 'ColorFilter.compose($outer, $inner)'; +} + class SkwasmMaskFilter extends SkwasmObjectWrapper { SkwasmMaskFilter._(MaskFilterHandle handle) : super(handle, _registry); diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paint.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paint.dart index 26e91164134df..507bf94c63ca6 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paint.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paint.dart @@ -11,6 +11,15 @@ import 'package:ui/ui.dart' as ui; class SkwasmPaint extends SkwasmObjectWrapper implements ui.Paint { SkwasmPaint() : super(paintCreate(), _registry); + // Must be kept in sync with the default in paint.cc. + static const double _kStrokeMiterLimitDefault = 4.0; + + // Must be kept in sync with the default in paint.cc. + static const int _kColorDefault = 0xFF000000; + + // Must be kept in sync with the default in paint.cc. + static final int _kBlendModeDefault = ui.BlendMode.srcOver.index; + static final SkwasmFinalizationRegistry _registry = SkwasmFinalizationRegistry(paintDispose); @@ -173,7 +182,73 @@ class SkwasmPaint extends SkwasmObjectWrapper implements ui.Paint { _setEffectiveColorFilter(); } - // TODO(yjbanov): https://github.com/flutter/flutter/issues/141639 @override - String toString() => 'Paint()'; + String toString() { + String resultString = 'Paint()'; + + assert(() { + final StringBuffer result = StringBuffer(); + String semicolon = ''; + result.write('Paint('); + if (style == ui.PaintingStyle.stroke) { + result.write('$style'); + if (strokeWidth != 0.0) { + result.write(' ${strokeWidth.toStringAsFixed(1)}'); + } else { + result.write(' hairline'); + } + if (strokeCap != ui.StrokeCap.butt) { + result.write(' $strokeCap'); + } + if (strokeJoin == ui.StrokeJoin.miter) { + if (strokeMiterLimit != _kStrokeMiterLimitDefault) { + result.write(' $strokeJoin up to ${strokeMiterLimit.toStringAsFixed(1)}'); + } + } else { + result.write(' $strokeJoin'); + } + semicolon = '; '; + } + if (!isAntiAlias) { + result.write('${semicolon}antialias off'); + semicolon = '; '; + } + if (color != const ui.Color(_kColorDefault)) { + result.write('$semicolon$color'); + semicolon = '; '; + } + if (blendMode.index != _kBlendModeDefault) { + result.write('$semicolon$blendMode'); + semicolon = '; '; + } + if (colorFilter != null) { + result.write('${semicolon}colorFilter: $colorFilter'); + semicolon = '; '; + } + if (maskFilter != null) { + result.write('${semicolon}maskFilter: $maskFilter'); + semicolon = '; '; + } + if (filterQuality != ui.FilterQuality.none) { + result.write('${semicolon}filterQuality: $filterQuality'); + semicolon = '; '; + } + if (shader != null) { + result.write('${semicolon}shader: $shader'); + semicolon = '; '; + } + if (imageFilter != null) { + result.write('${semicolon}imageFilter: $imageFilter'); + semicolon = '; '; + } + if (invertColors) { + result.write('${semicolon}invert: $invertColors'); + } + result.write(')'); + resultString = result.toString(); + return true; + }()); + + return resultString; + } } diff --git a/lib/web_ui/lib/src/engine/text_editing/text_editing.dart b/lib/web_ui/lib/src/engine/text_editing/text_editing.dart index 672a5ad3be61d..ac3c7b187fedf 100644 --- a/lib/web_ui/lib/src/engine/text_editing/text_editing.dart +++ b/lib/web_ui/lib/src/engine/text_editing/text_editing.dart @@ -18,6 +18,8 @@ import '../semantics.dart'; import '../services.dart'; import '../text/paragraph.dart'; import '../util.dart'; +import '../view_embedder/flutter_view_manager.dart'; +import '../window.dart'; import 'autofill_hint.dart'; import 'composition_aware_mixin.dart'; import 'input_action.dart'; @@ -48,12 +50,6 @@ const String transparentTextEditingClass = 'transparentTextEditing'; void _emptyCallback(dynamic _) {} -/// The default [HostNode] that hosts all DOM required for text editing when a11y is not enabled. -@visibleForTesting -// TODO(mdebbar): There could be multiple views with multiple text editing hosts. -// https://github.com/flutter/flutter/issues/137344 -DomElement get defaultTextEditingRoot => EnginePlatformDispatcher.instance.implicitView!.dom.textEditingHost; - /// These style attributes are constant throughout the life time of an input /// element. /// @@ -147,6 +143,37 @@ void _styleAutofillElements( elementStyle.setProperty('caret-color', 'transparent'); } +void _ensureEditingElementInView(DomElement element, int viewId) { + final bool isAlreadyAppended = element.isConnected ?? false; + if (!isAlreadyAppended) { + // If the element is not already appended to a view, we don't need to move + // it anywhere. + return; + } + + final FlutterViewManager viewManager = EnginePlatformDispatcher.instance.viewManager; + final EngineFlutterView? currentView = viewManager.findViewForElement(element); + if (currentView == null) { + // For some reason, the input element was in the DOM, but it wasn't part of + // any Flutter view. Should we throw? + return; + } + + if (currentView.viewId != viewId) { + _insertEditingElementInView(element, viewId); + } +} + +void _insertEditingElementInView(DomElement element, int viewId) { + final FlutterViewManager viewManager = EnginePlatformDispatcher.instance.viewManager; + final EngineFlutterView? view = viewManager[viewId]; + assert( + view != null, + 'Could not find View with id $viewId. This should never happen, please file a bug!', + ); + view!.dom.textEditingHost.append(element); +} + /// Form that contains all the fields in the same AutofillGroup. /// /// An [EngineAutofillForm] will only be constructed when autofill is enabled @@ -154,6 +181,7 @@ void _styleAutofillElements( /// static method. class EngineAutofillForm { EngineAutofillForm({ + required this.viewId, required this.formElement, this.elements, this.items, @@ -177,6 +205,9 @@ class EngineAutofillForm { /// See [formsOnTheDom]. final String formIdentifier; + /// The ID of the view that this form is rendered into. + final int viewId; + /// Creates an [EngineAutofillFrom] from the JSON representation of a Flutter /// framework `TextInputConfiguration` object. /// @@ -189,6 +220,7 @@ class EngineAutofillForm { /// /// Returns null if autofill is disabled for the input field. static EngineAutofillForm? fromFrameworkMessage( + int viewId, Map? focusedElementAutofill, List? fields, ) { @@ -312,6 +344,7 @@ class EngineAutofillForm { insertionReferenceNode ??= submitButton; return EngineAutofillForm( + viewId: viewId, formElement: formElement, elements: elements, items: items, @@ -330,7 +363,7 @@ class EngineAutofillForm { } formElement.insertBefore(mainTextEditingElement, insertionReferenceNode); - defaultTextEditingRoot.append(formElement); + _insertEditingElementInView(formElement, viewId); } void storeForm() { @@ -944,6 +977,7 @@ class EditingState { /// This corresponds to Flutter's [TextInputConfiguration]. class InputConfiguration { InputConfiguration({ + required this.viewId, this.inputType = EngineInputType.text, this.inputAction = 'TextInputAction.done', this.obscureText = false, @@ -958,7 +992,8 @@ class InputConfiguration { InputConfiguration.fromFrameworkMessage( Map flutterInputConfiguration) - : inputType = EngineInputType.fromName( + : viewId = flutterInputConfiguration.tryInt('viewId') ?? kImplicitViewId, + inputType = EngineInputType.fromName( flutterInputConfiguration.readJson('inputType').readString('name'), isDecimal: flutterInputConfiguration.readJson('inputType').tryBool('decimal') ?? false, isMultiline: flutterInputConfiguration.readJson('inputType').tryBool('isMultiline') ?? false, @@ -976,11 +1011,15 @@ class InputConfiguration { flutterInputConfiguration.readJson('autofill')) : null, autofillGroup = EngineAutofillForm.fromFrameworkMessage( + flutterInputConfiguration.tryInt('viewId') ?? kImplicitViewId, flutterInputConfiguration.tryJson('autofill'), flutterInputConfiguration.tryList('fields'), ), enableDeltaModel = flutterInputConfiguration.tryBool('enableDeltaModel') ?? false; + /// The ID of the view that contains the text field. + final int viewId; + /// The type of information being edited in the input control. final EngineInputType inputType; @@ -1257,7 +1296,7 @@ abstract class DefaultTextEditingStrategy with CompositionAwareMixin implements // DOM later, when the first location information arrived. // Otherwise, on Blink based Desktop browsers, the autofill menu appears // on top left of the screen. - defaultTextEditingRoot.append(activeDomElement); + _insertEditingElementInView(activeDomElement, inputConfig.viewId); _appendedToForm = false; } @@ -1293,6 +1332,9 @@ abstract class DefaultTextEditingStrategy with CompositionAwareMixin implements autofill.applyToDomElement(activeDomElement, focusedElement: true); } else { activeDomElement.setAttribute('autocomplete', 'off'); + // When the new input configuration contains a different view ID, we need + // to move the input element to the new view. + _ensureEditingElementInView(activeDomElement, inputConfiguration.viewId); } final String autocorrectValue = config.autocorrect ? 'on' : 'off'; @@ -1757,7 +1799,7 @@ class AndroidTextEditingStrategy extends GloballyPositionedTextEditingStrategy { if (hasAutofillGroup) { placeForm(); } else { - defaultTextEditingRoot.append(activeDomElement); + _insertEditingElementInView(activeDomElement, inputConfig.viewId); } inputConfig.textCapitalization.setAutocapitalizeAttribute( activeDomElement); diff --git a/lib/web_ui/lib/src/engine/view_embedder/flutter_view_manager.dart b/lib/web_ui/lib/src/engine/view_embedder/flutter_view_manager.dart index 684e5ea667c22..2d2bc639e361a 100644 --- a/lib/web_ui/lib/src/engine/view_embedder/flutter_view_manager.dart +++ b/lib/web_ui/lib/src/engine/view_embedder/flutter_view_manager.dart @@ -12,12 +12,15 @@ class FlutterViewManager { // A map of EngineFlutterViews indexed by their viewId. final Map _viewData = {}; + // A map of (optional) JsFlutterViewOptions, indexed by their viewId. final Map _jsViewOptions = {}; + // The controller of the [onViewCreated] stream. final StreamController _onViewCreatedController = StreamController.broadcast(sync: true); + // The controller of the [onViewDisposed] stream. final StreamController _onViewDisposedController = StreamController.broadcast(sync: true); @@ -82,7 +85,7 @@ class FlutterViewManager { /// /// Returns its [JsFlutterViewOptions] (if any). JsFlutterViewOptions? unregisterView(int viewId) { - _viewData.remove(viewId); // .dispose(); + _viewData.remove(viewId); final JsFlutterViewOptions? jsViewOptions = _jsViewOptions.remove(viewId); _onViewDisposedController.add(viewId); return jsViewOptions; @@ -96,14 +99,13 @@ class FlutterViewManager { return _jsViewOptions[viewId]; } - /// Returns the [viewId] if [rootElement] corresponds to any of the [views]. - int? viewIdForRootElement(DomElement rootElement) { - for(final EngineFlutterView view in views) { - if (view.dom.rootElement == rootElement) { - return view.viewId; - } - } - return null; + EngineFlutterView? findViewForElement(DomElement? element) { + const String viewRootSelector = + '${DomManager.flutterViewTagName}[${GlobalHtmlAttributes.flutterViewIdAttributeName}]'; + final DomElement? viewRoot = element?.closest(viewRootSelector); + final String? viewIdAttribute = viewRoot?.getAttribute(GlobalHtmlAttributes.flutterViewIdAttributeName); + final int? viewId = viewIdAttribute == null ? null : int.parse(viewIdAttribute); + return viewId == null ? null : _viewData[viewId]; } void dispose() { diff --git a/lib/web_ui/skwasm/paint.cpp b/lib/web_ui/skwasm/paint.cpp index d75264dad42a8..8bba9a5f8a819 100644 --- a/lib/web_ui/skwasm/paint.cpp +++ b/lib/web_ui/skwasm/paint.cpp @@ -82,7 +82,7 @@ SKWASM_EXPORT void paint_setMiterLimit(SkPaint* paint, SkScalar miterLimit) { paint->setStrokeMiter(miterLimit); } -SKWASM_EXPORT SkScalar paint_getMiterLImit(SkPaint* paint) { +SKWASM_EXPORT SkScalar paint_getMiterLimit(SkPaint* paint) { return paint->getStrokeMiter(); } diff --git a/lib/web_ui/test/canvaskit/canvaskit_api_test.dart b/lib/web_ui/test/canvaskit/canvaskit_api_test.dart index 69d9f6bf85967..bc7eec8660b17 100644 --- a/lib/web_ui/test/canvaskit/canvaskit_api_test.dart +++ b/lib/web_ui/test/canvaskit/canvaskit_api_test.dart @@ -1596,7 +1596,7 @@ void _paragraphTests() { ..weight = canvasKit.FontWeight.Bold) ..fontSize = 72 ..heightMultiplier = 1.5 - ..halfLeading = false + ..halfLeading = true ..leading = 0 ..strutEnabled = true ..forceStrutHeight = false; @@ -1673,7 +1673,7 @@ void _paragraphTests() { expect(actual, within(distance: actual / 100, from: expected)); } - expectAlmost(paragraph.getAlphabeticBaseline(), 85.5); + expectAlmost(paragraph.getAlphabeticBaseline(), 78.6); expect(paragraph.didExceedMaxLines(), isFalse); expectAlmost(paragraph.getHeight(), 108); expectAlmost(paragraph.getIdeographicBaseline(), 108); @@ -1699,7 +1699,7 @@ void _paragraphTests() { expectAlmost(lineMetrics.ascent, 55.6); expectAlmost(lineMetrics.descent, 14.8); expect(lineMetrics.isHardBreak, isTrue); - expectAlmost(lineMetrics.baseline, 85.5); + expectAlmost(lineMetrics.baseline, 78.6); expectAlmost(lineMetrics.height, 108); expectAlmost(lineMetrics.left, 2.5); expectAlmost(lineMetrics.width, 263); diff --git a/lib/web_ui/test/engine/composition_test.dart b/lib/web_ui/test/engine/composition_test.dart index 6e743139bd47d..75551745b3d56 100644 --- a/lib/web_ui/test/engine/composition_test.dart +++ b/lib/web_ui/test/engine/composition_test.dart @@ -6,14 +6,13 @@ import 'dart:async'; import 'package:test/bootstrap/browser.dart'; import 'package:test/test.dart'; -import 'package:ui/src/engine/browser_detection.dart'; - -import 'package:ui/src/engine/dom.dart'; -import 'package:ui/src/engine/text_editing/composition_aware_mixin.dart'; -import 'package:ui/src/engine/text_editing/text_editing.dart'; +import 'package:ui/src/engine.dart'; import '../common/test_initialization.dart'; +DomElement get defaultTextEditingRoot => + EnginePlatformDispatcher.instance.implicitView!.dom.textEditingHost; + void main() { internalBootstrapBrowserTest(() => testMain); } @@ -36,7 +35,10 @@ GloballyPositionedTextEditingStrategy _enableEditingStrategy({ }) { final HybridTextEditing owner = HybridTextEditing(); - owner.configuration = InputConfiguration(enableDeltaModel: deltaModel); + owner.configuration = InputConfiguration( + viewId: kImplicitViewId, + enableDeltaModel: deltaModel, + ); final GloballyPositionedTextEditingStrategy editingStrategy = GloballyPositionedTextEditingStrategy(owner); diff --git a/lib/web_ui/test/engine/semantics/text_field_test.dart b/lib/web_ui/test/engine/semantics/text_field_test.dart index 60e4cbaa31487..52a41b1ae197d 100644 --- a/lib/web_ui/test/engine/semantics/text_field_test.dart +++ b/lib/web_ui/test/engine/semantics/text_field_test.dart @@ -15,9 +15,10 @@ import 'package:ui/ui.dart' as ui; import '../../common/test_initialization.dart'; import 'semantics_tester.dart'; -final InputConfiguration singlelineConfig = InputConfiguration(); +final InputConfiguration singlelineConfig = InputConfiguration(viewId: kImplicitViewId); final InputConfiguration multilineConfig = InputConfiguration( + viewId: kImplicitViewId, inputType: EngineInputType.multiline, inputAction: 'TextInputAction.newline', ); diff --git a/lib/web_ui/test/engine/text_editing_test.dart b/lib/web_ui/test/engine/text_editing_test.dart index a9fa99443eb5b..1d9b026f2414f 100644 --- a/lib/web_ui/test/engine/text_editing_test.dart +++ b/lib/web_ui/test/engine/text_editing_test.dart @@ -8,16 +8,7 @@ import 'dart:typed_data'; import 'package:test/bootstrap/browser.dart'; import 'package:test/test.dart'; - -import 'package:ui/src/engine/browser_detection.dart'; -import 'package:ui/src/engine/dom.dart'; -import 'package:ui/src/engine/raw_keyboard.dart'; -import 'package:ui/src/engine/services.dart'; -import 'package:ui/src/engine/text_editing/autofill_hint.dart'; -import 'package:ui/src/engine/text_editing/input_type.dart'; -import 'package:ui/src/engine/text_editing/text_editing.dart'; -import 'package:ui/src/engine/util.dart'; -import 'package:ui/src/engine/vector_math.dart'; +import 'package:ui/src/engine.dart'; import 'package:ui/ui.dart' as ui; import '../common/spy.dart'; @@ -28,6 +19,11 @@ const int _kReturnKeyCode = 13; const MethodCodec codec = JSONMethodCodec(); +EnginePlatformDispatcher get dispatcher => EnginePlatformDispatcher.instance; + +DomElement get defaultTextEditingRoot => + dispatcher.implicitView!.dom.textEditingHost; + /// Add unit tests for [FirefoxTextEditingStrategy]. // TODO(mdebbar): https://github.com/flutter/flutter/issues/46891 @@ -36,11 +32,14 @@ EditingState? lastEditingState; TextEditingDeltaState? editingDeltaState; String? lastInputAction; -final InputConfiguration singlelineConfig = InputConfiguration(); +final InputConfiguration singlelineConfig = InputConfiguration( + viewId: kImplicitViewId, +); final Map flutterSinglelineConfig = createFlutterConfig('text'); final InputConfiguration multilineConfig = InputConfiguration( + viewId: kImplicitViewId, inputType: EngineInputType.multiline, inputAction: 'TextInputAction.newline', ); @@ -129,8 +128,38 @@ Future testMain() async { domDocument.body); }); + test('inserts element in the correct view', () { + final DomElement host = createDomElement('div'); + domDocument.body!.append(host); + final EngineFlutterView view = EngineFlutterView(dispatcher, host); + dispatcher.viewManager.registerView(view); + final DomElement textEditingHost = view.dom.textEditingHost; + + expect(domDocument.getElementsByTagName('input'), hasLength(0)); + expect(textEditingHost.getElementsByTagName('input'), hasLength(0)); + + final InputConfiguration config = InputConfiguration(viewId: view.viewId); + editingStrategy!.enable( + config, + onChange: trackEditingState, + onAction: trackInputAction, + ); + final DomElement input = editingStrategy!.domElement!; + + // Input is appended to the right view. + expect(textEditingHost.contains(input), isTrue); + + // Cleanup. + editingStrategy!.disable(); + expect(textEditingHost.querySelectorAll('input'), hasLength(0)); + dispatcher.viewManager.unregisterView(view.viewId); + view.dispose(); + host.remove(); + }); + test('Respects read-only config', () { final InputConfiguration config = InputConfiguration( + viewId: kImplicitViewId, readOnly: true, ); editingStrategy!.enable( @@ -148,6 +177,7 @@ Future testMain() async { test('Knows how to create password fields', () { final InputConfiguration config = InputConfiguration( + viewId: kImplicitViewId, obscureText: true, ); editingStrategy!.enable( @@ -165,6 +195,7 @@ Future testMain() async { test('Knows how to create non-default text actions', () { final InputConfiguration config = InputConfiguration( + viewId: kImplicitViewId, inputAction: 'TextInputAction.send' ); editingStrategy!.enable( @@ -186,6 +217,7 @@ Future testMain() async { test('Knows to turn autocorrect off', () { final InputConfiguration config = InputConfiguration( + viewId: kImplicitViewId, autocorrect: false, ); editingStrategy!.enable( @@ -202,7 +234,7 @@ Future testMain() async { }); test('Knows to turn autocorrect on', () { - final InputConfiguration config = InputConfiguration(); + final InputConfiguration config = InputConfiguration(viewId: kImplicitViewId); editingStrategy!.enable( config, onChange: trackEditingState, @@ -217,7 +249,7 @@ Future testMain() async { }); test('Knows to turn autofill off', () { - final InputConfiguration config = InputConfiguration(); + final InputConfiguration config = InputConfiguration(viewId: kImplicitViewId); editingStrategy!.enable( config, onChange: trackEditingState, @@ -352,7 +384,7 @@ Future testMain() async { }); test('Triggers input action', () { - final InputConfiguration config = InputConfiguration(); + final InputConfiguration config = InputConfiguration(viewId: kImplicitViewId); editingStrategy!.enable( config, onChange: trackEditingState, @@ -371,10 +403,10 @@ Future testMain() async { }); test('handling keyboard event prevents triggering input action', () { - final ui.PlatformMessageCallback? savedCallback = ui.PlatformDispatcher.instance.onPlatformMessage; + final ui.PlatformMessageCallback? savedCallback = dispatcher.onPlatformMessage; bool markTextEventHandled = false; - ui.PlatformDispatcher.instance.onPlatformMessage = (String channel, ByteData? data, + dispatcher.onPlatformMessage = (String channel, ByteData? data, ui.PlatformMessageResponseCallback? callback) { final ByteData response = const JSONMessageCodec() .encodeMessage({'handled': markTextEventHandled})!; @@ -382,7 +414,7 @@ Future testMain() async { }; RawKeyboard.initialize(); - final InputConfiguration config = InputConfiguration(); + final InputConfiguration config = InputConfiguration(viewId: kImplicitViewId); editingStrategy!.enable( config, onChange: trackEditingState, @@ -412,12 +444,13 @@ Future testMain() async { // Input action received. expect(lastInputAction, 'TextInputAction.done'); - ui.PlatformDispatcher.instance.onPlatformMessage = savedCallback; + dispatcher.onPlatformMessage = savedCallback; RawKeyboard.instance?.dispose(); }); test('Triggers input action in multi-line mode', () { final InputConfiguration config = InputConfiguration( + viewId: kImplicitViewId, inputType: EngineInputType.multiline, ); editingStrategy!.enable( @@ -443,6 +476,7 @@ Future testMain() async { test('Triggers input action in multiline-none mode', () { final InputConfiguration config = InputConfiguration( + viewId: kImplicitViewId, inputType: EngineInputType.multilineNone, ); editingStrategy!.enable( @@ -468,7 +502,7 @@ Future testMain() async { test('Triggers input action and prevent new line key event for single line field', () { // Regression test for https://github.com/flutter/flutter/issues/113559 - final InputConfiguration config = InputConfiguration(); + final InputConfiguration config = InputConfiguration(viewId: kImplicitViewId); editingStrategy!.enable( config, onChange: trackEditingState, @@ -590,16 +624,24 @@ Future testMain() async { /// Returns the `clientId` used in the platform message. int showKeyboard({ required String inputType, + int? viewId, String? inputAction, bool decimal = false, bool isMultiline = false, + bool autofillEnabled = true, }) { final MethodCall setClient = MethodCall( 'TextInput.setClient', [ ++clientId, - createFlutterConfig(inputType, - inputAction: inputAction, decimal: decimal, isMultiline: isMultiline), + createFlutterConfig( + inputType, + viewId: viewId, + inputAction: inputAction, + decimal: decimal, + isMultiline: isMultiline, + autofillEnabled: autofillEnabled, + ), ], ); sendFrameworkMessage(codec.encodeMethodCall(setClient)); @@ -2481,6 +2523,186 @@ Future testMain() async { expect(event.defaultPrevented, isFalse); }); + test('inserts element in the correct view', () async { + final DomElement host = createDomElement('div'); + domDocument.body!.append(host); + final EngineFlutterView view = EngineFlutterView(dispatcher, host); + dispatcher.viewManager.registerView(view); + + textEditing = HybridTextEditing(); + showKeyboard(inputType: 'text', viewId: view.viewId); + // The Safari strategy doesn't insert the input element into the DOM until + // it has received the geometry information. + final List transform = Matrix4.identity().storage.toList(); + final MethodCall setSizeAndTransform = configureSetSizeAndTransformMethodCall(10, 10, transform); + sendFrameworkMessage(codec.encodeMethodCall(setSizeAndTransform)); + + await waitForDesktopSafariFocus(); + + final DomElement input = textEditing!.strategy.domElement!; + + + // Input is appended to the right view. + expect(view.dom.textEditingHost.contains(input), isTrue); + + // Cleanup. + hideKeyboard(); + dispatcher.viewManager.unregisterView(view.viewId); + view.dispose(); + host.remove(); + }); + + test('moves element to correct view', () { + final DomElement host1 = createDomElement('div'); + domDocument.body!.append(host1); + final EngineFlutterView view1 = EngineFlutterView(dispatcher, host1); + dispatcher.viewManager.registerView(view1); + + final DomElement host2 = createDomElement('div'); + domDocument.body!.append(host2); + final EngineFlutterView view2 = EngineFlutterView(dispatcher, host2); + dispatcher.viewManager.registerView(view2); + + textEditing = HybridTextEditing(); + showKeyboard(inputType: 'text', viewId: view1.viewId, autofillEnabled: false); + + final DomElement input = textEditing!.strategy.domElement!; + + // Input is appended to view1. + expect(view1.dom.textEditingHost.contains(input), isTrue); + + sendFrameworkMessage(codec.encodeMethodCall(MethodCall( + 'TextInput.updateConfig', + createFlutterConfig('text', viewId: view2.viewId, autofillEnabled: false), + ))); + + // The input element is the same (no new element was created), but it has + // moved to view2. + expect(textEditing!.strategy.domElement, input); + expect(view2.dom.textEditingHost.contains(input), isTrue); + + // Cleanup. + hideKeyboard(); + dispatcher.viewManager.unregisterView(view1.viewId); + view1.dispose(); + dispatcher.viewManager.unregisterView(view2.viewId); + view2.dispose(); + host1.remove(); + host2.remove(); + }); + + test('places autofill form in the correct view', () async { + final DomElement host = createDomElement('div'); + domDocument.body!.append(host); + final EngineFlutterView view = EngineFlutterView(dispatcher, host); + dispatcher.viewManager.registerView(view); + + textEditing = HybridTextEditing(); + + // Create a configuration with an AutofillGroup of three text fields. + final Map flutterMultiAutofillElementConfig = + createFlutterConfig( + 'text', + viewId: view.viewId, + autofillHint: 'username', + autofillHintsForFields: ['username', 'email', 'name'], + ); + final MethodCall setClient = MethodCall( + 'TextInput.setClient', + [123, flutterMultiAutofillElementConfig], + ); + sendFrameworkMessage(codec.encodeMethodCall(setClient)); + + const MethodCall show = MethodCall('TextInput.show'); + sendFrameworkMessage(codec.encodeMethodCall(show)); + // The Safari strategy doesn't insert the input element into the DOM until + // it has received the geometry information. + final List transform = Matrix4.identity().storage.toList(); + final MethodCall setSizeAndTransform = configureSetSizeAndTransformMethodCall(10, 10, transform); + sendFrameworkMessage(codec.encodeMethodCall(setSizeAndTransform)); + + await waitForDesktopSafariFocus(); + + final DomElement input = textEditing!.strategy.domElement!; + final DomElement form = textEditing!.configuration!.autofillGroup!.formElement; + + // Input and form are appended to the right view. + expect(view.dom.textEditingHost.contains(input), isTrue); + expect(view.dom.textEditingHost.contains(form), isTrue); + + // Cleanup. + hideKeyboard(); + dispatcher.viewManager.unregisterView(view.viewId); + view.dispose(); + host.remove(); + }); + + test('moves autofill form to the correct view', () async { + final DomElement host1 = createDomElement('div'); + domDocument.body!.append(host1); + final EngineFlutterView view1 = EngineFlutterView(dispatcher, host1); + dispatcher.viewManager.registerView(view1); + + final DomElement host2 = createDomElement('div'); + domDocument.body!.append(host2); + final EngineFlutterView view2 = EngineFlutterView(dispatcher, host2); + dispatcher.viewManager.registerView(view2); + + textEditing = HybridTextEditing(); + + // Create a configuration with an AutofillGroup of three text fields. + final Map autofillConfig1 = createFlutterConfig( + 'text', + viewId: view1.viewId, + autofillHint: 'username', + autofillHintsForFields: ['username', 'email', 'name'], + ); + final MethodCall setClient = MethodCall( + 'TextInput.setClient', + [123, autofillConfig1], + ); + sendFrameworkMessage(codec.encodeMethodCall(setClient)); + + const MethodCall show = MethodCall('TextInput.show'); + sendFrameworkMessage(codec.encodeMethodCall(show)); + + await waitForDesktopSafariFocus(); + + final DomElement input = textEditing!.strategy.domElement!; + final DomElement form = textEditing!.configuration!.autofillGroup!.formElement; + + // Input and form are appended to view1. + expect(view1.dom.textEditingHost.contains(input), isTrue); + expect(view1.dom.textEditingHost.contains(form), isTrue); + + // Move the input and form to view2. + final Map autofillConfig2 = createFlutterConfig( + 'text', + viewId: view2.viewId, + autofillHint: 'username', + autofillHintsForFields: ['username', 'email', 'name'], + ); + sendFrameworkMessage(codec.encodeMethodCall(MethodCall( + 'TextInput.updateConfig', + autofillConfig2, + ))); + + // Input and form are in view2. + expect(view2.dom.textEditingHost.contains(input), isTrue); + expect(view2.dom.textEditingHost.contains(form), isTrue); + + // Cleanup. + hideKeyboard(); + dispatcher.viewManager.unregisterView(view1.viewId); + view1.dispose(); + dispatcher.viewManager.unregisterView(view2.viewId); + view2.dispose(); + host1.remove(); + host2.remove(); + // TODO(mdebbar): Autofill forms don't get updated in the current system. + // https://github.com/flutter/flutter/issues/145101 + }, skip: true); + tearDown(() { clearForms(); }); @@ -2493,7 +2715,10 @@ Future testMain() async { ['field1', 'field2', 'field3']); final EngineAutofillForm autofillForm = EngineAutofillForm.fromFrameworkMessage( - createAutofillInfo('username', 'field1'), fields)!; + kImplicitViewId, + createAutofillInfo('username', 'field1'), + fields, + )!; // Number of elements if number of fields sent to the constructor minus // one (for the focused text element). @@ -2550,7 +2775,10 @@ Future testMain() async { ['zzyyxx', 'aabbcc', 'jjkkll']); final EngineAutofillForm autofillForm = EngineAutofillForm.fromFrameworkMessage( - createAutofillInfo('username', 'field1'), fields)!; + kImplicitViewId, + createAutofillInfo('username', 'field1'), + fields, + )!; expect(autofillForm.formIdentifier, 'aabbcc*jjkkll*zzyyxx'); }); @@ -2563,7 +2791,10 @@ Future testMain() async { ['field1', 'fields2', 'field3']); final EngineAutofillForm autofillForm = EngineAutofillForm.fromFrameworkMessage( - createAutofillInfo('username', 'field1'), fields)!; + kImplicitViewId, + createAutofillInfo('username', 'field1'), + fields, + )!; final DomHTMLInputElement testInputElement = createDomHTMLInputElement(); autofillForm.placeForm(testInputElement); @@ -2590,7 +2821,10 @@ Future testMain() async { ); final EngineAutofillForm autofillForm = EngineAutofillForm.fromFrameworkMessage( - createAutofillInfo('username', 'field1'), fields)!; + kImplicitViewId, + createAutofillInfo('username', 'field1'), + fields, + )!; // The focused element is the only field. Form should be empty after // the initialization (focus element is appended later). @@ -2615,7 +2849,7 @@ Future testMain() async { ['field1'], ); final EngineAutofillForm? autofillForm = - EngineAutofillForm.fromFrameworkMessage(null, fields); + EngineAutofillForm.fromFrameworkMessage(kImplicitViewId, null, fields); expect(autofillForm, isNull); }); @@ -2632,7 +2866,10 @@ Future testMain() async { ]); final EngineAutofillForm autofillForm = EngineAutofillForm.fromFrameworkMessage( - createAutofillInfo('email', 'field1'), fields)!; + kImplicitViewId, + createAutofillInfo('email', 'field1'), + fields, + )!; expect(autofillForm.elements, hasLength(2)); @@ -2675,7 +2912,10 @@ Future testMain() async { ]); final EngineAutofillForm autofillForm = EngineAutofillForm.fromFrameworkMessage( - createAutofillInfo('email', 'field1'), fields)!; + kImplicitViewId, + createAutofillInfo('email', 'field1'), + fields, + )!; final List formChildNodes = autofillForm.formElement.childNodes.toList() as List; @@ -2707,7 +2947,10 @@ Future testMain() async { ]); final EngineAutofillForm autofillForm = EngineAutofillForm.fromFrameworkMessage( - createAutofillInfo('email', 'field1'), fields)!; + kImplicitViewId, + createAutofillInfo('email', 'field1'), + fields, + )!; final List formChildNodes = autofillForm.formElement.childNodes.toList() as List; @@ -2738,7 +2981,10 @@ Future testMain() async { ]); final EngineAutofillForm autofillForm = EngineAutofillForm.fromFrameworkMessage( - createAutofillInfo('email', 'field1'), fields)!; + kImplicitViewId, + createAutofillInfo('email', 'field1'), + fields, + )!; final DomHTMLInputElement testInputElement = createDomHTMLInputElement(); testInputElement.name = 'email'; @@ -3378,6 +3624,7 @@ void checkTextAreaEditingState( /// simplicity. Map createFlutterConfig( String inputType, { + int? viewId, bool readOnly = false, bool obscureText = false, bool autocorrect = true, @@ -3397,6 +3644,7 @@ Map createFlutterConfig( if (decimal) 'decimal': true, if (isMultiline) 'isMultiline': true, }, + if (viewId != null) 'viewId': viewId, 'readOnly': readOnly, 'obscureText': obscureText, 'autocorrect': autocorrect, diff --git a/lib/web_ui/test/engine/view_embedder/flutter_view_manager_test.dart b/lib/web_ui/test/engine/view_embedder/flutter_view_manager_test.dart index 5dbec513268d0..3ff2cd027e1df 100644 --- a/lib/web_ui/test/engine/view_embedder/flutter_view_manager_test.dart +++ b/lib/web_ui/test/engine/view_embedder/flutter_view_manager_test.dart @@ -102,13 +102,74 @@ Future doTests() async { }); }); - group('viewIdForRootElement', () { - test('works', () { - final EngineFlutterView view = EngineFlutterView(platformDispatcher, createDomElement('div')); - final int viewId = view.viewId; + group('findViewForElement', () { + test('finds view for root and descendant elements', () { + final DomElement host = createDomElement('div'); + final EngineFlutterView view = EngineFlutterView(platformDispatcher, host); + + viewManager.registerView(view); + + final DomElement rootElement = view.dom.rootElement; + final DomElement child1 = createDomElement('div'); + final DomElement child2 = createDomElement('div'); + final DomElement child3 = createDomElement('div'); + rootElement.append(child1); + rootElement.append(child2); + child2.append(child3); + + expect(viewManager.findViewForElement(rootElement), view); + expect(viewManager.findViewForElement(child1), view); + expect(viewManager.findViewForElement(child2), view); + expect(viewManager.findViewForElement(child3), view); + }); + + test('returns null for host element', () { + final DomElement host = createDomElement('div'); + final EngineFlutterView view = EngineFlutterView(platformDispatcher, host); + viewManager.registerView(view); + + expect(viewManager.findViewForElement(host), isNull); + }); + + test("returns null for elements that don't belong to any view", () { + final DomElement host = createDomElement('div'); + final EngineFlutterView view = EngineFlutterView(platformDispatcher, host); + viewManager.registerView(view); + + final DomElement disconnectedElement = createDomElement('div'); + final DomElement childOfBody = createDomElement('div'); + + domDocument.body!.append(childOfBody); + + expect(viewManager.findViewForElement(disconnectedElement), isNull); + expect(viewManager.findViewForElement(childOfBody), isNull); + expect(viewManager.findViewForElement(domDocument.body), isNull); + }); + + test('does not recognize elements from unregistered views', () { + final DomElement host = createDomElement('div'); + final EngineFlutterView view = EngineFlutterView(platformDispatcher, host); viewManager.registerView(view); - expect(viewManager.viewIdForRootElement(view.dom.rootElement), viewId); + final DomElement rootElement = view.dom.rootElement; + final DomElement child1 = createDomElement('div'); + final DomElement child2 = createDomElement('div'); + final DomElement child3 = createDomElement('div'); + rootElement.append(child1); + rootElement.append(child2); + child2.append(child3); + + expect(viewManager.findViewForElement(rootElement), view); + expect(viewManager.findViewForElement(child1), view); + expect(viewManager.findViewForElement(child2), view); + expect(viewManager.findViewForElement(child3), view); + + viewManager.unregisterView(view.viewId); + + expect(viewManager.findViewForElement(rootElement), isNull); + expect(viewManager.findViewForElement(child1), isNull); + expect(viewManager.findViewForElement(child2), isNull); + expect(viewManager.findViewForElement(child3), isNull); }); }); }); diff --git a/lib/web_ui/test/ui/paint_test.dart b/lib/web_ui/test/ui/paint_test.dart index 4518d08c4d3ec..a0264d7a98494 100644 --- a/lib/web_ui/test/ui/paint_test.dart +++ b/lib/web_ui/test/ui/paint_test.dart @@ -7,7 +7,6 @@ import 'package:test/test.dart'; import 'package:ui/ui.dart' as ui; import '../common/test_initialization.dart'; -import 'utils.dart'; void main() { internalBootstrapBrowserTest(() => testMain); @@ -49,23 +48,66 @@ Future testMain() async { tileMode: ui.TileMode.mirror, ); - if (!isSkwasm) { - expect( - paint.toString(), - 'Paint(' - 'Color(0xaabbccdd); ' - 'BlendMode.darken; ' - 'colorFilter: ColorFilter.linearToSrgbGamma(); ' - 'maskFilter: MaskFilter.blur(BlurStyle.normal, 1.7); ' - 'filterQuality: FilterQuality.high; ' - 'shader: Gradient(); ' - 'imageFilter: ImageFilter.blur(1.9, 2.1, mirror); ' - 'invert: true' - ')', - ); - } else { - // TODO(yjbanov): https://github.com/flutter/flutter/issues/141639 - expect(paint.toString(), 'Paint()'); - } + expect( + paint.toString(), + 'Paint(' + 'Color(0xaabbccdd); ' + 'BlendMode.darken; ' + 'colorFilter: ColorFilter.linearToSrgbGamma(); ' + 'maskFilter: MaskFilter.blur(BlurStyle.normal, 1.7); ' + 'filterQuality: FilterQuality.high; ' + 'shader: Gradient(); ' + 'imageFilter: ImageFilter.blur(1.9, 2.1, mirror); ' + 'invert: true' + ')', + ); + }); + + test('.from copies every field', () { + final ui.Paint paint = ui.Paint(); + paint.blendMode = ui.BlendMode.darken; + paint.style = ui.PaintingStyle.fill; + paint.strokeWidth = 1.2; + paint.strokeCap = ui.StrokeCap.square; + paint.strokeJoin = ui.StrokeJoin.bevel; + paint.isAntiAlias = true; + paint.color = const ui.Color(0xaabbccdd); + paint.invertColors = true; + paint.shader = ui.Gradient.linear( + const ui.Offset(0.1, 0.2), + const ui.Offset(1.5, 1.6), + const [ + ui.Color(0xaabbccdd), + ui.Color(0xbbccddee), + ], + [0.3, 0.4], + ui.TileMode.decal, + ); + paint.maskFilter = const ui.MaskFilter.blur(ui.BlurStyle.normal, 1.7); + paint.filterQuality = ui.FilterQuality.high; + paint.colorFilter = const ui.ColorFilter.linearToSrgbGamma(); + paint.strokeMiterLimit = 1.8; + paint.imageFilter = ui.ImageFilter.blur( + sigmaX: 1.9, + sigmaY: 2.1, + tileMode: ui.TileMode.mirror, + ); + + final ui.Paint copy = ui.Paint.from(paint); + + expect(copy.blendMode, paint.blendMode); + expect(copy.style, paint.style); + expect(copy.strokeWidth, paint.strokeWidth); + expect(copy.strokeCap, paint.strokeCap); + expect(copy.strokeJoin, paint.strokeJoin); + expect(copy.isAntiAlias, paint.isAntiAlias); + expect(copy.color, paint.color); + expect(copy.invertColors, paint.invertColors); + expect(copy.shader, paint.shader); + expect(copy.maskFilter, paint.maskFilter); + expect(copy.filterQuality, paint.filterQuality); + expect(copy.colorFilter, paint.colorFilter); + expect(copy.strokeMiterLimit, paint.strokeMiterLimit); + expect(copy.imageFilter, paint.imageFilter); }); } diff --git a/runtime/runtime_controller.cc b/runtime/runtime_controller.cc index b967b9cde9912..b336b3dde6c83 100644 --- a/runtime/runtime_controller.cc +++ b/runtime/runtime_controller.cc @@ -121,12 +121,30 @@ bool RuntimeController::FlushRuntimeStateToIsolate() { FML_DCHECK(!has_flushed_runtime_state_) << "FlushRuntimeStateToIsolate is called more than once somehow."; has_flushed_runtime_state_ = true; + + auto platform_configuration = GetPlatformConfigurationIfAvailable(); + if (!platform_configuration) { + return false; + } + for (auto const& [view_id, viewport_metrics] : platform_data_.viewport_metrics_for_views) { - if (!AddView(view_id, viewport_metrics)) { - return false; + bool added = platform_configuration->AddView(view_id, viewport_metrics); + + // Callbacks will have been already invoked if the engine was restarted. + if (pending_add_view_callbacks_.find(view_id) != + pending_add_view_callbacks_.end()) { + pending_add_view_callbacks_[view_id](added); + pending_add_view_callbacks_.erase(view_id); + } + + if (!added) { + FML_LOG(ERROR) << "Failed to flush view #" << view_id + << ". The Dart isolate may be in an inconsistent state."; } } + + FML_DCHECK(pending_add_view_callbacks_.empty()); return SetLocales(platform_data_.locale_data) && SetSemanticsEnabled(platform_data_.semantics_enabled) && SetAccessibilityFeatures( @@ -136,25 +154,53 @@ bool RuntimeController::FlushRuntimeStateToIsolate() { SetDisplays(platform_data_.displays); } -bool RuntimeController::AddView(int64_t view_id, - const ViewportMetrics& view_metrics) { - platform_data_.viewport_metrics_for_views[view_id] = view_metrics; - if (auto* platform_configuration = GetPlatformConfigurationIfAvailable()) { - platform_configuration->AddView(view_id, view_metrics); +void RuntimeController::AddView(int64_t view_id, + const ViewportMetrics& view_metrics, + AddViewCallback callback) { + // If the Dart isolate is not running, |FlushRuntimeStateToIsolate| will + // add the view and invoke the callback when the isolate is started. + auto* platform_configuration = GetPlatformConfigurationIfAvailable(); + if (!platform_configuration) { + FML_DCHECK(has_flushed_runtime_state_ == false); + + if (pending_add_view_callbacks_.find(view_id) != + pending_add_view_callbacks_.end()) { + FML_LOG(ERROR) << "View #" << view_id << " is already pending creation."; + callback(false); + return; + } - return true; + platform_data_.viewport_metrics_for_views[view_id] = view_metrics; + pending_add_view_callbacks_[view_id] = std::move(callback); + return; } - return false; + FML_DCHECK(has_flushed_runtime_state_ || pending_add_view_callbacks_.empty()); + + platform_data_.viewport_metrics_for_views[view_id] = view_metrics; + bool added = platform_configuration->AddView(view_id, view_metrics); + callback(added); } bool RuntimeController::RemoveView(int64_t view_id) { platform_data_.viewport_metrics_for_views.erase(view_id); - if (auto* platform_configuration = GetPlatformConfigurationIfAvailable()) { - return platform_configuration->RemoveView(view_id); + + // If the Dart isolate has not been launched yet, the pending + // add view operation's callback is stored by the runtime controller. + // Notify this callback of the cancellation. + auto* platform_configuration = GetPlatformConfigurationIfAvailable(); + if (!platform_configuration) { + FML_DCHECK(has_flushed_runtime_state_ == false); + if (pending_add_view_callbacks_.find(view_id) != + pending_add_view_callbacks_.end()) { + pending_add_view_callbacks_[view_id](false); + pending_add_view_callbacks_.erase(view_id); + } + + return false; } - return false; + return platform_configuration->RemoveView(view_id); } bool RuntimeController::SetViewportMetrics(int64_t view_id, diff --git a/runtime/runtime_controller.h b/runtime/runtime_controller.h index cb1618f9d3326..8bb01efecabc5 100644 --- a/runtime/runtime_controller.h +++ b/runtime/runtime_controller.h @@ -50,6 +50,16 @@ class Window; /// class RuntimeController : public PlatformConfigurationClient { public: + /// A callback that's invoked after this `RuntimeController` attempts to + /// add a view to the Dart isolate. + /// + /// If the Dart isolate is not launched yet, this callback will be stored + /// and invoked after the isolate is launched. + /// + /// The `added` parameter is false if the add operation fails or was + /// cancelled while pending using `RemoveView`. + using AddViewCallback = std::function; + //---------------------------------------------------------------------------- /// @brief Creates a new instance of a runtime controller. This is /// usually only done by the engine instance associated with the @@ -174,23 +184,43 @@ class RuntimeController : public PlatformConfigurationClient { /// /// A view must be added before other methods can refer to it, /// including the implicit view. Adding a view that already exists - /// triggers an assertion. + /// is an error. + /// + /// The `callback` is invoked when the add operation is attempted, + /// failed, or is cancelled. + /// + /// If the isolate is not running, the view add will be queued and + /// flushed to the isolate when it starts. Calling `RemoveView` + /// before the isolate is launched cancels the add operation. + /// /// /// @param[in] view_id The ID of the new view. /// @param[in] viewport_metrics The initial viewport metrics for the view. + /// @param[in] callback Callback that will be invoked after the add + /// operation is attempted or cancelled. /// - bool AddView(int64_t view_id, const ViewportMetrics& view_metrics); + void AddView(int64_t view_id, + const ViewportMetrics& view_metrics, + AddViewCallback callback); //---------------------------------------------------------------------------- /// @brief Notify the isolate that a view is no longer available. /// - /// Removing a view that does not exist triggers an assertion. + /// Views that are added before the isolate is started are + /// queued until the isolate is launched. If one of these + /// "pending" views are removed, the view add is cancelled: + /// the `AddViewCallback` will be invoked with an `added` of + /// false and `RemoveView` will return false. /// /// The implicit view (kFlutterImplicitViewId) should never be /// removed. Doing so triggers an assertion. /// /// @param[in] view_id The ID of the view. /// + /// @return If the remove view operation was forwarded to the running + /// isolate. False if the view does not exist. If the Dart isolate + /// is not running, then the pending view creation (if any) is + /// cancelled and the return value is always false. bool RemoveView(int64_t view_id); //---------------------------------------------------------------------------- @@ -660,6 +690,12 @@ class RuntimeController : public PlatformConfigurationClient { std::shared_ptr(new PlatformIsolateManager()); bool has_flushed_runtime_state_ = false; + // Callbacks when `AddView` was called before the Dart isolate is launched. + // + // These views will be added when `FlushRuntimeStateToIsolate` is called. + // This is no longer used once the Dart isolate starts. + std::unordered_map pending_add_view_callbacks_; + // Tracks the views that have been called `Render` during a frame. // // If all views that have been registered by `AddView` have been called diff --git a/shell/common/engine.cc b/shell/common/engine.cc index 7dd03ed19fb78..ac628eb929465 100644 --- a/shell/common/engine.cc +++ b/shell/common/engine.cc @@ -297,8 +297,10 @@ tonic::DartErrorHandleType Engine::GetUIIsolateLastError() { return runtime_controller_->GetLastError(); } -void Engine::AddView(int64_t view_id, const ViewportMetrics& view_metrics) { - runtime_controller_->AddView(view_id, view_metrics); +void Engine::AddView(int64_t view_id, + const ViewportMetrics& view_metrics, + std::function callback) { + runtime_controller_->AddView(view_id, view_metrics, std::move(callback)); } bool Engine::RemoveView(int64_t view_id) { diff --git a/shell/common/engine.h b/shell/common/engine.h index 65660f6124c3f..7819ed675cd9f 100644 --- a/shell/common/engine.h +++ b/shell/common/engine.h @@ -721,8 +721,12 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate { /// /// @param[in] view_id The ID of the new view. /// @param[in] viewport_metrics The initial viewport metrics for the view. + /// @param[in] callback Callback that will be invoked once + /// the engine attempts to add the view. /// - void AddView(int64_t view_id, const ViewportMetrics& view_metrics); + void AddView(int64_t view_id, + const ViewportMetrics& view_metrics, + std::function callback); //---------------------------------------------------------------------------- /// @brief Notify the Flutter application that a view is no diff --git a/shell/common/engine_animator_unittests.cc b/shell/common/engine_animator_unittests.cc index 331bab77e9a16..12bea41da96d3 100644 --- a/shell/common/engine_animator_unittests.cc +++ b/shell/common/engine_animator_unittests.cc @@ -313,8 +313,10 @@ TEST_F(EngineAnimatorTest, AnimatorAcceptsMultipleRenders) { engine_context->Run(std::move(configuration)); engine_context->EngineTaskSync([](Engine& engine) { - engine.AddView(1, ViewportMetrics{1, 10, 10, 22, 0}); - engine.AddView(2, ViewportMetrics{1, 10, 10, 22, 0}); + engine.AddView(1, ViewportMetrics{1, 10, 10, 22, 0}, + [](bool added) { ASSERT_TRUE(added); }); + engine.AddView(2, ViewportMetrics{1, 10, 10, 22, 0}, + [](bool added) { ASSERT_TRUE(added); }); }); native_latch.Wait(); @@ -368,8 +370,10 @@ TEST_F(EngineAnimatorTest, IgnoresOutOfFrameRenders) { std::move(animator)); engine_context->EngineTaskSync([](Engine& engine) { - engine.AddView(1, ViewportMetrics{1, 10, 10, 22, 0}); - engine.AddView(2, ViewportMetrics{1, 10, 10, 22, 0}); + engine.AddView(1, ViewportMetrics{1, 10, 10, 22, 0}, + [](bool added) { ASSERT_TRUE(added); }); + engine.AddView(2, ViewportMetrics{1, 10, 10, 22, 0}, + [](bool added) { ASSERT_TRUE(added); }); }); auto configuration = RunConfiguration::InferFromSettings(settings_); @@ -444,7 +448,8 @@ TEST_F(EngineAnimatorTest, IgnoresDuplicateRenders) { std::move(animator)); engine_context->EngineTaskSync([](Engine& engine) { - engine.AddView(kFlutterImplicitViewId, ViewportMetrics{1, 10, 10, 22, 0}); + engine.AddView(kFlutterImplicitViewId, ViewportMetrics{1, 10, 10, 22, 0}, + [](bool added) { ASSERT_TRUE(added); }); }); auto configuration = RunConfiguration::InferFromSettings(settings_); @@ -504,7 +509,8 @@ TEST_F(EngineAnimatorTest, AnimatorSubmitsImplicitViewBeforeDrawFrameEnds) { std::move(animator)); engine_context->EngineTaskSync([](Engine& engine) { - engine.AddView(kFlutterImplicitViewId, ViewportMetrics{1.0, 10, 10, 1, 0}); + engine.AddView(kFlutterImplicitViewId, ViewportMetrics{1.0, 10, 10, 1, 0}, + [](bool added) { ASSERT_TRUE(added); }); }); auto configuration = RunConfiguration::InferFromSettings(settings_); @@ -568,7 +574,8 @@ TEST_F(EngineAnimatorTest, AnimatorSubmitWarmUpImplicitView) { engine.ScheduleFrame(true); // Add the implicit view so that the engine recognizes it and that its // metrics is not empty. - engine.AddView(kFlutterImplicitViewId, ViewportMetrics{1.0, 10, 10, 1, 0}); + engine.AddView(kFlutterImplicitViewId, ViewportMetrics{1.0, 10, 10, 1, 0}, + [](bool added) { ASSERT_TRUE(added); }); }); continuation_ready_latch.Wait(); @@ -634,9 +641,12 @@ TEST_F(EngineAnimatorTest, AnimatorSubmitPartialViewsForWarmUp) { // Schedule a frame to make the animator create a continuation. engine.ScheduleFrame(true); // Add multiple views. - engine.AddView(0, ViewportMetrics{1, 10, 10, 22, 0}); - engine.AddView(1, ViewportMetrics{1, 10, 10, 22, 0}); - engine.AddView(2, ViewportMetrics{1, 10, 10, 22, 0}); + engine.AddView(0, ViewportMetrics{1, 10, 10, 22, 0}, + [](bool added) { ASSERT_TRUE(added); }); + engine.AddView(1, ViewportMetrics{1, 10, 10, 22, 0}, + [](bool added) { ASSERT_TRUE(added); }); + engine.AddView(2, ViewportMetrics{1, 10, 10, 22, 0}, + [](bool added) { ASSERT_TRUE(added); }); }); continuation_ready_latch.Wait(); diff --git a/shell/common/shell.cc b/shell/common/shell.cc index e53ca5f3e3ad0..a9a23d39ba3b3 100644 --- a/shell/common/shell.cc +++ b/shell/common/shell.cc @@ -762,7 +762,11 @@ bool Shell::Setup(std::unique_ptr platform_view, weak_rasterizer_ = rasterizer_->GetWeakPtr(); weak_platform_view_ = platform_view_->GetWeakPtr(); - engine_->AddView(kFlutterImplicitViewId, ViewportMetrics{}); + // Add the implicit view with empty metrics. + engine_->AddView(kFlutterImplicitViewId, ViewportMetrics{}, [](bool added) { + FML_DCHECK(added) << "Failed to add the implicit view"; + }); + // Setup the time-consuming default font manager right after engine created. if (!settings_.prefetched_default_font_manager) { fml::TaskRunner::RunNowOrPostTask(task_runners_.GetUITaskRunner(), @@ -2110,7 +2114,9 @@ bool Shell::OnServiceProtocolReloadAssetFonts( return true; } -void Shell::AddView(int64_t view_id, const ViewportMetrics& viewport_metrics) { +void Shell::AddView(int64_t view_id, + const ViewportMetrics& viewport_metrics, + AddViewCallback callback) { TRACE_EVENT0("flutter", "Shell::AddView"); FML_DCHECK(is_set_up_); FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread()); @@ -2120,10 +2126,11 @@ void Shell::AddView(int64_t view_id, const ViewportMetrics& viewport_metrics) { task_runners_.GetUITaskRunner()->PostTask([engine = engine_->GetWeakPtr(), // viewport_metrics, // - view_id // + view_id, // + callback = std::move(callback) // ] { if (engine) { - engine->AddView(view_id, viewport_metrics); + engine->AddView(view_id, viewport_metrics, callback); } }); } diff --git a/shell/common/shell.h b/shell/common/shell.h index 3e2da290ed130..a510e141ed6d6 100644 --- a/shell/common/shell.h +++ b/shell/common/shell.h @@ -133,6 +133,7 @@ class Shell final : public PlatformView::Delegate, const std::shared_ptr& gpu_disabled_switch, impeller::RuntimeStageBackend runtime_stage_type)> EngineCreateCallback; + using AddViewCallback = std::function; using RemoveViewCallback = std::function; //---------------------------------------------------------------------------- @@ -317,8 +318,12 @@ class Shell final : public PlatformView::Delegate, /// /// @param[in] view_id The view ID of the new view. /// @param[in] viewport_metrics The initial viewport metrics for the view. + /// @param[in] callback The callback that's invoked once the engine + /// has attempted to add the view. /// - void AddView(int64_t view_id, const ViewportMetrics& viewport_metrics); + void AddView(int64_t view_id, + const ViewportMetrics& viewport_metrics, + AddViewCallback callback); /// @brief Deallocates resources for a non-implicit view. /// diff --git a/shell/common/shell_unittests.cc b/shell/common/shell_unittests.cc index 70dfdb4da95b2..7754267f1e006 100644 --- a/shell/common/shell_unittests.cc +++ b/shell/common/shell_unittests.cc @@ -4504,8 +4504,10 @@ TEST_F(ShellTest, ShellCanAddViewOrRemoveView) { ASSERT_EQ(viewIds.size(), 1u); ASSERT_EQ(viewIds[0], 0ll); - PostSync(shell->GetTaskRunners().GetPlatformTaskRunner(), - [&shell] { shell->AddView(2, ViewportMetrics{}); }); + PostSync(shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell] { + shell->AddView(2, ViewportMetrics{}, + [](bool added) { EXPECT_TRUE(added); }); + }); reportLatch.Wait(); ASSERT_TRUE(hasImplicitView); ASSERT_EQ(viewIds.size(), 2u); @@ -4519,8 +4521,10 @@ TEST_F(ShellTest, ShellCanAddViewOrRemoveView) { ASSERT_EQ(viewIds.size(), 1u); ASSERT_EQ(viewIds[0], 0ll); - PostSync(shell->GetTaskRunners().GetPlatformTaskRunner(), - [&shell] { shell->AddView(4, ViewportMetrics{}); }); + PostSync(shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell] { + shell->AddView(4, ViewportMetrics{}, + [](bool added) { EXPECT_TRUE(added); }); + }); reportLatch.Wait(); ASSERT_TRUE(hasImplicitView); ASSERT_EQ(viewIds.size(), 2u); @@ -4530,6 +4534,122 @@ TEST_F(ShellTest, ShellCanAddViewOrRemoveView) { DestroyShell(std::move(shell), task_runners); } +// Test that add view fails if the view ID already exists. +TEST_F(ShellTest, ShellCannotAddDuplicateViewId) { + ASSERT_FALSE(DartVMRef::IsInstanceRunning()); + Settings settings = CreateSettingsForFixture(); + ThreadHost thread_host(ThreadHost::ThreadHostConfig( + "io.flutter.test." + GetCurrentTestName() + ".", + ThreadHost::Type::kPlatform | ThreadHost::Type::kRaster | + ThreadHost::Type::kIo | ThreadHost::Type::kUi)); + TaskRunners task_runners("test", thread_host.platform_thread->GetTaskRunner(), + thread_host.raster_thread->GetTaskRunner(), + thread_host.ui_thread->GetTaskRunner(), + thread_host.io_thread->GetTaskRunner()); + std::unique_ptr shell = CreateShell(settings, task_runners); + ASSERT_TRUE(shell); + + bool has_implicit_view; + std::vector view_ids; + fml::AutoResetWaitableEvent report_latch; + AddNativeCallback("NativeReportViewIdsCallback", + CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { + ParseViewIdsCallback(args, &has_implicit_view, &view_ids); + report_latch.Signal(); + })); + + PlatformViewNotifyCreated(shell.get()); + auto configuration = RunConfiguration::InferFromSettings(settings); + configuration.SetEntrypoint("testReportViewIds"); + RunEngine(shell.get(), std::move(configuration)); + + report_latch.Wait(); + ASSERT_TRUE(has_implicit_view); + ASSERT_EQ(view_ids.size(), 1u); + ASSERT_EQ(view_ids[0], kImplicitViewId); + + // Add view 123. + fml::AutoResetWaitableEvent add_latch; + PostSync(shell->GetTaskRunners().GetPlatformTaskRunner(), + [&shell, &add_latch] { + shell->AddView(123, ViewportMetrics{}, [&](bool added) { + EXPECT_TRUE(added); + add_latch.Signal(); + }); + }); + + add_latch.Wait(); + + report_latch.Wait(); + ASSERT_EQ(view_ids.size(), 2u); + ASSERT_EQ(view_ids[0], kImplicitViewId); + ASSERT_EQ(view_ids[1], 123); + + // Attempt to add duplicate view ID 123. This should fail. + PostSync(shell->GetTaskRunners().GetPlatformTaskRunner(), + [&shell, &add_latch] { + shell->AddView(123, ViewportMetrics{}, [&](bool added) { + EXPECT_FALSE(added); + add_latch.Signal(); + }); + }); + + add_latch.Wait(); + + PlatformViewNotifyDestroyed(shell.get()); + DestroyShell(std::move(shell), task_runners); +} + +// Test that remove view fails if the view ID does not exist. +TEST_F(ShellTest, ShellCannotRemoveNonexistentId) { + ASSERT_FALSE(DartVMRef::IsInstanceRunning()); + Settings settings = CreateSettingsForFixture(); + ThreadHost thread_host(ThreadHost::ThreadHostConfig( + "io.flutter.test." + GetCurrentTestName() + ".", + ThreadHost::Type::kPlatform | ThreadHost::Type::kRaster | + ThreadHost::Type::kIo | ThreadHost::Type::kUi)); + TaskRunners task_runners("test", thread_host.platform_thread->GetTaskRunner(), + thread_host.raster_thread->GetTaskRunner(), + thread_host.ui_thread->GetTaskRunner(), + thread_host.io_thread->GetTaskRunner()); + std::unique_ptr shell = CreateShell(settings, task_runners); + ASSERT_TRUE(shell); + + bool has_implicit_view; + std::vector view_ids; + fml::AutoResetWaitableEvent report_latch; + AddNativeCallback("NativeReportViewIdsCallback", + CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { + ParseViewIdsCallback(args, &has_implicit_view, &view_ids); + report_latch.Signal(); + })); + + PlatformViewNotifyCreated(shell.get()); + auto configuration = RunConfiguration::InferFromSettings(settings); + configuration.SetEntrypoint("testReportViewIds"); + RunEngine(shell.get(), std::move(configuration)); + + report_latch.Wait(); + ASSERT_TRUE(has_implicit_view); + ASSERT_EQ(view_ids.size(), 1u); + ASSERT_EQ(view_ids[0], kImplicitViewId); + + // Remove view 123. This should fail as this view doesn't exist. + fml::AutoResetWaitableEvent remove_latch; + PostSync(shell->GetTaskRunners().GetPlatformTaskRunner(), + [&shell, &remove_latch] { + shell->RemoveView(123, [&](bool removed) { + EXPECT_FALSE(removed); + remove_latch.Signal(); + }); + }); + + remove_latch.Wait(); + + PlatformViewNotifyDestroyed(shell.get()); + DestroyShell(std::move(shell), task_runners); +} + // Parse the arguments of NativeReportViewWidthsCallback and // store them in viewWidths. static void ParseViewWidthsCallback(const Dart_NativeArguments& args, @@ -4570,7 +4690,8 @@ TEST_F(ShellTest, ShellFlushesPlatformStatesByMain) { // The construtor for ViewportMetrics{_, width, _, _, _} (only the 2nd // argument matters in this test). platform_view->SetViewportMetrics(0, ViewportMetrics{1, 10, 1, 0, 0}); - shell->AddView(1, ViewportMetrics{1, 30, 1, 0, 0}); + shell->AddView(1, ViewportMetrics{1, 30, 1, 0, 0}, + [](bool added) { ASSERT_TRUE(added); }); platform_view->SetViewportMetrics(0, ViewportMetrics{1, 20, 1, 0, 0}); }); @@ -4601,6 +4722,112 @@ TEST_F(ShellTest, ShellFlushesPlatformStatesByMain) { DestroyShell(std::move(shell), task_runners); } +// A view can be added and removed before the Dart isolate is launched. +TEST_F(ShellTest, CanRemoveViewBeforeLaunchingIsolate) { + ASSERT_FALSE(DartVMRef::IsInstanceRunning()); + Settings settings = CreateSettingsForFixture(); + ThreadHost thread_host(ThreadHost::ThreadHostConfig( + "io.flutter.test." + GetCurrentTestName() + ".", + ThreadHost::Type::kPlatform | ThreadHost::Type::kRaster | + ThreadHost::Type::kIo | ThreadHost::Type::kUi)); + TaskRunners task_runners("test", thread_host.platform_thread->GetTaskRunner(), + thread_host.raster_thread->GetTaskRunner(), + thread_host.ui_thread->GetTaskRunner(), + thread_host.io_thread->GetTaskRunner()); + std::unique_ptr shell = CreateShell(settings, task_runners); + ASSERT_TRUE(shell); + + PostSync(shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell] { + auto platform_view = shell->GetPlatformView(); + + // A view can be added and removed all before the isolate launches. + // The pending add view operation is cancelled, the view is never + // added to the Dart isolate. + shell->AddView(123, ViewportMetrics{1, 30, 1, 0, 0}, + [](bool added) { ASSERT_FALSE(added); }); + shell->RemoveView(123, [](bool removed) { ASSERT_FALSE(removed); }); + }); + + bool first_report = true; + std::map view_widths; + fml::AutoResetWaitableEvent report_latch; + AddNativeCallback("NativeReportViewWidthsCallback", + CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { + EXPECT_TRUE(first_report); + first_report = false; + ParseViewWidthsCallback(args, &view_widths); + report_latch.Signal(); + })); + + PlatformViewNotifyCreated(shell.get()); + auto configuration = RunConfiguration::InferFromSettings(settings); + configuration.SetEntrypoint("testReportViewWidths"); + RunEngine(shell.get(), std::move(configuration)); + + report_latch.Wait(); + EXPECT_EQ(view_widths.size(), 1u); + + PlatformViewNotifyDestroyed(shell.get()); + DestroyShell(std::move(shell), task_runners); +} + +// Ensure pending "add views" failures are properly flushed when the Dart +// isolate is launched. +TEST_F(ShellTest, IgnoresBadAddViewsBeforeLaunchingIsolate) { + ASSERT_FALSE(DartVMRef::IsInstanceRunning()); + Settings settings = CreateSettingsForFixture(); + ThreadHost thread_host(ThreadHost::ThreadHostConfig( + "io.flutter.test." + GetCurrentTestName() + ".", + ThreadHost::Type::kPlatform | ThreadHost::Type::kRaster | + ThreadHost::Type::kIo | ThreadHost::Type::kUi)); + TaskRunners task_runners("test", thread_host.platform_thread->GetTaskRunner(), + thread_host.raster_thread->GetTaskRunner(), + thread_host.ui_thread->GetTaskRunner(), + thread_host.io_thread->GetTaskRunner()); + std::unique_ptr shell = CreateShell(settings, task_runners); + ASSERT_TRUE(shell); + + PostSync(shell->GetTaskRunners().GetPlatformTaskRunner(), [&shell] { + auto platform_view = shell->GetPlatformView(); + + // Add the same view twice. The second time should fail. + shell->AddView(123, ViewportMetrics{1, 100, 1, 0, 0}, + [](bool added) { ASSERT_TRUE(added); }); + + shell->AddView(123, ViewportMetrics{1, 200, 1, 0, 0}, + [](bool added) { ASSERT_FALSE(added); }); + + // Add another view. Previous failures should not affect this. + shell->AddView(456, ViewportMetrics{1, 300, 1, 0, 0}, + [](bool added) { ASSERT_TRUE(added); }); + }); + + bool first_report = true; + std::map view_widths; + fml::AutoResetWaitableEvent report_latch; + AddNativeCallback("NativeReportViewWidthsCallback", + CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { + EXPECT_TRUE(first_report); + first_report = false; + ParseViewWidthsCallback(args, &view_widths); + report_latch.Signal(); + })); + + PlatformViewNotifyCreated(shell.get()); + auto configuration = RunConfiguration::InferFromSettings(settings); + configuration.SetEntrypoint("testReportViewWidths"); + RunEngine(shell.get(), std::move(configuration)); + + report_latch.Wait(); + EXPECT_EQ(view_widths.size(), 3u); + EXPECT_EQ(view_widths[0], 0); + EXPECT_EQ(view_widths[123], 100); + EXPECT_EQ(view_widths[456], 300); + + PlatformViewNotifyDestroyed(shell.get()); + DestroyShell(std::move(shell), task_runners); +} + TEST_F(ShellTest, RuntimeStageBackendDefaultsToSkSLWithoutImpeller) { ASSERT_FALSE(DartVMRef::IsInstanceRunning()); Settings settings = CreateSettingsForFixture(); diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java index 2f1156704b982..1f6118bfe56c4 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java @@ -583,6 +583,7 @@ void onPostResume() { ensureAlive(); if (flutterEngine != null) { updateSystemUiOverlays(); + flutterEngine.getPlatformViewsController().onResume(); } else { Log.w(TAG, "onPostResume() invoked before FlutterFragment was attached to an Activity."); } @@ -1020,6 +1021,7 @@ void onTrimMemory(int level) { flutterEngine.getSystemChannel().sendMemoryPressureWarning(); } flutterEngine.getRenderer().onTrimMemory(level); + flutterEngine.getPlatformViewsController().onTrimMemory(level); } } diff --git a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java index 673f58bb25d6b..ee1f633275b46 100644 --- a/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java +++ b/shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java @@ -7,6 +7,7 @@ import static io.flutter.Build.API_LEVELS; import android.annotation.TargetApi; +import android.content.ComponentCallbacks2; import android.graphics.Bitmap; import android.graphics.ImageFormat; import android.graphics.Rect; @@ -29,11 +30,11 @@ import java.io.IOException; import java.lang.ref.WeakReference; import java.nio.ByteBuffer; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; -import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicLong; @@ -415,12 +416,18 @@ final class ImageReaderSurfaceProducer // Flip when debugging to see verbose logs. private static final boolean VERBOSE_LOGS = false; + // We must always cleanup on memory pressure on Android 14 due to a bug in Android. + // It is safe to do on all versions so we unconditionally have this set to true. + private static final boolean CLEANUP_ON_MEMORY_PRESSURE = true; + private final long id; private boolean released; // Will be true in tests and on Android API < 33. private boolean ignoringFence = false; + private boolean trimOnMemoryPressure = CLEANUP_ON_MEMORY_PRESSURE; + // The requested width and height are updated by setSize. private int requestedWidth = 1; private int requestedHeight = 1; @@ -434,10 +441,11 @@ final class ImageReaderSurfaceProducer private long lastDequeueTime = 0; private long lastQueueTime = 0; private long lastScheduleTime = 0; + private int numTrims = 0; private Object lock = new Object(); // REQUIRED: The following fields must only be accessed when lock is held. - private final LinkedList imageReaderQueue = new LinkedList(); + private final ArrayDeque imageReaderQueue = new ArrayDeque(); private final HashMap perImageReaders = new HashMap(); private PerImage lastDequeuedImage = null; @@ -457,7 +465,7 @@ public PerImage(Image image, long queuedTime) { /** Internal class: state held per ImageReader. */ private class PerImageReader { public final ImageReader reader; - private final LinkedList imageQueue = new LinkedList(); + private final ArrayDeque imageQueue = new ArrayDeque(); private boolean closed = false; private final ImageReader.OnImageAvailableListener onImageAvailableListener = @@ -651,6 +659,15 @@ PerImage dequeueImage() { @Override public void onTrimMemory(int level) { + if (!trimOnMemoryPressure) { + return; + } + if (level < ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) { + return; + } + synchronized (lock) { + numTrims++; + } cleanup(); createNewReader = true; } @@ -867,6 +884,13 @@ public int numImageReaders() { } } + @VisibleForTesting + public int numTrims() { + synchronized (lock) { + return numTrims; + } + } + @VisibleForTesting public int numImages() { int r = 0; diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java index 6e2b2ab250f82..093be1566cb4e 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java @@ -9,6 +9,7 @@ import static io.flutter.Build.API_LEVELS; import android.annotation.TargetApi; +import android.content.ComponentCallbacks2; import android.content.Context; import android.content.MutableContextWrapper; import android.os.Build; @@ -1053,6 +1054,24 @@ private void diposeAllViews() { } } + // Invoked when the Android system is requesting we reduce memory usage. + public void onTrimMemory(int level) { + if (level < ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) { + return; + } + for (VirtualDisplayController vdc : vdControllers.values()) { + vdc.clearSurface(); + } + } + + // Called after the application has been resumed. + // This is where we undo whatever may have been done in onTrimMemory. + public void onResume() { + for (VirtualDisplayController vdc : vdControllers.values()) { + vdc.resetSurface(); + } + } + /** * Disposes a single * diff --git a/shell/platform/android/io/flutter/plugin/platform/VirtualDisplayController.java b/shell/platform/android/io/flutter/plugin/platform/VirtualDisplayController.java index 5de42299e00c4..cdde0687a4a99 100644 --- a/shell/platform/android/io/flutter/plugin/platform/VirtualDisplayController.java +++ b/shell/platform/android/io/flutter/plugin/platform/VirtualDisplayController.java @@ -284,6 +284,49 @@ public void dispatchTouchEvent(MotionEvent event) { presentation.dispatchTouchEvent(event); } + public void clearSurface() { + virtualDisplay.setSurface(null); + } + + public void resetSurface() { + final int width = getRenderTargetWidth(); + final int height = getRenderTargetHeight(); + final boolean isFocused = getView().isFocused(); + final SingleViewPresentation.PresentationState presentationState = presentation.detachState(); + + // We detach the surface to prevent it being destroyed when releasing the vd. + virtualDisplay.setSurface(null); + virtualDisplay.release(); + final DisplayManager displayManager = + (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE); + int flags = 0; + virtualDisplay = + displayManager.createVirtualDisplay( + "flutter-vd#" + viewId, + width, + height, + densityDpi, + renderTarget.getSurface(), + flags, + callback, + null /* handler */); + // Create a new SingleViewPresentation and show() it before we cancel() the existing + // presentation. Calling show() and cancel() in this order fixes + // https://github.com/flutter/flutter/issues/26345 and maintains seamless transition + // of the contents of the presentation. + SingleViewPresentation newPresentation = + new SingleViewPresentation( + context, + virtualDisplay.getDisplay(), + accessibilityEventsDelegate, + presentationState, + focusChangeListener, + isFocused); + newPresentation.show(); + presentation.cancel(); + presentation = newPresentation; + } + static class OneTimeOnDrawListener implements ViewTreeObserver.OnDrawListener { static void schedule(View view, Runnable runnable) { OneTimeOnDrawListener listener = new OneTimeOnDrawListener(view, runnable); diff --git a/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java b/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java index 50f83d5fb8e0a..f19da788d3759 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java @@ -647,12 +647,41 @@ public void ImageReaderSurfaceProducerTrimMemoryCallback() { assertEquals(1, texture.numImageReaders()); assertEquals(1, texture.numImages()); - // Invoke the onTrimMemory callback. + // Invoke the onTrimMemory callback with level 0. + // This should do nothing. texture.onTrimMemory(0); shadowOf(Looper.getMainLooper()).idle(); + assertEquals(1, texture.numImageReaders()); + assertEquals(1, texture.numImages()); + assertEquals(0, texture.numTrims()); + + // Invoke the onTrimMemory callback with level 40. + // This should result in a trim. + texture.onTrimMemory(40); + shadowOf(Looper.getMainLooper()).idle(); + assertEquals(0, texture.numImageReaders()); assertEquals(0, texture.numImages()); + assertEquals(1, texture.numTrims()); + + // Request the surface, this should result in a new image reader. + surface = texture.getSurface(); + assertEquals(1, texture.numImageReaders()); + assertEquals(0, texture.numImages()); + assertEquals(1, texture.numTrims()); + + // Render an image. + canvas = surface.lockHardwareCanvas(); + canvas.drawARGB(255, 255, 0, 0); + surface.unlockCanvasAndPost(canvas); + + // Let callbacks run, this will produce a single frame. + shadowOf(Looper.getMainLooper()).idle(); + + assertEquals(1, texture.numImageReaders()); + assertEquals(1, texture.numImages()); + assertEquals(1, texture.numTrims()); } // A 0x0 ImageReader is a runtime error. diff --git a/shell/platform/darwin/ios/framework/Source/FlutterDartVMServicePublisher.mm b/shell/platform/darwin/ios/framework/Source/FlutterDartVMServicePublisher.mm index b9473566ca890..38288a1f247e2 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterDartVMServicePublisher.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterDartVMServicePublisher.mm @@ -65,7 +65,6 @@ @interface DartVMServiceDNSServiceDelegate : NSObject #include +#include #include "flutter/fml/macros.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController.h" @@ -34,9 +35,9 @@ class FlutterCompositor { // The view_provider is used to query FlutterViews from view IDs, // which are used for presenting and creating backing stores. // It must not be null, and is typically FlutterViewEngineProvider. - explicit FlutterCompositor(id view_provider, - FlutterTimeConverter* time_converter, - FlutterPlatformViewController* platform_views_controller); + FlutterCompositor(id view_provider, + FlutterTimeConverter* time_converter, + FlutterPlatformViewController* platform_views_controller); ~FlutterCompositor() = default; @@ -56,19 +57,34 @@ class FlutterCompositor { FlutterBackingStore* backing_store_out); // Presents the FlutterLayers by updating the FlutterView specified by - // `view_id` using the layer content. Sets frame_started_ to false. + // `view_id` using the layer content. bool Present(FlutterViewId view_id, const FlutterLayer** layers, size_t layers_count); private: - void PresentPlatformViews(FlutterView* default_base_view, - const std::vector& platform_views_layers); - - // Presents the platform view layer represented by `layer`. `layer_index` is - // used to position the layer in the z-axis. If the layer does not have a - // superview, it will become subview of `default_base_view`. - FlutterMutatorView* PresentPlatformView(FlutterView* default_base_view, - const PlatformViewLayer& layer, - size_t index); + // A class that contains the information for a view to be presented. + class ViewPresenter { + public: + ViewPresenter(); + + void PresentPlatformViews(FlutterView* default_base_view, + const std::vector& platform_views, + const FlutterPlatformViewController* platform_views_controller); + + private: + // Platform view to FlutterMutatorView that contains it. + NSMapTable* mutator_views_; + + // Presents the platform view layer represented by `layer`. `layer_index` is + // used to position the layer in the z-axis. If the layer does not have a + // superview, it will become subview of `default_base_view`. + FlutterMutatorView* PresentPlatformView( + FlutterView* default_base_view, + const PlatformViewLayer& layer, + size_t layer_position, + const FlutterPlatformViewController* platform_views_controller); + + FML_DISALLOW_COPY_AND_ASSIGN(ViewPresenter); + }; // Where the compositor can query FlutterViews. Must not be null. id const view_provider_; @@ -79,8 +95,7 @@ class FlutterCompositor { // The controller used to manage creation and deletion of platform views. const FlutterPlatformViewController* platform_view_controller_; - // Platform view to FlutterMutatorView that contains it. - NSMapTable* mutator_views_; + std::unordered_map presenters_; FML_DISALLOW_COPY_AND_ASSIGN(FlutterCompositor); }; diff --git a/shell/platform/darwin/macos/framework/Source/FlutterCompositor.mm b/shell/platform/darwin/macos/framework/Source/FlutterCompositor.mm index 890a4c0cfca3b..e7749ca0e539b 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterCompositor.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterCompositor.mm @@ -27,8 +27,7 @@ FlutterPlatformViewController* platform_view_controller) : view_provider_(view_provider), time_converter_(time_converter), - platform_view_controller_(platform_view_controller), - mutator_views_([NSMapTable strongToStrongObjectsMapTable]) { + platform_view_controller_(platform_view_controller) { FML_CHECK(view_provider != nullptr) << "view_provider cannot be nullptr"; } @@ -55,6 +54,10 @@ bool FlutterCompositor::Present(FlutterViewId view_id, const FlutterLayer** layers, size_t layers_count) { + // TODO(dkwingsmt): The macOS embedder only supports rendering to the implicit + // view for now. As it supports adding more views, this assertion should be + // lifted. https://github.com/flutter/flutter/issues/142845 + FML_DCHECK(view_id == kFlutterImplicitViewId); FlutterView* view = [view_provider_ viewForId:view_id]; if (!view) { return false; @@ -98,15 +101,22 @@ [view.surfaceManager presentSurfaces:surfaces atTime:presentation_time notify:^{ - PresentPlatformViews(view, *platform_views_layers); + // Gets a presenter or create a new one for the view. + ViewPresenter& presenter = presenters_[view_id]; + presenter.PresentPlatformViews(view, *platform_views_layers, + platform_view_controller_); }]; return true; } -void FlutterCompositor::PresentPlatformViews( +FlutterCompositor::ViewPresenter::ViewPresenter() + : mutator_views_([NSMapTable strongToStrongObjectsMapTable]) {} + +void FlutterCompositor::ViewPresenter::PresentPlatformViews( FlutterView* default_base_view, - const std::vector& platform_views) { + const std::vector& platform_views, + const FlutterPlatformViewController* platform_view_controller) { FML_DCHECK([[NSThread currentThread] isMainThread]) << "Must be on the main thread to present platform views"; @@ -114,8 +124,9 @@ NSMutableArray* present_mutators = [NSMutableArray array]; for (const auto& platform_view : platform_views) { - [present_mutators addObject:PresentPlatformView(default_base_view, platform_view.first, - platform_view.second)]; + FlutterMutatorView* container = PresentPlatformView( + default_base_view, platform_view.first, platform_view.second, platform_view_controller); + [present_mutators addObject:container]; } NSMutableArray* obsolete_mutators = @@ -127,17 +138,19 @@ [mutator removeFromSuperview]; } - [platform_view_controller_ disposePlatformViews]; + [platform_view_controller disposePlatformViews]; } -FlutterMutatorView* FlutterCompositor::PresentPlatformView(FlutterView* default_base_view, - const PlatformViewLayer& layer, - size_t index) { +FlutterMutatorView* FlutterCompositor::ViewPresenter::PresentPlatformView( + FlutterView* default_base_view, + const PlatformViewLayer& layer, + size_t index, + const FlutterPlatformViewController* platform_view_controller) { FML_DCHECK([[NSThread currentThread] isMainThread]) << "Must be on the main thread to present platform views"; int64_t platform_view_id = layer.identifier(); - NSView* platform_view = [platform_view_controller_ platformViewWithID:platform_view_id]; + NSView* platform_view = [platform_view_controller platformViewWithID:platform_view_id]; FML_DCHECK(platform_view) << "Platform view not found for id: " << platform_view_id; diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm index 56419b18a661c..f354a23675952 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm @@ -847,15 +847,9 @@ - (FlutterCompositor*)createFlutterCompositor { void* user_data // ) { return true; }; - _compositor.present_layers_callback = [](const FlutterLayer** layers, // - size_t layers_count, // - void* user_data // - ) { - // TODO(dkwingsmt): This callback only supports single-view, therefore it - // only operates on the implicit view. To support multi-view, we need a new - // callback that also receives a view ID. - return reinterpret_cast(user_data)->Present(kFlutterImplicitViewId, - layers, layers_count); + _compositor.present_view_callback = [](const FlutterPresentViewInfo* info) { + return reinterpret_cast(info->user_data) + ->Present(info->view_id, info->layers, info->layers_count); }; _compositor.avoid_backing_store_cache = true; diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm b/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm index 7b081318148d8..b1d4cfb591f67 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm @@ -535,7 +535,56 @@ @implementation MockableFlutterEngine // TODO(gw280): add support for screenshot tests in this test harness [engine shutDownEngine]; -} // namespace flutter::testing +} + +TEST_F(FlutterEngineTest, CompositorIgnoresUnknownView) { + FlutterEngine* engine = GetFlutterEngine(); + auto original_init = engine.embedderAPI.Initialize; + ::FlutterCompositor compositor; + engine.embedderAPI.Initialize = MOCK_ENGINE_PROC( + Initialize, ([&compositor, &original_init]( + size_t version, const FlutterRendererConfig* config, + const FlutterProjectArgs* args, void* user_data, auto engine_out) { + compositor = *args->compositor; + return original_init(version, config, args, user_data, engine_out); + })); + + FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine + nibName:nil + bundle:nil]; + [viewController loadView]; + + EXPECT_TRUE([engine runWithEntrypoint:@"empty"]); + + FlutterBackingStoreConfig config = { + .struct_size = sizeof(FlutterBackingStoreConfig), + .size = FlutterSize{10, 10}, + }; + FlutterBackingStore backing_store = {}; + EXPECT_NE(compositor.create_backing_store_callback, nullptr); + EXPECT_TRUE( + compositor.create_backing_store_callback(&config, &backing_store, compositor.user_data)); + + FlutterLayer layer{ + .type = kFlutterLayerContentTypeBackingStore, + .backing_store = &backing_store, + }; + std::vector layers = {&layer}; + + FlutterPresentViewInfo info = { + .struct_size = sizeof(FlutterPresentViewInfo), + .view_id = 123, + .layers = const_cast(layers.data()), + .layers_count = 1, + .user_data = compositor.user_data, + }; + EXPECT_NE(compositor.present_view_callback, nullptr); + EXPECT_FALSE(compositor.present_view_callback(&info)); + EXPECT_TRUE(compositor.collect_backing_store_callback(&backing_store, compositor.user_data)); + + (void)viewController; + [engine shutDownEngine]; +} TEST_F(FlutterEngineTest, DartEntrypointArguments) { NSString* fixtures = @(flutter::testing::GetFixturesPath()); diff --git a/shell/platform/darwin/macos/framework/Source/fixtures/flutter_desktop_test.dart b/shell/platform/darwin/macos/framework/Source/fixtures/flutter_desktop_test.dart index 835588ff354ac..7a9a69db73de7 100644 --- a/shell/platform/darwin/macos/framework/Source/fixtures/flutter_desktop_test.dart +++ b/shell/platform/darwin/macos/framework/Source/fixtures/flutter_desktop_test.dart @@ -12,6 +12,10 @@ external void signalNativeTest(); void main() { } +@pragma('vm:entry-point') +void empty() { +} + /// Notifies the test of a string value. /// /// This is used to notify the native side of the test of a string value from diff --git a/shell/platform/windows/BUILD.gn b/shell/platform/windows/BUILD.gn index bed6b8f8d7a5a..e80662e592a72 100644 --- a/shell/platform/windows/BUILD.gn +++ b/shell/platform/windows/BUILD.gn @@ -87,6 +87,7 @@ source_set("flutter_windows_source") { "flutter_windows_texture_registrar.h", "flutter_windows_view.cc", "flutter_windows_view.h", + "flutter_windows_view_controller.cc", "flutter_windows_view_controller.h", "keyboard_handler_base.h", "keyboard_key_channel_handler.cc", diff --git a/shell/platform/windows/fixtures/main.dart b/shell/platform/windows/fixtures/main.dart index 5f3d61c9e0a26..9c6ae19a38dc9 100644 --- a/shell/platform/windows/fixtures/main.dart +++ b/shell/platform/windows/fixtures/main.dart @@ -231,6 +231,47 @@ void sendCreatePlatformViewMethod() async { await completed.future; } +@pragma('vm:entry-point') +void sendGetKeyboardState() async { + // The keyboard method channel uses the standard method codec. + // See https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/services/message_codecs.dart#L262 + // for the implementation of the encoding and magic number identifiers. + const int valueNull = 0; + const int valueString = 7; + const int valueMap = 13; + + const String method = 'getKeyboardState'; + final List data = [ + // Method name + valueString, method.length, ...utf8.encode(method), + // Method arguments: null + valueNull, 2, + ]; + + final Completer completer = Completer(); + final ByteData bytes = ByteData.sublistView(Uint8List.fromList(data)); + ui.PlatformDispatcher.instance.sendPlatformMessage('flutter/keyboard', bytes, (ByteData? response) { + // For magic numbers for decoding a reply envelope, see: + // https://github.com/flutter/flutter/blob/67271f69f7f88a4edba6d8023099e3bd27a072d2/packages/flutter/lib/src/services/message_codecs.dart#L577-L587 + const int replyEnvelopeSuccess = 0; + + // Ensure the response is a success containing a map of keyboard states. + if (response == null) { + signalStringValue('Unexpected null response'); + } else if (response.lengthInBytes < 2) { + signalStringValue('Unexpected response length of ${response.lengthInBytes} bytes'); + } else if (response.getUint8(0) != replyEnvelopeSuccess) { + signalStringValue('Unexpected response envelope status: ${response.getUint8(0)}'); + } else if (response.getUint8(1) != valueMap) { + signalStringValue('Unexpected response value magic number: ${response.getUint8(1)}'); + } else { + signalStringValue('Success'); + } + completer.complete(); + }); + await completer.future; +} + @pragma('vm:entry-point') void customEntrypoint() {} diff --git a/shell/platform/windows/flutter_windows.cc b/shell/platform/windows/flutter_windows.cc index 874b97d537c22..d25d3f961dc5c 100644 --- a/shell/platform/windows/flutter_windows.cc +++ b/shell/platform/windows/flutter_windows.cc @@ -130,6 +130,7 @@ FlutterDesktopViewControllerRef FlutterDesktopEngineCreateViewController( void FlutterDesktopViewControllerDestroy(FlutterDesktopViewControllerRef ref) { auto controller = ViewControllerFromHandle(ref); + controller->Destroy(); delete controller; } diff --git a/shell/platform/windows/flutter_windows_engine.cc b/shell/platform/windows/flutter_windows_engine.cc index 988e1f47954c6..1969f4ebc22bd 100644 --- a/shell/platform/windows/flutter_windows_engine.cc +++ b/shell/platform/windows/flutter_windows_engine.cc @@ -473,6 +473,8 @@ bool FlutterWindowsEngine::Run(std::string_view entrypoint) { settings_plugin_->StartWatching(); settings_plugin_->SendSettings(); + InitializeKeyboard(); + return true; } @@ -497,11 +499,28 @@ std::unique_ptr FlutterWindowsEngine::CreateView( kImplicitViewId, this, std::move(window), windows_proc_table_); views_[kImplicitViewId] = view.get(); - InitializeKeyboard(); return std::move(view); } +void FlutterWindowsEngine::RemoveView(FlutterViewId view_id) { + FML_DCHECK(running()); + FML_DCHECK(views_.find(view_id) != views_.end()); + + if (view_id == kImplicitViewId) { + // The engine and framework assume the implicit view always exists. + // Attempts to render to the implicit view will be ignored. + views_.erase(view_id); + return; + } + + // TODO(loicsharma): Remove the view from the engine using the + // `FlutterEngineRemoveView` embedder API. Windows does not + // support views other than the implicit view yet. + // https://github.com/flutter/flutter/issues/144810 + FML_UNREACHABLE(); +} + void FlutterWindowsEngine::OnVsync(intptr_t baton) { std::chrono::nanoseconds current_time = std::chrono::nanoseconds(embedder_api_.GetCurrentTime()); @@ -675,10 +694,6 @@ void FlutterWindowsEngine::SendSystemLocales() { } void FlutterWindowsEngine::InitializeKeyboard() { - if (views_.empty()) { - FML_LOG(ERROR) << "Cannot initialize keyboard on Windows headless mode."; - } - auto internal_plugin_messenger = internal_plugin_registrar_->messenger(); KeyboardKeyEmbedderHandler::GetKeyStateHandler get_key_state = GetKeyState; KeyboardKeyEmbedderHandler::MapVirtualKeyToScanCode map_vk_to_scan = @@ -773,9 +788,7 @@ void FlutterWindowsEngine::UpdateSemanticsEnabled(bool enabled) { void FlutterWindowsEngine::OnPreEngineRestart() { // Reset the keyboard's state on hot restart. - if (!views_.empty()) { - InitializeKeyboard(); - } + InitializeKeyboard(); } std::string FlutterWindowsEngine::GetExecutableName() const { diff --git a/shell/platform/windows/flutter_windows_engine.h b/shell/platform/windows/flutter_windows_engine.h index 018dc82a3c7c3..40f3cc058ef51 100644 --- a/shell/platform/windows/flutter_windows_engine.h +++ b/shell/platform/windows/flutter_windows_engine.h @@ -124,6 +124,9 @@ class FlutterWindowsEngine { std::unique_ptr CreateView( std::unique_ptr window); + // Remove a view. The engine will no longer render into it. + virtual void RemoveView(FlutterViewId view_id); + // Get a view that displays this engine's content. // // Returns null if the view does not exist. diff --git a/shell/platform/windows/flutter_windows_unittests.cc b/shell/platform/windows/flutter_windows_unittests.cc index 37fbbcabd5c24..d8155d4e18d81 100644 --- a/shell/platform/windows/flutter_windows_unittests.cc +++ b/shell/platform/windows/flutter_windows_unittests.cc @@ -139,6 +139,9 @@ TEST_F(WindowsTest, EngineCanTransitionToHeadless) { // The engine is back in headless mode now. ASSERT_NE(engine, nullptr); + + auto engine_ptr = reinterpret_cast(engine.get()); + ASSERT_TRUE(engine_ptr->running()); } // Verify that accessibility features are initialized when a view is created. @@ -477,5 +480,43 @@ TEST_F(WindowsTest, Lifecycle) { /* bRepaint*/ false); } +TEST_F(WindowsTest, GetKeyboardStateHeadless) { + // Run the test on its own thread so that it can pump its event loop while + // this thread waits. + fml::AutoResetWaitableEvent latch; + auto platform_task_runner = CreateNewThread("test_platform_thread"); + platform_task_runner->PostTask([&]() { + auto& context = GetContext(); + WindowsConfigBuilder builder(context); + builder.SetDartEntrypoint("sendGetKeyboardState"); + + bool done = false; + context.AddNativeFunction( + "SignalStringValue", + CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { + auto handle = Dart_GetNativeArgument(args, 0); + ASSERT_FALSE(Dart_IsError(handle)); + auto value = tonic::DartConverter::FromDart(handle); + EXPECT_EQ(value, "Success"); + done = true; + latch.Signal(); + })); + + ViewControllerPtr controller{builder.Run()}; + ASSERT_NE(controller, nullptr); + + // Pump messages for the Windows platform task runner. + ::MSG msg; + while (!done) { + if (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + } + }); + + latch.Wait(); +} + } // namespace testing } // namespace flutter diff --git a/shell/platform/windows/flutter_windows_view.cc b/shell/platform/windows/flutter_windows_view.cc index f8a87c825979f..93f523194714a 100644 --- a/shell/platform/windows/flutter_windows_view.cc +++ b/shell/platform/windows/flutter_windows_view.cc @@ -7,7 +7,9 @@ #include #include "flutter/common/constants.h" +#include "flutter/fml/make_copyable.h" #include "flutter/fml/platform/win/wstring_conversion.h" +#include "flutter/fml/synchronization/waitable_event.h" #include "flutter/shell/platform/common/accessibility_bridge.h" #include "flutter/shell/platform/windows/keyboard_key_channel_handler.h" #include "flutter/shell/platform/windows/text_input_plugin.h" @@ -81,6 +83,22 @@ void UpdateVsync(const FlutterWindowsEngine& engine, } } +/// Destroys a rendering surface that backs a Flutter view. +void DestroyWindowSurface(const FlutterWindowsEngine& engine, + std::unique_ptr surface) { + // EGL surfaces are used on the raster thread if the engine is running. + // There may be pending raster tasks that use this surface. Destroy the + // surface on the raster thread to avoid concurrent uses. + if (engine.running()) { + engine.PostRasterThreadTask(fml::MakeCopyable( + [surface = std::move(surface)] { surface->Destroy(); })); + } else { + // There's no raster thread if engine isn't running. The surface can be + // destroyed on the platform thread. + surface->Destroy(); + } +} + } // namespace FlutterWindowsView::FlutterWindowsView( @@ -105,11 +123,9 @@ FlutterWindowsView::~FlutterWindowsView() { // Notify the engine the view's child window will no longer be visible. engine_->OnWindowStateEvent(GetWindowHandle(), WindowStateEvent::kHide); - // The engine renders into the view's surface. The engine must be - // shutdown before the view's resources can be destroyed. - engine_->Stop(); - - DestroyRenderSurface(); + if (surface_) { + DestroyWindowSurface(*engine_, std::move(surface_)); + } } bool FlutterWindowsView::OnEmptyFrameGenerated() { @@ -710,12 +726,6 @@ bool FlutterWindowsView::ResizeRenderSurface(size_t width, size_t height) { return true; } -void FlutterWindowsView::DestroyRenderSurface() { - if (surface_) { - surface_->Destroy(); - } -} - egl::WindowSurface* FlutterWindowsView::surface() const { return surface_.get(); } diff --git a/shell/platform/windows/flutter_windows_view.h b/shell/platform/windows/flutter_windows_view.h index 8cd4e5e71312b..782e63ad53136 100644 --- a/shell/platform/windows/flutter_windows_view.h +++ b/shell/platform/windows/flutter_windows_view.h @@ -50,9 +50,6 @@ class FlutterWindowsView : public WindowBindingHandlerDelegate { // Should be called before calling FlutterEngineRun using this view. void CreateRenderSurface(); - // Destroys current rendering surface if one has been allocated. - void DestroyRenderSurface(); - // Get the EGL surface that backs the Flutter view. // // This might be nullptr or an invalid surface. diff --git a/shell/platform/windows/flutter_windows_view_controller.cc b/shell/platform/windows/flutter_windows_view_controller.cc new file mode 100644 index 0000000000000..d89d3baa59a55 --- /dev/null +++ b/shell/platform/windows/flutter_windows_view_controller.cc @@ -0,0 +1,30 @@ +// Copyright 2013 The Flutter 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 "flutter/shell/platform/windows/flutter_windows_view_controller.h" + +namespace flutter { + +FlutterWindowsViewController::~FlutterWindowsViewController() { + Destroy(); +} + +void FlutterWindowsViewController::Destroy() { + if (!view_) { + return; + } + + // Prevent the engine from rendering into this view. + if (view_->GetEngine()->running()) { + auto view_id = view_->view_id(); + + view_->GetEngine()->RemoveView(view_id); + } + + // Destroy the view, followed by the engine if it is owned by this controller. + view_.reset(); + engine_.reset(); +} + +} // namespace flutter diff --git a/shell/platform/windows/flutter_windows_view_controller.h b/shell/platform/windows/flutter_windows_view_controller.h index 10175fc9546ba..2aca77cf38e78 100644 --- a/shell/platform/windows/flutter_windows_view_controller.h +++ b/shell/platform/windows/flutter_windows_view_controller.h @@ -7,8 +7,9 @@ #include -#include "flutter_windows_engine.h" -#include "flutter_windows_view.h" +#include "flutter/fml/macros.h" +#include "flutter/shell/platform/windows/flutter_windows_engine.h" +#include "flutter/shell/platform/windows/flutter_windows_view.h" namespace flutter { @@ -19,6 +20,13 @@ class FlutterWindowsViewController { std::unique_ptr view) : engine_(std::move(engine)), view_(std::move(view)) {} + ~FlutterWindowsViewController(); + + // Destroy this view controller and its view. + // + // If this view controller owns the engine, the engine is also destroyed. + void Destroy(); + FlutterWindowsEngine* engine() { return view_->GetEngine(); } FlutterWindowsView* view() { return view_.get(); } @@ -36,6 +44,8 @@ class FlutterWindowsViewController { std::unique_ptr engine_; std::unique_ptr view_; + + FML_DISALLOW_COPY_AND_ASSIGN(FlutterWindowsViewController); }; } // namespace flutter diff --git a/shell/platform/windows/flutter_windows_view_unittests.cc b/shell/platform/windows/flutter_windows_view_unittests.cc index a11e06f37ce09..c22459f32bd3d 100644 --- a/shell/platform/windows/flutter_windows_view_unittests.cc +++ b/shell/platform/windows/flutter_windows_view_unittests.cc @@ -18,6 +18,7 @@ #include "flutter/shell/platform/windows/flutter_window.h" #include "flutter/shell/platform/windows/flutter_windows_engine.h" #include "flutter/shell/platform/windows/flutter_windows_texture_registrar.h" +#include "flutter/shell/platform/windows/flutter_windows_view_controller.h" #include "flutter/shell/platform/windows/testing/egl/mock_context.h" #include "flutter/shell/platform/windows/testing/egl/mock_manager.h" #include "flutter/shell/platform/windows/testing/egl/mock_window_surface.h" @@ -121,6 +122,7 @@ class MockFlutterWindowsEngine : public FlutterWindowsEngine { MOCK_METHOD(bool, running, (), (const)); MOCK_METHOD(bool, Stop, (), ()); + MOCK_METHOD(void, RemoveView, (FlutterViewId view_id), ()); MOCK_METHOD(bool, PostRasterThreadTask, (fml::closure), (const)); private: @@ -241,6 +243,8 @@ TEST(FlutterWindowsViewTest, Shutdown) { std::make_unique>(); auto egl_manager = std::make_unique(); auto surface = std::make_unique(); + + auto engine_ptr = engine.get(); auto surface_ptr = surface.get(); EngineModifier modifier{engine.get()}; @@ -250,12 +254,22 @@ TEST(FlutterWindowsViewTest, Shutdown) { std::unique_ptr view = engine->CreateView(std::move(window_binding_handler)); + auto view_id = view->view_id(); ViewModifier view_modifier{view.get()}; view_modifier.SetSurface(std::move(surface)); - // The engine must be stopped before the surface can be destroyed. + FlutterWindowsViewController controller{std::move(engine), std::move(view)}; + + // The view must be removed before the surface can be destroyed. InSequence s; - EXPECT_CALL(*engine.get(), Stop).Times(1); + EXPECT_CALL(*engine_ptr, running).WillOnce(Return(true)); + EXPECT_CALL(*engine_ptr, RemoveView(view_id)).Times(1); + EXPECT_CALL(*engine_ptr, running).WillOnce(Return(true)); + EXPECT_CALL(*engine_ptr, PostRasterThreadTask) + .WillOnce([](fml::closure callback) { + callback(); + return true; + }); EXPECT_CALL(*surface_ptr, Destroy).Times(1); } } @@ -817,7 +831,14 @@ TEST(FlutterWindowsViewTest, WindowResizeTests) { auto windows_proc_table = std::make_shared>(); std::unique_ptr engine = GetTestEngine(windows_proc_table); + EngineModifier engine_modifier{engine.get()}; + engine_modifier.embedder_api().PostRenderThreadTask = MOCK_ENGINE_PROC( + PostRenderThreadTask, + ([](auto engine, VoidCallback callback, void* user_data) { + callback(user_data); + return kSuccess; + })); auto egl_manager = std::make_unique(); auto surface = std::make_unique(); @@ -875,7 +896,14 @@ TEST(FlutterWindowsViewTest, TestEmptyFrameResizes) { auto windows_proc_table = std::make_shared>(); std::unique_ptr engine = GetTestEngine(windows_proc_table); + EngineModifier engine_modifier{engine.get()}; + engine_modifier.embedder_api().PostRenderThreadTask = MOCK_ENGINE_PROC( + PostRenderThreadTask, + ([](auto engine, VoidCallback callback, void* user_data) { + callback(user_data); + return kSuccess; + })); auto egl_manager = std::make_unique(); auto surface = std::make_unique(); @@ -932,7 +960,14 @@ TEST(FlutterWindowsViewTest, TestEmptyFrameResizes) { // https://github.com/flutter/flutter/issues/141855 TEST(FlutterWindowsViewTest, WindowResizeRace) { std::unique_ptr engine = GetTestEngine(); + EngineModifier engine_modifier(engine.get()); + engine_modifier.embedder_api().PostRenderThreadTask = MOCK_ENGINE_PROC( + PostRenderThreadTask, + ([](auto engine, VoidCallback callback, void* user_data) { + callback(user_data); + return kSuccess; + })); auto egl_manager = std::make_unique(); auto surface = std::make_unique(); @@ -972,7 +1007,14 @@ TEST(FlutterWindowsViewTest, WindowResizeRace) { // even though EGL initialized successfully. TEST(FlutterWindowsViewTest, WindowResizeInvalidSurface) { std::unique_ptr engine = GetTestEngine(); + EngineModifier engine_modifier(engine.get()); + engine_modifier.embedder_api().PostRenderThreadTask = MOCK_ENGINE_PROC( + PostRenderThreadTask, + ([](auto engine, VoidCallback callback, void* user_data) { + callback(user_data); + return kSuccess; + })); auto egl_manager = std::make_unique(); auto surface = std::make_unique(); @@ -1392,7 +1434,6 @@ TEST(FlutterWindowsViewTest, DisablesVSyncAtStartup) { EXPECT_CALL(*surface_ptr, SetVSyncEnabled(false)).WillOnce(Return(true)); EXPECT_CALL(render_context, ClearCurrent).WillOnce(Return(true)); - EXPECT_CALL(*engine.get(), Stop).Times(1); EXPECT_CALL(*surface_ptr, Destroy).Times(1); EngineModifier modifier{engine.get()}; @@ -1430,7 +1471,6 @@ TEST(FlutterWindowsViewTest, EnablesVSyncAtStartup) { EXPECT_CALL(*surface_ptr, SetVSyncEnabled(true)).WillOnce(Return(true)); EXPECT_CALL(render_context, ClearCurrent).WillOnce(Return(true)); - EXPECT_CALL(*engine.get(), Stop).Times(1); EXPECT_CALL(*surface_ptr, Destroy).Times(1); EngineModifier modifier{engine.get()}; @@ -1471,7 +1511,11 @@ TEST(FlutterWindowsViewTest, DisablesVSyncAfterStartup) { EXPECT_CALL(*surface_ptr, MakeCurrent).WillOnce(Return(true)); EXPECT_CALL(*surface_ptr, SetVSyncEnabled(false)).WillOnce(Return(true)); EXPECT_CALL(render_context, ClearCurrent).WillOnce(Return(true)); - EXPECT_CALL(*engine.get(), Stop).Times(1); + EXPECT_CALL(*engine.get(), PostRasterThreadTask) + .WillOnce([](fml::closure callback) { + callback(); + return true; + }); EXPECT_CALL(*surface_ptr, Destroy).Times(1); EngineModifier modifier{engine.get()}; @@ -1514,7 +1558,12 @@ TEST(FlutterWindowsViewTest, EnablesVSyncAfterStartup) { EXPECT_CALL(*surface_ptr, MakeCurrent).WillOnce(Return(true)); EXPECT_CALL(*surface_ptr, SetVSyncEnabled(true)).WillOnce(Return(true)); EXPECT_CALL(render_context, ClearCurrent).WillOnce(Return(true)); - EXPECT_CALL(*engine.get(), Stop).Times(1); + + EXPECT_CALL(*engine.get(), PostRasterThreadTask) + .WillOnce([](fml::closure callback) { + callback(); + return true; + }); EXPECT_CALL(*surface_ptr, Destroy).Times(1); EngineModifier modifier{engine.get()}; @@ -1563,7 +1612,6 @@ TEST(FlutterWindowsViewTest, UpdatesVSyncOnDwmUpdates) { EXPECT_CALL(*surface_ptr, SetVSyncEnabled(false)).WillOnce(Return(true)); EXPECT_CALL(render_context, ClearCurrent).WillOnce(Return(true)); - EXPECT_CALL(*engine.get(), Stop).Times(1); EXPECT_CALL(*surface_ptr, Destroy).Times(1); EngineModifier engine_modifier{engine.get()}; diff --git a/sky/packages/sky_engine/LICENSE b/sky/packages/sky_engine/LICENSE index 3376a60b8e33a..dce01614b1516 100644 --- a/sky/packages/sky_engine/LICENSE +++ b/sky/packages/sky_engine/LICENSE @@ -22574,6 +22574,37 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- +pkg + +Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file +for details. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +-------------------------------------------------------------------------------- dart Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file @@ -22882,6 +22913,37 @@ shall not be used in advertising or otherwise to promote the sale, use or other dealings in these Data Files or Software without prior written authorization of the copyright holder. -------------------------------------------------------------------------------- +pkg + +Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file +for details. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +-------------------------------------------------------------------------------- dart Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file @@ -23054,6 +23116,55 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. -------------------------------------------------------------------------------- +pkg + +Copyright (c) 2015 Michael Bullington + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + + +Copyright 2012, the Dart project authors. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +-------------------------------------------------------------------------------- boringssl Copyright (c) 2015, Google Inc. @@ -23104,6 +23215,37 @@ shall not be used in advertising or otherwise to promote the sale, use or other dealings in these Data Files or Software without prior written authorization of the copyright holder. -------------------------------------------------------------------------------- +pkg + +Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +for details. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +-------------------------------------------------------------------------------- dart Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file @@ -23291,6 +23433,37 @@ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -------------------------------------------------------------------------------- +pkg + +Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file +for details. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +-------------------------------------------------------------------------------- dart Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file @@ -23762,6 +23935,37 @@ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- +pkg + +Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file +for details. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +-------------------------------------------------------------------------------- dart Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file @@ -24066,6 +24270,37 @@ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -------------------------------------------------------------------------------- +pkg + +Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file +for details. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +-------------------------------------------------------------------------------- dart Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file @@ -26833,6 +27068,37 @@ copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +-------------------------------------------------------------------------------- +pkg + +Copyright 2017, the Dart project authors. Please see the AUTHORS file +for details. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR @@ -28107,6 +28373,37 @@ copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +-------------------------------------------------------------------------------- +pkg + +Copyright 2021, the Dart project authors. Please see the AUTHORS file +for details. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR @@ -31735,7 +32032,7 @@ Exhibit B - "Incompatible With Secondary Licenses" Notice This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0. -You may obtain a copy of this library's Source Code Form from: https://dart.googlesource.com/sdk/+/bb65648e20e29abc47acf3dd984518d29fd625c3 +You may obtain a copy of this library's Source Code Form from: https://dart.googlesource.com/sdk/+/b735974580e7ad7461abf8175fec2fb27fc8b8e7 /third_party/fallback_root_certificates/ -------------------------------------------------------------------------------- diff --git a/testing/BUILD.gn b/testing/BUILD.gn index 28dab99a0b301..35251252f86b2 100644 --- a/testing/BUILD.gn +++ b/testing/BUILD.gn @@ -101,8 +101,7 @@ source_set("skia") { } dart_snapshot_kernel("vmservice_kernel") { - dart_main = - rebase_path("//flutter/shell/vmservice/empty.dart", root_build_dir) + dart_main = "//flutter/shell/vmservice/empty.dart" dart_kernel = "$target_gen_dir/assets/vmservice_kernel.bin" } diff --git a/testing/benchmark/generate_metrics.sh b/testing/benchmark/generate_metrics.sh index 173f28dda9cb8..eddd11e9bfe43 100644 --- a/testing/benchmark/generate_metrics.sh +++ b/testing/benchmark/generate_metrics.sh @@ -4,17 +4,19 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. # -# This script expects $ENGINE_PATH to be set. It is currently used only +# This script expects ${ENGINE_PATH} to be set. It is currently used only # by automation to collect and upload metrics. set -ex -$ENGINE_PATH/src/out/host_release/txt_benchmarks --benchmark_format=json > $ENGINE_PATH/src/out/host_release/txt_benchmarks.json -$ENGINE_PATH/src/out/host_release/fml_benchmarks --benchmark_format=json > $ENGINE_PATH/src/out/host_release/fml_benchmarks.json -$ENGINE_PATH/src/out/host_release/shell_benchmarks --benchmark_format=json > $ENGINE_PATH/src/out/host_release/shell_benchmarks.json -$ENGINE_PATH/src/out/host_release/ui_benchmarks --benchmark_format=json > $ENGINE_PATH/src/out/host_release/ui_benchmarks.json -$ENGINE_PATH/src/out/host_release/display_list_builder_benchmarks --benchmark_format=json > $ENGINE_PATH/src/out/host_release/display_list_builder_benchmarks.json -$ENGINE_PATH/src/out/host_release/display_list_region_benchmarks --benchmark_format=json > $ENGINE_PATH/src/out/host_release/display_list_region_benchmarks.json -$ENGINE_PATH/src/out/host_release/display_list_transform_benchmarks --benchmark_format=json > $ENGINE_PATH/src/out/host_release/display_list_transform_benchmarks.json -$ENGINE_PATH/src/out/host_release/geometry_benchmarks --benchmark_format=json > $ENGINE_PATH/src/out/host_release/geometry_benchmarks.json -$ENGINE_PATH/src/out/host_release/canvas_benchmarks --benchmark_format=json > $ENGINE_PATH/src/out/host_release/canvas_benchmarks.json +VARIANT=$1 + +${ENGINE_PATH}/src/out/${VARIANT}/txt_benchmarks --benchmark_format=json > ${ENGINE_PATH}/src/out/${VARIANT}/txt_benchmarks.json +${ENGINE_PATH}/src/out/${VARIANT}/fml_benchmarks --benchmark_format=json > ${ENGINE_PATH}/src/out/${VARIANT}/fml_benchmarks.json +${ENGINE_PATH}/src/out/${VARIANT}/shell_benchmarks --benchmark_format=json > ${ENGINE_PATH}/src/out/${VARIANT}/shell_benchmarks.json +${ENGINE_PATH}/src/out/${VARIANT}/ui_benchmarks --benchmark_format=json > ${ENGINE_PATH}/src/out/${VARIANT}/ui_benchmarks.json +${ENGINE_PATH}/src/out/${VARIANT}/display_list_builder_benchmarks --benchmark_format=json > ${ENGINE_PATH}/src/out/${VARIANT}/display_list_builder_benchmarks.json +${ENGINE_PATH}/src/out/${VARIANT}/display_list_region_benchmarks --benchmark_format=json > ${ENGINE_PATH}/src/out/${VARIANT}/display_list_region_benchmarks.json +${ENGINE_PATH}/src/out/${VARIANT}/display_list_transform_benchmarks --benchmark_format=json > ${ENGINE_PATH}/src/out/${VARIANT}/display_list_transform_benchmarks.json +${ENGINE_PATH}/src/out/${VARIANT}/geometry_benchmarks --benchmark_format=json > ${ENGINE_PATH}/src/out/${VARIANT}/geometry_benchmarks.json +${ENGINE_PATH}/src/out/${VARIANT}/canvas_benchmarks --benchmark_format=json > ${ENGINE_PATH}/src/out/${VARIANT}/canvas_benchmarks.json diff --git a/testing/benchmark/upload_metrics.sh b/testing/benchmark/upload_metrics.sh index a2a8f28a4f876..b32cd4b334a60 100644 --- a/testing/benchmark/upload_metrics.sh +++ b/testing/benchmark/upload_metrics.sh @@ -43,22 +43,25 @@ SRC_DIR="$(cd "$SCRIPT_DIR/../../.."; pwd -P)" DART_BIN=$(dart_bin "$SRC_DIR") DART="${DART_BIN}/dart" +VARIANT=$1 +shift 1 + cd "$SCRIPT_DIR" "$DART" --disable-dart-dev bin/parse_and_send.dart \ - --json $ENGINE_PATH/src/out/host_release/txt_benchmarks.json "$@" + --json $ENGINE_PATH/src/out/${VARIANT}/txt_benchmarks.json "$@" "$DART" --disable-dart-dev bin/parse_and_send.dart \ - --json $ENGINE_PATH/src/out/host_release/fml_benchmarks.json "$@" + --json $ENGINE_PATH/src/out/${VARIANT}/fml_benchmarks.json "$@" "$DART" --disable-dart-dev bin/parse_and_send.dart \ - --json $ENGINE_PATH/src/out/host_release/shell_benchmarks.json "$@" + --json $ENGINE_PATH/src/out/${VARIANT}/shell_benchmarks.json "$@" "$DART" --disable-dart-dev bin/parse_and_send.dart \ - --json $ENGINE_PATH/src/out/host_release/ui_benchmarks.json "$@" + --json $ENGINE_PATH/src/out/${VARIANT}/ui_benchmarks.json "$@" "$DART" --disable-dart-dev bin/parse_and_send.dart \ - --json $ENGINE_PATH/src/out/host_release/display_list_builder_benchmarks.json "$@" + --json $ENGINE_PATH/src/out/${VARIANT}/display_list_builder_benchmarks.json "$@" "$DART" --disable-dart-dev bin/parse_and_send.dart \ - --json $ENGINE_PATH/src/out/host_release/display_list_region_benchmarks.json "$@" + --json $ENGINE_PATH/src/out/${VARIANT}/display_list_region_benchmarks.json "$@" "$DART" --disable-dart-dev bin/parse_and_send.dart \ - --json $ENGINE_PATH/src/out/host_release/display_list_transform_benchmarks.json "$@" + --json $ENGINE_PATH/src/out/${VARIANT}/display_list_transform_benchmarks.json "$@" "$DART" --disable-dart-dev bin/parse_and_send.dart \ - --json $ENGINE_PATH/src/out/host_release/geometry_benchmarks.json "$@" + --json $ENGINE_PATH/src/out/${VARIANT}/geometry_benchmarks.json "$@" "$DART" --disable-dart-dev bin/parse_and_send.dart \ - --json $ENGINE_PATH/src/out/host_release/canvas_benchmarks.json "$@" + --json $ENGINE_PATH/src/out/${VARIANT}/canvas_benchmarks.json "$@" diff --git a/testing/dart/BUILD.gn b/testing/dart/BUILD.gn index 90b4ee96f4295..f67dad80f0b4e 100644 --- a/testing/dart/BUILD.gn +++ b/testing/dart/BUILD.gn @@ -55,7 +55,7 @@ tests = [ foreach(test, tests) { flutter_build_dir = rebase_path("$root_gen_dir") - flutter_src_dir = rebase_path("$root_out_dir/../../flutter") + flutter_src_dir = rebase_path("//flutter") skia_gold_work_dir = rebase_path("$root_gen_dir/skia_gold_$test") flutter_frontend_server("compile_$test") { main_dart = test diff --git a/testing/dart/canvas_test.dart b/testing/dart/canvas_test.dart index fe957e3340f12..b121a5d924052 100644 --- a/testing/dart/canvas_test.dart +++ b/testing/dart/canvas_test.dart @@ -1162,6 +1162,111 @@ void main() async { final Image image = await picture.toImage(200, 200); await comparer.addGoldenImage(image, 'text_decoration.png'); }); + + test('Paint, when copied, has equivalent fields', () { + final Paint paint = Paint() + ..color = const Color(0xFF0000FF) + ..strokeWidth = 10.0 + ..strokeCap = StrokeCap.round + ..strokeJoin = StrokeJoin.round + ..style = PaintingStyle.stroke + ..blendMode = BlendMode.srcOver + ..maskFilter = const MaskFilter.blur(BlurStyle.normal, 10.0) + ..filterQuality = FilterQuality.high + ..colorFilter = const ColorFilter.mode(Color(0xFF00FF00), BlendMode.color) + ..imageFilter = ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0); + + final Paint paintCopy = Paint.from(paint); + expect(paintCopy.color, equals(const Color(0xFF0000FF))); + expect(paintCopy.strokeWidth, equals(10.0)); + expect(paintCopy.strokeCap, equals(StrokeCap.round)); + expect(paintCopy.strokeJoin, equals(StrokeJoin.round)); + expect(paintCopy.style, equals(PaintingStyle.stroke)); + expect(paintCopy.blendMode, equals(BlendMode.srcOver)); + expect(paintCopy.maskFilter, equals(const MaskFilter.blur(BlurStyle.normal, 10.0))); + expect(paintCopy.filterQuality, equals(FilterQuality.high)); + expect(paintCopy.colorFilter, equals(const ColorFilter.mode(Color(0xFF00FF00), BlendMode.color))); + expect(paintCopy.imageFilter, equals(ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0))); + }); + + test('Paint, when copied, does not mutate the original instance', () { + final Paint paint = Paint() + ..color = const Color(0xFF0000FF) + ..strokeWidth = 10.0 + ..strokeCap = StrokeCap.round + ..strokeJoin = StrokeJoin.round + ..style = PaintingStyle.stroke + ..blendMode = BlendMode.srcOver + ..maskFilter = const MaskFilter.blur(BlurStyle.normal, 10.0) + ..filterQuality = FilterQuality.high + ..colorFilter = const ColorFilter.mode(Color(0xFF00FF00), BlendMode.color) + ..imageFilter = ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0); + + // Make a copy, and change every field of the copy. + Paint.from(paint) + ..color = const Color(0xFF00FF00) + ..strokeWidth = 20.0 + ..strokeCap = StrokeCap.butt + ..strokeJoin = StrokeJoin.bevel + ..style = PaintingStyle.fill + ..blendMode = BlendMode.srcIn + ..maskFilter = const MaskFilter.blur(BlurStyle.solid, 20.0) + ..filterQuality = FilterQuality.none + ..colorFilter = const ColorFilter.mode(Color(0xFFFF0000), BlendMode.modulate) + ..imageFilter = ImageFilter.blur(sigmaX: 20.0, sigmaY: 20.0); + + // The original paint should not have changed. + expect(paint.color, equals(const Color(0xFF0000FF))); + expect(paint.strokeWidth, equals(10.0)); + expect(paint.strokeCap, equals(StrokeCap.round)); + expect(paint.strokeJoin, equals(StrokeJoin.round)); + expect(paint.style, equals(PaintingStyle.stroke)); + expect(paint.blendMode, equals(BlendMode.srcOver)); + expect(paint.maskFilter, equals(const MaskFilter.blur(BlurStyle.normal, 10.0))); + expect(paint.filterQuality, equals(FilterQuality.high)); + expect(paint.colorFilter, equals(const ColorFilter.mode(Color(0xFF00FF00), BlendMode.color))); + expect(paint.imageFilter, equals(ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0))); + }); + + test('Paint, when copied, the original changing does not mutate the copy', () { + final Paint paint = Paint() + ..color = const Color(0xFF0000FF) + ..strokeWidth = 10.0 + ..strokeCap = StrokeCap.round + ..strokeJoin = StrokeJoin.round + ..style = PaintingStyle.stroke + ..blendMode = BlendMode.srcOver + ..maskFilter = const MaskFilter.blur(BlurStyle.normal, 10.0) + ..filterQuality = FilterQuality.high + ..colorFilter = const ColorFilter.mode(Color(0xFF00FF00), BlendMode.color) + ..imageFilter = ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0); + + // Make a copy, and change every field of the original. + final Paint paintCopy = Paint.from(paint); + paint + ..color = const Color(0xFF00FF00) + ..strokeWidth = 20.0 + ..strokeCap = StrokeCap.butt + ..strokeJoin = StrokeJoin.bevel + ..style = PaintingStyle.fill + ..blendMode = BlendMode.srcIn + ..maskFilter = const MaskFilter.blur(BlurStyle.solid, 20.0) + ..filterQuality = FilterQuality.none + ..colorFilter = const ColorFilter.mode(Color(0xFFFF0000), BlendMode.modulate) + ..imageFilter = ImageFilter.blur(sigmaX: 20.0, sigmaY: 20.0); + + // The copy should not have changed. + expect(paintCopy.color, equals(const Color(0xFF0000FF))); + expect(paintCopy.strokeWidth, equals(10.0)); + expect(paintCopy.strokeCap, equals(StrokeCap.round)); + expect(paintCopy.strokeJoin, equals(StrokeJoin.round)); + expect(paintCopy.style, equals(PaintingStyle.stroke)); + expect(paintCopy.blendMode, equals(BlendMode.srcOver)); + expect(paintCopy.maskFilter, equals(const MaskFilter.blur(BlurStyle.normal, 10.0))); + expect(paintCopy.filterQuality, equals(FilterQuality.high)); + expect(paintCopy.colorFilter, equals(const ColorFilter.mode(Color(0xFF00FF00), BlendMode.color))); + expect(paintCopy.imageFilter, equals(ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0))); + }); } Matcher listEquals(ByteData expected) => (dynamic v) { diff --git a/testing/fuchsia/run_tests.py b/testing/fuchsia/run_tests.py index f7155e5563cc0..f315a6887c57e 100755 --- a/testing/fuchsia/run_tests.py +++ b/testing/fuchsia/run_tests.py @@ -142,7 +142,7 @@ def _get_test_runner(runner_args: argparse.Namespace, *_) -> TestRunner: logging.basicConfig(level=logging.INFO) logging.info('Running tests in %s', OUT_DIR) sys.argv.append('--out-dir=' + OUT_DIR) - if VARIANT.endswith('_arm64'): + if VARIANT.endswith('_arm64') or VARIANT.endswith('_arm64_tester'): sys.argv.append('--product=terminal.qemu-arm64') # The 'flutter-test-type' is a place holder and has no specific meaning; the # _get_test_runner is overrided. diff --git a/testing/impeller_golden_tests_output.txt b/testing/impeller_golden_tests_output.txt index 6f301c29df503..47b290d6c968b 100644 --- a/testing/impeller_golden_tests_output.txt +++ b/testing/impeller_golden_tests_output.txt @@ -57,6 +57,11 @@ impeller_Play_AiksTest_BlendModeMultiply_Vulkan.png impeller_Play_AiksTest_BlendModeOverlay_Metal.png impeller_Play_AiksTest_BlendModeOverlay_OpenGLES.png impeller_Play_AiksTest_BlendModeOverlay_Vulkan.png +impeller_Play_AiksTest_BlendModePlusAdvanced_Metal.png +impeller_Play_AiksTest_BlendModePlusAdvanced_OpenGLES.png +impeller_Play_AiksTest_BlendModePlusAdvanced_Vulkan.png +impeller_Play_AiksTest_BlendModePlusAlphaColorFilterWideGamut_Metal.png +impeller_Play_AiksTest_BlendModePlusAlphaWideGamut_Metal.png impeller_Play_AiksTest_BlendModePlus_Metal.png impeller_Play_AiksTest_BlendModePlus_OpenGLES.png impeller_Play_AiksTest_BlendModePlus_Vulkan.png @@ -588,6 +593,9 @@ impeller_Play_AiksTest_MatrixImageFilterMagnify_Vulkan.png impeller_Play_AiksTest_MatrixSaveLayerFilter_Metal.png impeller_Play_AiksTest_MatrixSaveLayerFilter_OpenGLES.png impeller_Play_AiksTest_MatrixSaveLayerFilter_Vulkan.png +impeller_Play_AiksTest_MipmapGenerationWorksCorrectly_Metal.png +impeller_Play_AiksTest_MipmapGenerationWorksCorrectly_OpenGLES.png +impeller_Play_AiksTest_MipmapGenerationWorksCorrectly_Vulkan.png impeller_Play_AiksTest_PaintBlendModeIsRespected_Metal.png impeller_Play_AiksTest_PaintBlendModeIsRespected_OpenGLES.png impeller_Play_AiksTest_PaintBlendModeIsRespected_Vulkan.png diff --git a/testing/litetest/lib/src/matchers.dart b/testing/litetest/lib/src/matchers.dart index 5c40eacc093db..ba30a1620cf19 100644 --- a/testing/litetest/lib/src/matchers.dart +++ b/testing/litetest/lib/src/matchers.dart @@ -87,6 +87,11 @@ void throwsRangeError(dynamic d) { Expect.throwsRangeError(d as void Function()); } +/// A [Matcher] that matches functions that throw a [RangeError] when invoked. +void throwsFormatException(dynamic d) { + Expect.throwsFormatException(d as void Function()); +} + /// Gives a [Matcher] that asserts that the value being matched is a [String] /// that contains `s` as a substring. Matcher contains(String s) => (dynamic d) { diff --git a/testing/pkg_test_demo/README.md b/testing/pkg_test_demo/README.md new file mode 100644 index 0000000000000..85f548c9d52b4 --- /dev/null +++ b/testing/pkg_test_demo/README.md @@ -0,0 +1,32 @@ +# Demo of `package:test` with `DEPS`-vendored packages + +Historically, `flutter/engine` used a homegrown test framework, +[`package:litetest`](../litetest/) to avoid depending on the unwieldy set of +dependencies that `package:test` brings in. However, `package:test` is now +vendored in `DEPS` (used by the Dart SDK).' + +This demo shows that: + +- It's possible to use `package:test` with entirely local dependencies. +- The functionality of `package:test` (such as filtering, IDE integration, etc.) + is available. + +See for details. + +## Usage + +Navigate to this directory: + +```sh +cd testing/pkg_test_demo +``` + +And run the tests using `dart test`[^1]: + +```sh +dart test +``` + +[^1]: + In practice, you'll want to use the `dart` binary that is vendored in the + pre-built SDK. diff --git a/testing/pkg_test_demo/pubspec.yaml b/testing/pkg_test_demo/pubspec.yaml new file mode 100644 index 0000000000000..aca16b980b8a1 --- /dev/null +++ b/testing/pkg_test_demo/pubspec.yaml @@ -0,0 +1,117 @@ +# Copyright 2013 The Flutter Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +name: pkg_test_demo +publish_to: none + +# Do not add any dependencies that require more than what is provided in +# //third_party/dart/pkg or //third_party/dart/third_party/pkg. + +# If you do add packages here, make sure you can run `pub get --offline`, and +# check the .packages and .package_config to make sure all the paths are +# relative to this directory into //third_party/dart + +environment: + sdk: '>=3.2.0-0 <4.0.0' + +dev_dependencies: + test: + +dependency_overrides: + _fe_analyzer_shared: + path: ../../../third_party/dart/pkg/_fe_analyzer_shared + _macros: + path: ../../../third_party/dart/pkg/_macros + analyzer: + path: ../../../third_party/dart/pkg/analyzer + args: + path: ../../../third_party/dart/third_party/pkg/args + async: + path: ../../../third_party/dart/third_party/pkg/async + boolean_selector: + path: ../../../third_party/dart/third_party/pkg/boolean_selector + collection: + path: ../../../third_party/dart/third_party/pkg/collection + convert: + path: ../../../third_party/dart/third_party/pkg/convert + coverage: + path: ../../third_party/pkg/coverage + crypto: + path: ../../../third_party/dart/third_party/pkg/crypto + file: + path: ../../../third_party/dart/third_party/pkg/file/packages/file + frontend_server_client: + path: ../../../third_party/dart/third_party/pkg/webdev/frontend_server_client + glob: + path: ../../../third_party/dart/third_party/pkg/glob + http_multi_server: + path: ../../../third_party/dart/third_party/pkg/http_multi_server + http_parser: + path: ../../../third_party/dart/third_party/pkg/http_parser + io: + path: ../../third_party/pkg/io + js: + path: ../../../third_party/dart/pkg/js + logging: + path: ../../../third_party/dart/third_party/pkg/logging + macros: + path: ../../../third_party/dart/pkg/macros + matcher: + path: ../../../third_party/dart/third_party/pkg/matcher + meta: + path: ../../../third_party/dart/pkg/meta + mime: + path: ../../../third_party/dart/third_party/pkg/mime + node_preamble: + path: ../../third_party/pkg/node_preamble + package_config: + path: ../../../third_party/dart/third_party/pkg/package_config + path: + path: ../../../third_party/dart/third_party/pkg/path + pool: + path: ../../../third_party/dart/third_party/pkg/pool + pub_semver: + path: ../../../third_party/dart/third_party/pkg/pub_semver + shelf: + path: ../../../third_party/dart/third_party/pkg/shelf/pkgs/shelf + shelf_packages_handler: + path: ../../../third_party/dart/third_party/pkg/shelf/pkgs/shelf_packages_handler + shelf_static: + path: ../../../third_party/dart/third_party/pkg/shelf/pkgs/shelf_static + shelf_web_socket: + path: ../../../third_party/dart/third_party/pkg/shelf/pkgs/shelf_web_socket + source_map_stack_trace: + path: ../../../third_party/dart/third_party/pkg/source_map_stack_trace + source_maps: + path: ../../../third_party/dart/third_party/pkg/source_maps + source_span: + path: ../../../third_party/dart/third_party/pkg/source_span + stack_trace: + path: ../../../third_party/dart/third_party/pkg/stack_trace + stream_channel: + path: ../../../third_party/dart/third_party/pkg/stream_channel + string_scanner: + path: ../../../third_party/dart/third_party/pkg/string_scanner + term_glyph: + path: ../../../third_party/dart/third_party/pkg/term_glyph + test: + path: ../../../third_party/dart/third_party/pkg/test/pkgs/test + test_api: + path: ../../../third_party/dart/third_party/pkg/test/pkgs/test_api + test_core: + path: ../../../third_party/dart/third_party/pkg/test/pkgs/test_core + typed_data: + path: ../../../third_party/dart/third_party/pkg/typed_data + vm_service: + path: ../../../third_party/dart/pkg/vm_service + watcher: + path: ../../../third_party/dart/third_party/pkg/watcher + web: + path: ../../../third_party/dart/third_party/pkg/web + web_socket_channel: + path: ../../../third_party/dart/third_party/pkg/web_socket_channel + webkit_inspection_protocol: + path: ../../../third_party/dart/third_party/pkg/webkit_inspection_protocol + yaml: + path: ../../../third_party/dart/third_party/pkg/yaml diff --git a/testing/pkg_test_demo/test/example_test.dart b/testing/pkg_test_demo/test/example_test.dart new file mode 100644 index 0000000000000..01343d549eb58 --- /dev/null +++ b/testing/pkg_test_demo/test/example_test.dart @@ -0,0 +1,17 @@ +// Copyright 2013 The Flutter 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 'package:test/test.dart'; + +void main() { + test('String.split() splits the string on the delimiter', () { + const String string = 'foo,bar,baz'; + expect(string.split(','), equals(['foo', 'bar', 'baz'])); + }); + + test('String.trim() removes surrounding whitespace', () { + const String string = ' foo '; + expect(string.trim(), equals('foo')); + }); +} diff --git a/testing/run_tests.py b/testing/run_tests.py index 25af03c2eb3b1..9ec10f57e20b8 100755 --- a/testing/run_tests.py +++ b/testing/run_tests.py @@ -937,6 +937,7 @@ def build_dart_host_test_list(build_dir): ], ), (os.path.join('flutter', 'testing', 'litetest'), []), + (os.path.join('flutter', 'testing', 'pkg_test_demo'), []), (os.path.join('flutter', 'testing', 'skia_gold_client'), []), (os.path.join('flutter', 'testing', 'scenario_app'), []), ( @@ -1325,7 +1326,12 @@ def main(): run_engine_benchmarks(build_dir, engine_filter) variants_to_skip = ['host_release', 'host_profile'] - if ('engine' in types or 'font-subset' in types) and args.variant not in variants_to_skip: + + def should_skip(variant): + matches = [variant for variant in variants_to_skip if variant in args.variant] + return len(matches) > 0 + + if ('engine' in types or 'font-subset' in types) and not should_skip(args.variant): cmd = ['python3', 'test.py', '--variant', args.variant] if 'arm64' in args.variant: cmd += ['--target-cpu', 'arm64'] diff --git a/testing/scenario_app/android/README.md b/testing/scenario_app/android/README.md index c3d5e0cb7fc46..d4e4627eec5f2 100644 --- a/testing/scenario_app/android/README.md +++ b/testing/scenario_app/android/README.md @@ -355,6 +355,32 @@ device (you might find it easier trying the `stdout` of the test first, which uses rudimentary log filtering). In the case of multiple runs, the logs are prefixed with the test configuration and run attempt. +### Gradle is failing due to an "not part of the dependency lock state" error + +If you update dependencies in the `app/build.gradle` file, you may encounter an +error (probably in CI) that looks like: + +```txt +FAILED: scenario_app/reports/lint-results.xml +vpython3 ../../flutter/testing/rules/run_gradle.py /b/s/w/ir/cache/builder/src/flutter/testing/scenario_app/android lint --no-daemon -Pflutter_jar=/b/s/w/ir/cache/builder/src/out/android_emulator_skia_debug_x64/flutter.jar -Pout_dir=/b/s/w/ir/cache/builder/src/out/android_emulator_skia_debug_x64/scenario_app --project-cache-dir=/b/s/w/ir/cache/builder/src/out/android_emulator_skia_debug_x64/scenario_app/.gradle --gradle-user-home=/b/s/w/ir/cache/builder/src/out/android_emulator_skia_debug_x64/scenario_app/.gradle + +FAILURE: Build failed with an exception. + +* What went wrong: +Execution failed for task ':app:checkDebugAarMetadata'. +> Could not resolve all files for configuration ':app:debugRuntimeClasspath'. + > Resolved 'org.hamcrest:hamcrest-core:1.3' which is not part of the dependency lock state + > Resolved 'com.google.code.findbugs:jsr305:2.0.2' which is not part of the dependency lock state +``` + +This is because [`gradle.lockfile`](./app/gradle.lockfile) is out of date. To +update it, run: + +```sh +cd testing/scenario_app/android +$ENGINE_SRC/third_party/gradle/bin/gradle app:dependencies --write-locks +``` + ## Getting Help To suggest changes, or highlight problems, please [file an issue](https://github.com/flutter/flutter/issues/new?labels=e:%20scenario-app,engine,platform-android,fyi-android,team-engine). diff --git a/testing/scenario_app/android/app/build.gradle b/testing/scenario_app/android/app/build.gradle index c65cc89148e8f..821915b3e56c7 100644 --- a/testing/scenario_app/android/app/build.gradle +++ b/testing/scenario_app/android/app/build.gradle @@ -59,6 +59,7 @@ dependencies { } implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'com.google.android.material:material:1.0.0' + implementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test:rules:1.2.0' diff --git a/testing/scenario_app/android/app/gradle.lockfile b/testing/scenario_app/android/app/gradle.lockfile index e083af965d3fb..fedf60cf04806 100644 --- a/testing/scenario_app/android/app/gradle.lockfile +++ b/testing/scenario_app/android/app/gradle.lockfile @@ -2,7 +2,7 @@ # Manual edits can break the build and are not advised. # This file is expected to be part of source control. androidx.activity:activity:1.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.annotation:annotation-experimental:1.1.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.annotation:annotation-experimental:1.1.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.annotation:annotation:1.0.0=androidTestImplementationDependenciesMetadata,debugAndroidTestImplementationDependenciesMetadata androidx.annotation:annotation:1.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.appcompat:appcompat:1.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath @@ -11,6 +11,7 @@ androidx.arch.core:core-runtime:2.0.0=debugAndroidTestCompileClasspath,debugComp androidx.asynclayoutinflater:asynclayoutinflater:1.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.cardview:cardview:1.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.collection:collection:1.1.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.concurrent:concurrent-futures:1.1.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.constraintlayout:constraintlayout-solver:1.1.3=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.constraintlayout:constraintlayout:1.1.3=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.coordinatorlayout:coordinatorlayout:1.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath @@ -24,7 +25,7 @@ androidx.interpolator:interpolator:1.0.0=debugAndroidTestCompileClasspath,debugC androidx.legacy:legacy-support-core-ui:1.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.legacy:legacy-support-core-utils:1.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.lifecycle:lifecycle-common-java8:2.2.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.lifecycle:lifecycle-common:2.2.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-common:2.3.1=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.lifecycle:lifecycle-livedata-core:2.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.lifecycle:lifecycle-livedata:2.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.lifecycle:lifecycle-runtime:2.2.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath @@ -38,10 +39,15 @@ androidx.slidingpanelayout:slidingpanelayout:1.0.0=debugAndroidTestCompileClassp androidx.swiperefreshlayout:swiperefreshlayout:1.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.test.espresso:espresso-core:3.2.0=androidTestImplementationDependenciesMetadata,debugAndroidTestCompileClasspath,debugAndroidTestImplementationDependenciesMetadata,debugAndroidTestRuntimeClasspath androidx.test.espresso:espresso-idling-resource:3.2.0=androidTestImplementationDependenciesMetadata,debugAndroidTestCompileClasspath,debugAndroidTestImplementationDependenciesMetadata,debugAndroidTestRuntimeClasspath -androidx.test:monitor:1.2.0=androidTestImplementationDependenciesMetadata,debugAndroidTestCompileClasspath,debugAndroidTestImplementationDependenciesMetadata,debugAndroidTestRuntimeClasspath +androidx.test.ext:junit:1.1.5=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.test.services:storage:1.4.2=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.test:annotation:1.0.1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.test:core:1.5.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.test:monitor:1.2.0=androidTestImplementationDependenciesMetadata,debugAndroidTestImplementationDependenciesMetadata +androidx.test:monitor:1.6.1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.test:rules:1.2.0=androidTestImplementationDependenciesMetadata,debugAndroidTestCompileClasspath,debugAndroidTestImplementationDependenciesMetadata,debugAndroidTestRuntimeClasspath androidx.test:runner:1.2.0=androidTestImplementationDependenciesMetadata,debugAndroidTestCompileClasspath,debugAndroidTestImplementationDependenciesMetadata,debugAndroidTestRuntimeClasspath -androidx.tracing:tracing:1.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.tracing:tracing:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.transition:transition:1.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.vectordrawable:vectordrawable-animated:1.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.vectordrawable:vectordrawable:1.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath @@ -80,12 +86,14 @@ com.android:zipflinger:7.0.0=lintClassPath com.beust:jcommander:1.78=lintClassPath com.github.javaparser:javaparser-core:3.17.0=lintClassPath com.google.android.material:material:1.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -com.google.code.findbugs:jsr305:3.0.2=androidTestImplementationDependenciesMetadata,debugAndroidTestCompileClasspath,debugAndroidTestImplementationDependenciesMetadata,debugAndroidTestRuntimeClasspath,lintClassPath +com.google.code.findbugs:jsr305:2.0.2=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.code.findbugs:jsr305:3.0.2=androidTestImplementationDependenciesMetadata,debugAndroidTestImplementationDependenciesMetadata,lintClassPath com.google.code.gson:gson:2.8.6=lintClassPath com.google.errorprone:error_prone_annotations:2.3.4=androidTestImplementationDependenciesMetadata,debugAndroidTestCompileClasspath,debugAndroidTestImplementationDependenciesMetadata,debugAndroidTestRuntimeClasspath,lintClassPath com.google.guava:failureaccess:1.0.1=androidTestImplementationDependenciesMetadata,debugAndroidTestCompileClasspath,debugAndroidTestImplementationDependenciesMetadata,debugAndroidTestRuntimeClasspath,lintClassPath com.google.guava:guava:30.1-android=androidTestImplementationDependenciesMetadata,debugAndroidTestCompileClasspath,debugAndroidTestImplementationDependenciesMetadata,debugAndroidTestRuntimeClasspath com.google.guava:guava:30.1-jre=lintClassPath +com.google.guava:listenablefuture:1.0=debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=androidTestImplementationDependenciesMetadata,debugAndroidTestCompileClasspath,debugAndroidTestImplementationDependenciesMetadata,debugAndroidTestRuntimeClasspath,lintClassPath com.google.j2objc:j2objc-annotations:1.3=androidTestImplementationDependenciesMetadata,debugAndroidTestCompileClasspath,debugAndroidTestImplementationDependenciesMetadata,debugAndroidTestRuntimeClasspath,lintClassPath com.google.jimfs:jimfs:1.1=lintClassPath @@ -122,8 +130,9 @@ jakarta.activation:jakarta.activation-api:1.2.1=lintClassPath jakarta.xml.bind:jakarta.xml.bind-api:2.3.2=lintClassPath javax.inject:javax.inject:1=androidTestImplementationDependenciesMetadata,debugAndroidTestCompileClasspath,debugAndroidTestImplementationDependenciesMetadata,debugAndroidTestRuntimeClasspath,lintClassPath jline:jline:2.14.6=lintClassPath -junit:junit:4.12=androidTestImplementationDependenciesMetadata,debugAndroidTestCompileClasspath,debugAndroidTestImplementationDependenciesMetadata,debugAndroidTestRuntimeClasspath +junit:junit:4.12=androidTestImplementationDependenciesMetadata,debugAndroidTestImplementationDependenciesMetadata junit:junit:4.13.1=lintClassPath +junit:junit:4.13.2=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath net.java.dev.jna:jna-platform:5.6.0=lintClassPath net.java.dev.jna:jna:5.6.0=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath,lintClassPath net.sf.jopt-simple:jopt-simple:4.9=lintClassPath @@ -165,7 +174,7 @@ org.codehaus.groovy:groovy-xml:3.0.7=lintClassPath org.codehaus.groovy:groovy:3.0.7=lintClassPath org.glassfish.jaxb:jaxb-runtime:2.3.2=lintClassPath org.glassfish.jaxb:txw2:2.3.2=lintClassPath -org.hamcrest:hamcrest-core:1.3=androidTestImplementationDependenciesMetadata,debugAndroidTestCompileClasspath,debugAndroidTestImplementationDependenciesMetadata,debugAndroidTestRuntimeClasspath,lintClassPath +org.hamcrest:hamcrest-core:1.3=androidTestImplementationDependenciesMetadata,debugAndroidTestCompileClasspath,debugAndroidTestImplementationDependenciesMetadata,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,lintClassPath,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.hamcrest:hamcrest-integration:1.3=androidTestImplementationDependenciesMetadata,debugAndroidTestCompileClasspath,debugAndroidTestImplementationDependenciesMetadata,debugAndroidTestRuntimeClasspath org.hamcrest:hamcrest-library:1.3=androidTestImplementationDependenciesMetadata,debugAndroidTestCompileClasspath,debugAndroidTestImplementationDependenciesMetadata,debugAndroidTestRuntimeClasspath org.jacoco:org.jacoco.agent:0.8.8=androidJacocoAnt @@ -181,14 +190,16 @@ org.jetbrains.kotlin:kotlin-reflect:1.6.10=kotlinCompilerClasspath,kotlinKlibCom org.jetbrains.kotlin:kotlin-script-runtime:1.6.10=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath org.jetbrains.kotlin:kotlin-stdlib-common:1.4.21=androidTestImplementationDependenciesMetadata,debugAndroidTestImplementationDependenciesMetadata org.jetbrains.kotlin:kotlin-stdlib-common:1.4.32=lintClassPath -org.jetbrains.kotlin:kotlin-stdlib-common:1.6.10=apiDependenciesMetadata,debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-common:1.6.10=apiDependenciesMetadata,debugApiDependenciesMetadata,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath,releaseApiDependenciesMetadata +org.jetbrains.kotlin:kotlin-stdlib-common:1.7.10=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.4.32=lintClassPath org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10=apiDependenciesMetadata,debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.32=lintClassPath org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.6.10=apiDependenciesMetadata,debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jetbrains.kotlin:kotlin-stdlib:1.4.21=androidTestImplementationDependenciesMetadata,debugAndroidTestImplementationDependenciesMetadata org.jetbrains.kotlin:kotlin-stdlib:1.4.32=lintClassPath -org.jetbrains.kotlin:kotlin-stdlib:1.6.10=apiDependenciesMetadata,debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib:1.6.10=apiDependenciesMetadata,debugApiDependenciesMetadata,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath,releaseApiDependenciesMetadata +org.jetbrains.kotlin:kotlin-stdlib:1.7.10=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.2=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath diff --git a/testing/scenario_app/android/app/src/androidTest/java/dev/flutter/scenariosui/ExternalTextureTests.java b/testing/scenario_app/android/app/src/androidTest/java/dev/flutter/scenariosui/ExternalTextureTests.java index dd02b4956edcb..38383810ae96c 100644 --- a/testing/scenario_app/android/app/src/androidTest/java/dev/flutter/scenariosui/ExternalTextureTests.java +++ b/testing/scenario_app/android/app/src/androidTest/java/dev/flutter/scenariosui/ExternalTextureTests.java @@ -4,15 +4,11 @@ package dev.flutter.scenariosui; -import static io.flutter.Build.API_LEVELS; - import android.content.Intent; -import android.graphics.Rect; import androidx.annotation.NonNull; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.LargeTest; -import androidx.test.filters.SdkSuppress; import androidx.test.rule.ActivityTestRule; -import androidx.test.runner.AndroidJUnit4; import dev.flutter.scenarios.ExternalTextureFlutterActivity; import org.junit.Before; import org.junit.Rule; @@ -22,10 +18,7 @@ @RunWith(AndroidJUnit4.class) @LargeTest public class ExternalTextureTests { - private static final int SURFACE_WIDTH = 192; - private static final int SURFACE_HEIGHT = 256; - - Intent intent; + private Intent intent; @Rule @NonNull public ActivityTestRule activityRule = @@ -54,66 +47,4 @@ public void testMediaSurface() throws Exception { ScreenshotUtil.capture( activityRule.launchActivity(intent), "ExternalTextureTests_testMediaSurface"); } - - @Test - public void testRotatedMediaSurface_90() throws Exception { - intent.putExtra("scenario_name", "display_texture"); - intent.putExtra("surface_renderer", "media"); - intent.putExtra("rotation", 90); - ScreenshotUtil.capture( - activityRule.launchActivity(intent), "ExternalTextureTests_testRotatedMediaSurface_90"); - } - - @Test - public void testRotatedMediaSurface_180() throws Exception { - intent.putExtra("scenario_name", "display_texture"); - intent.putExtra("surface_renderer", "media"); - intent.putExtra("rotation", 180); - ScreenshotUtil.capture( - activityRule.launchActivity(intent), "ExternalTextureTests_testRotatedMediaSurface_180"); - } - - @Test - public void testRotatedMediaSurface_270() throws Exception { - intent.putExtra("scenario_name", "display_texture"); - intent.putExtra("surface_renderer", "media"); - intent.putExtra("rotation", 270); - ScreenshotUtil.capture( - activityRule.launchActivity(intent), "ExternalTextureTests_testRotatedMediaSurface_270"); - } - - @Test - @SdkSuppress(minSdkVersion = API_LEVELS.API_23) - public void testCroppedMediaSurface_bottomLeft() throws Exception { - intent.putExtra("scenario_name", "display_texture"); - intent.putExtra("surface_renderer", "image"); - intent.putExtra("crop", new Rect(0, 0, SURFACE_WIDTH / 2, SURFACE_HEIGHT / 2)); - ScreenshotUtil.capture( - activityRule.launchActivity(intent), - "ExternalTextureTests_testCroppedMediaSurface_bottomLeft"); - } - - @Test - @SdkSuppress(minSdkVersion = API_LEVELS.API_23) - public void testCroppedMediaSurface_topRight() throws Exception { - intent.putExtra("scenario_name", "display_texture"); - intent.putExtra("surface_renderer", "image"); - intent.putExtra( - "crop", new Rect(SURFACE_WIDTH / 2, SURFACE_HEIGHT / 2, SURFACE_WIDTH, SURFACE_HEIGHT)); - ScreenshotUtil.capture( - activityRule.launchActivity(intent), - "ExternalTextureTests_testCroppedMediaSurface_topRight"); - } - - @Test - @SdkSuppress(minSdkVersion = API_LEVELS.API_23) - public void testCroppedRotatedMediaSurface_bottomLeft_90() throws Exception { - intent.putExtra("scenario_name", "display_texture"); - intent.putExtra("surface_renderer", "image"); - intent.putExtra("crop", new Rect(0, 0, SURFACE_WIDTH / 2, SURFACE_HEIGHT / 2)); - intent.putExtra("rotation", 90); - ScreenshotUtil.capture( - activityRule.launchActivity(intent), - "ExternalTextureTests_testCroppedRotatedMediaSurface_bottomLeft_90"); - } } diff --git a/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/ExternalTextureFlutterActivity.java b/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/ExternalTextureFlutterActivity.java index 9b42bff82a3d6..e363680777b9d 100644 --- a/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/ExternalTextureFlutterActivity.java +++ b/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/ExternalTextureFlutterActivity.java @@ -11,7 +11,6 @@ import android.graphics.ImageFormat; import android.graphics.LinearGradient; import android.graphics.Paint; -import android.graphics.Rect; import android.graphics.Shader.TileMode; import android.hardware.HardwareBuffer; import android.media.Image; @@ -62,7 +61,7 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { String surfaceRenderer = getIntent().getStringExtra("surface_renderer"); assert surfaceRenderer != null; - flutterRenderer = selectSurfaceRenderer(surfaceRenderer, getIntent().getExtras()); + flutterRenderer = selectSurfaceRenderer(surfaceRenderer); // Create and place a SurfaceView above the Flutter content. SurfaceView surfaceView = new SurfaceView(getContext()); @@ -92,25 +91,26 @@ public void waitUntilFlutterRendered() { super.waitUntilFlutterRendered(); try { + // TODO: Remove after debugging https://github.com/flutter/flutter/issues/145988. + io.flutter.Log.i("Scenarios", "waitUntilFlutterRendered() | firstFrameLatch"); firstFrameLatch.await(); } catch (InterruptedException e) { throw new RuntimeException(e); } } - private SurfaceRenderer selectSurfaceRenderer(String surfaceRenderer, Bundle extras) { + private SurfaceRenderer selectSurfaceRenderer(String surfaceRenderer) { switch (surfaceRenderer) { case "image": if (VERSION.SDK_INT >= API_LEVELS.API_23) { // CanvasSurfaceRenderer doesn't work correctly when used with ImageSurfaceRenderer. // Use MediaSurfaceRenderer for now. - return new ImageSurfaceRenderer( - selectSurfaceRenderer("media", extras), extras.getParcelable("crop")); + return new ImageSurfaceRenderer(selectSurfaceRenderer("media")); } else { throw new RuntimeException("ImageSurfaceRenderer not supported"); } case "media": - return new MediaSurfaceRenderer(this::createMediaExtractor, extras.getInt("rotation", 0)); + return new MediaSurfaceRenderer(this::createMediaExtractor); case "canvas": default: return new CanvasSurfaceRenderer(); @@ -220,7 +220,6 @@ public void destroy() {} /** Decodes a sample video into the attached Surface. */ private static class MediaSurfaceRenderer implements SurfaceRenderer { private final Supplier extractorSupplier; - private final int rotation; private CountDownLatch onFirstFrame; private Surface surface; @@ -228,9 +227,8 @@ private static class MediaSurfaceRenderer implements SurfaceRenderer { private MediaFormat format; private Thread decodeThread; - protected MediaSurfaceRenderer(Supplier extractorSupplier, int rotation) { + protected MediaSurfaceRenderer(Supplier extractorSupplier) { this.extractorSupplier = extractorSupplier; - this.rotation = rotation; } @Override @@ -241,10 +239,6 @@ public void attach(Surface surface, CountDownLatch onFirstFrame) { extractor = extractorSupplier.get(); format = extractor.getTrackFormat(0); - // NOTE: MediaFormat.KEY_ROTATION was not available until 23+, but the key is still handled on - // API 21+. - format.setInteger("rotation-degrees", rotation); - decodeThread = new Thread(this::decodeThreadMain); decodeThread.start(); } @@ -338,8 +332,6 @@ public void destroy() { @RequiresApi(API_LEVELS.API_23) private static class ImageSurfaceRenderer implements SurfaceRenderer { private final SurfaceRenderer inner; - private final Rect crop; - private CountDownLatch onFirstFrame; private ImageReader reader; private ImageWriter writer; @@ -350,9 +342,8 @@ private static class ImageSurfaceRenderer implements SurfaceRenderer { private boolean canReadImage = true; private boolean canWriteImage = true; - protected ImageSurfaceRenderer(SurfaceRenderer inner, Rect crop) { + protected ImageSurfaceRenderer(SurfaceRenderer inner) { this.inner = inner; - this.crop = crop; } @Override @@ -399,7 +390,6 @@ private void onImageAvailable(ImageReader reader) { canReadImage = false; Image image = reader.acquireLatestImage(); - image.setCropRect(crop); try { canWriteImage = false; writer.queueInputImage(image); diff --git a/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/TestableFlutterActivity.java b/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/TestableFlutterActivity.java index 69c9dd0260353..bb163be4defc9 100644 --- a/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/TestableFlutterActivity.java +++ b/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/TestableFlutterActivity.java @@ -8,6 +8,7 @@ import android.view.WindowManager; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import io.flutter.Log; import io.flutter.embedding.android.FlutterActivity; import io.flutter.embedding.engine.FlutterEngine; import java.util.concurrent.CountDownLatch; @@ -44,6 +45,8 @@ protected void notifyFlutterRendered() { public void waitUntilFlutterRendered() { try { + // TODO: Remove after debugging https://github.com/flutter/flutter/issues/145988. + Log.i("Scenarios", "waitUntilFlutterRendered() | flutterUiRenderedLatch"); flutterUiRenderedLatch.await(); } catch (InterruptedException e) { throw new RuntimeException(e); diff --git a/testing/scenario_app/android/expected_golden_output.txt b/testing/scenario_app/android/expected_golden_output.txt index da412e233e0ce..2d4b3ad7b451f 100644 --- a/testing/scenario_app/android/expected_golden_output.txt +++ b/testing/scenario_app/android/expected_golden_output.txt @@ -1,12 +1,6 @@ DrawSolidBlueScreenTest.png ExternalTextureTests_testCanvasSurface.png -ExternalTextureTests_testCroppedMediaSurface_bottomLeft.png -ExternalTextureTests_testCroppedMediaSurface_topRight.png -ExternalTextureTests_testCroppedRotatedMediaSurface_bottomLeft_90.png ExternalTextureTests_testMediaSurface.png -ExternalTextureTests_testRotatedMediaSurface_180.png -ExternalTextureTests_testRotatedMediaSurface_270.png -ExternalTextureTests_testRotatedMediaSurface_90.png PlatformTextureUiTests_testPlatformView.png PlatformTextureUiTests_testPlatformViewClippath.png PlatformTextureUiTests_testPlatformViewCliprect.png @@ -60,4 +54,4 @@ PlatformViewWithTextureViewUiTest_testPlatformViewTwoIntersectingOverlays.png PlatformViewWithTextureViewUiTest_testPlatformViewWithoutOverlayIntersection.png SpawnEngineTests_testSpawnedEngine.png logcat.txt -noop.txt \ No newline at end of file +noop.txt diff --git a/testing/scenario_app/bin/run_android_tests.dart b/testing/scenario_app/bin/run_android_tests.dart index 669a255440f06..c0b8ffaf4b0cc 100644 --- a/testing/scenario_app/bin/run_android_tests.dart +++ b/testing/scenario_app/bin/run_android_tests.dart @@ -61,12 +61,23 @@ void main(List args) async { // Capture CTRL-C. late final StreamSubscription onSigint; + + // Capture requested termination. The goal is to catch timeouts. + late final StreamSubscription onSigterm; + void cancelSignalHandlers() { + onSigint.cancel(); + onSigterm.cancel(); + } runZonedGuarded( () async { onSigint = ProcessSignal.sigint.watch().listen((_) { - onSigint.cancel(); + cancelSignalHandlers(); panic(['Received SIGINT']); }); + onSigterm = ProcessSignal.sigterm.watch().listen((_) { + cancelSignalHandlers(); + panic(['Received SIGTERM']); + }); await _run( verbose: options.verbose, outDir: Directory(options.outDir), diff --git a/testing/scenario_app/lib/main.dart b/testing/scenario_app/lib/main.dart index 5e76f1d7306bf..080959becce46 100644 --- a/testing/scenario_app/lib/main.dart +++ b/testing/scenario_app/lib/main.dart @@ -92,10 +92,16 @@ void _onBeginFrame(Duration duration) { return; } currentScenario!.onBeginFrame(duration); + + // TODO(team): Remove after debugging https://github.com/flutter/flutter/issues/145988. + print('onBeginFrame: $duration'); } void _onDrawFrame() { currentScenario?.onDrawFrame(); + + // TODO(team): Remove after debugging https://github.com/flutter/flutter/issues/145988. + print('onDrawFrame'); } void _onMetricsChanged() { diff --git a/testing/scenario_app/tool/deflake_android_tests.sh b/testing/scenario_app/tool/deflake_android_tests.sh new file mode 100755 index 0000000000000..7940e00ff5dc7 --- /dev/null +++ b/testing/scenario_app/tool/deflake_android_tests.sh @@ -0,0 +1,73 @@ +#!/bin/bash + +# Copyright 2013 The Flutter Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +set -e + +# Needed because if it is set, cd may print the path it changed to. +unset CDPATH + +# On Mac OS, readlink -f doesn't work, so follow_links traverses the path one +# link at a time, and then cds into the link destination and find out where it +# ends up. +# +# The function is enclosed in a subshell to avoid changing the working directory +# of the caller. +function follow_links() ( + cd -P "$(dirname -- "$1")" + file="$PWD/$(basename -- "$1")" + while [[ -L "$file" ]]; do + cd -P "$(dirname -- "$file")" + file="$(readlink -- "$file")" + cd -P "$(dirname -- "$file")" + file="$PWD/$(basename -- "$file")" + done + echo "$file" +) + +SCRIPT_DIR=$(follow_links "$(dirname -- "${BASH_SOURCE[0]}")") +ENGINE_DIR="$(cd "$SCRIPT_DIR/../../.."; pwd -P)" + +# Find the Dart executable. +case "$(uname -s)" in + Linux) + OS="linux" + ;; + Darwin) + OS="macos" + ;; + *) + echo "The host platform is not supported by this tool" + exit 1 +esac + +case "$(uname -m)" in + arm64) + CPU="arm64" + ;; + x86_64) + CPU="x64" + ;; + *) + echo "The host platform is not supported by this tool" + exit 1 +esac + +PLATFORM="${OS}-${CPU}" +DART_SDK_DIR="${ENGINE_DIR}/prebuilts/${PLATFORM}/dart-sdk" +DART="${DART_SDK_DIR}/bin/dart" + +# Run the tool indefinitely until there is an error. +COUNT=0 +while true; do + COUNT=$((COUNT + 1)) + echo "Running test iteration $COUNT" + "$DART" "$SCRIPT_DIR/bin/run_android_tests.dart" "$@" + # Break if non-zero exit code. + if [ $? -ne 0 ]; then + echo "Error running tests. Exiting." + break + fi +done diff --git a/testing/skia_gold_client/README.md b/testing/skia_gold_client/README.md index 8c4c68e19985c..e63486b13b038 100644 --- a/testing/skia_gold_client/README.md +++ b/testing/skia_gold_client/README.md @@ -1,70 +1,111 @@ -# skia_gold_client +# Skia Gold Client -This package allows to create a Skia gold client in the engine repo. +This package interacts with [Skia Gold][] for uploading and comparing +screenshots. -The client uses the `goldctl` tool on LUCI builders to upload screenshots, -and verify if a new screenshot matches the baseline screenshot. +[skia gold]: https://skia.org/docs/dev/testing/skiagold/ -The web UI is available on https://flutter-engine-gold.skia.org/. +The web UI for the engine is located at . -## Using the client +## Usage -1. In `.ci.yaml`, ensure that the task has a dependency on `goldctl`: +In the simplest case, import the package and establish a working directory: -```yaml -dependencies: [{ "dependency": "goldctl" }] -``` +```dart +import 'dart:io' as io; -2. In the builder `.json` file, ensure the drone has a dependency on `goldctl`: +import 'package:skia_gold_client/skia_gold_client.dart'; -```yaml - "dependencies": [ - { - "dependency": "goldctl", - "version": "git_revision:" - } - ], +void main() async { + // Create a temporary working directory. + final io.Directory tmpDirectory = io.Directory.systemTemp.createTempSync('skia_gold_wd'); + try { + final SkiaGoldClient client = SkiaGoldClient(tmpDirectory); + await client.auth(); + // ... + } finally { + tmpDirectory.deleteSync(recursive: true); + } +} ``` -3. Add dependency in `pubspec.yaml`: +Once you have an authorized instance, use `addImg` to upload a screenshot: -```yaml -dependencies: - # needed for skia_gold_client to avoid a cache miss. - engine_repo_tools: - path: /tools/pkg/engine_repo_tools - skia_gold_client: - path: /testing/skia_gold_client +```dart +await client.addImg( + 'my-screenshot', + io.File('path/to/screenshot.png'), + screenshotSize: 400, // i.e. a 20x20 image +); ``` -4. Use the client: +## Configuring CI -```dart -import 'package:skia_gold_client/skia_gold_client.dart'; +Currently[^1], the client is only available on Flutter Engine's CI platform, and +will fail to authenticate if run elsewhere. -Future main() { - final Directory tmpDirectory = Directory.current.createTempSync('skia_gold_wd'); - final SkiaGoldClient client = SkiaGoldClient( - tmpDirectory, - dimensions: {'': ''}, - ); - - try { - if (SkiaGoldClient.isAvailable()) { - await client.auth(); - - await client.addImg( - '', - File('gold-file.png'), - screenshotSize: 1024, - ); - } - } catch (error) { - // Failed to authenticate or compare pixels. - stderr.write(error.toString()); - rethrow; - } finally { - tmpDirectory.deleteSync(recursive: true); - } -} -``` +To use the client in CI, you'll need to make two changes: + +[^1]: + The `flutter/flutter` repository has a workaround which downloads digests + and does basic local image comparison, but because we have forked the + client and not kept it up-to-date, we cannot use that workaround. Send + a PR or file an issue if you'd like to see this fixed! + +1. **Add a dependency on `goldctl`** + + In your task's configuration in [`.ci.yaml`](../../.ci.yaml) file, add a + dependency on `goldctl`: + + ```diff + # This is just an example. + targets: + - name: Linux linux_android_emulator_tests + properties: + config_name: linux_android_emulator + + dependencies: >- + + [ + + {"dependency": "goldctl", "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd"} + + ] + ``` + +2. **Ensure the builder (i.e. `config_name: {name}`) also has a dependency** + + For example, for `linux_android_emulator`, modify + [`ci/builders/linux_android_emulator.json`](../../ci/builders/linux_android_emulator.json): + + ```json + "dependencies": [ + { + "dependency": "goldctl", + "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd" + } + ] + ``` + +## Release Testing + +> [!NOTE] +> This workflow is a work in progress. Contact @matanlurey for more information. + +When we create a release branch (i.e. for a beta or stable release), all +golden-file tests will have to be regenerated for the new release. This is +because it's possible that the rendering of the engine has changed in a way +that affects the golden files (either due to a bug, or intentionally) as we +apply cherry-picks and other changes to the release branch. + +Fortunately this process is easy and mostly automated. Here's how it works: + +1. Create your release branch, e.g. `flutter-3.21-candidate.1`. +1. Edit [`.engine-release.verison`](../../.engine-release.version) to the new + release version (e.g. `3.21`). +1. Run all the tests, generating new golden files. +1. Bulk triage all of the images as positive using the web UI. + + ![Screenshot](https://github.com/flutter/flutter/assets/168174/a327ffc0-95b3-4d3a-9d36-052e0607a1e5) + +All of the tests will have a unique `_Release_{major}}_{minor}` suffix, so you +can easily filter them in the web UI and they can diverge from the `main` branch +as needed. As cherry-picks are applied to the release branch, the tests should +continue to pass, and the golden files should either _not_ change, or change in +a way that is expected (i.e. fixing a bug). diff --git a/testing/skia_gold_client/lib/skia_gold_client.dart b/testing/skia_gold_client/lib/skia_gold_client.dart index e9d28ff504286..9d14d0116f074 100644 --- a/testing/skia_gold_client/lib/skia_gold_client.dart +++ b/testing/skia_gold_client/lib/skia_gold_client.dart @@ -12,6 +12,7 @@ import 'package:path/path.dart' as path; import 'package:process/process.dart'; import 'src/errors.dart'; +import 'src/release_version.dart'; export 'src/errors.dart' show SkiaGoldProcessError; @@ -22,14 +23,57 @@ const String _kLuciEnvName = 'LUCI_CONTEXT'; const String _skiaGoldHost = 'https://flutter-engine-gold.skia.org'; const String _instance = 'flutter-engine'; -/// A client for uploading image tests and making baseline requests to the -/// Flutter Gold Dashboard. +/// Uploads images and makes baseline requests to Skia Gold. +/// +/// For an example of how to use this class, see `tool/e2e_test.dart`. interface class SkiaGoldClient { /// Creates a [SkiaGoldClient] with the given [workDirectory]. /// - /// [dimensions] allows to add attributes about the environment - /// used to generate the screenshots. - SkiaGoldClient( + /// A set of [dimensions] can be provided to add attributes about the + /// environment used to generate the screenshots, which are treated as keys + /// for the image: + /// + /// ```dart + /// final SkiaGoldClient skiaGoldClient = SkiaGoldClient( + /// someDir, + /// dimensions: { + /// 'platform': 'linux', + /// }, + /// ); + /// ``` + /// + /// The [verbose] flag is intended for use in debugging CI issues, and + /// produces more detailed output that some may find useful, but would be too + /// spammy for regular use. + factory SkiaGoldClient( + io.Directory workDirectory, { + Map? dimensions, + bool verbose = false, + }) { + return SkiaGoldClient.forTesting( + workDirectory, + dimensions: dimensions, + verbose: verbose, + ); + } + + /// Creates a [SkiaGoldClient] for testing. + /// + /// Similar to the default constructor, but allows for dependency injection + /// for testing purposes: + /// + /// - [httpClient] makes requests to Skia Gold to fetch expectations. + /// - [processManager] launches sub-processes. + /// - [stderr] is where output is written for diagnostics. + /// - [environment] is the environment variables for the currently running + /// process, and is used to determine if Skia Gold is available, and whether + /// the current environment is CI, and if so, if it's pre-submit or + /// post-submit. + /// - [engineRoot] is the root of the engine repository, which is used for + /// finding the current commit hash, as well as the location of the + /// `.engine-release.version` file. + @visibleForTesting + SkiaGoldClient.forTesting( this.workDirectory, { this.dimensions, this.verbose = false, @@ -37,10 +81,31 @@ interface class SkiaGoldClient { ProcessManager? processManager, StringSink? stderr, Map? environment, + Engine? engineRoot, }) : httpClient = httpClient ?? io.HttpClient(), process = processManager ?? const LocalProcessManager(), _stderr = stderr ?? io.stderr, - _environment = environment ?? io.Platform.environment; + _environment = environment ?? io.Platform.environment, + _engineRoot = engineRoot ?? Engine.findWithin() { + // Lookup the release version from the engine repository. + final io.File releaseVersionFile = io.File(path.join( + _engineRoot.flutterDir.path, + '.engine-release.version', + )); + + // If the file is not found or cannot be read, we are in an invalid state. + try { + _releaseVersion = ReleaseVersion.parse(releaseVersionFile.readAsStringSync()); + } on FormatException catch (error) { + throw StateError('Failed to parse release version file: $error.'); + } on io.FileSystemException catch (error) { + throw StateError('Failed to read release version file: $error.'); + } + } + + /// The root of the engine repository. + final Engine _engineRoot; + ReleaseVersion? _releaseVersion; /// Whether the client is available and can be used in this environment. static bool isAvailable({ @@ -244,6 +309,14 @@ interface class SkiaGoldClient { /// determined by the [pixelColorDelta] parameter. It's in the range [0.0, /// 1.0] and defaults to 0.01. A value of 0.01 means that 1% of the pixels are /// allowed to be different. + /// + /// ## Release Testing + /// + /// In release branches, we add a unique test suffix to the test name. For + /// example "testName" -> "testName_Release_3_21", based on the version in the + /// `.engine-release.version` file at the root of the engine repository. + /// + /// See <../README.md#release-testing> for more information. Future addImg( String testName, io.File goldenFile, { @@ -252,6 +325,17 @@ interface class SkiaGoldClient { required int screenshotSize, }) async { assert(_isPresubmit || _isPostsubmit); + + // Clean the test name to remove the file extension. + testName = path.basenameWithoutExtension(testName); + + // In release branches, we add a unique test suffix to the test name. + // For example "testName" -> "testName_Release_3_21". + // See ../README.md#release-testing for more information. + if (_releaseVersion case final ReleaseVersion v) { + testName = '${testName}_Release_${v.major}_${v.minor}'; + } + if (_isPresubmit) { await _tryjobAdd(testName, goldenFile, screenshotSize, pixelColorDelta, differentPixelsRate); } @@ -287,7 +371,7 @@ interface class SkiaGoldClient { '--work-dir', _tempPath, '--test-name', - _cleanTestName(testName), + testName, '--png-file', goldenFile.path, // Otherwise post submit will not fail. @@ -391,7 +475,7 @@ interface class SkiaGoldClient { '--work-dir', _tempPath, '--test-name', - _cleanTestName(testName), + testName, '--png-file', goldenFile.path, ..._getMatchingArguments(testName, screenshotSize, pixelDeltaThreshold, differentPixelsRate), @@ -489,7 +573,7 @@ interface class SkiaGoldClient { /// Returns the current commit hash of the engine repository. Future _getCurrentCommit() async { - final String engineCheckout = Engine.findWithin().flutterDir.path; + final String engineCheckout = _engineRoot.flutterDir.path; final io.ProcessResult revParse = await process.run( ['git', 'rev-parse', 'HEAD'], workingDirectory: engineCheckout, @@ -521,12 +605,6 @@ interface class SkiaGoldClient { return json.encode(_getKeys()); } - /// Removes the file extension from the [fileName] to represent the test name - /// properly. - static String _cleanTestName(String fileName) { - return path.basenameWithoutExtension(fileName); - } - /// Returns a list of arguments for initializing a tryjob based on the testing /// environment. List _getCIArguments() { diff --git a/testing/skia_gold_client/lib/src/errors.dart b/testing/skia_gold_client/lib/src/errors.dart index d5590091bf46d..830555229d35b 100644 --- a/testing/skia_gold_client/lib/src/errors.dart +++ b/testing/skia_gold_client/lib/src/errors.dart @@ -1,3 +1,7 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + /// Skia Gold errors thrown by intepreting process exits and [stdout]/[stderr]. final class SkiaGoldProcessError extends Error { /// Creates a new [SkiaGoldProcessError] from the provided origin. diff --git a/testing/skia_gold_client/lib/src/release_version.dart b/testing/skia_gold_client/lib/src/release_version.dart new file mode 100644 index 0000000000000..a62953e349c60 --- /dev/null +++ b/testing/skia_gold_client/lib/src/release_version.dart @@ -0,0 +1,130 @@ +// Copyright 2013 The Flutter 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 'dart:convert'; + +import 'package:meta/meta.dart'; + +/// Signals that the output of golden files may diverge from `main`. +/// +/// By default, Skia Gold will compare the output of a test to the golden file +/// in the `main` branch. During a release branch, the output of the test may +/// diverge (intentionally or accidentally, i.e. a bug) from the output in +/// `main`. +/// +/// This class signals to Skia Gold to use a unique test name for the release. +@immutable +final class ReleaseVersion { + /// Creates a [ReleaseVersion] with the given [major] and [minor] version. + /// + /// For example, for the `3.21` release: + /// ```dart + /// ReleaseVersion( + /// major: 3, + /// minor: 21, + /// ) + /// ``` + /// + /// Each number must be non-negative. + ReleaseVersion({ + required this.major, + required this.minor, + }) { + RangeError.checkNotNegative(major, 'major'); + RangeError.checkNotNegative(minor, 'minor'); + } + + /// Parses a [ReleaseVersion] from the contents of a `release.version` file. + /// + /// Returns `null` if and only if the version is parsed as the string `none`. + /// + /// The format of the file is plaintext, and any lines that are empty + /// (newline characters only) or start with `#` are ignored. The first line + /// that is not ignored must be in the format `major.minor`, where `major` and + /// `minor` are non-negative integers. + /// + /// For example, the following file contents: + /// ```txt + /// # This is a comment + /// + /// 3.21 + /// ``` + /// + /// ... would parse to `ReleaseVersion(major: 3, minior: 21)`. + /// + /// Throws a [FormatException] if the file contents are not in the expected + /// format (either empty/comments only, or missing a `major.minor` line in + /// the format described in [ReleaseVersion.new]). + static ReleaseVersion? parse(String fileContents) { + bool parsedNone = false; + ReleaseVersion? parsed; + for (final String line in LineSplitter.split(fileContents)) { + final String trimmed = line.trim(); + if (trimmed.isEmpty || trimmed.startsWith('#')) { + continue; + } + if (trimmed == 'none') { + parsedNone = true; + continue; + } + final List parts = trimmed.split('.'); + if (parts.length != 2) { + throw FormatException( + 'Expected "major.minor" format, got "$trimmed"', + fileContents, + ); + } + final int? major = int.tryParse(parts[0]); + final int? minor = int.tryParse(parts[1]); + if (major == null || minor == null) { + throw FormatException( + 'Expected "major.minor", each as an integer, got "$trimmed"', + fileContents, + ); + } + if (parsed != null) { + throw FormatException( + 'Multiple "major.minor" versions found', + fileContents, + ); + } + parsed = ReleaseVersion( + major: major, + minor: minor, + ); + } + if (parsed != null && parsedNone) { + throw FormatException( + 'Both "none" and a "major.minor" version found', + fileContents, + ); + } + if (parsed != null) { + return parsed; + } + if (parsedNone) { + return null; + } + throw FormatException('No "major.minor" version found', fileContents); + } + + /// The major version number. + final int major; + + /// The minor version number. + final int minor; + + @override + bool operator ==(Object other) { + return other is ReleaseVersion && + other.major == major && + other.minor == minor; + } + + @override + int get hashCode => Object.hash(major, minor); + + @override + String toString() => '$major.$minor'; +} diff --git a/testing/skia_gold_client/test/release_version_test.dart b/testing/skia_gold_client/test/release_version_test.dart new file mode 100644 index 0000000000000..72f7e73a31f53 --- /dev/null +++ b/testing/skia_gold_client/test/release_version_test.dart @@ -0,0 +1,68 @@ +// Copyright 2013 The Flutter 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 'package:litetest/litetest.dart'; +import 'package:skia_gold_client/src/release_version.dart'; + +void main() { + test('should accept a major and minor version', () { + final ReleaseVersion version = ReleaseVersion(major: 3, minor: 21); + expect(version.major, equals(3)); + expect(version.minor, equals(21)); + }); + + test('should accept the string "none"', () { + final ReleaseVersion? version = ReleaseVersion.parse('none'); + expect(version, isNull); + }); + + test('should fail on a negative major version', () { + expect(() => ReleaseVersion(major: -1, minor: 0), throwsRangeError); + }); + + test('should fail on a negative minor version', () { + expect(() => ReleaseVersion(major: 0, minor: -1), throwsRangeError); + }); + + test('should parse a release version from a file', () { + final ReleaseVersion version = ReleaseVersion.parse('3.21')!; + expect(version.major, equals(3)); + expect(version.minor, equals(21)); + }); + + test('should ignore comments and empty lines', () { + final ReleaseVersion version = ReleaseVersion.parse([ + '# This is a comment', + '', + '3.21', + '', + ].join('\n'))!; + expect(version.major, equals(3)); + expect(version.minor, equals(21)); + }); + + test('should fail on a missing version line', () { + expect(() => ReleaseVersion.parse(''), throwsFormatException); + }); + + test('should fail on a malformed version line', () { + expect(() => ReleaseVersion.parse('3.21.0'), throwsFormatException); + }); + + test('should fail on a non-integer major version', () { + expect(() => ReleaseVersion.parse('a.21'), throwsFormatException); + }); + + test('should fail on a non-integer minor version', () { + expect(() => ReleaseVersion.parse('3.b'), throwsFormatException); + }); + + test('should fail on multiple version lines', () { + expect(() => ReleaseVersion.parse('3.21\n3.22'), throwsFormatException); + }); + + test('should fail if both "none" and a version are present', () { + expect(() => ReleaseVersion.parse('none\n3.21'), throwsFormatException); + }); +} diff --git a/testing/skia_gold_client/test/skia_gold_client_test.dart b/testing/skia_gold_client/test/skia_gold_client_test.dart index 1f91d8a35231a..b3265866da3da 100644 --- a/testing/skia_gold_client/test/skia_gold_client_test.dart +++ b/testing/skia_gold_client/test/skia_gold_client_test.dart @@ -7,10 +7,12 @@ import 'dart:convert'; import 'dart:io' as io; import 'dart:typed_data'; +import 'package:engine_repo_tools/engine_repo_tools.dart'; import 'package:litetest/litetest.dart'; import 'package:path/path.dart' as p; import 'package:process_fakes/process_fakes.dart'; import 'package:skia_gold_client/skia_gold_client.dart'; +import 'package:skia_gold_client/src/release_version.dart'; void main() { /// A mock commit hash that is used to simulate a successful git call. @@ -44,13 +46,15 @@ void main() { SkiaGoldClient createClient( _TestFixture fixture, { required Map environment, + ReleaseVersion? engineVersion, Map? dimensions, bool verbose = false, io.ProcessResult Function(List command) onRun = _runUnhandled, }) { - return SkiaGoldClient( + return SkiaGoldClient.forTesting( fixture.workDirectory, dimensions: dimensions, + engineRoot: Engine.fromSrcPath(fixture.engineSrcDir.path), httpClient: fixture.httpClient, processManager: FakeProcessManager( onRun: onRun, @@ -237,6 +241,58 @@ void main() { } }); + test('addImg [pre-submit] executes successfully with a release version', () async { + // Adds a suffix of "_Release_3_21" to the test name. + final _TestFixture fixture = _TestFixture( + // Creates a file called "engine/src/fluter/.engine-release.version" with the contents "3.21". + engineVersion: ReleaseVersion( + major: 3, + minor: 21, + ), + ); + try { + final SkiaGoldClient client = createClient( + fixture, + environment: presubmitEnv, + onRun: (List command) { + if (command case ['git', ...]) { + return io.ProcessResult(0, 0, mockCommitHash, ''); + } + if (command case ['python tools/goldctl.py', 'imgtest', 'init', ...]) { + return io.ProcessResult(0, 0, '', ''); + } + expect(command, [ + 'python tools/goldctl.py', + 'imgtest', + 'add', + '--work-dir', + p.join(fixture.workDirectory.path, 'temp'), + '--test-name', + // This is the significant change. + 'test-name_Release_3_21', + '--png-file', + p.join(fixture.workDirectory.path, 'temp', 'golden.png'), + '--add-test-optional-key', + 'image_matching_algorithm:fuzzy', + '--add-test-optional-key', + 'fuzzy_max_different_pixels:10', + '--add-test-optional-key', + 'fuzzy_pixel_delta_threshold:0', + ]); + return io.ProcessResult(0, 0, '', ''); + }, + ); + + await client.addImg( + 'test-name.foo', + io.File(p.join(fixture.workDirectory.path, 'temp', 'golden.png')), + screenshotSize: 1000, + ); + } finally { + fixture.dispose(); + } + }); + test('addImg [pre-submit] executes successfully with verbose logging', () async { final _TestFixture fixture = _TestFixture(); try { @@ -536,14 +592,32 @@ void main() { } final class _TestFixture { - _TestFixture(); + _TestFixture({ + ReleaseVersion? engineVersion, + }) { + workDirectory = rootDirectory.createTempSync('working'); + + // Create the engine/src directory. + engineSrcDir = io.Directory(p.join(rootDirectory.path, 'engine', 'src')); + engineSrcDir.createSync(recursive: true); + + // Create a .engine-release.version file in the engine root. + final io.Directory flutterDir = io.Directory(p.join(engineSrcDir.path, 'flutter')); + flutterDir.createSync(recursive: true); + + final String version = engineVersion?.toString() ?? 'none'; + io.File(p.join(flutterDir.path, '.engine-release.version')).writeAsStringSync(version); + } + + final io.Directory rootDirectory = io.Directory.systemTemp.createTempSync('skia_gold_client_test'); + late final io.Directory workDirectory; + late final io.Directory engineSrcDir; - final io.Directory workDirectory = io.Directory.systemTemp.createTempSync('skia_gold_client_test'); final _FakeHttpClient httpClient = _FakeHttpClient(); final StringSink outputSink = StringBuffer(); void dispose() { - workDirectory.deleteSync(recursive: true); + rootDirectory.deleteSync(recursive: true); } } diff --git a/tools/clang_tidy/test/clang_tidy_test.dart b/tools/clang_tidy/test/clang_tidy_test.dart index edcc4c9af647d..cf3e575f0c82d 100644 --- a/tools/clang_tidy/test/clang_tidy_test.dart +++ b/tools/clang_tidy/test/clang_tidy_test.dart @@ -213,6 +213,7 @@ Future main(List args) async { test('clang-tidy specified', () async { _withTempFile('shard-id-valid', (String path) { final Options options = Options.fromCommandLine( [ + '--compile-commands=$path', '--clang-tidy=foo/bar', ],); expect(options.clangTidyPath, isNotNull); diff --git a/tools/clangd_check/README.md b/tools/clangd_check/README.md new file mode 100644 index 0000000000000..88f8c9e48faed --- /dev/null +++ b/tools/clangd_check/README.md @@ -0,0 +1,27 @@ +# `clangd_check` + +`clangd_check` is a tool to run clangd on a codebase and check for diagnostics. + +The practical use of this tool is intentionally limited; it's designed to +provide a quick way to verify that `clangd` is able to parse and analyze a +C++ codebase. + +## Usage + +```sh +dart ./tools/clangd_check/bin/main.dart +``` + +On success, and with no diagnostics, `clangd_check` will exit with status 0. + +By default, `clangd_check` will try to infer the path of `clangd`, as well as +the path to `--compile-commands-dir` based on what artifacts are present in +`$ENGINE/src/out`. + +You can also specify the path to `clangd` and `--compile-commands-dir` manually: + +```sh +dart ./tools/clangd_check/bin/main.dart \ + --clangd ../buildtools/mac-arm64/clang/bin/clangd \ + --compile-commands-dir ../out/host_Debug_unopt_arm64 +``` diff --git a/tools/clangd_check/bin/main.dart b/tools/clangd_check/bin/main.dart new file mode 100644 index 0000000000000..d2e19dbb5c7dd --- /dev/null +++ b/tools/clangd_check/bin/main.dart @@ -0,0 +1,132 @@ +// Copyright 2013 The Flutter 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 'dart:convert'; +import 'dart:io' as io; + +import 'package:args/args.dart'; +import 'package:engine_repo_tools/engine_repo_tools.dart'; +import 'package:path/path.dart' as p; + +void main(List args) { + final Engine? engine = Engine.tryFindWithin(); + final ArgParser parser = ArgParser() + ..addFlag( + 'help', + abbr: 'h', + help: 'Print this usage information.', + negatable: false, + ) + ..addOption( + 'clangd', + help: 'Path to clangd. Defaults to deriving the path from compile_commands.json.', + ) + ..addOption( + 'compile-commands-dir', + help: 'Path to a directory containing compile_commands.json.', + defaultsTo: engine?.latestOutput()?.compileCommandsJson.parent.path, + ); + final ArgResults results = parser.parse(args); + if (results['help'] as bool) { + io.stdout.writeln(parser.usage); + return; + } + + final String? compileCommandsDir = results['compile-commands-dir'] as String?; + if (compileCommandsDir == null) { + io.stderr.writeln('Must provide a path to compile_commands.json'); + io.exitCode = 1; + return; + } + final io.File compileCommandsFile = io.File(p.join(compileCommandsDir, 'compile_commands.json')); + if (!compileCommandsFile.existsSync()) { + io.stderr.writeln('No compile_commands.json found in $compileCommandsDir'); + io.exitCode = 1; + return; + } + + final List compileCommands = json.decode(compileCommandsFile.readAsStringSync()) as List; + if (compileCommands.isEmpty) { + io.stderr.writeln('Unexpected: compile_commands.json is empty'); + io.exitCode = 1; + return; + } + + String? clangd = results['clangd'] as String?; + final Map entry = compileCommands.first! as Map; + final String checkFile; + if (entry case { + 'command': final String command, + 'directory': final String directory, + 'file': final String file, + }) { + // Given a path like ../../flutter/foo.cc, we want to check foo.cc. + checkFile = p.join(directory, file); + // On CI, the command path is different from the local path. + // Find the engine root and derive the clangd path from there. + if (clangd == null) { + // Strip the command to find the path to the engine root. + // i.e. "command": "/path/to/engine/src/... arg1 arg2 ..." + // + // This now looks like "../../flutter/buildtools/{platform}/{...}" + final String commandPath = p.dirname(command.split(' ').first); + + // Find the canonical path to the command (i.e. resolve "../" and ".") + // + // This now looks like "/path/to/engine/src/flutter/buildtools/{platform}/{...}" + final String path = p.canonicalize( + p.join(compileCommandsDir, commandPath), + ); + + // Extract which platform we're building for (e.g. linux-x64, mac-arm64, mac-x64). + final String platform = RegExp( + r'buildtools/([^/]+)/', + ).firstMatch(path)!.group(1)!; + + // Find the engine root and derive the clangd path from there. + final Engine compileCommandsEngineRoot = Engine.findWithin(path); + clangd = p.join( + // engine/src/flutter + compileCommandsEngineRoot.flutterDir.path, + // buildtools + 'buildtools', + // {platform} + platform, + // clangd + 'clang', + 'bin', + 'clangd', + ); + } + } else { + io.stderr.writeln('Unexpected: compile_commands.json has an unexpected format'); + io.stderr.writeln('First entry: ${const JsonEncoder.withIndent(' ').convert(entry)}'); + io.exitCode = 1; + return; + } + + // Run clangd. + try { + final io.ProcessResult result = io.Process.runSync(clangd, [ + '--compile-commands-dir', + compileCommandsDir, + '--check=$checkFile', + ]); + io.stdout.write(result.stdout); + io.stderr.write(result.stderr); + if ((result.stderr as String).contains('Path specified by --compile-commands-dir does not exist')) { + io.stdout.writeln('clangd_check failed: --compile-commands-dir does not exist'); + io.exitCode = 1; + } else if ((result.stderr as String).contains('Failed to resolve path')) { + io.stdout.writeln('clangd_check failed: --check file does not exist'); + io.exitCode = 1; + } else { + io.exitCode = result.exitCode; + } + } on io.ProcessException catch (e) { + io.stderr.writeln('Failed to run clangd: $e'); + io.stderr.writeln(const JsonEncoder.withIndent(' ').convert(entry)); + io.exitCode = 1; + } +} diff --git a/tools/clangd_check/pubspec.yaml b/tools/clangd_check/pubspec.yaml new file mode 100644 index 0000000000000..b82318fa62fee --- /dev/null +++ b/tools/clangd_check/pubspec.yaml @@ -0,0 +1,68 @@ +# Copyright 2013 The Flutter Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +name: clangd_check +publish_to: none + +# Do not add any dependencies that require more than what is provided in +# //third_party/dart/pkg or //third_party/dart/third_party/pkg. +# In particular, package:test is not usable here. + +# If you do add packages here, make sure you can run `pub get --offline`, and +# check the .packages and .package_config to make sure all the paths are +# relative to this directory into //third_party/dart + +environment: + sdk: '>=3.2.0-0 <4.0.0' + +dependencies: + args: any + engine_repo_tools: any + path: any + source_span: any + +dev_dependencies: + async_helper: any + expect: any + litetest: any + process_fakes: any + smith: any + +dependency_overrides: + args: + path: ../../../third_party/dart/third_party/pkg/args + async: + path: ../../../third_party/dart/third_party/pkg/async + async_helper: + path: ../../../third_party/dart/pkg/async_helper + collection: + path: ../../../third_party/dart/third_party/pkg/collection + engine_repo_tools: + path: ../pkg/engine_repo_tools + expect: + path: ../../../third_party/dart/pkg/expect + file: + path: ../../../third_party/dart/third_party/pkg/file/packages/file + git_repo_tools: + path: ../pkg/git_repo_tools + litetest: + path: ../../testing/litetest + meta: + path: ../../../third_party/dart/pkg/meta + path: + path: ../../../third_party/dart/third_party/pkg/path + platform: + path: ../../third_party/pkg/platform + process: + path: ../../third_party/pkg/process + process_fakes: + path: ../pkg/process_fakes + process_runner: + path: ../../third_party/pkg/process_runner + smith: + path: ../../../third_party/dart/pkg/smith + source_span: + path: ../../../third_party/dart/third_party/pkg/source_span + term_glyph: + path: ../../../third_party/dart/third_party/pkg/term_glyph diff --git a/tools/engine_tool/lib/src/build_utils.dart b/tools/engine_tool/lib/src/build_utils.dart index 0be0e587258d4..433f57ce9ab6c 100644 --- a/tools/engine_tool/lib/src/build_utils.dart +++ b/tools/engine_tool/lib/src/build_utils.dart @@ -66,11 +66,9 @@ void debugCheckBuilds(List builds) { } /// Build the build target in the environment. -Future runBuild( - Environment environment, - Build build, { - List extraGnArgs = const [], -}) async { +Future runBuild(Environment environment, Build build, + {List extraGnArgs = const [], + List targets = const []}) async { // If RBE config files aren't in the tree, then disable RBE. final String rbeConfigPath = p.join( environment.engine.srcDir.path, @@ -79,10 +77,11 @@ Future runBuild( 'rbe', ); final List gnArgs = [ - ...extraGnArgs, if (!io.Directory(rbeConfigPath).existsSync()) '--no-rbe', + ...extraGnArgs, ]; + // TODO(loic-sharma): Fetch dependencies if needed. final BuildRunner buildRunner = BuildRunner( platform: environment.platform, processRunner: environment.processRunner, @@ -91,6 +90,7 @@ Future runBuild( build: build, extraGnArgs: gnArgs, runTests: false, + extraNinjaArgs: targets, ); Spinner? spinner; diff --git a/tools/engine_tool/lib/src/commands/build_command.dart b/tools/engine_tool/lib/src/commands/build_command.dart index 366a831be2069..fc6a81dad54f8 100644 --- a/tools/engine_tool/lib/src/commands/build_command.dart +++ b/tools/engine_tool/lib/src/commands/build_command.dart @@ -17,25 +17,12 @@ final class BuildCommand extends CommandBase { }) { builds = runnableBuilds(environment, configs); debugCheckBuilds(builds); - argParser.addOption( - configFlag, - abbr: 'c', - defaultsTo: 'host_debug', - help: 'Specify the build config to use', - allowed: [ - for (final Build config in runnableBuilds(environment, configs)) - config.name, - ], - allowedHelp: { - for (final Build config in runnableBuilds(environment, configs)) - config.name: config.gn.join(' '), - }, - ); + addConfigOption(argParser, runnableBuilds(environment, configs)); argParser.addFlag( rbeFlag, defaultsTo: true, help: 'RBE is enabled by default when available. Use --no-rbe to ' - 'disable it.', + 'disable it.', ); } @@ -63,7 +50,6 @@ final class BuildCommand extends CommandBase { if (!useRbe) '--no-rbe', ]; - // TODO(loic-sharma): Fetch dependencies if needed. return runBuild(environment, build, extraGnArgs: extraGnArgs); } } diff --git a/tools/engine_tool/lib/src/commands/command.dart b/tools/engine_tool/lib/src/commands/command.dart index 3e61ff388e4a1..3224f7654cc56 100644 --- a/tools/engine_tool/lib/src/commands/command.dart +++ b/tools/engine_tool/lib/src/commands/command.dart @@ -2,17 +2,35 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:args/args.dart'; import 'package:args/command_runner.dart'; +import 'package:engine_build_configs/engine_build_configs.dart'; import '../environment.dart'; +import 'flags.dart'; /// The base class that all commands and subcommands should inherit from. abstract base class CommandBase extends Command { /// Constructs the base command. - CommandBase({ - required this.environment - }); + CommandBase({required this.environment}); /// The host system environment. final Environment environment; } + +/// Adds the -c (--config) option to the parser. +void addConfigOption(ArgParser parser, List builds, + {String defaultsTo = 'host_debug'}) { + parser.addOption( + configFlag, + abbr: 'c', + defaultsTo: defaultsTo, + help: 'Specify the build config to use', + allowed: [ + for (final Build config in builds) config.name, + ], + allowedHelp: { + for (final Build config in builds) config.name: config.gn.join(' '), + }, + ); +} diff --git a/tools/engine_tool/lib/src/commands/command_runner.dart b/tools/engine_tool/lib/src/commands/command_runner.dart index cf4bc92f83d54..12c1d09edb832 100644 --- a/tools/engine_tool/lib/src/commands/command_runner.dart +++ b/tools/engine_tool/lib/src/commands/command_runner.dart @@ -13,6 +13,7 @@ import 'format_command.dart'; import 'lint_command.dart'; import 'query_command.dart'; import 'run_command.dart'; +import 'test_command.dart'; const int _usageLineLength = 80; @@ -31,6 +32,7 @@ final class ToolCommandRunner extends CommandRunner { BuildCommand(environment: environment, configs: configs), RunCommand(environment: environment, configs: configs), LintCommand(environment: environment), + TestCommand(environment: environment, configs: configs), ]; commands.forEach(addCommand); diff --git a/tools/engine_tool/lib/src/commands/lint_command.dart b/tools/engine_tool/lib/src/commands/lint_command.dart index 1fa9907f0fc34..75989a4ff1c55 100644 --- a/tools/engine_tool/lib/src/commands/lint_command.dart +++ b/tools/engine_tool/lib/src/commands/lint_command.dart @@ -3,13 +3,11 @@ // found in the LICENSE file. import 'dart:io' show Directory; -import 'dart:math'; import 'package:path/path.dart' as p; import '../dart_utils.dart'; -import '../environment.dart'; -import '../logger.dart'; + import '../proc_utils.dart'; import '../worker_pool.dart'; import 'command.dart'; @@ -113,114 +111,3 @@ final class LintCommand extends CommandBase { return r ? 0 : 1; } } - -/// A WorkerPoolProgressReporter designed to work with ProcessTasks. -class ProcessTaskProgressReporter implements WorkerPoolProgressReporter { - /// Construct a new reporter. - ProcessTaskProgressReporter(this._environment); - - final Environment _environment; - Spinner? _spinner; - bool _finished = false; - int _longestName = 0; - - @override - void onRun(Set tasks) { - for (final WorkerTask task in tasks) { - _longestName = max(_longestName, task.name.length); - } - } - - @override - void onFinish() { - _finished = true; - _updateSpinner([]); - } - - List _makeProcessTaskList(WorkerPool pool) { - final List runningTasks = []; - for (final WorkerTask task in pool.running) { - if (task is! ProcessTask) { - continue; - } - runningTasks.add(task); - } - return runningTasks; - } - - @override - void onTaskStart(WorkerPool pool, WorkerTask task) { - final List running = _makeProcessTaskList(pool); - _updateSpinner(running); - } - - @override - void onTaskDone(WorkerPool pool, WorkerTask task, [Object? err]) { - final List running = _makeProcessTaskList(pool); - task as ProcessTask; - final ProcessArtifacts pa = task.processArtifacts; - final String dt = _formatDurationShort(task.runTime); - if (pa.exitCode != 0) { - final String paPath = task.processArtifactsPath; - _environment.logger.clearLine(); - _environment.logger.status( - 'FAIL: ${task.name.padLeft(_longestName)} after $dt [details in $paPath]'); - } else { - _environment.logger.clearLine(); - _environment.logger - .status('OKAY: ${task.name.padLeft(_longestName)} after $dt'); - } - _updateSpinner(running); - } - - void _updateSpinner(List tasks) { - if (_spinner != null) { - _spinner!.finish(); - _spinner = null; - } - if (_finished) { - return; - } - _environment.logger.clearLine(); - String runStatus = '['; - for (final ProcessTask pt in tasks) { - if (runStatus != '[') { - runStatus += ' '; - } - runStatus += pt.name; - } - if (tasks.isNotEmpty) { - runStatus += '...'; - } - runStatus += '] '; - _environment.logger.status('Linting $runStatus', newline: false); - _spinner = _environment.logger.startSpinner(); - } -} - -String _formatDurationShort(Duration dur) { - int micros = dur.inMicroseconds; - String r = ''; - if (micros >= Duration.microsecondsPerMinute) { - final int minutes = micros ~/ Duration.microsecondsPerMinute; - micros -= minutes * Duration.microsecondsPerMinute; - r += '${minutes}m'; - } - if (micros >= Duration.microsecondsPerSecond) { - final int seconds = micros ~/ Duration.microsecondsPerSecond; - micros -= seconds * Duration.microsecondsPerSecond; - if (r.isNotEmpty) { - r += '.'; - } - r += '${seconds}s'; - } - if (micros >= Duration.microsecondsPerMillisecond) { - final int millis = micros ~/ Duration.microsecondsPerMillisecond; - micros -= millis * Duration.microsecondsPerMillisecond; - if (r.isNotEmpty) { - r += '.'; - } - r += '${millis}ms'; - } - return r; -} diff --git a/tools/engine_tool/lib/src/commands/query_command.dart b/tools/engine_tool/lib/src/commands/query_command.dart index b70ee43ae8989..6f4c6ee367e37 100644 --- a/tools/engine_tool/lib/src/commands/query_command.dart +++ b/tools/engine_tool/lib/src/commands/query_command.dart @@ -2,8 +2,14 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:io'; + import 'package:engine_build_configs/engine_build_configs.dart'; +import 'package:path/path.dart' as p; + +import '../build_utils.dart'; +import '../gn_utils.dart'; import 'command.dart'; import 'flags.dart'; @@ -42,6 +48,10 @@ final class QueryCommand extends CommandBase { environment: environment, configs: configs, )); + addSubcommand(QueryTestsCommand( + environment: environment, + configs: configs, + )); } /// Build configurations loaded from the engine from under ci/builders. @@ -55,9 +65,9 @@ final class QueryCommand extends CommandBase { 'and tests.'; } -/// The 'query builds' command. +/// The 'query builders' command. final class QueryBuildersCommand extends CommandBase { - /// Constructs the 'query build' command. + /// Constructs the 'query builders' command. QueryBuildersCommand({ required super.environment, required this.configs, @@ -120,3 +130,45 @@ final class QueryBuildersCommand extends CommandBase { return 0; } } + +/// The query tests command. +final class QueryTestsCommand extends CommandBase { + /// Constructs the 'query tests' command. + QueryTestsCommand({ + required super.environment, + required this.configs, + }) { + builds = runnableBuilds(environment, configs); + debugCheckBuilds(builds); + addConfigOption(argParser, runnableBuilds(environment, configs)); + } + + /// Build configurations loaded from the engine from under ci/builders. + final Map configs; + + /// List of compatible builds. + late final List builds; + + @override + String get name => 'tests'; + + @override + String get description => 'Provides information about test targets'; + + @override + Future run() async { + final String configName = argResults![configFlag] as String; + final Build? build = + builds.where((Build build) => build.name == configName).firstOrNull; + if (build == null) { + environment.logger.error('Could not find config $configName'); + return 1; + } + final Map targets = await findTestTargets(environment, + Directory(p.join(environment.engine.outDir.path, build.ninja.config))); + for (final TestTarget target in targets.values) { + environment.logger.status(target.label); + } + return 0; + } +} diff --git a/tools/engine_tool/lib/src/commands/run_command.dart b/tools/engine_tool/lib/src/commands/run_command.dart index 3976aef6f769f..35e5657c58941 100644 --- a/tools/engine_tool/lib/src/commands/run_command.dart +++ b/tools/engine_tool/lib/src/commands/run_command.dart @@ -21,27 +21,15 @@ final class RunCommand extends CommandBase { }) { builds = runnableBuilds(environment, configs); debugCheckBuilds(builds); - - argParser.addOption( - configFlag, - abbr: 'c', - defaultsTo: '', - help: - 'Specify the build config to use for the target build (usually auto detected)', - allowed: [ - for (final Build build in runnableBuilds(environment, configs)) - build.name, - ], - allowedHelp: { - for (final Build build in runnableBuilds(environment, configs)) - build.name: build.gn.join(' '), - }, - ); + // We default to nothing in order to automatically detect attached devices + // and select an appropriate target from them. + addConfigOption(argParser, runnableBuilds(environment, configs), + defaultsTo: ''); argParser.addFlag( rbeFlag, defaultsTo: true, help: 'RBE is enabled by default when available. Use --no-rbe to ' - 'disable it.', + 'disable it.', ); } diff --git a/tools/engine_tool/lib/src/commands/test_command.dart b/tools/engine_tool/lib/src/commands/test_command.dart new file mode 100644 index 0000000000000..7e7d82faae0ac --- /dev/null +++ b/tools/engine_tool/lib/src/commands/test_command.dart @@ -0,0 +1,81 @@ +// Copyright 2013 The Flutter 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 'dart:io'; + +import 'package:engine_build_configs/engine_build_configs.dart'; +import 'package:path/path.dart' as p; + +import '../build_utils.dart'; +import '../gn_utils.dart'; +import '../proc_utils.dart'; +import '../worker_pool.dart'; +import 'command.dart'; +import 'flags.dart'; + +/// The root 'test' command. +final class TestCommand extends CommandBase { + /// Constructs the 'test' command. + TestCommand({ + required super.environment, + required Map configs, + }) { + builds = runnableBuilds(environment, configs); + debugCheckBuilds(builds); + addConfigOption(argParser, runnableBuilds(environment, configs)); + } + + /// List of compatible builds. + late final List builds; + + @override + String get name => 'test'; + + @override + String get description => 'Runs a test target' + 'et test //flutter/fml/... # Run all test targets in `//flutter/fml/`' + 'et test //flutter/fml:fml_benchmarks # Run a single test target in `//flutter/fml/`'; + + @override + Future run() async { + final String configName = argResults![configFlag] as String; + final Build? build = + builds.where((Build build) => build.name == configName).firstOrNull; + if (build == null) { + environment.logger.error('Could not find config $configName'); + return 1; + } + + final Map allTargets = await findTestTargets( + environment, + Directory(p.join(environment.engine.outDir.path, build.ninja.config))); + final Set selectedTargets = + selectTargets(argResults!.rest, allTargets); + if (selectedTargets.isEmpty) { + environment.logger.error( + 'No build targets matched ${argResults!.rest}\nRun `et query tests` to see list of targets.'); + return 1; + } + // Chop off the '//' prefix. + final List buildTargets = selectedTargets + .map((TestTarget target) => target.label.substring('//'.length)) + .toList(); + // TODO(johnmccutchan): runBuild manipulates buildTargets and adds some + // targets listed in Build. Fix this. + final int buildExitCode = + await runBuild(environment, build, targets: buildTargets); + if (buildExitCode != 0) { + return buildExitCode; + } + final WorkerPool workerPool = + WorkerPool(environment, ProcessTaskProgressReporter(environment)); + final Set tasks = {}; + for (final TestTarget target in selectedTargets) { + final List commandLine = [target.executable.path]; + tasks.add(ProcessTask( + target.label, environment, environment.engine.srcDir, commandLine)); + } + return await workerPool.run(tasks) ? 0 : 1; + } +} diff --git a/tools/engine_tool/lib/src/gn_utils.dart b/tools/engine_tool/lib/src/gn_utils.dart new file mode 100644 index 0000000000000..ace20254ebca7 --- /dev/null +++ b/tools/engine_tool/lib/src/gn_utils.dart @@ -0,0 +1,137 @@ +// Copyright 2013 The Flutter 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 'dart:io'; + +import 'package:path/path.dart' as p; +import 'package:process_runner/process_runner.dart'; + +import 'environment.dart'; +import 'proc_utils.dart'; + +/// Canonicalized build targets start with this prefix. +const String buildTargetPrefix = '//'; + +/// A suffix to build targets that recursively selects all child build targets. +const String _buildTargetGlobSuffix = '/...'; + +/// Information about a test build target. +final class TestTarget { + /// Construct a test target. + TestTarget(this.label, this.executable); + + /// The build target label. `//flutter/fml:fml_unittests` + final String label; + + /// The executable file produced after the build target is built. + final File executable; + + @override + String toString() { + return 'target=$label executable=${executable.path}'; + } +} + +/// Returns test targets for a given build directory. +Future> findTestTargets( + Environment environment, Directory buildDir) async { + final Map r = {}; + final List getLabelsCommandLine = [ + gnBinPath(environment), + 'ls', + buildDir.path, + '--type=executable', + '--testonly=true', + '--as=label', + ]; + final List getOutputsCommandLine = [ + gnBinPath(environment), + 'ls', + buildDir.path, + '--type=executable', + '--testonly=true', + '--as=output' + ]; + + // Spawn the two processes concurrently. + final Future futureLabelsResult = + environment.processRunner.runProcess(getLabelsCommandLine, + workingDirectory: environment.engine.srcDir, failOk: true); + final Future futureOutputsResult = + environment.processRunner.runProcess(getOutputsCommandLine, + workingDirectory: environment.engine.srcDir, failOk: true); + + // Await the futures, we need both to complete so the order doesn't matter. + final ProcessRunnerResult labelsResult = await futureLabelsResult; + final ProcessRunnerResult outputsResult = await futureOutputsResult; + + // Handle any process failures. + fatalIfFailed(environment, getLabelsCommandLine, labelsResult); + fatalIfFailed(environment, getOutputsCommandLine, outputsResult); + + // Extract the labels + final String rawLabels = labelsResult.stdout; + final String rawOutputs = outputsResult.stdout; + final List labels = rawLabels.split('\n'); + final List outputs = rawOutputs.split('\n'); + if (labels.length != outputs.length) { + environment.logger.fatal( + 'gn ls output is inconsistent A and B should be the same length:\nA=$labels\nB=$outputs'); + } + // Drop the empty line at the end of the output. + if (labels.isNotEmpty) { + if (labels.last.isNotEmpty || outputs.last.isNotEmpty) { + throw AssertionError('expected last line of output to be blank.'); + } + labels.removeLast(); + outputs.removeLast(); + } + for (int i = 0; i < labels.length; i++) { + final String label = labels[i]; + final String output = outputs[i]; + if (label.isEmpty) { + throw AssertionError('expected line to not be empty.'); + } + if (output.isEmpty) { + throw AssertionError('expected line to not be empty.'); + } + r[label] = TestTarget(label, File(p.join(buildDir.path, output))); + } + return r; +} + +/// Process selectors and filter allTargets for matches. +/// +/// We support: +/// 1) Exact label matches (the '//' prefix will be stripped off). +/// 2) '/...' suffix which selects all targets that match the prefix. +Set selectTargets( + List selectors, Map allTargets) { + final Set selected = {}; + for (String selector in selectors) { + if (!selector.startsWith(buildTargetPrefix)) { + // Insert the prefix when necessary. + selector = '$buildTargetPrefix$selector'; + } + final bool recursiveMatch = selector.endsWith(_buildTargetGlobSuffix); + if (recursiveMatch) { + // Remove the /... suffix. + selector = selector.substring( + 0, selector.length - _buildTargetGlobSuffix.length); + // TODO(johnmccutchan): Accelerate this by using a trie. + for (final TestTarget target in allTargets.values) { + if (target.label.startsWith(selector)) { + selected.add(target); + } + } + } else { + for (final TestTarget target in allTargets.values) { + if (target.label == selector) { + selected.add(target); + } + } + } + } + return selected; +} diff --git a/tools/engine_tool/lib/src/proc_utils.dart b/tools/engine_tool/lib/src/proc_utils.dart index cbcc226bde6be..fa43c80c4c30d 100644 --- a/tools/engine_tool/lib/src/proc_utils.dart +++ b/tools/engine_tool/lib/src/proc_utils.dart @@ -5,12 +5,14 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'dart:math'; import 'package:path/path.dart' as p; import 'package:process_runner/process_runner.dart'; import 'environment.dart'; import 'json_utils.dart'; +import 'logger.dart'; import 'worker_pool.dart'; /// Artifacts from an exited sub-process. @@ -20,6 +22,15 @@ final class ProcessArtifacts { this.cwd, this.commandLine, this.exitCode, this.stdout, this.stderr, {this.pid}); + /// Constructs an instance of ProcessArtifacts from a ProcessRunnerResult + /// and the spawning context. + factory ProcessArtifacts.fromResult( + Directory cwd, List commandLine, ProcessRunnerResult result) { + return ProcessArtifacts( + cwd, commandLine, result.exitCode, result.stdout, result.stderr, + pid: result.pid); + } + /// Constructs an instance of ProcessArtifacts from serialized JSON text. factory ProcessArtifacts.fromJson(String serialized) { final Map artifact = @@ -31,7 +42,9 @@ final class ProcessArtifacts { final int exitCode = intOfJson(artifact, 'exitCode', errors)!; final String stdout = stringOfJson(artifact, 'stdout', errors)!; final String stderr = stringOfJson(artifact, 'stderr', errors)!; - return ProcessArtifacts(cwd, commandLine, exitCode, stdout, stderr); + final int? pid = intOfJson(artifact, 'pid', errors); + return ProcessArtifacts(cwd, commandLine, exitCode, stdout, stderr, + pid: pid); } /// Constructs an instance of ProcessArtifacts from a file containing JSON. @@ -116,3 +129,127 @@ class ProcessTask extends WorkerTask { return _processArtifactsPath!; } } + +/// A WorkerPoolProgressReporter designed to work with ProcessTasks. +class ProcessTaskProgressReporter implements WorkerPoolProgressReporter { + /// Construct a new reporter. + ProcessTaskProgressReporter(this._environment); + + final Environment _environment; + Spinner? _spinner; + bool _finished = false; + int _longestName = 0; + int _doneCount = 0; + int _totalCount = 0; + + @override + void onRun(Set tasks) { + _totalCount = tasks.length; + for (final WorkerTask task in tasks) { + assert(task is ProcessTask); + _longestName = max(_longestName, task.name.length); + } + } + + @override + void onFinish() { + _finished = true; + _updateSpinner({}); + } + + @override + void onTaskStart(WorkerPool pool, WorkerTask task) { + _updateSpinner(pool.running); + } + + @override + void onTaskDone(WorkerPool pool, WorkerTask task, [Object? err]) { + _doneCount++; + task as ProcessTask; + final ProcessArtifacts pa = task.processArtifacts; + final String dt = _formatDurationShort(task.runTime); + if (pa.exitCode != 0) { + final String paPath = task.processArtifactsPath; + _environment.logger.clearLine(); + _environment.logger.status('FAIL: $dt ${task.name} [details in $paPath]'); + } else { + _environment.logger.clearLine(); + _environment.logger.status('OKAY: $dt ${task.name}'); + } + _updateSpinner(pool.running); + } + + void _updateSpinner(Set tasks) { + if (_spinner != null) { + _spinner!.finish(); + _spinner = null; + } + if (_finished) { + return; + } + _environment.logger.clearLine(); + final String taskName = tasks.isEmpty ? '' : tasks.first.name; + final String etc = tasks.length > 1 ? '... [${tasks.length}]' : ''; + _environment.logger.status( + 'Running $_doneCount/$_totalCount $taskName$etc ', + newline: false); + _spinner = _environment.logger.startSpinner(); + } + + String _formatDurationShort(Duration dur) { + int micros = dur.inMicroseconds; + String r = ''; + if (micros >= Duration.microsecondsPerMinute) { + final int minutes = micros ~/ Duration.microsecondsPerMinute; + micros -= minutes * Duration.microsecondsPerMinute; + r += '${minutes}m'; + } + if (micros >= Duration.microsecondsPerSecond) { + final int seconds = micros ~/ Duration.microsecondsPerSecond; + micros -= seconds * Duration.microsecondsPerSecond; + if (r.isNotEmpty) { + r += '.'; + } + r += '${seconds}s'; + } + if (micros >= Duration.microsecondsPerMillisecond) { + final int millis = micros ~/ Duration.microsecondsPerMillisecond; + micros -= millis * Duration.microsecondsPerMillisecond; + if (r.isNotEmpty) { + r += '.'; + } + r += '${millis}ms'; + } + return r.padLeft(15); + } +} + +/// If result.exitCode != 0, will call logger.fatal with relevant information +/// and terminate the program. +void fatalIfFailed(Environment environment, List commandLine, + ProcessRunnerResult result) { + if (result.exitCode == 0) { + return; + } + environment.logger.fatal( + 'Process "${commandLine.join(' ')}" failed exitCode=${result.exitCode}\n' + 'STDOUT:\n${result.stdout}' + 'STDERR:\n${result.stderr}'); +} + +/// Ensures that pathToBinary includes a '.exe' suffix on relevant platforms. +String exePath(Environment environment, String pathToBinary) { + String suffix = ''; + if (environment.platform.isWindows) { + suffix = '.exe'; + } + return '$pathToBinary$suffix'; +} + +/// Returns the path to the gn binary. +String gnBinPath(Environment environment) { + return exePath( + environment, + p.join(environment.engine.srcDir.path, 'flutter', 'third_party', 'gn', + 'gn')); +} diff --git a/tools/engine_tool/test/fixtures.dart b/tools/engine_tool/test/fixtures.dart index b450136d7ca37..16096fe0ad618 100644 --- a/tools/engine_tool/test/fixtures.dart +++ b/tools/engine_tool/test/fixtures.dart @@ -194,3 +194,15 @@ String attachedDevices() => ''' } ] '''; + +// NOTE: The final empty line is intentional. +String gnLsTestOutputs() => ''' +display_list_unittests +flow_unittests +fml_arc_unittests'''; + +// NOTE: The empty blank line is intentional. +String gnLsTestLabels() => ''' +//flutter/display_list:display_list_unittests +//flutter/flow:flow_unittests +//flutter/fml:fml_arc_unittests'''; diff --git a/tools/engine_tool/test/gn_utils_test.dart b/tools/engine_tool/test/gn_utils_test.dart new file mode 100644 index 0000000000000..e2a9846d73042 --- /dev/null +++ b/tools/engine_tool/test/gn_utils_test.dart @@ -0,0 +1,75 @@ +// Copyright 2013 The Flutter 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 'dart:io' as io; + +import 'package:engine_repo_tools/engine_repo_tools.dart'; +import 'package:engine_tool/src/environment.dart'; +import 'package:engine_tool/src/gn_utils.dart'; +import 'package:litetest/litetest.dart'; + +import 'utils.dart'; + +void main() { + final Engine engine; + try { + engine = Engine.findWithin(); + } catch (e) { + io.stderr.writeln(e); + io.exitCode = 1; + return; + } + + final List cannedProcesses = [ + CannedProcess((List command) => command.contains('--as=label'), + stdout: ''' +//flutter/display_list:display_list_unittests +//flutter/flow:flow_unittests +//flutter/fml:fml_arc_unittests +'''), + CannedProcess((List command) => command.contains('--as=output'), + stdout: ''' +display_list_unittests +flow_unittests +fml_arc_unittests +''') + ]; + + test('find test targets', () async { + final TestEnvironment testEnvironment = + TestEnvironment(engine, cannedProcesses: cannedProcesses); + final Environment env = testEnvironment.environment; + final Map testTargets = + await findTestTargets(env, engine.outDir); + expect(testTargets.length, equals(3)); + expect(testTargets['//flutter/display_list:display_list_unittests'], + notEquals(null)); + expect( + testTargets['//flutter/display_list:display_list_unittests']! + .executable + .path, + endsWith('display_list_unittests')); + }); + + test('process queue failure', () async { + final TestEnvironment testEnvironment = + TestEnvironment(engine, cannedProcesses: cannedProcesses); + final Environment env = testEnvironment.environment; + final Map testTargets = + await findTestTargets(env, engine.outDir); + expect(selectTargets(['//...'], testTargets).length, equals(3)); + expect( + selectTargets(['//flutter/display_list'], testTargets).length, + equals(0)); + expect( + selectTargets(['//flutter/display_list/...'], testTargets) + .length, + equals(1)); + expect( + selectTargets(['//flutter/display_list:display_list_unittests'], + testTargets) + .length, + equals(1)); + }); +} diff --git a/tools/engine_tool/test/query_command_test.dart b/tools/engine_tool/test/query_command_test.dart index 4abed0318507c..b570ad2e28dac 100644 --- a/tools/engine_tool/test/query_command_test.dart +++ b/tools/engine_tool/test/query_command_test.dart @@ -10,14 +10,11 @@ import 'package:engine_build_configs/engine_build_configs.dart'; import 'package:engine_repo_tools/engine_repo_tools.dart'; import 'package:engine_tool/src/commands/command_runner.dart'; import 'package:engine_tool/src/environment.dart'; -import 'package:engine_tool/src/logger.dart'; import 'package:litetest/litetest.dart'; import 'package:logging/logging.dart' as log; -import 'package:platform/platform.dart'; -import 'package:process_fakes/process_fakes.dart'; -import 'package:process_runner/process_runner.dart'; import 'fixtures.dart' as fixtures; +import 'utils.dart'; void main() { final Engine engine; @@ -54,27 +51,29 @@ void main() { 'win_test_config': winTestConfig, }; - Environment linuxEnv(Logger logger) { - return Environment( - abi: ffi.Abi.linuxX64, - engine: engine, - platform: FakePlatform( - operatingSystem: Platform.linux, - resolvedExecutable: io.Platform.resolvedExecutable), - processRunner: ProcessRunner( - processManager: FakeProcessManager(), - ), - logger: logger, - ); - } - List stringsFromLogs(List logs) { return logs.map((log.LogRecord r) => r.message).toList(); } + final List cannedProcesses = [ + CannedProcess((List command) => command.contains('--as=label'), + stdout: ''' +//flutter/display_list:display_list_unittests +//flutter/flow:flow_unittests +//flutter/fml:fml_arc_unittests +'''), + CannedProcess((List command) => command.contains('--as=output'), + stdout: ''' +display_list_unittests +flow_unittests +fml_arc_unittests +''') + ]; + test('query command returns builds for the host platform.', () async { - final Logger logger = Logger.test(); - final Environment env = linuxEnv(logger); + final TestEnvironment testEnvironment = TestEnvironment(engine, + abi: ffi.Abi.linuxX64, cannedProcesses: cannedProcesses); + final Environment env = testEnvironment.environment; final ToolCommandRunner runner = ToolCommandRunner( environment: env, configs: configs, @@ -85,7 +84,7 @@ void main() { ]); expect(result, equals(0)); expect( - stringsFromLogs(logger.testLogs), + stringsFromLogs(env.logger.testLogs), equals([ 'Add --verbose to see detailed information about each builder\n', '\n', @@ -105,8 +104,9 @@ void main() { test('query command with --builder returns only from the named builder.', () async { - final Logger logger = Logger.test(); - final Environment env = linuxEnv(logger); + final TestEnvironment testEnvironment = TestEnvironment(engine, + abi: ffi.Abi.linuxX64, cannedProcesses: cannedProcesses); + final Environment env = testEnvironment.environment; final ToolCommandRunner runner = ToolCommandRunner( environment: env, configs: configs, @@ -119,7 +119,7 @@ void main() { ]); expect(result, equals(0)); expect( - stringsFromLogs(logger.testLogs), + stringsFromLogs(env.logger.testLogs), equals([ 'Add --verbose to see detailed information about each builder\n', '\n', @@ -132,8 +132,9 @@ void main() { }); test('query command with --all returns all builds.', () async { - final Logger logger = Logger.test(); - final Environment env = linuxEnv(logger); + final TestEnvironment testEnvironment = TestEnvironment(engine, + abi: ffi.Abi.linuxX64, cannedProcesses: cannedProcesses); + final Environment env = testEnvironment.environment; final ToolCommandRunner runner = ToolCommandRunner( environment: env, configs: configs, @@ -145,8 +146,29 @@ void main() { ]); expect(result, equals(0)); expect( - logger.testLogs.length, + env.logger.testLogs.length, equals(30), ); }); + + test('query tests', () async { + final TestEnvironment testEnvironment = TestEnvironment(engine, + abi: ffi.Abi.linuxX64, cannedProcesses: cannedProcesses); + final Environment env = testEnvironment.environment; + final ToolCommandRunner runner = ToolCommandRunner( + environment: env, + configs: configs, + ); + final int result = await runner.run([ + 'query', + 'tests', + ]); + expect(result, equals(0)); + expect( + env.logger.testLogs.length, + equals(3), + ); + expect(env.logger.testLogs[0].message, + startsWith('//flutter/display_list:display_list_unittests')); + }); } diff --git a/tools/engine_tool/test/test_command_test.dart b/tools/engine_tool/test/test_command_test.dart new file mode 100644 index 0000000000000..d7629cedbeb79 --- /dev/null +++ b/tools/engine_tool/test/test_command_test.dart @@ -0,0 +1,86 @@ +// Copyright 2013 The Flutter 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 'dart:convert' as convert; +import 'dart:ffi' as ffi show Abi; +import 'dart:io' as io; + +import 'package:engine_build_configs/engine_build_configs.dart'; +import 'package:engine_repo_tools/engine_repo_tools.dart'; +import 'package:engine_tool/src/commands/command_runner.dart'; +import 'package:engine_tool/src/environment.dart'; +import 'package:litetest/litetest.dart'; + +import 'fixtures.dart' as fixtures; +import 'utils.dart'; + +void main() { + final Engine engine; + try { + engine = Engine.findWithin(); + } catch (e) { + io.stderr.writeln(e); + io.exitCode = 1; + return; + } + + final BuilderConfig linuxTestConfig = BuilderConfig.fromJson( + path: 'ci/builders/linux_test_config.json', + map: convert.jsonDecode(fixtures.testConfig('Linux')) + as Map, + ); + + final BuilderConfig macTestConfig = BuilderConfig.fromJson( + path: 'ci/builders/mac_test_config.json', + map: convert.jsonDecode(fixtures.testConfig('Mac-12')) + as Map, + ); + + final BuilderConfig winTestConfig = BuilderConfig.fromJson( + path: 'ci/builders/win_test_config.json', + map: convert.jsonDecode(fixtures.testConfig('Windows-11')) + as Map, + ); + + final Map configs = { + 'linux_test_config': linuxTestConfig, + 'linux_test_config2': linuxTestConfig, + 'mac_test_config': macTestConfig, + 'win_test_config': winTestConfig, + }; + + final List cannedProcesses = [ + CannedProcess((List command) => command.contains('--as=label'), + stdout: ''' +//flutter/display_list:display_list_unittests +//flutter/flow:flow_unittests +//flutter/fml:fml_arc_unittests +'''), + CannedProcess((List command) => command.contains('--as=output'), + stdout: ''' +display_list_unittests +flow_unittests +fml_arc_unittests +''') + ]; + + test('test command executes test', () async { + final TestEnvironment testEnvironment = TestEnvironment(engine, + abi: ffi.Abi.linuxX64, cannedProcesses: cannedProcesses); + final Environment env = testEnvironment.environment; + final ToolCommandRunner runner = ToolCommandRunner( + environment: env, + configs: configs, + ); + final int result = await runner.run([ + 'test', + '//flutter/display_list:display_list_unittests', + ]); + expect(result, equals(0)); + expect(testEnvironment.processHistory.length, greaterThan(3)); + final int offset = testEnvironment.processHistory.length - 1; + expect(testEnvironment.processHistory[offset].command[0], + endsWith('display_list_unittests')); + }); +} diff --git a/tools/engine_tool/test/utils.dart b/tools/engine_tool/test/utils.dart new file mode 100644 index 0000000000000..02abc466e10f2 --- /dev/null +++ b/tools/engine_tool/test/utils.dart @@ -0,0 +1,114 @@ +// Copyright 2013 The Flutter 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 'dart:ffi' as ffi show Abi; +import 'dart:io' as io; + +import 'package:engine_repo_tools/engine_repo_tools.dart'; +import 'package:engine_tool/src/environment.dart'; +import 'package:engine_tool/src/logger.dart'; +import 'package:platform/platform.dart'; +import 'package:process_fakes/process_fakes.dart'; +import 'package:process_runner/process_runner.dart'; + +/// Each CannedProcess has a command matcher and the result of an executed +/// process. The matcher is used to determine when to use a registered +/// CannedProcess. +class CannedProcess { + CannedProcess( + this.commandMatcher, { + int exitCode = 0, + String stdout = '', + String stderr = '', + }) : _exitCode = exitCode, + _stdout = stdout, + _stderr = stderr; + + final bool Function(List command) commandMatcher; + final int _exitCode; + final String _stdout; + final String _stderr; + + FakeProcess get fakeProcess { + return FakeProcess(exitCode: _exitCode, stdout: _stdout, stderr: _stderr); + } +} + +/// ExecutedProcess includes the command and the result. +class ExecutedProcess { + ExecutedProcess(this.command, this.result); + final List command; + final FakeProcess result; + + @override + String toString() { + return command.join(' '); + } +} + +/// TestEnvironment includes an Environment with some test-specific extras. +class TestEnvironment { + TestEnvironment( + Engine engine, { + Logger? logger, + ffi.Abi abi = ffi.Abi.macosArm64, + this.cannedProcesses = const [], + }) { + logger ??= Logger.test(); + environment = Environment( + abi: abi, + engine: engine, + platform: FakePlatform( + operatingSystem: _operatingSystemForAbi(abi), + resolvedExecutable: io.Platform.resolvedExecutable), + processRunner: ProcessRunner( + processManager: FakeProcessManager(onStart: (List command) { + final FakeProcess processResult = + _getCannedResult(command, cannedProcesses); + processHistory.add(ExecutedProcess(command, processResult)); + return processResult; + }, onRun: (List command) { + throw UnimplementedError('onRun'); + })), + logger: logger, + ); + } + + /// Environment. + late final Environment environment; + + /// List of CannedProcesses that are registered in this environment. + final List cannedProcesses; + + /// A history of all executed processes. + final List processHistory = []; +} + +String _operatingSystemForAbi(ffi.Abi abi) { + switch (abi) { + case ffi.Abi.linuxArm: + case ffi.Abi.linuxArm64: + case ffi.Abi.linuxIA32: + case ffi.Abi.linuxX64: + case ffi.Abi.linuxRiscv32: + case ffi.Abi.linuxRiscv64: + return Platform.linux; + case ffi.Abi.macosArm64: + case ffi.Abi.macosX64: + return Platform.macOS; + default: + throw UnimplementedError('Unhandled abi=$abi'); + } +} + +FakeProcess _getCannedResult( + List command, List cannedProcesses) { + for (final CannedProcess cp in cannedProcesses) { + final bool matched = cp.commandMatcher(command); + if (matched) { + return cp.fakeProcess; + } + } + return FakeProcess(); +} diff --git a/tools/fuchsia/build_fuchsia_artifacts.py b/tools/fuchsia/build_fuchsia_artifacts.py index e170a699555f5..8cb3ea25d39a3 100755 --- a/tools/fuchsia/build_fuchsia_artifacts.py +++ b/tools/fuchsia/build_fuchsia_artifacts.py @@ -22,7 +22,7 @@ _script_dir = os.path.abspath(os.path.join(os.path.realpath(__file__), '..')) _src_root_dir = os.path.join(_script_dir, '..', '..', '..') -_out_dir = os.path.join(_src_root_dir, 'out') +_out_dir = os.path.join(_src_root_dir, 'out', 'ci') _bucket_directory = os.path.join(_out_dir, 'fuchsia_bucket') diff --git a/tools/pkg/engine_build_configs/bin/check.dart b/tools/pkg/engine_build_configs/bin/check.dart index 7c3e759311a71..2e057e93f4597 100644 --- a/tools/pkg/engine_build_configs/bin/check.dart +++ b/tools/pkg/engine_build_configs/bin/check.dart @@ -2,11 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:io' as io; +import 'dart:io' as io show Directory, exitCode, stderr; import 'package:engine_build_configs/engine_build_configs.dart'; import 'package:engine_repo_tools/engine_repo_tools.dart'; import 'package:path/path.dart' as p; +import 'package:platform/platform.dart'; // Usage: // $ dart bin/check.dart [/path/to/engine/src] @@ -55,30 +56,102 @@ void main(List args) { } // Check the parsed build configs for validity. + final List invalidErrors = checkForInvalidConfigs(configs); + if (invalidErrors.isNotEmpty) { + invalidErrors.forEach(io.stderr.writeln); + io.exitCode = 1; + } + + // We require all builds within a builder config to be uniquely named. + final List duplicateErrors = checkForDuplicateConfigs(configs); + if (duplicateErrors.isNotEmpty) { + duplicateErrors.forEach(io.stderr.writeln); + io.exitCode = 1; + } + + // We require all builds to be named in a way that is understood by et. + final List buildNameErrors = checkForInvalidBuildNames(configs); + if (buildNameErrors.isNotEmpty) { + buildNameErrors.forEach(io.stderr.writeln); + io.exitCode = 1; + } +} + +// This check ensures that all the json files were deserialized without errors. +List checkForInvalidConfigs(Map configs) { + final List errors = []; for (final String name in configs.keys) { final BuilderConfig buildConfig = configs[name]!; final List buildConfigErrors = buildConfig.check(name); if (buildConfigErrors.isNotEmpty) { - io.stderr.writeln('Errors in ${buildConfig.path}:'); - io.exitCode = 1; + errors.add('Errors in ${buildConfig.path}:'); } for (final String error in buildConfigErrors) { - io.stderr.writeln(' $error'); - io.exitCode = 1; + errors.add(' $error'); } } + return errors; +} - // We require all builds within a builder config to be uniquely named. +// Thjs check ensures that json files do not contain builds with duplicate +// names. +List checkForDuplicateConfigs(Map configs) { + final List errors = []; final Map> builderBuildSet = >{}; + _forEachBuild(configs, (String name, BuilderConfig config, Build build) { + final Set builds = builderBuildSet.putIfAbsent(name, () => {}); + if (builds.contains(build.name)) { + errors.add('${build.name} is duplicated in $name\n'); + } else { + builds.add(build.name); + } + }); + return errors; +} + +// This check ensures that builds are named in a way that is understood by +// `et`. +List checkForInvalidBuildNames(Map configs) { + final List errors = []; + + // In local_engine.json, allowed OS names are linux, macos, and windows. + final List osNames = [ + Platform.linux, Platform.macOS, Platform.windows, + ].expand((String s) => ['$s/', '$s\\']).toList(); + + // In all other build json files, allowed prefix names are ci and web_tests. + final List ciNames = [ + 'ci', 'web_tests' + ].expand((String s) => ['$s/', '$s\\']).toList(); + + _forEachBuild(configs, (String name, BuilderConfig config, Build build) { + final List goodPrefixes = name.contains('local_engine') + ? osNames + : ciNames; + if (!goodPrefixes.any(build.name.startsWith)) { + if (name.contains('local_engine')) { + // TODO(zanderso): Check these builds as well after local_engine is + // fixed. + // https://github.com/flutter/flutter/issues/145263 + return; + } + errors.add( + '${build.name} in $name must start with one of ' + '{${goodPrefixes.join(', ')}}', + ); + } + }); + return errors; +} + +void _forEachBuild( + Map configs, + void Function(String configName, BuilderConfig config, Build build) fn, +) { for (final String builderName in configs.keys) { final BuilderConfig builderConfig = configs[builderName]!; - final Set builds = - builderBuildSet.putIfAbsent(builderName, () => {}); for (final Build build in builderConfig.builds) { - if (builds.contains(build.name)) { - io.stderr.writeln('${build.name} is duplicated in $builderName\n'); - io.exitCode = 1; - } + fn(builderName, builderConfig, build); } } } diff --git a/tools/pub_get_offline.py b/tools/pub_get_offline.py index 5f603b3a6f62a..a5b70a1ba9fa9 100644 --- a/tools/pub_get_offline.py +++ b/tools/pub_get_offline.py @@ -26,6 +26,7 @@ os.path.join(ENGINE_DIR, 'testing', 'benchmark'), os.path.join(ENGINE_DIR, 'testing', 'dart'), os.path.join(ENGINE_DIR, 'testing', 'litetest'), + os.path.join(ENGINE_DIR, 'testing', 'pkg_test_demo'), os.path.join(ENGINE_DIR, 'testing', 'scenario_app'), os.path.join(ENGINE_DIR, 'testing', 'skia_gold_client'), os.path.join(ENGINE_DIR, 'testing', 'smoke_test_failure'), @@ -34,6 +35,7 @@ os.path.join(ENGINE_DIR, 'tools', 'api_check'), os.path.join(ENGINE_DIR, 'tools', 'build_bucket_golden_scraper'), os.path.join(ENGINE_DIR, 'tools', 'clang_tidy'), + os.path.join(ENGINE_DIR, 'tools', 'clangd_check'), os.path.join(ENGINE_DIR, 'tools', 'compare_goldens'), os.path.join(ENGINE_DIR, 'tools', 'const_finder'), os.path.join(ENGINE_DIR, 'tools', 'dir_contents_diff'), @@ -91,6 +93,7 @@ def check_package(package): os.path.join(ENGINE_DIR, 'shell', 'platform', 'fuchsia'), os.path.join(ENGINE_DIR, 'shell', 'vmservice'), os.path.join(ENGINE_DIR, 'sky', 'packages'), + os.path.join(ENGINE_DIR, 'testing', 'pkg_test_demo'), os.path.join(ENGINE_DIR, 'third_party'), os.path.join(ENGINE_DIR, 'web_sdk'), ]