diff --git a/.cirrus.yml b/.cirrus.yml index 1f9e1e6b505ee..53f29cd4d5190 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -3,13 +3,15 @@ gcp_credentials: ENCRYPTED[987a78af29b91ce8489594c9ab3fec21845bbe5ba68294b8f6def # LINUX task: gke_container: - image: gcr.io/flutter-cirrus/build-engine-image:latest - cluster_name: build-32-cluster - zone: us-central1-a - namespace: default - cpu: 30 # can't use all 30-cores; system pods needs cores too - memory: 100Gb # similarly, can't use all 100Gb memory + dockerfile: "ci/docker/build/Dockerfile" + builder_image_name: docker-builder # gce vm image + cluster_name: build-32-cluster + zone: us-central1-a + namespace: default + cpu: 30 # can't use all 30-cores; system pods needs cores too + memory: 100Gb # similarly, can't use all 100Gb memory env: + CIRRUS_DOCKER_CONTEXT: "ci/docker/build" CIRRUS_WORKING_DIR: "/tmp/github_repo" ENGINE_PATH: "/tmp/clean_engine" DEPOT_TOOLS: "/tmp/depot_tools" @@ -17,6 +19,9 @@ task: FRAMEWORK_PATH: "/tmp/master_framework" PATH: "$FLUTTER_ENGINE/third_party/dart/tools/sdks/dart-sdk/bin:$DEPOT_TOOLS:$PATH" USE_ANDROID: "False" + # TODO(liyuqian): currently we're using flutter-cirrus GCP project. Migrate + # to flutter-infra project once the metrics_center service is stabilized, + BENCHMARK_GCP_CREDENTIALS: ENCRYPTED[da76d2b7b39894de70fae1fc9182c97cc41400adc93f0f1c49bc7442f15fb933da8d756ed88523810a9a77c34f51a693] setup_script: | git clone --depth 1 https://chromium.googlesource.com/chromium/tools/depot_tools.git $DEPOT_TOOLS mkdir -p $ENGINE_PATH/src @@ -27,6 +32,18 @@ task: mv $CIRRUS_WORKING_DIR flutter gclient sync matrix: + - name: build_and_benchmark_linux_release + only_if: $CIRRUS_BRANCH == 'master' # Only run for post-submit commits. + compile_host_script: | + cd $ENGINE_PATH/src + ./flutter/tools/gn --runtime-mode=release + ninja -C out/host_release + benchmark_host_script: | + cd $ENGINE_PATH/src/out/host_release/ + ./txt_benchmarks --benchmark_format=json > txt_benchmarks.json + cd $ENGINE_PATH/src/flutter/testing/benchmark + pub get + dart bin/parse_and_send.dart ../../../out/host_release/txt_benchmarks.json - name: build_and_test_linux_unopt_debug compile_host_script: | cd $ENGINE_PATH/src @@ -73,8 +90,8 @@ task: $ENGINE_PATH/src/out/host_debug_unopt/dart-sdk/bin/pub get cd $ENGINE_PATH/src/flutter/lib/web_ui $ENGINE_PATH/src/out/host_debug_unopt/dart-sdk/bin/pub get - export DART="$ENGINE_PATH/src/out/host_debug_unopt/dart-sdk/bin/dart" - $DART dev/firefox_installer_test.dart + export FELT="$ENGINE_PATH/src/out/host_debug_unopt/dart-sdk/bin/dart dev/felt.dart" + $FELT test --browser=firefox - name: build_and_test_android_unopt_debug env: USE_ANDROID: "True" diff --git a/.github/auto_assign.yml b/.github/auto_assign.yml new file mode 100644 index 0000000000000..5f1411b07cadd --- /dev/null +++ b/.github/auto_assign.yml @@ -0,0 +1,40 @@ +# This is the config file for `auto-assign` bot. +# https://github.com/kentaro-m/auto-assign/ + +# Set to true to add reviewers to pull requests +addReviewers: true + +# Set to true to add assignees to pull requests +addAssignees: false + +# A list of reviewers to be added to pull requests (GitHub user name) +# Note: Add new engine contributors here when joining the team. +reviewers: + - gaaclarke + - liyuqian + - gw280 + - chinmaygarde + - GaryQian + - jason-simmons + - iskakaushik + - franciscojma86 + - cbracken + - flar + - stuartmorgan + +# A number of reviewers added to the pull request +# Set 0 to add all the reviewers (default: 0) +numberOfReviewers: 1 + +# A list of assignees, overrides reviewers if set +# assignees: +# - assigneeA + +# A number of assignees to add to the pull request +# Set to 0 to add all of the assignees. +# Uses numberOfReviewers if unset. +# numberOfAssignees: 2 + +# A list of keywords to be skipped the process that add reviewers if pull requests include it +skipKeywords: + - Roll diff --git a/BUILD.gn b/BUILD.gn index 1cffa58132c4c..ce3a55c500b22 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -31,6 +31,10 @@ group("flutter") { "$flutter_root/sky", ] + if (current_toolchain == host_toolchain) { + public_deps += [ "$flutter_root/tools/font-subset" ] + } + if (current_toolchain == host_toolchain) { public_deps += [ "$flutter_root/shell/testing" ] } @@ -73,6 +77,7 @@ group("flutter") { "$flutter_root/shell/platform/common/cpp/client_wrapper:client_wrapper_unittests", "$flutter_root/shell/platform/embedder:embedder_unittests", "$flutter_root/shell/platform/glfw/client_wrapper:client_wrapper_glfw_unittests", + "$flutter_root/testing:testing_unittests", "$flutter_root/third_party/txt:txt_unittests", ] @@ -118,3 +123,18 @@ group("dist") { "$flutter_root/sky/dist", ] } + +# Fuchsia currently only supports a subset of our unit tests +# When adding a new dep here, please also ensure the dep is added to +# testing/fuchsia/run_tests.sh and testing/fuchsia/test_fars +if (is_fuchsia) { + group("fuchsia_tests") { + testonly = true + + deps = [ + "$flutter_root/flow:flow_tests", + "$flutter_root/fml:fml_tests", + "$flutter_root/shell/platform/fuchsia/flutter:flutter_runner_tests", + ] + } +} diff --git a/DEPS b/DEPS index 4519e6a5ff476..a5486b9899a26 100644 --- a/DEPS +++ b/DEPS @@ -26,7 +26,7 @@ vars = { 'skia_git': 'https://skia.googlesource.com', # OCMock is for testing only so there is no google clone 'ocmock_git': 'https://github.com/erikdoe/ocmock.git', - 'skia_revision': '8e083eee8ece4e1acaeb01ae4192050969c4b0c6', + 'skia_revision': '91e0d7526944f714706ec9510c346bd4eab3d5f1', # When updating the Dart revision, ensure that all entries that are # dependencies of Dart are also updated to match the entries in the @@ -34,7 +34,7 @@ vars = { # Dart is: https://github.com/dart-lang/sdk/blob/master/DEPS. # You can use //tools/dart/create_updated_flutter_deps.py to produce # updated revision list of existing dependencies. - 'dart_revision': 'bbe2ac28c9ab5bfec4e13d5df9360e7aa691cfe6', + 'dart_revision': 'c0ca187f2699fb7f46fccb8c664a2ba3a0958fce', # WARNING: DO NOT EDIT MANUALLY # The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py @@ -63,7 +63,7 @@ vars = { 'dart_http_throttle_tag': '1.0.2', 'dart_intl_tag': '0.15.7', 'dart_json_rpc_2_tag': '2.0.9', - 'dart_linter_tag': '0.1.102', + 'dart_linter_tag': '0.1.106', 'dart_logging_tag': '0.11.3+2', 'dart_markdown_tag': '2.1.1', 'dart_matcher_tag': '0.12.3', @@ -78,7 +78,7 @@ vars = { 'dart_pedantic_tag': 'v1.8.0', 'dart_pool_tag': '1.3.6', 'dart_protobuf_rev': '3746c8fd3f2b0147623a8e3db89c3ff4330de760', - 'dart_pub_rev': '80ac76400ff58fde3c5a335d860d196c3febe837', + 'dart_pub_rev': '4d8ecbd409d773fec47da33b7e6c8db0b51487fd', 'dart_pub_semver_tag': '1.4.2', 'dart_quiver-dart_tag': '2.0.0+1', 'dart_resource_rev': 'f8e37558a1c4f54550aa463b88a6a831e3e33cd6', @@ -99,7 +99,7 @@ vars = { 'dart_tflite_native_rev': '3c777c40608a2a9f1427bfe0028ab48e7116b4c1', 'dart_typed_data_tag': '1.1.6', 'dart_usage_tag': '3.4.0', - 'dart_watcher_rev': '0.9.7+12-pub', + 'dart_watcher_rev': '0.9.7+13', 'dart_web_socket_channel_tag': '1.0.9', 'dart_yaml_tag': '2.2.0', @@ -137,7 +137,7 @@ allowed_hosts = [ ] deps = { - 'src': 'https://github.com/flutter/buildroot.git' + '@' + 'a518e359c41e00f964f7cc079cbc5ef525f82516', + 'src': 'https://github.com/flutter/buildroot.git' + '@' + '8a6510fad8f4e0cb7570a677fa25720f751d34a9', # Fuchsia compatibility # @@ -152,7 +152,7 @@ deps = { Var('fuchsia_git') + '/third_party/benchmark' + '@' + 'a779ffce872b4c811beef482e18bd0b63626aa42', 'src/third_party/googletest': - Var('fuchsia_git') + '/third_party/googletest' + '@' + 'd8827ca8e397b725a3039b19cc116e309c47815e', + Var('fuchsia_git') + '/third_party/googletest' + '@' + '3fef9015bfe7617d57806cd7c73a653b28559fae', 'src/third_party/rapidjson': Var('fuchsia_git') + '/third_party/rapidjson' + '@' + '32d07c55db1bb6c2ae17cba4033491a667647753', @@ -192,7 +192,7 @@ deps = { # WARNING: Unused Dart dependencies in the list below till "WARNING:" marker are removed automatically - see create_updated_flutter_deps.py. 'src/third_party/dart/pkg/analysis_server/language_model': - {'packages': [{'version': '9fJQZ0TrnAGQKrEtuL3-AXbUfPzYxqpN_OBHr9P4hE4C', 'package': 'dart/language_model'}], 'dep_type': 'cipd'}, + {'packages': [{'version': 'lIRt14qoA1Cocb8j3yw_Fx5cfYou2ddam6ArBm4AI6QC', 'package': 'dart/language_model'}], 'dep_type': 'cipd'}, 'src/third_party/dart/third_party/observatory_pub_packages': Var('dart_git') + '/observatory_pub_packages.git' + '@' + Var('dart_observatory_pub_packages_rev'), @@ -231,7 +231,7 @@ deps = { Var('dart_git') + '/dart2js_info.git' + '@' + Var('dart_dart2js_info_tag'), 'src/third_party/dart/third_party/pkg/dartdoc': - Var('dart_git') + '/dartdoc.git@v0.28.8', + Var('dart_git') + '/dartdoc.git@v0.29.3', 'src/third_party/dart/third_party/pkg/ffi': Var('dart_git') + '/ffi.git' + '@' + Var('dart_ffi_tag'), @@ -381,7 +381,7 @@ deps = { Var('dart_git') + '/package_resolver.git' + '@' + Var('dart_package_resolver_tag'), 'src/third_party/dart/tools/sdks': - {'packages': [{'version': 'version:2.6.0-dev.4.0', 'package': 'dart/dart-sdk/${{platform}}'}], 'dep_type': 'cipd'}, + {'packages': [{'version': 'version:2.8.0-dev.0.0', 'package': 'dart/dart-sdk/${{platform}}'}], 'dep_type': 'cipd'}, # WARNING: end of dart dependencies list that is cleaned up automatically - see create_updated_flutter_deps.py. @@ -420,7 +420,7 @@ deps = { Var('github_git') + '/KhronosGroup/Vulkan-Docs.git' + '@' + 'v1.1.91', 'src/third_party/swiftshader': - Var('swiftshader_git') + '/SwiftShader.git' + '@' + 'd70129a3d3409dac58e14f819b62620393afb652', + Var('swiftshader_git') + '/SwiftShader.git' + '@' + '5d1e8540407c138f47028d64684f3da599430aa4', 'src/third_party/angle': Var('github_git') + '/google/angle.git' + '@' + '3ea90d609720b7b9b9d05ca094860382f2425294', @@ -494,21 +494,11 @@ deps = { 'dep_type': 'cipd', }, - 'src/third_party/dart/tools/sdks': { - 'packages': [ - { - 'package': 'dart/dart-sdk/${{platform}}', - 'version': 'version:2.4.0' - } - ], - 'dep_type': 'cipd', - }, - 'src/third_party/dart/pkg/analysis_server/language_model': { 'packages': [ { 'package': 'dart/language_model', - 'version': '9fJQZ0TrnAGQKrEtuL3-AXbUfPzYxqpN_OBHr9P4hE4C', + 'version': 'lIRt14qoA1Cocb8j3yw_Fx5cfYou2ddam6ArBm4AI6QC', } ], 'dep_type': 'cipd', @@ -542,7 +532,7 @@ deps = { 'packages': [ { 'package': 'fuchsia/sdk/core/mac-amd64', - 'version': 'ggsQ5vCgXEhoHFZSNBKKZWxStVwTPSlaOD2fRdJyL_gC' + 'version': 'gL-XGLFjK54v2X1duSqq3mbo3SqNeCu9gl0eNqLxTJoC' } ], 'condition': 'host_os == "mac"', @@ -552,7 +542,7 @@ deps = { 'packages': [ { 'package': 'fuchsia/clang/mac-amd64', - 'version': '6tttOTlynvfnjkMSTokwfI4sj7gfuyeF02Y6F7zwBKkC' + 'version': 'OzTZOKkICT0yD82Dbx0jvVn5hN5eOSi6ByVTDseE7i0C' } ], 'condition': 'host_os == "mac"', @@ -562,7 +552,7 @@ deps = { 'packages': [ { 'package': 'fuchsia/sdk/core/linux-amd64', - 'version': 'dhwMRVa1EqOssqHjmArBMM7Ne4-LwwiF1c51klzy318C' + 'version': 'oNKzr_f8lbFMtdLNoE5sJHpi8K-9fm1uYYV7Z-lwR8gC' } ], 'condition': 'host_os == "linux"', @@ -572,7 +562,7 @@ deps = { 'packages': [ { 'package': 'fuchsia/clang/linux-amd64', - 'version': 'WxGHgTOyvYvcynHb6cBxdHIwSdivQJecIrb5NRHvxfwC' + 'version': 'OT6p30bQQhyCzRSy7xPsSbZ88J3PWOnneenkMZ0j7kIC' } ], 'condition': 'host_os == "linux"', diff --git a/README.md b/README.md index fe2d5b2ac4ee0..97cf4c099c218 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,8 @@ Flutter Engine [![Build Status - Cirrus][]][Build status] Flutter is Google's mobile app SDK for crafting high-quality native interfaces -on iOS and Android in record time. Flutter works with existing code, is used by -developers and organizations around the world, and is free and open source. +in record time. Flutter works with existing code, is used by developers and +organizations around the world, and is free and open source. The Flutter Engine is a portable runtime for hosting [Flutter](https://flutter.dev) applications. It implements Flutter's core diff --git a/ci/docker/build/README.md b/ci/docker/build/README.md index a4f12e16ca34e..79f0d92708418 100644 --- a/ci/docker/build/README.md +++ b/ci/docker/build/README.md @@ -4,9 +4,16 @@ building flutter/engine in our CI system (currently [Cirrus](cirrus-ci.org)). In order to run the scripts, you have to setup `docker` and `gcloud`. Please refer to internal doc go/installdocker for how to setup `docker` on gLinux. -After setup, -* edit `Dockerfile` to change how the container image is built. -* run `./build_docker.sh` to build the container image. -* run `./push_docker.sh` to push the image to google cloud registry. This will - affect our CI tests. +Cirrus will build (and cache) a Docker image based on this `Dockerfile` for +Linux tasks using its +[Dockerfile as CI](https://cirrus-ci.org/guide/docker-builder-vm/) feature. +Any change to the `Dockerfile` will cause a new task to be triggered to build +and tag a new version of the Docker image which will be a dependency of the +other Linux tasks. This task will instantiate a new GCP VM based on the image +specified in the `.cirrus.yml` `builder_image_name` field. +To test changes to the Linux `Dockerfile`, create a PR with the changes, and +Cirrus will attempt to build a new image. + +To debug locally, you can build an image with `./build_docker.sh`, but pushing +to the registry is no longer necessary. diff --git a/ci/format.sh b/ci/format.sh index bff94d77b07dd..63818fc2d1f89 100755 --- a/ci/format.sh +++ b/ci/format.sh @@ -42,7 +42,7 @@ fi; BASE_SHA="$(git fetch $UPSTREAM master > /dev/null 2>&1 && \ (git merge-base --fork-point FETCH_HEAD HEAD || git merge-base FETCH_HEAD HEAD))" -FILES_TO_CHECK="$(git diff $DIFF_OPTS $BASE_SHA..HEAD -- $FILETYPES)" +FILES_TO_CHECK="$(git diff $DIFF_OPTS $BASE_SHA -- $FILETYPES)" FAILED_CHECKS=0 for f in $FILES_TO_CHECK; do diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index d9b30258d49e8..0c1905ccd6cd7 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -30,24 +30,39 @@ FILE: ../../../flutter/flow/instrumentation.cc FILE: ../../../flutter/flow/instrumentation.h FILE: ../../../flutter/flow/layers/backdrop_filter_layer.cc FILE: ../../../flutter/flow/layers/backdrop_filter_layer.h +FILE: ../../../flutter/flow/layers/backdrop_filter_layer_unittests.cc FILE: ../../../flutter/flow/layers/child_scene_layer.cc FILE: ../../../flutter/flow/layers/child_scene_layer.h FILE: ../../../flutter/flow/layers/clip_path_layer.cc FILE: ../../../flutter/flow/layers/clip_path_layer.h +FILE: ../../../flutter/flow/layers/clip_path_layer_unittests.cc FILE: ../../../flutter/flow/layers/clip_rect_layer.cc FILE: ../../../flutter/flow/layers/clip_rect_layer.h +FILE: ../../../flutter/flow/layers/clip_rect_layer_unittests.cc FILE: ../../../flutter/flow/layers/clip_rrect_layer.cc FILE: ../../../flutter/flow/layers/clip_rrect_layer.h +FILE: ../../../flutter/flow/layers/clip_rrect_layer_unittests.cc FILE: ../../../flutter/flow/layers/color_filter_layer.cc FILE: ../../../flutter/flow/layers/color_filter_layer.h +FILE: ../../../flutter/flow/layers/color_filter_layer_unittests.cc FILE: ../../../flutter/flow/layers/container_layer.cc FILE: ../../../flutter/flow/layers/container_layer.h +FILE: ../../../flutter/flow/layers/container_layer_unittests.cc +FILE: ../../../flutter/flow/layers/elevated_container_layer.cc +FILE: ../../../flutter/flow/layers/elevated_container_layer.h +FILE: ../../../flutter/flow/layers/fuchsia_system_composited_layer.cc +FILE: ../../../flutter/flow/layers/fuchsia_system_composited_layer.h +FILE: ../../../flutter/flow/layers/image_filter_layer.cc +FILE: ../../../flutter/flow/layers/image_filter_layer.h +FILE: ../../../flutter/flow/layers/image_filter_layer_unittests.cc FILE: ../../../flutter/flow/layers/layer.cc FILE: ../../../flutter/flow/layers/layer.h FILE: ../../../flutter/flow/layers/layer_tree.cc FILE: ../../../flutter/flow/layers/layer_tree.h +FILE: ../../../flutter/flow/layers/layer_tree_unittests.cc FILE: ../../../flutter/flow/layers/opacity_layer.cc FILE: ../../../flutter/flow/layers/opacity_layer.h +FILE: ../../../flutter/flow/layers/opacity_layer_unittests.cc FILE: ../../../flutter/flow/layers/performance_overlay_layer.cc FILE: ../../../flutter/flow/layers/performance_overlay_layer.h FILE: ../../../flutter/flow/layers/performance_overlay_layer_unittests.cc @@ -56,14 +71,19 @@ FILE: ../../../flutter/flow/layers/physical_shape_layer.h FILE: ../../../flutter/flow/layers/physical_shape_layer_unittests.cc FILE: ../../../flutter/flow/layers/picture_layer.cc FILE: ../../../flutter/flow/layers/picture_layer.h +FILE: ../../../flutter/flow/layers/picture_layer_unittests.cc FILE: ../../../flutter/flow/layers/platform_view_layer.cc FILE: ../../../flutter/flow/layers/platform_view_layer.h +FILE: ../../../flutter/flow/layers/platform_view_layer_unittests.cc FILE: ../../../flutter/flow/layers/shader_mask_layer.cc FILE: ../../../flutter/flow/layers/shader_mask_layer.h +FILE: ../../../flutter/flow/layers/shader_mask_layer_unittests.cc FILE: ../../../flutter/flow/layers/texture_layer.cc FILE: ../../../flutter/flow/layers/texture_layer.h +FILE: ../../../flutter/flow/layers/texture_layer_unittests.cc FILE: ../../../flutter/flow/layers/transform_layer.cc FILE: ../../../flutter/flow/layers/transform_layer.h +FILE: ../../../flutter/flow/layers/transform_layer_unittests.cc FILE: ../../../flutter/flow/matrix_decomposition.cc FILE: ../../../flutter/flow/matrix_decomposition.h FILE: ../../../flutter/flow/matrix_decomposition_unittests.cc @@ -98,6 +118,8 @@ FILE: ../../../flutter/fml/command_line_unittest.cc FILE: ../../../flutter/fml/compiler_specific.h FILE: ../../../flutter/fml/concurrent_message_loop.cc FILE: ../../../flutter/fml/concurrent_message_loop.h +FILE: ../../../flutter/fml/dart/dart_converter.cc +FILE: ../../../flutter/fml/dart/dart_converter.h FILE: ../../../flutter/fml/delayed_task.cc FILE: ../../../flutter/fml/delayed_task.h FILE: ../../../flutter/fml/eintr_wrapper.h @@ -170,6 +192,8 @@ FILE: ../../../flutter/fml/platform/darwin/scoped_nsobject.mm FILE: ../../../flutter/fml/platform/darwin/string_range_sanitization.h FILE: ../../../flutter/fml/platform/darwin/string_range_sanitization.mm FILE: ../../../flutter/fml/platform/darwin/string_range_sanitization_unittests.mm +FILE: ../../../flutter/fml/platform/fuchsia/message_loop_fuchsia.cc +FILE: ../../../flutter/fml/platform/fuchsia/message_loop_fuchsia.h FILE: ../../../flutter/fml/platform/fuchsia/paths_fuchsia.cc FILE: ../../../flutter/fml/platform/linux/message_loop_linux.cc FILE: ../../../flutter/fml/platform/linux/message_loop_linux.h @@ -203,6 +227,9 @@ FILE: ../../../flutter/fml/synchronization/semaphore_unittest.cc FILE: ../../../flutter/fml/synchronization/shared_mutex.h FILE: ../../../flutter/fml/synchronization/shared_mutex_std.cc FILE: ../../../flutter/fml/synchronization/shared_mutex_std.h +FILE: ../../../flutter/fml/synchronization/sync_switch.cc +FILE: ../../../flutter/fml/synchronization/sync_switch.h +FILE: ../../../flutter/fml/synchronization/sync_switch_unittest.cc FILE: ../../../flutter/fml/synchronization/waitable_event.cc FILE: ../../../flutter/fml/synchronization/waitable_event.h FILE: ../../../flutter/fml/synchronization/waitable_event_unittest.cc @@ -245,6 +272,8 @@ FILE: ../../../flutter/lib/ui/dart_ui.h FILE: ../../../flutter/lib/ui/dart_wrapper.h FILE: ../../../flutter/lib/ui/fixtures/DashInNooglerHat.jpg FILE: ../../../flutter/lib/ui/fixtures/Horizontal.jpg +FILE: ../../../flutter/lib/ui/fixtures/hello_loop_2.gif +FILE: ../../../flutter/lib/ui/fixtures/hello_loop_2.webp FILE: ../../../flutter/lib/ui/fixtures/ui_test.dart FILE: ../../../flutter/lib/ui/geometry.dart FILE: ../../../flutter/lib/ui/hash_codes.dart @@ -344,6 +373,9 @@ FILE: ../../../flutter/lib/ui/window/pointer_data.cc FILE: ../../../flutter/lib/ui/window/pointer_data.h FILE: ../../../flutter/lib/ui/window/pointer_data_packet.cc FILE: ../../../flutter/lib/ui/window/pointer_data_packet.h +FILE: ../../../flutter/lib/ui/window/pointer_data_packet_converter.cc +FILE: ../../../flutter/lib/ui/window/pointer_data_packet_converter.h +FILE: ../../../flutter/lib/ui/window/pointer_data_packet_converter_unittests.cc FILE: ../../../flutter/lib/ui/window/viewport_metrics.cc FILE: ../../../flutter/lib/ui/window/viewport_metrics.h FILE: ../../../flutter/lib/ui/window/window.cc @@ -355,25 +387,32 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/assets.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/bitmap_canvas.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/browser_detection.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/browser_location.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvas_pool.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/color_filter.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/canvas.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/canvas_kit_canvas.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/color_filter.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/embedded_views.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/engine_delegate.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/fonts.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/image.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/image_filter.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/initialization.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/layer.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/layer_scene_builder.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/layer_tree.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/n_way_canvas.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/painting.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/path.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/path_metrics.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/picture.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/picture_recorder.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/platform_message.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/raster_cache.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/rasterizer.dart -FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/recording_canvas.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/runtime_delegate.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/surface.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/text.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/util.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/vertices.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/compositor/viewport_metrics.dart @@ -387,10 +426,11 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/html_image_codec.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/keyboard.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/onscreen_logging.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/path_to_svg.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/picture.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/platform_views.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/plugins.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/pointer_binding.dart -FILE: ../../../flutter/lib/web_ui/lib/src/engine/recording_canvas.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/pointer_converter.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/render_vertices.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/rrect_renderer.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/accessibility.dart @@ -401,6 +441,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/label_and_value.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/live_region.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/scrollable.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/semantics.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/semantics_helper.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/tappable.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/text_field.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/services/buffers.dart @@ -412,10 +453,14 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/shadow.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/backdrop_filter.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/clip.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/debug_canvas_reuse_overlay.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/image_filter.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/offset.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/opacity.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/painting.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/path_metrics.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/picture.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/platform_view.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/recording_canvas.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/scene.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/scene_builder.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/surface.dart @@ -444,6 +489,8 @@ FILE: ../../../flutter/lib/web_ui/lib/src/ui/initialization.dart FILE: ../../../flutter/lib/web_ui/lib/src/ui/lerp.dart FILE: ../../../flutter/lib/web_ui/lib/src/ui/natives.dart FILE: ../../../flutter/lib/web_ui/lib/src/ui/painting.dart +FILE: ../../../flutter/lib/web_ui/lib/src/ui/path.dart +FILE: ../../../flutter/lib/web_ui/lib/src/ui/path_metrics.dart FILE: ../../../flutter/lib/web_ui/lib/src/ui/pointer.dart FILE: ../../../flutter/lib/web_ui/lib/src/ui/semantics.dart FILE: ../../../flutter/lib/web_ui/lib/src/ui/test_embedding.dart @@ -454,6 +501,8 @@ FILE: ../../../flutter/lib/web_ui/lib/ui.dart FILE: ../../../flutter/lib/web_ui/tool/unicode_sync_script.dart FILE: ../../../flutter/runtime/dart_isolate.cc FILE: ../../../flutter/runtime/dart_isolate.h +FILE: ../../../flutter/runtime/dart_isolate_group_data.cc +FILE: ../../../flutter/runtime/dart_isolate_group_data.h FILE: ../../../flutter/runtime/dart_isolate_unittests.cc FILE: ../../../flutter/runtime/dart_lifecycle_unittests.cc FILE: ../../../flutter/runtime/dart_service_isolate.cc @@ -489,12 +538,14 @@ FILE: ../../../flutter/runtime/test_font_data.cc FILE: ../../../flutter/runtime/test_font_data.h FILE: ../../../flutter/shell/common/animator.cc FILE: ../../../flutter/shell/common/animator.h +FILE: ../../../flutter/shell/common/animator_unittests.cc FILE: ../../../flutter/shell/common/canvas_spy.cc FILE: ../../../flutter/shell/common/canvas_spy.h FILE: ../../../flutter/shell/common/canvas_spy_unittests.cc FILE: ../../../flutter/shell/common/engine.cc FILE: ../../../flutter/shell/common/engine.h FILE: ../../../flutter/shell/common/fixtures/shell_test.dart +FILE: ../../../flutter/shell/common/fixtures/shelltest_screenshot.png FILE: ../../../flutter/shell/common/input_events_unittests.cc FILE: ../../../flutter/shell/common/isolate_configuration.cc FILE: ../../../flutter/shell/common/isolate_configuration.h @@ -532,6 +583,8 @@ FILE: ../../../flutter/shell/common/vsync_waiter.cc FILE: ../../../flutter/shell/common/vsync_waiter.h FILE: ../../../flutter/shell/common/vsync_waiter_fallback.cc FILE: ../../../flutter/shell/common/vsync_waiter_fallback.h +FILE: ../../../flutter/shell/common/vsync_waiters_test.cc +FILE: ../../../flutter/shell/common/vsync_waiters_test.h FILE: ../../../flutter/shell/gpu/gpu_surface_delegate.h FILE: ../../../flutter/shell/gpu/gpu_surface_gl.cc FILE: ../../../flutter/shell/gpu/gpu_surface_gl.h @@ -849,6 +902,8 @@ FILE: ../../../flutter/shell/platform/embedder/embedder_external_texture_gl.h FILE: ../../../flutter/shell/platform/embedder/embedder_external_view_embedder.cc FILE: ../../../flutter/shell/platform/embedder/embedder_external_view_embedder.h FILE: ../../../flutter/shell/platform/embedder/embedder_include.c +FILE: ../../../flutter/shell/platform/embedder/embedder_layers.cc +FILE: ../../../flutter/shell/platform/embedder/embedder_layers.h FILE: ../../../flutter/shell/platform/embedder/embedder_platform_message_response.cc FILE: ../../../flutter/shell/platform/embedder/embedder_platform_message_response.h FILE: ../../../flutter/shell/platform/embedder/embedder_render_target.cc @@ -864,17 +919,21 @@ FILE: ../../../flutter/shell/platform/embedder/embedder_task_runner.cc FILE: ../../../flutter/shell/platform/embedder/embedder_task_runner.h FILE: ../../../flutter/shell/platform/embedder/embedder_thread_host.cc FILE: ../../../flutter/shell/platform/embedder/embedder_thread_host.h +FILE: ../../../flutter/shell/platform/embedder/fixtures/arc_end_caps.png FILE: ../../../flutter/shell/platform/embedder/fixtures/compositor.png FILE: ../../../flutter/shell/platform/embedder/fixtures/compositor_root_surface_xformation.png FILE: ../../../flutter/shell/platform/embedder/fixtures/compositor_software.png FILE: ../../../flutter/shell/platform/embedder/fixtures/compositor_with_platform_layer_on_bottom.png FILE: ../../../flutter/shell/platform/embedder/fixtures/compositor_with_root_layer_only.png +FILE: ../../../flutter/shell/platform/embedder/fixtures/dpr_noxform.png +FILE: ../../../flutter/shell/platform/embedder/fixtures/dpr_xform.png FILE: ../../../flutter/shell/platform/embedder/fixtures/gradient.png FILE: ../../../flutter/shell/platform/embedder/fixtures/gradient_xform.png FILE: ../../../flutter/shell/platform/embedder/fixtures/main.dart FILE: ../../../flutter/shell/platform/embedder/fixtures/scene_without_custom_compositor.png FILE: ../../../flutter/shell/platform/embedder/fixtures/scene_without_custom_compositor_with_xform.png FILE: ../../../flutter/shell/platform/embedder/fixtures/verifyb143464703.png +FILE: ../../../flutter/shell/platform/embedder/fixtures/verifyb143464703_soft_noxform.png FILE: ../../../flutter/shell/platform/embedder/platform_view_embedder.cc FILE: ../../../flutter/shell/platform/embedder/platform_view_embedder.h FILE: ../../../flutter/shell/platform/embedder/vsync_waiter_embedder.cc @@ -963,8 +1022,6 @@ FILE: ../../../flutter/shell/platform/fuchsia/flutter/kernel/extract_far.dart FILE: ../../../flutter/shell/platform/fuchsia/flutter/kernel/framework_shim.dart FILE: ../../../flutter/shell/platform/fuchsia/flutter/kernel/libraries.json FILE: ../../../flutter/shell/platform/fuchsia/flutter/logging.h -FILE: ../../../flutter/shell/platform/fuchsia/flutter/loop.cc -FILE: ../../../flutter/shell/platform/fuchsia/flutter/loop.h FILE: ../../../flutter/shell/platform/fuchsia/flutter/main.cc FILE: ../../../flutter/shell/platform/fuchsia/flutter/meta/aot_product_runtime FILE: ../../../flutter/shell/platform/fuchsia/flutter/meta/aot_runtime @@ -973,6 +1030,7 @@ FILE: ../../../flutter/shell/platform/fuchsia/flutter/meta/flutter_aot_runner.cm FILE: ../../../flutter/shell/platform/fuchsia/flutter/meta/flutter_jit_product_runner.cmx FILE: ../../../flutter/shell/platform/fuchsia/flutter/meta/flutter_jit_runner.cmx FILE: ../../../flutter/shell/platform/fuchsia/flutter/meta/flutter_runner_tests.cmx +FILE: ../../../flutter/shell/platform/fuchsia/flutter/meta/flutter_runner_tzdata_tests.cmx FILE: ../../../flutter/shell/platform/fuchsia/flutter/meta/jit_product_runtime FILE: ../../../flutter/shell/platform/fuchsia/flutter/meta/jit_runtime FILE: ../../../flutter/shell/platform/fuchsia/flutter/platform_view.cc @@ -980,21 +1038,18 @@ FILE: ../../../flutter/shell/platform/fuchsia/flutter/platform_view.h FILE: ../../../flutter/shell/platform/fuchsia/flutter/platform_view_unittest.cc FILE: ../../../flutter/shell/platform/fuchsia/flutter/runner.cc FILE: ../../../flutter/shell/platform/fuchsia/flutter/runner.h +FILE: ../../../flutter/shell/platform/fuchsia/flutter/runner_tzdata_unittest.cc +FILE: ../../../flutter/shell/platform/fuchsia/flutter/runner_unittest.cc FILE: ../../../flutter/shell/platform/fuchsia/flutter/session_connection.cc FILE: ../../../flutter/shell/platform/fuchsia/flutter/session_connection.h FILE: ../../../flutter/shell/platform/fuchsia/flutter/surface.cc FILE: ../../../flutter/shell/platform/fuchsia/flutter/surface.h -FILE: ../../../flutter/shell/platform/fuchsia/flutter/task_observers.cc -FILE: ../../../flutter/shell/platform/fuchsia/flutter/task_observers.h -FILE: ../../../flutter/shell/platform/fuchsia/flutter/task_runner_adapter.cc -FILE: ../../../flutter/shell/platform/fuchsia/flutter/task_runner_adapter.h -FILE: ../../../flutter/shell/platform/fuchsia/flutter/thread.cc -FILE: ../../../flutter/shell/platform/fuchsia/flutter/thread.h FILE: ../../../flutter/shell/platform/fuchsia/flutter/unique_fdio_ns.h FILE: ../../../flutter/shell/platform/fuchsia/flutter/vsync_recorder.cc FILE: ../../../flutter/shell/platform/fuchsia/flutter/vsync_recorder.h FILE: ../../../flutter/shell/platform/fuchsia/flutter/vsync_waiter.cc FILE: ../../../flutter/shell/platform/fuchsia/flutter/vsync_waiter.h +FILE: ../../../flutter/shell/platform/fuchsia/flutter/vsync_waiter_unittests.cc FILE: ../../../flutter/shell/platform/fuchsia/flutter/vulkan_surface.cc FILE: ../../../flutter/shell/platform/fuchsia/flutter/vulkan_surface.h FILE: ../../../flutter/shell/platform/fuchsia/flutter/vulkan_surface_pool.cc @@ -1016,6 +1071,7 @@ FILE: ../../../flutter/shell/platform/fuchsia/runtime/dart/utils/vmservice_objec FILE: ../../../flutter/shell/platform/fuchsia/runtime/dart/utils/vmservice_object.h FILE: ../../../flutter/shell/platform/glfw/client_wrapper/flutter_window_controller.cc FILE: ../../../flutter/shell/platform/glfw/client_wrapper/flutter_window_controller_unittests.cc +FILE: ../../../flutter/shell/platform/glfw/client_wrapper/flutter_window_unittests.cc FILE: ../../../flutter/shell/platform/glfw/client_wrapper/include/flutter/flutter_window.h FILE: ../../../flutter/shell/platform/glfw/client_wrapper/include/flutter/flutter_window_controller.h FILE: ../../../flutter/shell/platform/glfw/client_wrapper/include/flutter/plugin_registrar_glfw.h diff --git a/ci/licenses_golden/licenses_fuchsia b/ci/licenses_golden/licenses_fuchsia index 2402af9bb6498..79b0c1988a662 100644 --- a/ci/licenses_golden/licenses_fuchsia +++ b/ci/licenses_golden/licenses_fuchsia @@ -1,4 +1,4 @@ -Signature: 9ccc5f19cc74f1d3b284857bd4436fbe +Signature: cbf66bd53213b2c9d93eff505947b7af UNUSED LICENSES: @@ -20,7 +20,6 @@ FILE: ../../../fuchsia/sdk/linux/arch/arm64/dist/VkLayer_unique_objects.so FILE: ../../../fuchsia/sdk/linux/arch/arm64/dist/libVkLayer_image_pipe_swapchain.so FILE: ../../../fuchsia/sdk/linux/arch/arm64/dist/libasync-default.so FILE: ../../../fuchsia/sdk/linux/arch/arm64/dist/libfdio.so -FILE: ../../../fuchsia/sdk/linux/arch/arm64/dist/libinspect-common.so FILE: ../../../fuchsia/sdk/linux/arch/arm64/dist/libmemfs.so FILE: ../../../fuchsia/sdk/linux/arch/arm64/dist/libsvc.so FILE: ../../../fuchsia/sdk/linux/arch/arm64/dist/libsyslog.so @@ -30,7 +29,6 @@ FILE: ../../../fuchsia/sdk/linux/arch/arm64/dist/libvulkan.so FILE: ../../../fuchsia/sdk/linux/arch/arm64/lib/libasync-default.so FILE: ../../../fuchsia/sdk/linux/arch/arm64/lib/libasync-loop-default.a FILE: ../../../fuchsia/sdk/linux/arch/arm64/lib/libfdio.so -FILE: ../../../fuchsia/sdk/linux/arch/arm64/lib/libinspect-common.so FILE: ../../../fuchsia/sdk/linux/arch/arm64/lib/libmemfs.so FILE: ../../../fuchsia/sdk/linux/arch/arm64/lib/libsvc.so FILE: ../../../fuchsia/sdk/linux/arch/arm64/lib/libsync.a @@ -230,7 +228,6 @@ FILE: ../../../fuchsia/sdk/linux/arch/x64/dist/VkLayer_unique_objects.so FILE: ../../../fuchsia/sdk/linux/arch/x64/dist/libVkLayer_image_pipe_swapchain.so FILE: ../../../fuchsia/sdk/linux/arch/x64/dist/libasync-default.so FILE: ../../../fuchsia/sdk/linux/arch/x64/dist/libfdio.so -FILE: ../../../fuchsia/sdk/linux/arch/x64/dist/libinspect-common.so FILE: ../../../fuchsia/sdk/linux/arch/x64/dist/libmemfs.so FILE: ../../../fuchsia/sdk/linux/arch/x64/dist/libsvc.so FILE: ../../../fuchsia/sdk/linux/arch/x64/dist/libsyslog.so @@ -240,7 +237,6 @@ FILE: ../../../fuchsia/sdk/linux/arch/x64/dist/libvulkan.so FILE: ../../../fuchsia/sdk/linux/arch/x64/lib/libasync-default.so FILE: ../../../fuchsia/sdk/linux/arch/x64/lib/libasync-loop-default.a FILE: ../../../fuchsia/sdk/linux/arch/x64/lib/libfdio.so -FILE: ../../../fuchsia/sdk/linux/arch/x64/lib/libinspect-common.so FILE: ../../../fuchsia/sdk/linux/arch/x64/lib/libmemfs.so FILE: ../../../fuchsia/sdk/linux/arch/x64/lib/libsvc.so FILE: ../../../fuchsia/sdk/linux/arch/x64/lib/libsync.a @@ -472,12 +468,14 @@ FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.feedback/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.fonts/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.ethernet/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.goldfish/meta.json +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.power.statecontrol/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.hwinfo/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.images/meta.json +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.inspect/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.intl/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.io/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ldsvc/meta.json -FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ledger/meta.json +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.location.namedplace/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.logger/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.math/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.media.audio/meta.json @@ -513,6 +511,7 @@ FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.app/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.gfx/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.input/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.input2/meta.json +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.lifecycle/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.policy/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.scenic/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.types/meta.json @@ -540,8 +539,8 @@ FILE: ../../../fuchsia/sdk/linux/pkg/fidl_cpp_base/meta.json FILE: ../../../fuchsia/sdk/linux/pkg/fidl_cpp_sync/meta.json FILE: ../../../fuchsia/sdk/linux/pkg/fit/meta.json FILE: ../../../fuchsia/sdk/linux/pkg/images_cpp/meta.json -FILE: ../../../fuchsia/sdk/linux/pkg/inspect-common/meta.json FILE: ../../../fuchsia/sdk/linux/pkg/inspect/meta.json +FILE: ../../../fuchsia/sdk/linux/pkg/inspect_service_cpp/meta.json FILE: ../../../fuchsia/sdk/linux/pkg/media_cpp/meta.json FILE: ../../../fuchsia/sdk/linux/pkg/media_cpp_no_converters/meta.json FILE: ../../../fuchsia/sdk/linux/pkg/memfs/meta.json @@ -715,7 +714,6 @@ FILE: ../../../fuchsia/sdk/linux/arch/arm64/dist/VkLayer_unique_objects.so FILE: ../../../fuchsia/sdk/linux/arch/arm64/dist/libVkLayer_image_pipe_swapchain.so FILE: ../../../fuchsia/sdk/linux/arch/arm64/dist/libasync-default.so FILE: ../../../fuchsia/sdk/linux/arch/arm64/dist/libfdio.so -FILE: ../../../fuchsia/sdk/linux/arch/arm64/dist/libinspect-common.so FILE: ../../../fuchsia/sdk/linux/arch/arm64/dist/libmemfs.so FILE: ../../../fuchsia/sdk/linux/arch/arm64/dist/libsvc.so FILE: ../../../fuchsia/sdk/linux/arch/arm64/dist/libsyslog.so @@ -725,7 +723,6 @@ FILE: ../../../fuchsia/sdk/linux/arch/arm64/dist/libvulkan.so FILE: ../../../fuchsia/sdk/linux/arch/arm64/lib/libasync-default.so FILE: ../../../fuchsia/sdk/linux/arch/arm64/lib/libasync-loop-default.a FILE: ../../../fuchsia/sdk/linux/arch/arm64/lib/libfdio.so -FILE: ../../../fuchsia/sdk/linux/arch/arm64/lib/libinspect-common.so FILE: ../../../fuchsia/sdk/linux/arch/arm64/lib/libmemfs.so FILE: ../../../fuchsia/sdk/linux/arch/arm64/lib/libsvc.so FILE: ../../../fuchsia/sdk/linux/arch/arm64/lib/libsync.a @@ -953,6 +950,7 @@ FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/wait.h FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/wchar.h FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/wctype.h FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/wordexp.h +FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/exception.h FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/syscalls/clock.h FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/syscalls/definitions.h FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/lib/Scrt1.o @@ -971,7 +969,6 @@ FILE: ../../../fuchsia/sdk/linux/arch/x64/dist/VkLayer_unique_objects.so FILE: ../../../fuchsia/sdk/linux/arch/x64/dist/libVkLayer_image_pipe_swapchain.so FILE: ../../../fuchsia/sdk/linux/arch/x64/dist/libasync-default.so FILE: ../../../fuchsia/sdk/linux/arch/x64/dist/libfdio.so -FILE: ../../../fuchsia/sdk/linux/arch/x64/dist/libinspect-common.so FILE: ../../../fuchsia/sdk/linux/arch/x64/dist/libmemfs.so FILE: ../../../fuchsia/sdk/linux/arch/x64/dist/libsvc.so FILE: ../../../fuchsia/sdk/linux/arch/x64/dist/libsyslog.so @@ -981,7 +978,6 @@ FILE: ../../../fuchsia/sdk/linux/arch/x64/dist/libvulkan.so FILE: ../../../fuchsia/sdk/linux/arch/x64/lib/libasync-default.so FILE: ../../../fuchsia/sdk/linux/arch/x64/lib/libasync-loop-default.a FILE: ../../../fuchsia/sdk/linux/arch/x64/lib/libfdio.so -FILE: ../../../fuchsia/sdk/linux/arch/x64/lib/libinspect-common.so FILE: ../../../fuchsia/sdk/linux/arch/x64/lib/libmemfs.so FILE: ../../../fuchsia/sdk/linux/arch/x64/lib/libsvc.so FILE: ../../../fuchsia/sdk/linux/arch/x64/lib/libsync.a @@ -1209,6 +1205,7 @@ FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/wait.h FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/wchar.h FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/wctype.h FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/wordexp.h +FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/exception.h FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/syscalls/clock.h FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/syscalls/definitions.h FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/lib/Scrt1.o @@ -1293,7 +1290,22 @@ FILE: ../../../fuchsia/sdk/linux/dart/sl4f/lib/src/sl4f_client.dart FILE: ../../../fuchsia/sdk/linux/dart/sl4f/lib/src/ssh.dart FILE: ../../../fuchsia/sdk/linux/dart/sl4f/lib/src/storage.dart FILE: ../../../fuchsia/sdk/linux/dart/sl4f/lib/src/test.dart +FILE: ../../../fuchsia/sdk/linux/dart/sl4f/lib/src/trace_processing/metrics/common.dart +FILE: ../../../fuchsia/sdk/linux/dart/sl4f/lib/src/trace_processing/metrics/cpu_metrics.dart +FILE: ../../../fuchsia/sdk/linux/dart/sl4f/lib/src/trace_processing/metrics/drm_fps.dart +FILE: ../../../fuchsia/sdk/linux/dart/sl4f/lib/src/trace_processing/metrics/flutter_frame_stats.dart +FILE: ../../../fuchsia/sdk/linux/dart/sl4f/lib/src/trace_processing/metrics/input_latency.dart +FILE: ../../../fuchsia/sdk/linux/dart/sl4f/lib/src/trace_processing/metrics/memory_metrics.dart +FILE: ../../../fuchsia/sdk/linux/dart/sl4f/lib/src/trace_processing/metrics/scenic_frame_stats.dart +FILE: ../../../fuchsia/sdk/linux/dart/sl4f/lib/src/trace_processing/metrics/temperature_metrics.dart +FILE: ../../../fuchsia/sdk/linux/dart/sl4f/lib/src/trace_processing/metrics_results.dart +FILE: ../../../fuchsia/sdk/linux/dart/sl4f/lib/src/trace_processing/metrics_spec.dart +FILE: ../../../fuchsia/sdk/linux/dart/sl4f/lib/src/trace_processing/time_delta.dart +FILE: ../../../fuchsia/sdk/linux/dart/sl4f/lib/src/trace_processing/time_point.dart +FILE: ../../../fuchsia/sdk/linux/dart/sl4f/lib/src/trace_processing/trace_importing.dart +FILE: ../../../fuchsia/sdk/linux/dart/sl4f/lib/src/trace_processing/trace_model.dart FILE: ../../../fuchsia/sdk/linux/dart/sl4f/lib/src/webdriver.dart +FILE: ../../../fuchsia/sdk/linux/dart/sl4f/lib/trace_processing.dart FILE: ../../../fuchsia/sdk/linux/dart/sl4f/meta.json FILE: ../../../fuchsia/sdk/linux/dart/zircon/meta.json FILE: ../../../fuchsia/sdk/linux/device/generic-arm64.json @@ -1307,6 +1319,7 @@ FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.auth.oldtokens/credentials_produce FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.auth.oldtokens/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.auth/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.bluetooth.control/meta.json +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.bluetooth.control/pairing_options.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.bluetooth.gatt/constants.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.bluetooth.gatt/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.bluetooth.le/advertising_data.fidl @@ -1354,15 +1367,20 @@ FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.goldfish/goldfish_address FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.goldfish/goldfish_control.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.goldfish/goldfish_pipe.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.goldfish/meta.json +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.power.statecontrol/admin.fidl +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.power.statecontrol/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.hwinfo/hwinfo.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.hwinfo/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.images/image_pipe2.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.images/meta.json +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.inspect/meta.json +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.inspect/tree.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.intl/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.intl/property_provider.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.io/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ldsvc/meta.json -FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ledger/meta.json +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.location.namedplace/meta.json +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.location.namedplace/namedplace.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.logger/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.math/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.media.audio/meta.json @@ -1379,6 +1397,7 @@ FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.media.sessions2/images.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.media.sessions2/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.media.sessions2/player.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.media.sessions2/publisher.fidl +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.media/audio_consumer.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.media/audio_core.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.media/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.media/usage_reporter.fidl @@ -1445,12 +1464,16 @@ FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.input2/layout.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.input2/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.input2/modifiers.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.input2/semantic_keys.fidl +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.lifecycle/lifecycle_controller.fidl +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.lifecycle/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.policy/device_listener.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.policy/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.scenic/meta.json +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.scenic/pointer_capture.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.types/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.types/types.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.vectorial/meta.json +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.views/focuser.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.views/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.views/view.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.views/view_token.fidl @@ -1491,11 +1514,13 @@ FILE: ../../../fuchsia/sdk/linux/pkg/fidl-async/meta.json FILE: ../../../fuchsia/sdk/linux/pkg/fidl/meta.json FILE: ../../../fuchsia/sdk/linux/pkg/fidl_base/include/lib/fidl/envelope_frames.h FILE: ../../../fuchsia/sdk/linux/pkg/fidl_base/include/lib/fidl/internal_callable_traits.h +FILE: ../../../fuchsia/sdk/linux/pkg/fidl_base/include/lib/fidl/runtime_flag.h FILE: ../../../fuchsia/sdk/linux/pkg/fidl_base/include/lib/fidl/transformer.h FILE: ../../../fuchsia/sdk/linux/pkg/fidl_base/include/lib/fidl/txn_header.h FILE: ../../../fuchsia/sdk/linux/pkg/fidl_base/include/lib/fidl/visitor.h -FILE: ../../../fuchsia/sdk/linux/pkg/fidl_base/internal.cc +FILE: ../../../fuchsia/sdk/linux/pkg/fidl_base/internal.c FILE: ../../../fuchsia/sdk/linux/pkg/fidl_base/meta.json +FILE: ../../../fuchsia/sdk/linux/pkg/fidl_base/runtime_flag.cc FILE: ../../../fuchsia/sdk/linux/pkg/fidl_base/transformer.cc FILE: ../../../fuchsia/sdk/linux/pkg/fidl_base/txn_header.c FILE: ../../../fuchsia/sdk/linux/pkg/fidl_cpp/include/lib/fidl/cpp/event_sender.h @@ -1517,13 +1542,13 @@ FILE: ../../../fuchsia/sdk/linux/pkg/fit/include/lib/fit/string_view.h FILE: ../../../fuchsia/sdk/linux/pkg/fit/include/lib/fit/utility_internal.h FILE: ../../../fuchsia/sdk/linux/pkg/fit/meta.json FILE: ../../../fuchsia/sdk/linux/pkg/images_cpp/meta.json -FILE: ../../../fuchsia/sdk/linux/pkg/inspect-common/include/lib/inspect/common.h -FILE: ../../../fuchsia/sdk/linux/pkg/inspect-common/meta.json FILE: ../../../fuchsia/sdk/linux/pkg/inspect/health.cc FILE: ../../../fuchsia/sdk/linux/pkg/inspect/hierarchy.cc FILE: ../../../fuchsia/sdk/linux/pkg/inspect/include/lib/inspect/cpp/health.h FILE: ../../../fuchsia/sdk/linux/pkg/inspect/include/lib/inspect/cpp/hierarchy.h +FILE: ../../../fuchsia/sdk/linux/pkg/inspect/include/lib/inspect/cpp/inspect.h FILE: ../../../fuchsia/sdk/linux/pkg/inspect/include/lib/inspect/cpp/reader.h +FILE: ../../../fuchsia/sdk/linux/pkg/inspect/include/lib/inspect/cpp/value_list.h FILE: ../../../fuchsia/sdk/linux/pkg/inspect/include/lib/inspect/cpp/vmo/block.h FILE: ../../../fuchsia/sdk/linux/pkg/inspect/include/lib/inspect/cpp/vmo/scanner.h FILE: ../../../fuchsia/sdk/linux/pkg/inspect/include/lib/inspect/cpp/vmo/snapshot.h @@ -1531,6 +1556,11 @@ FILE: ../../../fuchsia/sdk/linux/pkg/inspect/meta.json FILE: ../../../fuchsia/sdk/linux/pkg/inspect/reader.cc FILE: ../../../fuchsia/sdk/linux/pkg/inspect/vmo/scanner.cc FILE: ../../../fuchsia/sdk/linux/pkg/inspect/vmo/snapshot.cc +FILE: ../../../fuchsia/sdk/linux/pkg/inspect_service_cpp/include/lib/inspect/service/cpp/reader.h +FILE: ../../../fuchsia/sdk/linux/pkg/inspect_service_cpp/include/lib/inspect/service/cpp/service.h +FILE: ../../../fuchsia/sdk/linux/pkg/inspect_service_cpp/meta.json +FILE: ../../../fuchsia/sdk/linux/pkg/inspect_service_cpp/reader.cc +FILE: ../../../fuchsia/sdk/linux/pkg/inspect_service_cpp/service.cc FILE: ../../../fuchsia/sdk/linux/pkg/media_cpp/meta.json FILE: ../../../fuchsia/sdk/linux/pkg/media_cpp_no_converters/meta.json FILE: ../../../fuchsia/sdk/linux/pkg/memfs/meta.json @@ -1668,7 +1698,6 @@ FILE: ../../../fuchsia/sdk/linux/arch/arm64/dist/VkLayer_unique_objects.so FILE: ../../../fuchsia/sdk/linux/arch/arm64/dist/libVkLayer_image_pipe_swapchain.so FILE: ../../../fuchsia/sdk/linux/arch/arm64/dist/libasync-default.so FILE: ../../../fuchsia/sdk/linux/arch/arm64/dist/libfdio.so -FILE: ../../../fuchsia/sdk/linux/arch/arm64/dist/libinspect-common.so FILE: ../../../fuchsia/sdk/linux/arch/arm64/dist/libmemfs.so FILE: ../../../fuchsia/sdk/linux/arch/arm64/dist/libsvc.so FILE: ../../../fuchsia/sdk/linux/arch/arm64/dist/libsyslog.so @@ -1678,7 +1707,6 @@ FILE: ../../../fuchsia/sdk/linux/arch/arm64/dist/libvulkan.so FILE: ../../../fuchsia/sdk/linux/arch/arm64/lib/libasync-default.so FILE: ../../../fuchsia/sdk/linux/arch/arm64/lib/libasync-loop-default.a FILE: ../../../fuchsia/sdk/linux/arch/arm64/lib/libfdio.so -FILE: ../../../fuchsia/sdk/linux/arch/arm64/lib/libinspect-common.so FILE: ../../../fuchsia/sdk/linux/arch/arm64/lib/libmemfs.so FILE: ../../../fuchsia/sdk/linux/arch/arm64/lib/libsvc.so FILE: ../../../fuchsia/sdk/linux/arch/arm64/lib/libsync.a @@ -1878,7 +1906,6 @@ FILE: ../../../fuchsia/sdk/linux/arch/x64/dist/VkLayer_unique_objects.so FILE: ../../../fuchsia/sdk/linux/arch/x64/dist/libVkLayer_image_pipe_swapchain.so FILE: ../../../fuchsia/sdk/linux/arch/x64/dist/libasync-default.so FILE: ../../../fuchsia/sdk/linux/arch/x64/dist/libfdio.so -FILE: ../../../fuchsia/sdk/linux/arch/x64/dist/libinspect-common.so FILE: ../../../fuchsia/sdk/linux/arch/x64/dist/libmemfs.so FILE: ../../../fuchsia/sdk/linux/arch/x64/dist/libsvc.so FILE: ../../../fuchsia/sdk/linux/arch/x64/dist/libsyslog.so @@ -1888,7 +1915,6 @@ FILE: ../../../fuchsia/sdk/linux/arch/x64/dist/libvulkan.so FILE: ../../../fuchsia/sdk/linux/arch/x64/lib/libasync-default.so FILE: ../../../fuchsia/sdk/linux/arch/x64/lib/libasync-loop-default.a FILE: ../../../fuchsia/sdk/linux/arch/x64/lib/libfdio.so -FILE: ../../../fuchsia/sdk/linux/arch/x64/lib/libinspect-common.so FILE: ../../../fuchsia/sdk/linux/arch/x64/lib/libmemfs.so FILE: ../../../fuchsia/sdk/linux/arch/x64/lib/libsvc.so FILE: ../../../fuchsia/sdk/linux/arch/x64/lib/libsync.a @@ -2120,12 +2146,14 @@ FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.feedback/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.fonts/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.ethernet/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.goldfish/meta.json +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.hardware.power.statecontrol/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.hwinfo/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.images/meta.json +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.inspect/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.intl/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.io/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ldsvc/meta.json -FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ledger/meta.json +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.location.namedplace/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.logger/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.math/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.media.audio/meta.json @@ -2161,6 +2189,7 @@ FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.app/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.gfx/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.input/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.input2/meta.json +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.lifecycle/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.policy/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.scenic/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.types/meta.json @@ -2188,8 +2217,8 @@ FILE: ../../../fuchsia/sdk/linux/pkg/fidl_cpp_base/meta.json FILE: ../../../fuchsia/sdk/linux/pkg/fidl_cpp_sync/meta.json FILE: ../../../fuchsia/sdk/linux/pkg/fit/meta.json FILE: ../../../fuchsia/sdk/linux/pkg/images_cpp/meta.json -FILE: ../../../fuchsia/sdk/linux/pkg/inspect-common/meta.json FILE: ../../../fuchsia/sdk/linux/pkg/inspect/meta.json +FILE: ../../../fuchsia/sdk/linux/pkg/inspect_service_cpp/meta.json FILE: ../../../fuchsia/sdk/linux/pkg/media_cpp/meta.json FILE: ../../../fuchsia/sdk/linux/pkg/media_cpp_no_converters/meta.json FILE: ../../../fuchsia/sdk/linux/pkg/memfs/meta.json @@ -2482,7 +2511,6 @@ FILE: ../../../fuchsia/sdk/linux/dart/fidl/lib/src/message.dart FILE: ../../../fuchsia/sdk/linux/dart/fidl/lib/src/struct.dart FILE: ../../../fuchsia/sdk/linux/dart/fidl/lib/src/table.dart FILE: ../../../fuchsia/sdk/linux/dart/fidl/lib/src/types.dart -FILE: ../../../fuchsia/sdk/linux/dart/fidl/lib/src/union.dart FILE: ../../../fuchsia/sdk/linux/dart/fuchsia/lib/src/fakes/fuchsia_fakes.dart FILE: ../../../fuchsia/sdk/linux/dart/fuchsia_logger/lib/logger.dart FILE: ../../../fuchsia/sdk/linux/dart/fuchsia_logger/lib/src/internal/_fuchsia_log_writer.dart @@ -2708,12 +2736,12 @@ FILE: ../../../fuchsia/sdk/linux/pkg/fit/sequencer.cc FILE: ../../../fuchsia/sdk/linux/pkg/fit/single_threaded_executor.cc FILE: ../../../fuchsia/sdk/linux/pkg/images_cpp/images.cc FILE: ../../../fuchsia/sdk/linux/pkg/images_cpp/include/lib/images/cpp/images.h -FILE: ../../../fuchsia/sdk/linux/pkg/inspect/include/lib/inspect/cpp/inspect.h +FILE: ../../../fuchsia/sdk/linux/pkg/inspect/include/lib/inspect/cpp/inspector.h FILE: ../../../fuchsia/sdk/linux/pkg/inspect/include/lib/inspect/cpp/vmo/heap.h FILE: ../../../fuchsia/sdk/linux/pkg/inspect/include/lib/inspect/cpp/vmo/limits.h FILE: ../../../fuchsia/sdk/linux/pkg/inspect/include/lib/inspect/cpp/vmo/state.h FILE: ../../../fuchsia/sdk/linux/pkg/inspect/include/lib/inspect/cpp/vmo/types.h -FILE: ../../../fuchsia/sdk/linux/pkg/inspect/inspect.cc +FILE: ../../../fuchsia/sdk/linux/pkg/inspect/inspector.cc FILE: ../../../fuchsia/sdk/linux/pkg/inspect/vmo/heap.cc FILE: ../../../fuchsia/sdk/linux/pkg/inspect/vmo/state.cc FILE: ../../../fuchsia/sdk/linux/pkg/inspect/vmo/types.cc @@ -2817,7 +2845,6 @@ FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/syscalls/system FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/tls.h FILE: ../../../fuchsia/sdk/linux/dart/composition_delegate/lib/src/surface/surface.dart FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.auth/auth_provider.fidl -FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.auth/auth_provider_factory.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.auth/token_manager.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.bluetooth.control/control.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.bluetooth.gatt/client.fidl @@ -2833,7 +2860,6 @@ FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.modular.auth/account/account.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.modular/agent/agent.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.modular/agent/agent_context.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.modular/agent/agent_controller/agent_controller.fidl -FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.modular/clipboard/clipboard.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.modular/component/component_context.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.modular/config/config.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.modular/entity/entity.fidl @@ -2851,7 +2877,6 @@ FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.modular/story/create_module_parame FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.modular/story/story_shell.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.modular/story/story_state.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.modular/surface/surface.fidl -FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.modular/user_intelligence/user_intelligence_provider.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.net.mdns/mdns.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.sys/flat_namespace.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.gfx/commands.fidl @@ -3205,7 +3230,6 @@ FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/syscalls/types. FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/types.h FILE: ../../../fuchsia/sdk/linux/dart/fidl/lib/src/interface.dart FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.fonts/font_provider.fidl -FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ledger/ledger.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.math/math.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.media.playback/problem.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.media.playback/seeking_reader.fidl diff --git a/ci/licenses_golden/licenses_skia b/ci/licenses_golden/licenses_skia index 3683ad8f6e01f..516c6991eae69 100644 --- a/ci/licenses_golden/licenses_skia +++ b/ci/licenses_golden/licenses_skia @@ -1,4 +1,4 @@ -Signature: 77f23f792f731e0334254b8005795881 +Signature: 281b747ba0f64c8a40728d9c9b17b934 UNUSED LICENSES: @@ -572,7 +572,6 @@ FILE: ../../../third_party/skia/src/core/SkImageFilterCache.h FILE: ../../../third_party/skia/src/core/SkLRUCache.h FILE: ../../../third_party/skia/src/core/SkLeanWindows.h FILE: ../../../third_party/skia/src/core/SkMSAN.h -FILE: ../../../third_party/skia/src/core/SkMakeUnique.h FILE: ../../../third_party/skia/src/core/SkMatrixPriv.h FILE: ../../../third_party/skia/src/core/SkModeColorFilter.h FILE: ../../../third_party/skia/src/core/SkNormalFlatSource.cpp @@ -634,8 +633,6 @@ FILE: ../../../third_party/skia/src/gpu/GrSurfaceProxy.h FILE: ../../../third_party/skia/src/gpu/GrSwizzle.h FILE: ../../../third_party/skia/src/gpu/GrTextureAdjuster.cpp FILE: ../../../third_party/skia/src/gpu/GrTextureAdjuster.h -FILE: ../../../third_party/skia/src/gpu/GrTextureContext.cpp -FILE: ../../../third_party/skia/src/gpu/GrTextureContext.h FILE: ../../../third_party/skia/src/gpu/GrTextureMaker.cpp FILE: ../../../third_party/skia/src/gpu/GrTextureMaker.h FILE: ../../../third_party/skia/src/gpu/GrTextureProducer.cpp @@ -665,7 +662,6 @@ FILE: ../../../third_party/skia/src/gpu/ops/GrRegionOp.cpp FILE: ../../../third_party/skia/src/gpu/ops/GrRegionOp.h FILE: ../../../third_party/skia/src/gpu/ops/GrShadowRRectOp.cpp FILE: ../../../third_party/skia/src/gpu/ops/GrShadowRRectOp.h -FILE: ../../../third_party/skia/src/gpu/text/GrTextBlobVertexRegenerator.cpp FILE: ../../../third_party/skia/src/gpu/vk/GrVkDescriptorPool.cpp FILE: ../../../third_party/skia/src/gpu/vk/GrVkDescriptorPool.h FILE: ../../../third_party/skia/src/gpu/vk/GrVkDescriptorSet.cpp @@ -802,6 +798,49 @@ FILE: ../../../third_party/skia/third_party/libsdl/SDL_config_premake.h ---------------------------------------------------------------------------------------------------- Copyright 2016 Google Inc. +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 the copyright holder 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: skcms +LIBRARY: vulkanmemoryallocator +ORIGIN: ../../../third_party/skia/include/third_party/skcms/skcms.h + ../../../third_party/skia/include/third_party/skcms/LICENSE +TYPE: LicenseType.bsd +FILE: ../../../third_party/skia/include/third_party/skcms/skcms.h +FILE: ../../../third_party/skia/third_party/skcms/skcms.cc +FILE: ../../../third_party/skia/third_party/skcms/skcms_internal.h +FILE: ../../../third_party/skia/third_party/skcms/src/Transform_inl.h +FILE: ../../../third_party/skia/third_party/vulkanmemoryallocator/GrVulkanMemoryAllocator.cpp +FILE: ../../../third_party/skia/third_party/vulkanmemoryallocator/GrVulkanMemoryAllocator.h +---------------------------------------------------------------------------------------------------- +Copyright 2018 Google Inc. + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -831,414 +870,42 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================================================== LIBRARY: skcms +ORIGIN: ../../../third_party/skia/include/third_party/skcms/LICENSE +TYPE: LicenseType.bsd +FILE: ../../../third_party/skia/third_party/skcms/version.sha1 +---------------------------------------------------------------------------------------------------- +Copyright (c) 2018 Google Inc. 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: skia -LIBRARY: vulkanmemoryallocator -ORIGIN: ../../../third_party/skia/bench/ClearBench.cpp + ../../../third_party/skia/LICENSE -TYPE: LicenseType.bsd -FILE: ../../../third_party/skia/bench/ClearBench.cpp -FILE: ../../../third_party/skia/bench/CompositingImagesBench.cpp -FILE: ../../../third_party/skia/bench/CubicMapBench.cpp -FILE: ../../../third_party/skia/bench/GrCCFillGeometryBench.cpp -FILE: ../../../third_party/skia/bench/ImageCycleBench.cpp -FILE: ../../../third_party/skia/bench/JSONBench.cpp -FILE: ../../../third_party/skia/bench/PathOpsBench.cpp -FILE: ../../../third_party/skia/bench/PolyUtilsBench.cpp -FILE: ../../../third_party/skia/bench/ShaderMaskFilterBench.cpp -FILE: ../../../third_party/skia/bench/TypefaceBench.cpp -FILE: ../../../third_party/skia/experimental/pvg/draw_msg.proto -FILE: ../../../third_party/skia/fuzz/FuzzCommon.cpp -FILE: ../../../third_party/skia/fuzz/FuzzPathMeasure.cpp -FILE: ../../../third_party/skia/fuzz/FuzzRegionOp.cpp -FILE: ../../../third_party/skia/fuzz/oss_fuzz/FuzzImageFilterDeserialize.cpp -FILE: ../../../third_party/skia/fuzz/oss_fuzz/FuzzPathDeserialize.cpp -FILE: ../../../third_party/skia/fuzz/oss_fuzz/FuzzRegionDeserialize.cpp -FILE: ../../../third_party/skia/fuzz/oss_fuzz/FuzzRegionSetPath.cpp -FILE: ../../../third_party/skia/gm/analytic_gradients.cpp -FILE: ../../../third_party/skia/gm/androidblendmodes.cpp -FILE: ../../../third_party/skia/gm/b_119394958.cpp -FILE: ../../../third_party/skia/gm/clockwise.cpp -FILE: ../../../third_party/skia/gm/crbug_847759.cpp -FILE: ../../../third_party/skia/gm/crbug_884166.cpp -FILE: ../../../third_party/skia/gm/crbug_887103.cpp -FILE: ../../../third_party/skia/gm/crbug_892988.cpp -FILE: ../../../third_party/skia/gm/crbug_899512.cpp -FILE: ../../../third_party/skia/gm/crbug_905548.cpp -FILE: ../../../third_party/skia/gm/daa.cpp -FILE: ../../../third_party/skia/gm/drawimageset.cpp -FILE: ../../../third_party/skia/gm/drawquadset.cpp -FILE: ../../../third_party/skia/gm/fontregen.cpp -FILE: ../../../third_party/skia/gm/fwidth_squircle.cpp -FILE: ../../../third_party/skia/gm/gradients_degenerate.cpp -FILE: ../../../third_party/skia/gm/hugepath.cpp -FILE: ../../../third_party/skia/gm/localmatrixshader.cpp -FILE: ../../../third_party/skia/gm/make_raster_image.cpp -FILE: ../../../third_party/skia/gm/mandoline.cpp -FILE: ../../../third_party/skia/gm/orientation.cpp -FILE: ../../../third_party/skia/gm/p3.cpp -FILE: ../../../third_party/skia/gm/pathmeasure.cpp -FILE: ../../../third_party/skia/gm/perspimages.cpp -FILE: ../../../third_party/skia/gm/polygonoffset.cpp -FILE: ../../../third_party/skia/gm/scaledemoji.cpp -FILE: ../../../third_party/skia/gm/scaledemoji_rendering.cpp -FILE: ../../../third_party/skia/gm/shadermaskfilter.cpp -FILE: ../../../third_party/skia/gm/sharedcorners.cpp -FILE: ../../../third_party/skia/gm/skinning.cpp -FILE: ../../../third_party/skia/gm/trickycubicstrokes.cpp -FILE: ../../../third_party/skia/gm/unpremul.cpp -FILE: ../../../third_party/skia/gm/wacky_yuv_formats.cpp -FILE: ../../../third_party/skia/include/android/SkAnimatedImage.h -FILE: ../../../third_party/skia/include/c/sk_colorspace.h -FILE: ../../../third_party/skia/include/c/sk_imageinfo.h -FILE: ../../../third_party/skia/include/core/SkCanvasVirtualEnforcer.h -FILE: ../../../third_party/skia/include/core/SkContourMeasure.h -FILE: ../../../third_party/skia/include/core/SkCoverageMode.h -FILE: ../../../third_party/skia/include/core/SkCubicMap.h -FILE: ../../../third_party/skia/include/core/SkFontMetrics.h -FILE: ../../../third_party/skia/include/core/SkFontParameters.h -FILE: ../../../third_party/skia/include/core/SkFontTypes.h -FILE: ../../../third_party/skia/include/core/SkYUVAIndex.h -FILE: ../../../third_party/skia/include/effects/SkOpPathEffect.h -FILE: ../../../third_party/skia/include/effects/SkShaderMaskFilter.h -FILE: ../../../third_party/skia/include/effects/SkTrimPathEffect.h -FILE: ../../../third_party/skia/include/gpu/GrBackendDrawableInfo.h -FILE: ../../../third_party/skia/include/gpu/GrDriverBugWorkarounds.h -FILE: ../../../third_party/skia/include/gpu/vk/GrVkMemoryAllocator.h -FILE: ../../../third_party/skia/include/gpu/vk/GrVkVulkan.h -FILE: ../../../third_party/skia/include/ports/SkFontMgr_fuchsia.h -FILE: ../../../third_party/skia/include/private/GrVkTypesPriv.h -FILE: ../../../third_party/skia/include/private/SkMacros.h -FILE: ../../../third_party/skia/include/private/SkSafe32.h -FILE: ../../../third_party/skia/include/private/SkTo.h -FILE: ../../../third_party/skia/include/third_party/skcms/skcms.h -FILE: ../../../third_party/skia/include/utils/Sk3D.h -FILE: ../../../third_party/skia/include/utils/SkAnimCodecPlayer.h -FILE: ../../../third_party/skia/include/utils/SkTextUtils.h -FILE: ../../../third_party/skia/infra/cts/run_testlab.go -FILE: ../../../third_party/skia/modules/skottie/gm/3dgm.cpp -FILE: ../../../third_party/skia/modules/skottie/include/SkottieProperty.h -FILE: ../../../third_party/skia/modules/skottie/src/SkottieAdapter.cpp -FILE: ../../../third_party/skia/modules/skottie/src/SkottieAdapter.h -FILE: ../../../third_party/skia/modules/skottie/src/SkottieJson.cpp -FILE: ../../../third_party/skia/modules/skottie/src/SkottieJson.h -FILE: ../../../third_party/skia/modules/skottie/src/SkottiePriv.h -FILE: ../../../third_party/skia/modules/skottie/src/SkottieProperty.cpp -FILE: ../../../third_party/skia/modules/skottie/src/SkottieTest.cpp -FILE: ../../../third_party/skia/modules/skottie/src/SkottieTool.cpp -FILE: ../../../third_party/skia/modules/skottie/src/layers/PrecompLayer.cpp -FILE: ../../../third_party/skia/modules/skottie/src/layers/ShapeLayer.cpp -FILE: ../../../third_party/skia/modules/skottie/src/layers/TextLayer.cpp -FILE: ../../../third_party/skia/modules/skottie/utils/SkottieUtils.cpp -FILE: ../../../third_party/skia/modules/skottie/utils/SkottieUtils.h -FILE: ../../../third_party/skia/modules/sksg/include/SkSGClipEffect.h -FILE: ../../../third_party/skia/modules/sksg/include/SkSGColorFilter.h -FILE: ../../../third_party/skia/modules/sksg/include/SkSGGeometryTransform.h -FILE: ../../../third_party/skia/modules/sksg/include/SkSGGradient.h -FILE: ../../../third_party/skia/modules/sksg/include/SkSGImage.h -FILE: ../../../third_party/skia/modules/sksg/include/SkSGMaskEffect.h -FILE: ../../../third_party/skia/modules/sksg/include/SkSGOpacityEffect.h -FILE: ../../../third_party/skia/modules/sksg/include/SkSGPlane.h -FILE: ../../../third_party/skia/modules/sksg/include/SkSGRoundEffect.h -FILE: ../../../third_party/skia/modules/sksg/include/SkSGScene.h -FILE: ../../../third_party/skia/modules/sksg/include/SkSGText.h -FILE: ../../../third_party/skia/modules/sksg/src/SkSGClipEffect.cpp -FILE: ../../../third_party/skia/modules/sksg/src/SkSGColorFilter.cpp -FILE: ../../../third_party/skia/modules/sksg/src/SkSGGeometryTransform.cpp -FILE: ../../../third_party/skia/modules/sksg/src/SkSGGradient.cpp -FILE: ../../../third_party/skia/modules/sksg/src/SkSGImage.cpp -FILE: ../../../third_party/skia/modules/sksg/src/SkSGMaskEffect.cpp -FILE: ../../../third_party/skia/modules/sksg/src/SkSGOpacityEffect.cpp -FILE: ../../../third_party/skia/modules/sksg/src/SkSGPlane.cpp -FILE: ../../../third_party/skia/modules/sksg/src/SkSGRoundEffect.cpp -FILE: ../../../third_party/skia/modules/sksg/src/SkSGScene.cpp -FILE: ../../../third_party/skia/modules/sksg/src/SkSGText.cpp -FILE: ../../../third_party/skia/modules/skshaper/src/SkShaper.cpp -FILE: ../../../third_party/skia/samplecode/SampleAnimatedImage.cpp -FILE: ../../../third_party/skia/samplecode/SampleCusp.cpp -FILE: ../../../third_party/skia/samplecode/SampleFlutterAnimate.cpp -FILE: ../../../third_party/skia/samplecode/SampleGlyphTransform.cpp -FILE: ../../../third_party/skia/src/android/SkAnimatedImage.cpp -FILE: ../../../third_party/skia/src/c/sk_imageinfo.cpp -FILE: ../../../third_party/skia/src/codec/SkEncodedInfo.cpp -FILE: ../../../third_party/skia/src/codec/SkParseEncodedOrigin.cpp -FILE: ../../../third_party/skia/src/codec/SkWuffsCodec.cpp -FILE: ../../../third_party/skia/src/codec/SkWuffsCodec.h -FILE: ../../../third_party/skia/src/core/SkBlurPriv.h -FILE: ../../../third_party/skia/src/core/SkCanvasPriv.cpp -FILE: ../../../third_party/skia/src/core/SkColorSpaceXformSteps.cpp -FILE: ../../../third_party/skia/src/core/SkColorSpaceXformSteps.h -FILE: ../../../third_party/skia/src/core/SkContourMeasure.cpp -FILE: ../../../third_party/skia/src/core/SkCoverageModePriv.h -FILE: ../../../third_party/skia/src/core/SkCubicMap.cpp -FILE: ../../../third_party/skia/src/core/SkCubicSolver.h -FILE: ../../../third_party/skia/src/core/SkDeferredDisplayList.cpp -FILE: ../../../third_party/skia/src/core/SkDeferredDisplayListPriv.h -FILE: ../../../third_party/skia/src/core/SkDraw_text.cpp -FILE: ../../../third_party/skia/src/core/SkFontPriv.h -FILE: ../../../third_party/skia/src/core/SkGlyph.cpp -FILE: ../../../third_party/skia/src/core/SkIPoint16.h -FILE: ../../../third_party/skia/src/core/SkMaskFilterBase.h -FILE: ../../../third_party/skia/src/core/SkPath_serial.cpp -FILE: ../../../third_party/skia/src/core/SkPicturePriv.h -FILE: ../../../third_party/skia/src/core/SkRRectPriv.h -FILE: ../../../third_party/skia/src/core/SkRectPriv.h -FILE: ../../../third_party/skia/src/core/SkRemoteGlyphCache.cpp -FILE: ../../../third_party/skia/src/core/SkRemoteGlyphCache.h -FILE: ../../../third_party/skia/src/core/SkSafeRange.h -FILE: ../../../third_party/skia/src/core/SkSpan.h -FILE: ../../../third_party/skia/src/core/SkStrikeCache.cpp -FILE: ../../../third_party/skia/src/core/SkSurfaceCharacterization.cpp -FILE: ../../../third_party/skia/src/core/SkTextBlobPriv.h -FILE: ../../../third_party/skia/src/core/SkTypeface_remote.cpp -FILE: ../../../third_party/skia/src/core/SkTypeface_remote.h -FILE: ../../../third_party/skia/src/core/SkYUVASizeInfo.cpp -FILE: ../../../third_party/skia/src/effects/SkOpPE.h -FILE: ../../../third_party/skia/src/effects/SkOpPathEffect.cpp -FILE: ../../../third_party/skia/src/effects/SkShaderMaskFilter.cpp -FILE: ../../../third_party/skia/src/effects/SkTrimPE.h -FILE: ../../../third_party/skia/src/effects/SkTrimPathEffect.cpp -FILE: ../../../third_party/skia/src/gpu/GrContextThreadSafeProxyPriv.h -FILE: ../../../third_party/skia/src/gpu/GrDDLContext.cpp -FILE: ../../../third_party/skia/src/gpu/GrDriverBugWorkarounds.cpp -FILE: ../../../third_party/skia/src/gpu/GrFPArgs.h -FILE: ../../../third_party/skia/src/gpu/GrLegacyDirectContext.cpp -FILE: ../../../third_party/skia/src/gpu/GrProxyProvider.cpp -FILE: ../../../third_party/skia/src/gpu/GrProxyProvider.h -FILE: ../../../third_party/skia/src/gpu/GrRenderTargetProxyPriv.h -FILE: ../../../third_party/skia/src/gpu/GrResourceProviderPriv.h -FILE: ../../../third_party/skia/src/gpu/ccpr/GrCCClipPath.cpp -FILE: ../../../third_party/skia/src/gpu/ccpr/GrCCClipPath.h -FILE: ../../../third_party/skia/src/gpu/ccpr/GrCCConicShader.cpp -FILE: ../../../third_party/skia/src/gpu/ccpr/GrCCConicShader.h -FILE: ../../../third_party/skia/src/gpu/ccpr/GrCCDrawPathsOp.cpp -FILE: ../../../third_party/skia/src/gpu/ccpr/GrCCDrawPathsOp.h -FILE: ../../../third_party/skia/src/gpu/ccpr/GrCCPathCache.cpp -FILE: ../../../third_party/skia/src/gpu/ccpr/GrCCPathCache.h -FILE: ../../../third_party/skia/src/gpu/ccpr/GrCCPerFlushResources.cpp -FILE: ../../../third_party/skia/src/gpu/ccpr/GrCCPerFlushResources.h -FILE: ../../../third_party/skia/src/gpu/ccpr/GrCCPerOpsTaskPaths.h -FILE: ../../../third_party/skia/src/gpu/ccpr/GrCCSTLList.h -FILE: ../../../third_party/skia/src/gpu/ccpr/GrCCStrokeGeometry.cpp -FILE: ../../../third_party/skia/src/gpu/ccpr/GrCCStrokeGeometry.h -FILE: ../../../third_party/skia/src/gpu/ccpr/GrCCStroker.cpp -FILE: ../../../third_party/skia/src/gpu/ccpr/GrCCStroker.h -FILE: ../../../third_party/skia/src/gpu/effects/GrAARectEffect.fp -FILE: ../../../third_party/skia/src/gpu/effects/GrAlphaThresholdFragmentProcessor.fp -FILE: ../../../third_party/skia/src/gpu/effects/GrCircleBlurFragmentProcessor.fp -FILE: ../../../third_party/skia/src/gpu/effects/GrConfigConversionEffect.fp -FILE: ../../../third_party/skia/src/gpu/effects/GrConstColorProcessor.fp -FILE: ../../../third_party/skia/src/gpu/effects/GrLumaColorFilterEffect.fp -FILE: ../../../third_party/skia/src/gpu/effects/GrMagnifierEffect.fp -FILE: ../../../third_party/skia/src/gpu/effects/GrPremulInputFragmentProcessor.fp -FILE: ../../../third_party/skia/src/gpu/effects/GrRRectBlurEffect.fp -FILE: ../../../third_party/skia/src/gpu/effects/GrRectBlurEffect.fp -FILE: ../../../third_party/skia/src/gpu/effects/GrSkSLFP.cpp -FILE: ../../../third_party/skia/src/gpu/effects/GrSkSLFP.h -FILE: ../../../third_party/skia/src/gpu/effects/GrYUVtoRGBEffect.cpp -FILE: ../../../third_party/skia/src/gpu/effects/GrYUVtoRGBEffect.h -FILE: ../../../third_party/skia/src/gpu/effects/generated/GrAARectEffect.cpp -FILE: ../../../third_party/skia/src/gpu/effects/generated/GrAARectEffect.h -FILE: ../../../third_party/skia/src/gpu/effects/generated/GrAlphaThresholdFragmentProcessor.cpp -FILE: ../../../third_party/skia/src/gpu/effects/generated/GrAlphaThresholdFragmentProcessor.h -FILE: ../../../third_party/skia/src/gpu/effects/generated/GrCircleBlurFragmentProcessor.cpp -FILE: ../../../third_party/skia/src/gpu/effects/generated/GrCircleBlurFragmentProcessor.h -FILE: ../../../third_party/skia/src/gpu/effects/generated/GrConfigConversionEffect.cpp -FILE: ../../../third_party/skia/src/gpu/effects/generated/GrConfigConversionEffect.h -FILE: ../../../third_party/skia/src/gpu/effects/generated/GrConstColorProcessor.cpp -FILE: ../../../third_party/skia/src/gpu/effects/generated/GrConstColorProcessor.h -FILE: ../../../third_party/skia/src/gpu/effects/generated/GrLumaColorFilterEffect.cpp -FILE: ../../../third_party/skia/src/gpu/effects/generated/GrLumaColorFilterEffect.h -FILE: ../../../third_party/skia/src/gpu/effects/generated/GrMagnifierEffect.cpp -FILE: ../../../third_party/skia/src/gpu/effects/generated/GrMagnifierEffect.h -FILE: ../../../third_party/skia/src/gpu/effects/generated/GrPremulInputFragmentProcessor.cpp -FILE: ../../../third_party/skia/src/gpu/effects/generated/GrPremulInputFragmentProcessor.h -FILE: ../../../third_party/skia/src/gpu/effects/generated/GrRRectBlurEffect.cpp -FILE: ../../../third_party/skia/src/gpu/effects/generated/GrRRectBlurEffect.h -FILE: ../../../third_party/skia/src/gpu/effects/generated/GrRectBlurEffect.cpp -FILE: ../../../third_party/skia/src/gpu/effects/generated/GrRectBlurEffect.h -FILE: ../../../third_party/skia/src/gpu/geometry/GrQuad.cpp -FILE: ../../../third_party/skia/src/gpu/gradients/GrClampedGradientEffect.fp -FILE: ../../../third_party/skia/src/gpu/gradients/GrDualIntervalGradientColorizer.fp -FILE: ../../../third_party/skia/src/gpu/gradients/GrGradientBitmapCache.cpp -FILE: ../../../third_party/skia/src/gpu/gradients/GrGradientBitmapCache.h -FILE: ../../../third_party/skia/src/gpu/gradients/GrGradientShader.cpp -FILE: ../../../third_party/skia/src/gpu/gradients/GrGradientShader.h -FILE: ../../../third_party/skia/src/gpu/gradients/GrLinearGradientLayout.fp -FILE: ../../../third_party/skia/src/gpu/gradients/GrRadialGradientLayout.fp -FILE: ../../../third_party/skia/src/gpu/gradients/GrSingleIntervalGradientColorizer.fp -FILE: ../../../third_party/skia/src/gpu/gradients/GrSweepGradientLayout.fp -FILE: ../../../third_party/skia/src/gpu/gradients/GrTextureGradientColorizer.fp -FILE: ../../../third_party/skia/src/gpu/gradients/GrTiledGradientEffect.fp -FILE: ../../../third_party/skia/src/gpu/gradients/GrTwoPointConicalGradientLayout.fp -FILE: ../../../third_party/skia/src/gpu/gradients/GrUnrolledBinaryGradientColorizer.fp -FILE: ../../../third_party/skia/src/gpu/gradients/generated/GrClampedGradientEffect.cpp -FILE: ../../../third_party/skia/src/gpu/gradients/generated/GrClampedGradientEffect.h -FILE: ../../../third_party/skia/src/gpu/gradients/generated/GrDualIntervalGradientColorizer.cpp -FILE: ../../../third_party/skia/src/gpu/gradients/generated/GrDualIntervalGradientColorizer.h -FILE: ../../../third_party/skia/src/gpu/gradients/generated/GrLinearGradientLayout.cpp -FILE: ../../../third_party/skia/src/gpu/gradients/generated/GrLinearGradientLayout.h -FILE: ../../../third_party/skia/src/gpu/gradients/generated/GrRadialGradientLayout.cpp -FILE: ../../../third_party/skia/src/gpu/gradients/generated/GrRadialGradientLayout.h -FILE: ../../../third_party/skia/src/gpu/gradients/generated/GrSingleIntervalGradientColorizer.cpp -FILE: ../../../third_party/skia/src/gpu/gradients/generated/GrSingleIntervalGradientColorizer.h -FILE: ../../../third_party/skia/src/gpu/gradients/generated/GrSweepGradientLayout.cpp -FILE: ../../../third_party/skia/src/gpu/gradients/generated/GrSweepGradientLayout.h -FILE: ../../../third_party/skia/src/gpu/gradients/generated/GrTextureGradientColorizer.cpp -FILE: ../../../third_party/skia/src/gpu/gradients/generated/GrTextureGradientColorizer.h -FILE: ../../../third_party/skia/src/gpu/gradients/generated/GrTiledGradientEffect.cpp -FILE: ../../../third_party/skia/src/gpu/gradients/generated/GrTiledGradientEffect.h -FILE: ../../../third_party/skia/src/gpu/gradients/generated/GrTwoPointConicalGradientLayout.cpp -FILE: ../../../third_party/skia/src/gpu/gradients/generated/GrTwoPointConicalGradientLayout.h -FILE: ../../../third_party/skia/src/gpu/gradients/generated/GrUnrolledBinaryGradientColorizer.cpp -FILE: ../../../third_party/skia/src/gpu/gradients/generated/GrUnrolledBinaryGradientColorizer.h -FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlBuffer.h -FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlBuffer.mm -FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlCppUtil.h -FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlDepthStencil.mm -FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlOpsRenderPass.h -FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlOpsRenderPass.mm -FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlPipelineState.h -FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlPipelineState.mm -FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlPipelineStateBuilder.h -FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlPipelineStateBuilder.mm -FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlPipelineStateDataManager.h -FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlPipelineStateDataManager.mm -FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlResourceProvider.h -FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlResourceProvider.mm -FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlSampler.h -FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlSampler.mm -FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlStencilAttachment.h -FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlStencilAttachment.mm -FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlTextureRenderTarget.h -FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlTextureRenderTarget.mm -FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlUniformHandler.h -FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlUniformHandler.mm -FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlVaryingHandler.h -FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlVaryingHandler.mm -FILE: ../../../third_party/skia/src/gpu/ops/GrClearStencilClipOp.cpp -FILE: ../../../third_party/skia/src/gpu/ops/GrDebugMarkerOp.cpp -FILE: ../../../third_party/skia/src/gpu/ops/GrDrawableOp.cpp -FILE: ../../../third_party/skia/src/gpu/ops/GrDrawableOp.h -FILE: ../../../third_party/skia/src/gpu/ops/GrFillRRectOp.cpp -FILE: ../../../third_party/skia/src/gpu/ops/GrFillRRectOp.h -FILE: ../../../third_party/skia/src/gpu/ops/GrFillRectOp.cpp -FILE: ../../../third_party/skia/src/gpu/ops/GrFillRectOp.h -FILE: ../../../third_party/skia/src/gpu/ops/GrQuadPerEdgeAA.cpp -FILE: ../../../third_party/skia/src/gpu/ops/GrQuadPerEdgeAA.h -FILE: ../../../third_party/skia/src/gpu/ops/GrStrokeRectOp.cpp -FILE: ../../../third_party/skia/src/gpu/ops/GrStrokeRectOp.h -FILE: ../../../third_party/skia/src/gpu/text/GrAtlasManager.cpp -FILE: ../../../third_party/skia/src/gpu/text/GrAtlasManager.h -FILE: ../../../third_party/skia/src/gpu/text/GrSDFMaskFilter.cpp -FILE: ../../../third_party/skia/src/gpu/text/GrSDFMaskFilter.h -FILE: ../../../third_party/skia/src/gpu/vk/GrVkAMDMemoryAllocator.cpp -FILE: ../../../third_party/skia/src/gpu/vk/GrVkAMDMemoryAllocator.h -FILE: ../../../third_party/skia/src/gpu/vk/GrVkCommandPool.cpp -FILE: ../../../third_party/skia/src/gpu/vk/GrVkCommandPool.h -FILE: ../../../third_party/skia/src/gpu/vk/GrVkImageLayout.h -FILE: ../../../third_party/skia/src/gpu/vk/GrVkSamplerYcbcrConversion.cpp -FILE: ../../../third_party/skia/src/gpu/vk/GrVkSamplerYcbcrConversion.h -FILE: ../../../third_party/skia/src/gpu/vk/GrVkTypesPriv.cpp -FILE: ../../../third_party/skia/src/image/SkImage_GpuBase.cpp -FILE: ../../../third_party/skia/src/image/SkImage_GpuBase.h -FILE: ../../../third_party/skia/src/image/SkImage_GpuYUVA.cpp -FILE: ../../../third_party/skia/src/image/SkImage_GpuYUVA.h -FILE: ../../../third_party/skia/src/image/SkImage_Lazy.h -FILE: ../../../third_party/skia/src/opts/SkBitmapProcState_opts.h -FILE: ../../../third_party/skia/src/opts/SkOpts_hsw.cpp -FILE: ../../../third_party/skia/src/opts/SkRasterPipeline_opts.h -FILE: ../../../third_party/skia/src/pathops/SkPathOpsAsWinding.cpp -FILE: ../../../third_party/skia/src/pathops/SkPathOpsTCurve.h -FILE: ../../../third_party/skia/src/pdf/SkClusterator.cpp -FILE: ../../../third_party/skia/src/pdf/SkClusterator.h -FILE: ../../../third_party/skia/src/pdf/SkPDFTag.cpp -FILE: ../../../third_party/skia/src/pdf/SkPDFTag.h -FILE: ../../../third_party/skia/src/ports/SkFontMgr_fuchsia.cpp -FILE: ../../../third_party/skia/src/sksl/SkSLByteCode.cpp -FILE: ../../../third_party/skia/src/sksl/SkSLCPPUniformCTypes.cpp -FILE: ../../../third_party/skia/src/sksl/SkSLCPPUniformCTypes.h -FILE: ../../../third_party/skia/src/sksl/SkSLPipelineStageCodeGenerator.cpp -FILE: ../../../third_party/skia/src/sksl/ir/SkSLVariableReference.cpp -FILE: ../../../third_party/skia/src/utils/Sk3D.cpp -FILE: ../../../third_party/skia/src/utils/SkAnimCodecPlayer.cpp -FILE: ../../../third_party/skia/src/utils/SkCallableTraits.h -FILE: ../../../third_party/skia/src/utils/SkJSON.cpp -FILE: ../../../third_party/skia/src/utils/SkJSON.h -FILE: ../../../third_party/skia/src/utils/SkTextUtils.cpp -FILE: ../../../third_party/skia/src/utils/mac/SkUniqueCFRef.h -FILE: ../../../third_party/skia/src/utils/win/SkDWriteNTDDI_VERSION.h -FILE: ../../../third_party/skia/third_party/skcms/skcms.cc -FILE: ../../../third_party/skia/third_party/skcms/skcms_internal.h -FILE: ../../../third_party/skia/third_party/skcms/src/Transform_inl.h -FILE: ../../../third_party/skia/third_party/vulkanmemoryallocator/GrVulkanMemoryAllocator.cpp -FILE: ../../../third_party/skia/third_party/vulkanmemoryallocator/GrVulkanMemoryAllocator.h ----------------------------------------------------------------------------------------------------- -Copyright 2018 Google Inc. - -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: skcms -ORIGIN: ../../../third_party/skia/include/third_party/skcms/LICENSE -TYPE: LicenseType.bsd -FILE: ../../../third_party/skia/third_party/skcms/version.sha1 ----------------------------------------------------------------------------------------------------- -Copyright (c) 2018 Google Inc. 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: skia -ORIGIN: ../../../third_party/icu/scripts/LICENSE +ORIGIN: ../../../third_party/icu/scripts/LICENSE TYPE: LicenseType.bsd FILE: ../../../third_party/skia/src/core/SkTraceEventCommon.h ---------------------------------------------------------------------------------------------------- @@ -1290,6 +957,8 @@ FILE: ../../../third_party/skia/animations/redcross#1.jpg FILE: ../../../third_party/skia/animations/text#1.xml FILE: ../../../third_party/skia/bench/microbench.json FILE: ../../../third_party/skia/bench/skpbench.json +FILE: ../../../third_party/skia/build/fuchsia/skqp/skqp.cmx +FILE: ../../../third_party/skia/build/fuchsia/skqp/test_manifest.json FILE: ../../../third_party/skia/docker/binary-size/Dockerfile FILE: ../../../third_party/skia/docker/cmake-release/Dockerfile FILE: ../../../third_party/skia/docker/skia-build-tools/Dockerfile @@ -1340,7 +1009,6 @@ FILE: ../../../third_party/skia/infra/bots/assets/linux_vulkan_sdk/VERSION FILE: ../../../third_party/skia/infra/bots/assets/lottie-samples/VERSION FILE: ../../../third_party/skia/infra/bots/assets/mesa_intel_driver_linux/VERSION FILE: ../../../third_party/skia/infra/bots/assets/mesa_intel_driver_linux/mesa-driver-builder/Dockerfile -FILE: ../../../third_party/skia/infra/bots/assets/mips64el_toolchain_linux/VERSION FILE: ../../../third_party/skia/infra/bots/assets/moltenvk/VERSION FILE: ../../../third_party/skia/infra/bots/assets/mskp/VERSION FILE: ../../../third_party/skia/infra/bots/assets/node/VERSION @@ -1359,6 +1027,7 @@ FILE: ../../../third_party/skia/infra/bots/assets/valgrind/VERSION FILE: ../../../third_party/skia/infra/bots/assets/win_ninja/VERSION FILE: ../../../third_party/skia/infra/bots/assets/win_toolchain/VERSION FILE: ../../../third_party/skia/infra/bots/calmbench.isolate +FILE: ../../../third_party/skia/infra/bots/canvaskit.isolate FILE: ../../../third_party/skia/infra/bots/cfg.json FILE: ../../../third_party/skia/infra/bots/compile.isolate FILE: ../../../third_party/skia/infra/bots/compile_android_framework.isolate @@ -1371,11 +1040,20 @@ FILE: ../../../third_party/skia/infra/bots/isolate_android_sdk_linux.isolate FILE: ../../../third_party/skia/infra/bots/isolate_gcloud_linux.isolate FILE: ../../../third_party/skia/infra/bots/isolate_go.isolate FILE: ../../../third_party/skia/infra/bots/jobs.json +FILE: ../../../third_party/skia/infra/bots/lottie_web.isolate +FILE: ../../../third_party/skia/infra/bots/pathkit.isolate FILE: ../../../third_party/skia/infra/bots/perf_skia_bundled.isolate +FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian10-GCC-loongson3a-Release-Docker.json +FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian10-GCC-x86-Debug-Docker.json +FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian10-GCC-x86_64-Debug-Docker.json +FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian10-GCC-x86_64-Release-NoGPU_Docker.json +FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian10-GCC-x86_64-Release-Shared_Docker.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-arm-Release-Android_API26.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-arm-Release-Android_ASAN.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-arm-Release-Chromebook_GLES.json +FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-arm-Release-Chromecast.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-arm-Release-Flutter_Android.json +FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-arm64-Release-Android_Wuffs.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86-devrel-Android_SKQP.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Debug-Chromebook_GLES.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Debug-Coverage.json @@ -1386,6 +1064,7 @@ FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.ex FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Debug-SwiftShader_MSAN.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Debug-Tidy.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Debug-Wuffs.json +FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Release-ANGLE.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Release-ASAN.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Release-CMake.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-Clang-x86_64-Release-Fast.json @@ -1399,11 +1078,6 @@ FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.ex FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-wasm-Debug-PathKit.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-wasm-Release-CanvasKit_CPU.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-EMCC-wasm-Release-PathKit.json -FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-GCC-arm-Release-Chromecast.json -FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-GCC-loongson3a-Release.json -FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-GCC-x86_64-Release-ANGLE.json -FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-GCC-x86_64-Release-NoGPU.json -FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian9-GCC-x86_64-Release-Shared.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/Build-Mac-Clang-arm-Debug-iOS.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/Build-Mac-Clang-arm64-Debug-Android_Vulkan.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/Build-Mac-Clang-arm64-Debug-iOS.json @@ -1413,20 +1087,22 @@ FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.ex FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/Build-Mac-Clang-x86_64-Release-MoltenVK_Vulkan.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/Build-Win-Clang-arm64-Release-Android.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/Build-Win-Clang-x86-Debug-Exceptions.json +FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/Build-Win-Clang-x86_64-Debug-ANGLE.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/Build-Win-Clang-x86_64-Debug-OpenCL.json +FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/Build-Win-Clang-x86_64-Release-Shared.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/Build-Win-Clang-x86_64-Release-Vulkan.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/Housekeeper-PerCommit-CheckGeneratedFiles.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/Test-Debian9-Clang-GCE-CPU-AVX2-universal-devrel-All-Android_SKQP.json +FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/unknown-docker-image.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/builder_name_schema/builder_name_schema.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/builder_name_schema/examples/full.expected/test.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/checkout/examples/full.expected/Build-Debian9-Clang-x86_64-Release-NoDEPS.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/checkout/examples/full.expected/Build-Mac-Clang-x86_64-Debug-CommandBuffer.json -FILE: ../../../third_party/skia/infra/bots/recipe_modules/checkout/examples/full.expected/Build-Win-Clang-x86_64-Release-ParentRevision.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/checkout/examples/full.expected/Housekeeper-Weekly-RecreateSKPs.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/checkout/examples/full.expected/cross_repo_trybot.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/checkout/examples/full.expected/flutter_trybot.json -FILE: ../../../third_party/skia/infra/bots/recipe_modules/checkout/examples/full.expected/parent_revision_trybot.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/checkout/examples/full.expected/trybot.json +FILE: ../../../third_party/skia/infra/bots/recipe_modules/docker/examples/full.expected/test.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/doxygen/examples/full.expected/doxygen.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/env/examples/full.expected/test.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-All-Android_SkottieTracing.json @@ -1438,13 +1114,15 @@ FILE: ../../../third_party/skia/infra/bots/recipe_modules/flavor/examples/full.e FILE: ../../../third_party/skia/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-Chromecast-Clang-Chorizo-CPU-Cortex_A7-arm-Release-All.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-MSAN.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-ASAN.json -FILE: ../../../third_party/skia/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All-UBSAN.json +FILE: ../../../third_party/skia/infra/bots/recipe_modules/flavor/examples/full.expected/Perf-Win2019-Clang-GCE-CPU-AVX2-x86_64-Debug-All-ASAN.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-All-Android.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Android-Clang-GalaxyS7_G930FD-GPU-MaliT880-arm64-Debug-All-Android.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Debug-All-Android.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Android-Clang-Nexus5x-GPU-Adreno418-arm64-Release-All-Android_ASAN.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Android-Clang-Pixel3a-GPU-Adreno615-arm64-Debug-All-Android_Vulkan.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/flavor/examples/full.expected/Test-ChromeOS-Clang-SamsungChromebookPlus-GPU-MaliT860-arm-Release-All.json +FILE: ../../../third_party/skia/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Debian10-GCC-GCE-CPU-AVX2-x86-Debug-All-Docker.json +FILE: ../../../third_party/skia/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Debian10-GCC-GCE-CPU-AVX2-x86_64-Debug-All-Docker.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-Coverage.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-Lottie.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All-TSAN.json @@ -1452,7 +1130,7 @@ FILE: ../../../third_party/skia/infra/bots/recipe_modules/flavor/examples/full.e FILE: ../../../third_party/skia/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All-OpenCL.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Debian9-Clang-NUC7i5BNK-GPU-IntelIris640-x86_64-Debug-All-Vulkan.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Mac10.13-Clang-MacBookPro11.5-CPU-AVX2-x86_64-Debug-All-ASAN.json -FILE: ../../../third_party/skia/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_AbandonGpuContext_SK_CPU_LIMIT_SSE41.json +FILE: ../../../third_party/skia/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_AbandonGpuContext_SK_CPU_LIMIT_SSE41.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan_ProcDump.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/flavor/examples/full.expected/Test-Win10-MSVC-LenovoYogaC630-GPU-Adreno630-arm64-Debug-All-ANGLE.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/flavor/examples/full.expected/cpu_scale_failed.json @@ -1482,8 +1160,6 @@ FILE: ../../../third_party/skia/infra/bots/recipes/android_compile.expected/andr FILE: ../../../third_party/skia/infra/bots/recipes/android_compile.expected/android_compile_trybot.json FILE: ../../../third_party/skia/infra/bots/recipes/android_compile.expected/android_compile_trybot_failure.json FILE: ../../../third_party/skia/infra/bots/recipes/android_compile.expected/android_compile_unrecognized_target.json -FILE: ../../../third_party/skia/infra/bots/recipes/calmbench.expected/Calmbench-Debian9-Clang-GCE-CPU-AVX2-x86_64-Release-All.json -FILE: ../../../third_party/skia/infra/bots/recipes/calmbench.expected/Calmbench-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Release-All.json FILE: ../../../third_party/skia/infra/bots/recipes/check_generated_files.expected/Housekeeper-PerCommit-CheckGeneratedFiles.json FILE: ../../../third_party/skia/infra/bots/recipes/compile.expected/Build-Win-Clang-x86-Debug.json FILE: ../../../third_party/skia/infra/bots/recipes/compute_buildstats.expected/normal_bot.json @@ -1513,7 +1189,7 @@ FILE: ../../../third_party/skia/infra/bots/recipes/perf.expected/Perf-Debian9-Cl FILE: ../../../third_party/skia/infra/bots/recipes/perf.expected/Perf-Mac10.13-Clang-MacBook10.1-GPU-IntelHD615-x86_64-Release-All-CommandBuffer.json FILE: ../../../third_party/skia/infra/bots/recipes/perf.expected/Perf-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Release-All-Metal.json FILE: ../../../third_party/skia/infra/bots/recipes/perf.expected/Perf-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Release-All-CommandBuffer.json -FILE: ../../../third_party/skia/infra/bots/recipes/perf.expected/Perf-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_SK_CPU_LIMIT_SSE41.json +FILE: ../../../third_party/skia/infra/bots/recipes/perf.expected/Perf-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_SK_CPU_LIMIT_SSE41.json FILE: ../../../third_party/skia/infra/bots/recipes/perf.expected/Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-ANGLE.json FILE: ../../../third_party/skia/infra/bots/recipes/perf.expected/Perf-Win10-Clang-ShuttleA-GPU-GTX660-x86_64-Release-All-Vulkan.json FILE: ../../../third_party/skia/infra/bots/recipes/perf.expected/Perf-iOS-Clang-iPadPro-GPU-PowerVRGT7800-arm64-Release-All.json @@ -1547,9 +1223,8 @@ FILE: ../../../third_party/skia/infra/bots/recipes/skpbench.expected/Perf-Win10- FILE: ../../../third_party/skia/infra/bots/recipes/skpbench.expected/Perf-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Vulkan_Skpbench_DDLTotal_9x9.json FILE: ../../../third_party/skia/infra/bots/recipes/skpbench.expected/trybot.json FILE: ../../../third_party/skia/infra/bots/recipes/skqp_test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-universal-devrel-All-Android_SKQP.json +FILE: ../../../third_party/skia/infra/bots/recipes/sync_and_compile.expected/Build-Debian9-Clang-arm-Release-Flutter_Android.json FILE: ../../../third_party/skia/infra/bots/recipes/sync_and_compile.expected/Build-Debian9-Clang-universal-devrel-Android_SKQP.json -FILE: ../../../third_party/skia/infra/bots/recipes/sync_and_compile.expected/Build-Debian9-Clang-x86_64-Release-ParentRevision.json -FILE: ../../../third_party/skia/infra/bots/recipes/sync_and_compile.expected/Build-Debian9-GCC-x86_64-Release-Flutter_Android.json FILE: ../../../third_party/skia/infra/bots/recipes/sync_and_compile.expected/Build-Mac-Clang-x86_64-Debug-CommandBuffer.json FILE: ../../../third_party/skia/infra/bots/recipes/sync_and_compile.expected/Build-Win10-Clang-x86_64-Release-NoDEPS.json FILE: ../../../third_party/skia/infra/bots/recipes/test.expected/Test-Android-Clang-AndroidOne-GPU-Mali400MP2-arm-Release-All-Android.json @@ -1569,6 +1244,7 @@ FILE: ../../../third_party/skia/infra/bots/recipes/test.expected/Test-Android-Cl FILE: ../../../third_party/skia/infra/bots/recipes/test.expected/Test-ChromeOS-Clang-AcerChromebookR13Convertible-GPU-PowerVRGX6250-arm-Debug-All.json FILE: ../../../third_party/skia/infra/bots/recipes/test.expected/Test-Chromecast-Clang-Chorizo-CPU-Cortex_A7-arm-Release-All.json FILE: ../../../third_party/skia/infra/bots/recipes/test.expected/Test-Chromecast-Clang-Chorizo-GPU-Cortex_A7-arm-Release-All.json +FILE: ../../../third_party/skia/infra/bots/recipes/test.expected/Test-Debian10-GCC-GCE-CPU-AVX2-x86_64-Debug-All-Docker.json FILE: ../../../third_party/skia/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-ASAN.json FILE: ../../../third_party/skia/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-BonusConfigs.json FILE: ../../../third_party/skia/infra/bots/recipes/test.expected/Test-Debian9-Clang-GCE-CPU-AVX2-x86_64-Debug-All-MSAN.json @@ -1586,12 +1262,11 @@ FILE: ../../../third_party/skia/infra/bots/recipes/test.expected/Test-Mac10.13-C FILE: ../../../third_party/skia/infra/bots/recipes/test.expected/Test-Mac10.13-Clang-MacBookPro11.5-GPU-RadeonHD8870M-x86_64-Debug-All-Metal.json FILE: ../../../third_party/skia/infra/bots/recipes/test.expected/Test-Mac10.13-Clang-MacMini7.1-GPU-IntelIris5100-x86_64-Debug-All-CommandBuffer.json FILE: ../../../third_party/skia/infra/bots/recipes/test.expected/Test-Mac10.14-Clang-MacBookAir7.2-GPU-IntelHD6000-x86_64-Debug-All.json -FILE: ../../../third_party/skia/infra/bots/recipes/test.expected/Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL1.json -FILE: ../../../third_party/skia/infra/bots/recipes/test.expected/Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3.json -FILE: ../../../third_party/skia/infra/bots/recipes/test.expected/Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Lottie.json -FILE: ../../../third_party/skia/infra/bots/recipes/test.expected/Test-Ubuntu17-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan_Coverage.json -FILE: ../../../third_party/skia/infra/bots/recipes/test.expected/Test-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_AbandonGpuContext_SK_CPU_LIMIT_SSE41.json -FILE: ../../../third_party/skia/infra/bots/recipes/test.expected/Test-Ubuntu17-GCC-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_PreAbandonGpuContext_SK_CPU_LIMIT_SSE41.json +FILE: ../../../third_party/skia/infra/bots/recipes/test.expected/Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL1.json +FILE: ../../../third_party/skia/infra/bots/recipes/test.expected/Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-DDL3.json +FILE: ../../../third_party/skia/infra/bots/recipes/test.expected/Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-Vulkan.json +FILE: ../../../third_party/skia/infra/bots/recipes/test.expected/Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_AbandonGpuContext_SK_CPU_LIMIT_SSE41.json +FILE: ../../../third_party/skia/infra/bots/recipes/test.expected/Test-Ubuntu18-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-Valgrind_PreAbandonGpuContext_SK_CPU_LIMIT_SSE41.json FILE: ../../../third_party/skia/infra/bots/recipes/test.expected/Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Debug-All-NonNVPR.json FILE: ../../../third_party/skia/infra/bots/recipes/test.expected/Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-BonusConfigs.json FILE: ../../../third_party/skia/infra/bots/recipes/test.expected/Test-Win10-Clang-Golo-GPU-QuadroP400-x86_64-Release-All-ReleaseAndAbandonGpuContext.json @@ -1602,15 +1277,14 @@ FILE: ../../../third_party/skia/infra/bots/recipes/test.expected/Test-Win10-Clan FILE: ../../../third_party/skia/infra/bots/recipes/test.expected/Test-Win10-Clang-ShuttleA-GPU-RadeonHD7770-x86_64-Release-All-Vulkan.json FILE: ../../../third_party/skia/infra/bots/recipes/test.expected/Test-Win10-Clang-ShuttleC-GPU-GTX960-x86_64-Debug-All-ANGLE.json FILE: ../../../third_party/skia/infra/bots/recipes/test.expected/Test-Win10-MSVC-LenovoYogaC630-GPU-Adreno630-arm64-Debug-All-ANGLE.json -FILE: ../../../third_party/skia/infra/bots/recipes/test.expected/Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All-FAAA.json -FILE: ../../../third_party/skia/infra/bots/recipes/test.expected/Test-Win2016-Clang-GCE-CPU-AVX2-x86_64-Debug-All-FSAA.json +FILE: ../../../third_party/skia/infra/bots/recipes/test.expected/Test-Win2019-Clang-GCE-CPU-AVX2-x86_64-Debug-All-FAAA.json +FILE: ../../../third_party/skia/infra/bots/recipes/test.expected/Test-Win2019-Clang-GCE-CPU-AVX2-x86_64-Debug-All-FSAA.json FILE: ../../../third_party/skia/infra/bots/recipes/test.expected/Test-iOS-Clang-iPadPro-GPU-PowerVRGT7800-arm64-Release-All.json FILE: ../../../third_party/skia/infra/bots/recipes/test.expected/Test-iOS-Clang-iPhone6-GPU-PowerVRGX6450-arm64-Release-All-Metal.json FILE: ../../../third_party/skia/infra/bots/recipes/test.expected/failed_dm.json FILE: ../../../third_party/skia/infra/bots/recipes/test.expected/failed_get_hashes.json FILE: ../../../third_party/skia/infra/bots/recipes/test.expected/failed_pull.json FILE: ../../../third_party/skia/infra/bots/recipes/test.expected/failed_push.json -FILE: ../../../third_party/skia/infra/bots/recipes/test.expected/internal_bot_2.json FILE: ../../../third_party/skia/infra/bots/recipes/test.expected/internal_bot_5.json FILE: ../../../third_party/skia/infra/bots/recipes/test.expected/trybot.json FILE: ../../../third_party/skia/infra/bots/recipes/test_canvaskit.expected/Test-Debian9-EMCC-GCE-GPU-WEBGL1-wasm-Debug-All-CanvasKit.json @@ -1624,8 +1298,6 @@ FILE: ../../../third_party/skia/infra/bots/recipes/test_pathkit.expected/pathkit FILE: ../../../third_party/skia/infra/bots/recipes/test_skqp_emulator.expected/Test-Debian9-Clang-GCE-CPU-Emulator-x86-devrel-All-Android_SKQP.json FILE: ../../../third_party/skia/infra/bots/recipes/upload_buildstats_results.expected/normal_bot.json FILE: ../../../third_party/skia/infra/bots/recipes/upload_buildstats_results.expected/trybot.json -FILE: ../../../third_party/skia/infra/bots/recipes/upload_calmbench_results.expected/normal_bot.json -FILE: ../../../third_party/skia/infra/bots/recipes/upload_calmbench_results.expected/trybot.json FILE: ../../../third_party/skia/infra/bots/recipes/upload_dm_results.expected/alternate_bucket.json FILE: ../../../third_party/skia/infra/bots/recipes/upload_dm_results.expected/failed_all.json FILE: ../../../third_party/skia/infra/bots/recipes/upload_dm_results.expected/failed_once.json @@ -1635,7 +1307,9 @@ FILE: ../../../third_party/skia/infra/bots/recipes/upload_nano_results.expected/ FILE: ../../../third_party/skia/infra/bots/recipes/upload_nano_results.expected/trybot.json FILE: ../../../third_party/skia/infra/bots/resources.isolate FILE: ../../../third_party/skia/infra/bots/run_recipe.isolate +FILE: ../../../third_party/skia/infra/bots/skottie_wasm.isolate FILE: ../../../third_party/skia/infra/bots/skpbench_skia_bundled.isolate +FILE: ../../../third_party/skia/infra/bots/skqp.isolate FILE: ../../../third_party/skia/infra/bots/swarm_recipe.isolate FILE: ../../../third_party/skia/infra/bots/task_drivers.isolate FILE: ../../../third_party/skia/infra/bots/tasks.json @@ -1647,6 +1321,9 @@ FILE: ../../../third_party/skia/infra/canvaskit/docker/canvaskit-emsdk/Dockerfil FILE: ../../../third_party/skia/infra/config/recipes.cfg FILE: ../../../third_party/skia/infra/cross-compile/docker/cross-linux-arm64/Dockerfile FILE: ../../../third_party/skia/infra/cts/whitelist_devices.json +FILE: ../../../third_party/skia/infra/gcc/Debian10-mips64el/Dockerfile +FILE: ../../../third_party/skia/infra/gcc/Debian10-x86/Dockerfile +FILE: ../../../third_party/skia/infra/gcc/Debian10/Dockerfile FILE: ../../../third_party/skia/infra/lottiecap/docker/gold-lottie-web-puppeteer/Dockerfile FILE: ../../../third_party/skia/infra/lottiecap/docker/lottie-web-puppeteer/Dockerfile FILE: ../../../third_party/skia/infra/project-config/cr-buildbucket.cfg @@ -1657,17 +1334,10 @@ FILE: ../../../third_party/skia/infra/wasm-common/docker/emsdk-base/Dockerfile FILE: ../../../third_party/skia/infra/wasm-common/docker/gold-karma-chrome-tests/Dockerfile FILE: ../../../third_party/skia/infra/wasm-common/docker/karma-chrome-tests/Dockerfile FILE: ../../../third_party/skia/infra/wasm-common/docker/perf-karma-chrome-tests/Dockerfile -FILE: ../../../third_party/skia/modules/canvaskit/canvaskit/NotoSerif-Regular.ttf -FILE: ../../../third_party/skia/modules/canvaskit/canvaskit/Roboto-Regular.ttf -FILE: ../../../third_party/skia/modules/canvaskit/canvaskit/Roboto-Regular.woff -FILE: ../../../third_party/skia/modules/canvaskit/canvaskit/example.html -FILE: ../../../third_party/skia/modules/canvaskit/canvaskit/extra.html -FILE: ../../../third_party/skia/modules/canvaskit/canvaskit/node.example.js -FILE: ../../../third_party/skia/modules/canvaskit/canvaskit/package.json -FILE: ../../../third_party/skia/modules/canvaskit/canvaskit/test.png FILE: ../../../third_party/skia/modules/canvaskit/cpu.js FILE: ../../../third_party/skia/modules/canvaskit/debug.js FILE: ../../../third_party/skia/modules/canvaskit/externs.js +FILE: ../../../third_party/skia/modules/canvaskit/font.js FILE: ../../../third_party/skia/modules/canvaskit/fonts/NotoMono-Regular.ttf FILE: ../../../third_party/skia/modules/canvaskit/gpu.js FILE: ../../../third_party/skia/modules/canvaskit/helper.js @@ -1689,6 +1359,8 @@ FILE: ../../../third_party/skia/modules/canvaskit/karma.bench.conf.js FILE: ../../../third_party/skia/modules/canvaskit/karma.conf.js FILE: ../../../third_party/skia/modules/canvaskit/package.json FILE: ../../../third_party/skia/modules/canvaskit/paragraph.js +FILE: ../../../third_party/skia/modules/canvaskit/particles.js +FILE: ../../../third_party/skia/modules/canvaskit/pathops.js FILE: ../../../third_party/skia/modules/canvaskit/perf/animation.bench.js FILE: ../../../third_party/skia/modules/canvaskit/perf/assets/confetti.json FILE: ../../../third_party/skia/modules/canvaskit/perf/assets/drinks.json @@ -1698,16 +1370,14 @@ FILE: ../../../third_party/skia/modules/canvaskit/postamble.js FILE: ../../../third_party/skia/modules/canvaskit/preamble.js FILE: ../../../third_party/skia/modules/canvaskit/ready.js FILE: ../../../third_party/skia/modules/canvaskit/release.js +FILE: ../../../third_party/skia/modules/canvaskit/rt_shader.js FILE: ../../../third_party/skia/modules/canvaskit/skottie.js +FILE: ../../../third_party/skia/modules/canvaskit/skp.js FILE: ../../../third_party/skia/modules/pathkit/chaining.js FILE: ../../../third_party/skia/modules/pathkit/externs.js FILE: ../../../third_party/skia/modules/pathkit/helper.js FILE: ../../../third_party/skia/modules/pathkit/karma.bench.conf.js FILE: ../../../third_party/skia/modules/pathkit/karma.conf.js -FILE: ../../../third_party/skia/modules/pathkit/npm-asmjs/example.html -FILE: ../../../third_party/skia/modules/pathkit/npm-asmjs/package.json -FILE: ../../../third_party/skia/modules/pathkit/npm-wasm/example.html -FILE: ../../../third_party/skia/modules/pathkit/npm-wasm/package.json FILE: ../../../third_party/skia/modules/pathkit/package.json FILE: ../../../third_party/skia/modules/pathkit/perf/effects.bench.js FILE: ../../../third_party/skia/modules/pathkit/perf/path.bench.js @@ -1725,9 +1395,9 @@ FILE: ../../../third_party/skia/modules/skparagraph/include/TextShadow.h FILE: ../../../third_party/skia/modules/skparagraph/include/TextStyle.h FILE: ../../../third_party/skia/modules/skparagraph/include/TypefaceFontProvider.h FILE: ../../../third_party/skia/modules/skparagraph/src/FontCollection.cpp -FILE: ../../../third_party/skia/modules/skparagraph/src/FontResolver.cpp -FILE: ../../../third_party/skia/modules/skparagraph/src/FontResolver.h FILE: ../../../third_party/skia/modules/skparagraph/src/Iterators.h +FILE: ../../../third_party/skia/modules/skparagraph/src/OneLineShaper.cpp +FILE: ../../../third_party/skia/modules/skparagraph/src/OneLineShaper.h FILE: ../../../third_party/skia/modules/skparagraph/src/ParagraphBuilderImpl.cpp FILE: ../../../third_party/skia/modules/skparagraph/src/ParagraphBuilderImpl.h FILE: ../../../third_party/skia/modules/skparagraph/src/ParagraphCache.cpp @@ -1743,6 +1413,7 @@ FILE: ../../../third_party/skia/modules/skparagraph/src/TextStyle.cpp FILE: ../../../third_party/skia/modules/skparagraph/src/TextWrapper.cpp FILE: ../../../third_party/skia/modules/skparagraph/src/TextWrapper.h FILE: ../../../third_party/skia/modules/skparagraph/src/TypefaceFontProvider.cpp +FILE: ../../../third_party/skia/modules/skparagraph/test.html FILE: ../../../third_party/skia/modules/skparagraph/utils/TestFontCollection.cpp FILE: ../../../third_party/skia/modules/skparagraph/utils/TestFontCollection.h FILE: ../../../third_party/skia/public.bzl @@ -1792,9 +1463,20 @@ FILE: ../../../third_party/skia/site/dev/design/conical/lemma4.ggb!/geogebra.xml FILE: ../../../third_party/skia/site/dev/design/conical/lemma4.ggb!/geogebra_javascript.js FILE: ../../../third_party/skia/site/dev/design/conical/lemma4.ggb!/geogebra_thumbnail.png FILE: ../../../third_party/skia/site/dev/design/conical/lemma4.svg +FILE: ../../../third_party/skia/site/dev/tools/buttons.png +FILE: ../../../third_party/skia/site/dev/tools/calendar.mskp +FILE: ../../../third_party/skia/site/dev/tools/crosshair.png FILE: ../../../third_party/skia/site/dev/tools/debugger.png +FILE: ../../../third_party/skia/site/dev/tools/end.png +FILE: ../../../third_party/skia/site/dev/tools/expand.png +FILE: ../../../third_party/skia/site/dev/tools/frameplayback.png +FILE: ../../../third_party/skia/site/dev/tools/gpuop.png FILE: ../../../third_party/skia/site/dev/tools/image.png +FILE: ../../../third_party/skia/site/dev/tools/layers.png FILE: ../../../third_party/skia/site/dev/tools/onlinedebugger.png +FILE: ../../../third_party/skia/site/dev/tools/playcommands.png +FILE: ../../../third_party/skia/site/dev/tools/resources.png +FILE: ../../../third_party/skia/site/dev/tools/settings.png FILE: ../../../third_party/skia/site/dev/tools/tracing.png FILE: ../../../third_party/skia/site/dev/tools/tracing_load.png FILE: ../../../third_party/skia/site/favicon.ico @@ -1808,6 +1490,7 @@ FILE: ../../../third_party/skia/specs/web-img-decode/proposed/impl/impl.js FILE: ../../../third_party/skia/specs/web-img-decode/proposed/index.html FILE: ../../../third_party/skia/src/core/SkOrderedReadBuffer.h FILE: ../../../third_party/skia/src/sksl/lex/sksl.lex +FILE: ../../../third_party/skia/src/sksl/sksl_blend.inc FILE: ../../../third_party/skia/src/sksl/sksl_fp.inc FILE: ../../../third_party/skia/src/sksl/sksl_frag.inc FILE: ../../../third_party/skia/src/sksl/sksl_geom.inc @@ -1822,15 +1505,17 @@ 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. + * 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 the copyright holder 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 @@ -2110,15 +1795,17 @@ 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. + * 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 the copyright holder 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 @@ -2181,7 +1868,6 @@ FILE: ../../../third_party/skia/gm/imagefilterscropexpand.cpp FILE: ../../../third_party/skia/gm/imagefiltersscaled.cpp FILE: ../../../third_party/skia/gm/imageresizetiled.cpp FILE: ../../../third_party/skia/gm/matriximagefilter.cpp -FILE: ../../../third_party/skia/gm/multipicturedraw.cpp FILE: ../../../third_party/skia/gm/patch.cpp FILE: ../../../third_party/skia/gm/picture.cpp FILE: ../../../third_party/skia/gm/pictureshader.cpp @@ -2212,7 +1898,6 @@ FILE: ../../../third_party/skia/include/core/SkBBHFactory.h FILE: ../../../third_party/skia/include/core/SkBlurTypes.h FILE: ../../../third_party/skia/include/core/SkDrawable.h FILE: ../../../third_party/skia/include/core/SkFont.h -FILE: ../../../third_party/skia/include/core/SkMultiPictureDraw.h FILE: ../../../third_party/skia/include/core/SkPictureRecorder.h FILE: ../../../third_party/skia/include/core/SkSurfaceProps.h FILE: ../../../third_party/skia/include/core/SkTextBlob.h @@ -2238,12 +1923,11 @@ FILE: ../../../third_party/skia/src/core/SkDistanceFieldGen.cpp FILE: ../../../third_party/skia/src/core/SkDistanceFieldGen.h FILE: ../../../third_party/skia/src/core/SkDrawable.cpp FILE: ../../../third_party/skia/src/core/SkFont.cpp -FILE: ../../../third_party/skia/src/core/SkForceCPlusPlusLinking.cpp +FILE: ../../../third_party/skia/src/core/SkFont_serial.cpp FILE: ../../../third_party/skia/src/core/SkHalf.cpp FILE: ../../../third_party/skia/src/core/SkImageGenerator.cpp FILE: ../../../third_party/skia/src/core/SkMaskCache.cpp FILE: ../../../third_party/skia/src/core/SkMaskCache.h -FILE: ../../../third_party/skia/src/core/SkMultiPictureDraw.cpp FILE: ../../../third_party/skia/src/core/SkPicturePlayback.cpp FILE: ../../../third_party/skia/src/core/SkPicturePlayback.h FILE: ../../../third_party/skia/src/core/SkPictureRecorder.cpp @@ -2357,15 +2041,17 @@ 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. + * 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 the copyright holder 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 @@ -2578,15 +2264,17 @@ 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. + * 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 the copyright holder 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 @@ -2748,8 +2436,6 @@ FILE: ../../../third_party/skia/src/codec/SkBmpStandardCodec.h FILE: ../../../third_party/skia/src/codec/SkCodec.cpp FILE: ../../../third_party/skia/src/codec/SkCodecImageGenerator.cpp FILE: ../../../third_party/skia/src/codec/SkCodecImageGenerator.h -FILE: ../../../third_party/skia/src/codec/SkGifCodec.cpp -FILE: ../../../third_party/skia/src/codec/SkGifCodec.h FILE: ../../../third_party/skia/src/codec/SkIcoCodec.cpp FILE: ../../../third_party/skia/src/codec/SkIcoCodec.h FILE: ../../../third_party/skia/src/codec/SkJpegCodec.cpp @@ -2989,15 +2675,17 @@ 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. + * 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 the copyright holder 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 @@ -3221,15 +2909,17 @@ 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. + * 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 the copyright holder 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 @@ -3262,6 +2952,7 @@ FILE: ../../../third_party/skia/gm/runtimefunctions.cpp FILE: ../../../third_party/skia/gm/runtimeshader.cpp FILE: ../../../third_party/skia/gm/skbug_9319.cpp FILE: ../../../third_party/skia/include/effects/SkImageFilters.h +FILE: ../../../third_party/skia/include/effects/SkRuntimeEffect.h FILE: ../../../third_party/skia/include/gpu/gl/GrGLAssembleHelpers.h FILE: ../../../third_party/skia/include/private/GrGLTypesPriv.h FILE: ../../../third_party/skia/include/private/SkThreadAnnotations.h @@ -3279,12 +2970,14 @@ FILE: ../../../third_party/skia/modules/particles/src/SkParticleBinding.cpp FILE: ../../../third_party/skia/modules/particles/src/SkParticleDrawable.cpp FILE: ../../../third_party/skia/modules/particles/src/SkParticleEffect.cpp FILE: ../../../third_party/skia/modules/particles/src/SkReflected.cpp +FILE: ../../../third_party/skia/modules/skresources/include/SkResources.h +FILE: ../../../third_party/skia/modules/skresources/src/SkResources.cpp FILE: ../../../third_party/skia/samplecode/SampleBackdropBounds.cpp FILE: ../../../third_party/skia/samplecode/SampleImageFilterDAG.cpp -FILE: ../../../third_party/skia/src/core/SkColorFilterPriv.h FILE: ../../../third_party/skia/src/core/SkImageFilterTypes.cpp FILE: ../../../third_party/skia/src/core/SkImageFilterTypes.h FILE: ../../../third_party/skia/src/core/SkImageFilter_Base.h +FILE: ../../../third_party/skia/src/core/SkRuntimeEffect.cpp FILE: ../../../third_party/skia/src/core/SkVM.cpp FILE: ../../../third_party/skia/src/core/SkVM.h FILE: ../../../third_party/skia/src/effects/imagefilters/SkImageFilters.cpp @@ -3347,15 +3040,17 @@ 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. + * 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 the copyright holder 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 @@ -3383,12 +3078,14 @@ FILE: ../../../third_party/skia/experimental/ffmpeg/SkVideoEncoder.h FILE: ../../../third_party/skia/gm/backdrop.cpp FILE: ../../../third_party/skia/gm/backdrop_imagefilter_croprect.cpp FILE: ../../../third_party/skia/gm/bug9331.cpp +FILE: ../../../third_party/skia/gm/clip_sierpinski_region.cpp FILE: ../../../third_party/skia/gm/collapsepaths.cpp FILE: ../../../third_party/skia/gm/compositor_quads.cpp FILE: ../../../third_party/skia/gm/crbug_913349.cpp FILE: ../../../third_party/skia/gm/crbug_938592.cpp FILE: ../../../third_party/skia/gm/crbug_947055.cpp FILE: ../../../third_party/skia/gm/crbug_996140.cpp +FILE: ../../../third_party/skia/gm/ducky_yuv_blend.cpp FILE: ../../../third_party/skia/gm/fiddle.cpp FILE: ../../../third_party/skia/gm/mac_aa_explorer.cpp FILE: ../../../third_party/skia/gm/mixercolorfilter.cpp @@ -3397,8 +3094,9 @@ FILE: ../../../third_party/skia/gm/postercircle.cpp FILE: ../../../third_party/skia/gm/samplelocations.cpp FILE: ../../../third_party/skia/gm/skbug_8664.cpp FILE: ../../../third_party/skia/gm/skbug_8955.cpp -FILE: ../../../third_party/skia/gm/skvm.cpp +FILE: ../../../third_party/skia/gm/tessellation.cpp FILE: ../../../third_party/skia/gm/video_decoder.cpp +FILE: ../../../third_party/skia/gm/yuv420_odd_dim.cpp FILE: ../../../third_party/skia/include/core/SkTileMode.h FILE: ../../../third_party/skia/include/gpu/GrContextThreadSafeProxy.h FILE: ../../../third_party/skia/include/gpu/dawn/GrDawnTypes.h @@ -3418,6 +3116,7 @@ FILE: ../../../third_party/skia/modules/skottie/src/effects/FillEffect.cpp FILE: ../../../third_party/skia/modules/skottie/src/effects/GaussianBlurEffect.cpp FILE: ../../../third_party/skia/modules/skottie/src/effects/GradientEffect.cpp FILE: ../../../third_party/skia/modules/skottie/src/effects/HueSaturationEffect.cpp +FILE: ../../../third_party/skia/modules/skottie/src/effects/InvertEffect.cpp FILE: ../../../third_party/skia/modules/skottie/src/effects/LevelsEffect.cpp FILE: ../../../third_party/skia/modules/skottie/src/effects/LinearWipeEffect.cpp FILE: ../../../third_party/skia/modules/skottie/src/effects/MotionBlurEffect.cpp @@ -3461,6 +3160,7 @@ FILE: ../../../third_party/skia/src/core/SkStrikeForGPU.cpp FILE: ../../../third_party/skia/src/core/SkStrikeForGPU.h FILE: ../../../third_party/skia/src/core/SkStrikeSpec.h FILE: ../../../third_party/skia/src/core/SkVMBlitter.cpp +FILE: ../../../third_party/skia/src/core/SkVMBlitter.h FILE: ../../../third_party/skia/src/core/SkYUVMath.cpp FILE: ../../../third_party/skia/src/core/SkYUVMath.h FILE: ../../../third_party/skia/src/core/SkZip.h @@ -3539,25 +3239,394 @@ FILE: ../../../third_party/skia/src/sksl/SkSLDefines.h FILE: ../../../third_party/skia/src/sksl/SkSLOutputStream.cpp FILE: ../../../third_party/skia/src/utils/SkCharToGlyphCache.cpp FILE: ../../../third_party/skia/src/utils/SkCharToGlyphCache.h +FILE: ../../../third_party/skia/src/utils/SkClipStackUtils.cpp +FILE: ../../../third_party/skia/src/utils/SkClipStackUtils.h FILE: ../../../third_party/skia/src/utils/SkShaperJSONWriter.cpp FILE: ../../../third_party/skia/src/utils/SkShaperJSONWriter.h FILE: ../../../third_party/skia/src/utils/win/SkObjBase.h ---------------------------------------------------------------------------------------------------- -Copyright 2019 Google Inc. +Copyright 2019 Google Inc. + +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 the copyright holder 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: skia +ORIGIN: ../../../third_party/skia/bench/ClearBench.cpp + ../../../third_party/skia/LICENSE +TYPE: LicenseType.bsd +FILE: ../../../third_party/skia/bench/ClearBench.cpp +FILE: ../../../third_party/skia/bench/CompositingImagesBench.cpp +FILE: ../../../third_party/skia/bench/CubicMapBench.cpp +FILE: ../../../third_party/skia/bench/GrCCFillGeometryBench.cpp +FILE: ../../../third_party/skia/bench/ImageCycleBench.cpp +FILE: ../../../third_party/skia/bench/JSONBench.cpp +FILE: ../../../third_party/skia/bench/PathOpsBench.cpp +FILE: ../../../third_party/skia/bench/PolyUtilsBench.cpp +FILE: ../../../third_party/skia/bench/ShaderMaskFilterBench.cpp +FILE: ../../../third_party/skia/bench/TypefaceBench.cpp +FILE: ../../../third_party/skia/experimental/pvg/draw_msg.proto +FILE: ../../../third_party/skia/fuzz/FuzzCommon.cpp +FILE: ../../../third_party/skia/fuzz/FuzzPathMeasure.cpp +FILE: ../../../third_party/skia/fuzz/FuzzRegionOp.cpp +FILE: ../../../third_party/skia/fuzz/oss_fuzz/FuzzImageFilterDeserialize.cpp +FILE: ../../../third_party/skia/fuzz/oss_fuzz/FuzzPathDeserialize.cpp +FILE: ../../../third_party/skia/fuzz/oss_fuzz/FuzzRegionDeserialize.cpp +FILE: ../../../third_party/skia/fuzz/oss_fuzz/FuzzRegionSetPath.cpp +FILE: ../../../third_party/skia/gm/analytic_gradients.cpp +FILE: ../../../third_party/skia/gm/androidblendmodes.cpp +FILE: ../../../third_party/skia/gm/b_119394958.cpp +FILE: ../../../third_party/skia/gm/clockwise.cpp +FILE: ../../../third_party/skia/gm/crbug_847759.cpp +FILE: ../../../third_party/skia/gm/crbug_884166.cpp +FILE: ../../../third_party/skia/gm/crbug_887103.cpp +FILE: ../../../third_party/skia/gm/crbug_892988.cpp +FILE: ../../../third_party/skia/gm/crbug_899512.cpp +FILE: ../../../third_party/skia/gm/crbug_905548.cpp +FILE: ../../../third_party/skia/gm/daa.cpp +FILE: ../../../third_party/skia/gm/drawimageset.cpp +FILE: ../../../third_party/skia/gm/drawquadset.cpp +FILE: ../../../third_party/skia/gm/fontregen.cpp +FILE: ../../../third_party/skia/gm/fwidth_squircle.cpp +FILE: ../../../third_party/skia/gm/gradients_degenerate.cpp +FILE: ../../../third_party/skia/gm/hugepath.cpp +FILE: ../../../third_party/skia/gm/localmatrixshader.cpp +FILE: ../../../third_party/skia/gm/make_raster_image.cpp +FILE: ../../../third_party/skia/gm/mandoline.cpp +FILE: ../../../third_party/skia/gm/orientation.cpp +FILE: ../../../third_party/skia/gm/p3.cpp +FILE: ../../../third_party/skia/gm/pathmeasure.cpp +FILE: ../../../third_party/skia/gm/perspimages.cpp +FILE: ../../../third_party/skia/gm/polygonoffset.cpp +FILE: ../../../third_party/skia/gm/scaledemoji.cpp +FILE: ../../../third_party/skia/gm/scaledemoji_rendering.cpp +FILE: ../../../third_party/skia/gm/shadermaskfilter.cpp +FILE: ../../../third_party/skia/gm/sharedcorners.cpp +FILE: ../../../third_party/skia/gm/skinning.cpp +FILE: ../../../third_party/skia/gm/trickycubicstrokes.cpp +FILE: ../../../third_party/skia/gm/unpremul.cpp +FILE: ../../../third_party/skia/gm/wacky_yuv_formats.cpp +FILE: ../../../third_party/skia/include/android/SkAnimatedImage.h +FILE: ../../../third_party/skia/include/c/sk_colorspace.h +FILE: ../../../third_party/skia/include/c/sk_imageinfo.h +FILE: ../../../third_party/skia/include/core/SkCanvasVirtualEnforcer.h +FILE: ../../../third_party/skia/include/core/SkContourMeasure.h +FILE: ../../../third_party/skia/include/core/SkCoverageMode.h +FILE: ../../../third_party/skia/include/core/SkCubicMap.h +FILE: ../../../third_party/skia/include/core/SkFontMetrics.h +FILE: ../../../third_party/skia/include/core/SkFontParameters.h +FILE: ../../../third_party/skia/include/core/SkFontTypes.h +FILE: ../../../third_party/skia/include/core/SkYUVAIndex.h +FILE: ../../../third_party/skia/include/effects/SkOpPathEffect.h +FILE: ../../../third_party/skia/include/effects/SkShaderMaskFilter.h +FILE: ../../../third_party/skia/include/effects/SkTrimPathEffect.h +FILE: ../../../third_party/skia/include/gpu/GrBackendDrawableInfo.h +FILE: ../../../third_party/skia/include/gpu/GrDriverBugWorkarounds.h +FILE: ../../../third_party/skia/include/gpu/vk/GrVkMemoryAllocator.h +FILE: ../../../third_party/skia/include/gpu/vk/GrVkVulkan.h +FILE: ../../../third_party/skia/include/ports/SkFontMgr_fuchsia.h +FILE: ../../../third_party/skia/include/private/GrVkTypesPriv.h +FILE: ../../../third_party/skia/include/private/SkMacros.h +FILE: ../../../third_party/skia/include/private/SkSafe32.h +FILE: ../../../third_party/skia/include/private/SkTo.h +FILE: ../../../third_party/skia/include/utils/Sk3D.h +FILE: ../../../third_party/skia/include/utils/SkAnimCodecPlayer.h +FILE: ../../../third_party/skia/include/utils/SkTextUtils.h +FILE: ../../../third_party/skia/infra/cts/run_testlab.go +FILE: ../../../third_party/skia/modules/skottie/gm/3dgm.cpp +FILE: ../../../third_party/skia/modules/skottie/include/SkottieProperty.h +FILE: ../../../third_party/skia/modules/skottie/src/SkottieAdapter.cpp +FILE: ../../../third_party/skia/modules/skottie/src/SkottieAdapter.h +FILE: ../../../third_party/skia/modules/skottie/src/SkottieJson.cpp +FILE: ../../../third_party/skia/modules/skottie/src/SkottieJson.h +FILE: ../../../third_party/skia/modules/skottie/src/SkottiePriv.h +FILE: ../../../third_party/skia/modules/skottie/src/SkottieProperty.cpp +FILE: ../../../third_party/skia/modules/skottie/src/SkottieTest.cpp +FILE: ../../../third_party/skia/modules/skottie/src/SkottieTool.cpp +FILE: ../../../third_party/skia/modules/skottie/src/layers/PrecompLayer.cpp +FILE: ../../../third_party/skia/modules/skottie/src/layers/ShapeLayer.cpp +FILE: ../../../third_party/skia/modules/skottie/src/layers/TextLayer.cpp +FILE: ../../../third_party/skia/modules/skottie/utils/SkottieUtils.cpp +FILE: ../../../third_party/skia/modules/skottie/utils/SkottieUtils.h +FILE: ../../../third_party/skia/modules/sksg/include/SkSGClipEffect.h +FILE: ../../../third_party/skia/modules/sksg/include/SkSGColorFilter.h +FILE: ../../../third_party/skia/modules/sksg/include/SkSGGeometryTransform.h +FILE: ../../../third_party/skia/modules/sksg/include/SkSGGradient.h +FILE: ../../../third_party/skia/modules/sksg/include/SkSGImage.h +FILE: ../../../third_party/skia/modules/sksg/include/SkSGMaskEffect.h +FILE: ../../../third_party/skia/modules/sksg/include/SkSGOpacityEffect.h +FILE: ../../../third_party/skia/modules/sksg/include/SkSGPlane.h +FILE: ../../../third_party/skia/modules/sksg/include/SkSGRoundEffect.h +FILE: ../../../third_party/skia/modules/sksg/include/SkSGScene.h +FILE: ../../../third_party/skia/modules/sksg/include/SkSGText.h +FILE: ../../../third_party/skia/modules/sksg/src/SkSGClipEffect.cpp +FILE: ../../../third_party/skia/modules/sksg/src/SkSGColorFilter.cpp +FILE: ../../../third_party/skia/modules/sksg/src/SkSGGeometryTransform.cpp +FILE: ../../../third_party/skia/modules/sksg/src/SkSGGradient.cpp +FILE: ../../../third_party/skia/modules/sksg/src/SkSGImage.cpp +FILE: ../../../third_party/skia/modules/sksg/src/SkSGMaskEffect.cpp +FILE: ../../../third_party/skia/modules/sksg/src/SkSGOpacityEffect.cpp +FILE: ../../../third_party/skia/modules/sksg/src/SkSGPlane.cpp +FILE: ../../../third_party/skia/modules/sksg/src/SkSGRoundEffect.cpp +FILE: ../../../third_party/skia/modules/sksg/src/SkSGScene.cpp +FILE: ../../../third_party/skia/modules/sksg/src/SkSGText.cpp +FILE: ../../../third_party/skia/modules/skshaper/src/SkShaper.cpp +FILE: ../../../third_party/skia/samplecode/SampleAnimatedImage.cpp +FILE: ../../../third_party/skia/samplecode/SampleCusp.cpp +FILE: ../../../third_party/skia/samplecode/SampleFlutterAnimate.cpp +FILE: ../../../third_party/skia/samplecode/SampleGlyphTransform.cpp +FILE: ../../../third_party/skia/src/android/SkAnimatedImage.cpp +FILE: ../../../third_party/skia/src/c/sk_imageinfo.cpp +FILE: ../../../third_party/skia/src/codec/SkEncodedInfo.cpp +FILE: ../../../third_party/skia/src/codec/SkParseEncodedOrigin.cpp +FILE: ../../../third_party/skia/src/codec/SkWuffsCodec.cpp +FILE: ../../../third_party/skia/src/codec/SkWuffsCodec.h +FILE: ../../../third_party/skia/src/core/SkBlurPriv.h +FILE: ../../../third_party/skia/src/core/SkCanvasPriv.cpp +FILE: ../../../third_party/skia/src/core/SkColorSpaceXformSteps.cpp +FILE: ../../../third_party/skia/src/core/SkColorSpaceXformSteps.h +FILE: ../../../third_party/skia/src/core/SkContourMeasure.cpp +FILE: ../../../third_party/skia/src/core/SkCoverageModePriv.h +FILE: ../../../third_party/skia/src/core/SkCubicMap.cpp +FILE: ../../../third_party/skia/src/core/SkCubicSolver.h +FILE: ../../../third_party/skia/src/core/SkDeferredDisplayList.cpp +FILE: ../../../third_party/skia/src/core/SkDeferredDisplayListPriv.h +FILE: ../../../third_party/skia/src/core/SkDraw_text.cpp +FILE: ../../../third_party/skia/src/core/SkFontPriv.h +FILE: ../../../third_party/skia/src/core/SkGlyph.cpp +FILE: ../../../third_party/skia/src/core/SkIPoint16.h +FILE: ../../../third_party/skia/src/core/SkMaskFilterBase.h +FILE: ../../../third_party/skia/src/core/SkPath_serial.cpp +FILE: ../../../third_party/skia/src/core/SkPicturePriv.h +FILE: ../../../third_party/skia/src/core/SkRRectPriv.h +FILE: ../../../third_party/skia/src/core/SkRectPriv.h +FILE: ../../../third_party/skia/src/core/SkRemoteGlyphCache.cpp +FILE: ../../../third_party/skia/src/core/SkRemoteGlyphCache.h +FILE: ../../../third_party/skia/src/core/SkSafeRange.h +FILE: ../../../third_party/skia/src/core/SkSpan.h +FILE: ../../../third_party/skia/src/core/SkStrikeCache.cpp +FILE: ../../../third_party/skia/src/core/SkSurfaceCharacterization.cpp +FILE: ../../../third_party/skia/src/core/SkTextBlobPriv.h +FILE: ../../../third_party/skia/src/core/SkTypeface_remote.cpp +FILE: ../../../third_party/skia/src/core/SkTypeface_remote.h +FILE: ../../../third_party/skia/src/core/SkYUVASizeInfo.cpp +FILE: ../../../third_party/skia/src/effects/SkOpPE.h +FILE: ../../../third_party/skia/src/effects/SkOpPathEffect.cpp +FILE: ../../../third_party/skia/src/effects/SkShaderMaskFilter.cpp +FILE: ../../../third_party/skia/src/effects/SkTrimPE.h +FILE: ../../../third_party/skia/src/effects/SkTrimPathEffect.cpp +FILE: ../../../third_party/skia/src/gpu/GrContextThreadSafeProxyPriv.h +FILE: ../../../third_party/skia/src/gpu/GrDDLContext.cpp +FILE: ../../../third_party/skia/src/gpu/GrDriverBugWorkarounds.cpp +FILE: ../../../third_party/skia/src/gpu/GrFPArgs.h +FILE: ../../../third_party/skia/src/gpu/GrLegacyDirectContext.cpp +FILE: ../../../third_party/skia/src/gpu/GrProxyProvider.cpp +FILE: ../../../third_party/skia/src/gpu/GrProxyProvider.h +FILE: ../../../third_party/skia/src/gpu/GrRenderTargetProxyPriv.h +FILE: ../../../third_party/skia/src/gpu/GrResourceProviderPriv.h +FILE: ../../../third_party/skia/src/gpu/ccpr/GrCCClipPath.cpp +FILE: ../../../third_party/skia/src/gpu/ccpr/GrCCClipPath.h +FILE: ../../../third_party/skia/src/gpu/ccpr/GrCCConicShader.cpp +FILE: ../../../third_party/skia/src/gpu/ccpr/GrCCConicShader.h +FILE: ../../../third_party/skia/src/gpu/ccpr/GrCCDrawPathsOp.cpp +FILE: ../../../third_party/skia/src/gpu/ccpr/GrCCDrawPathsOp.h +FILE: ../../../third_party/skia/src/gpu/ccpr/GrCCPathCache.cpp +FILE: ../../../third_party/skia/src/gpu/ccpr/GrCCPathCache.h +FILE: ../../../third_party/skia/src/gpu/ccpr/GrCCPerFlushResources.cpp +FILE: ../../../third_party/skia/src/gpu/ccpr/GrCCPerFlushResources.h +FILE: ../../../third_party/skia/src/gpu/ccpr/GrCCPerOpsTaskPaths.h +FILE: ../../../third_party/skia/src/gpu/ccpr/GrCCSTLList.h +FILE: ../../../third_party/skia/src/gpu/ccpr/GrCCStrokeGeometry.cpp +FILE: ../../../third_party/skia/src/gpu/ccpr/GrCCStrokeGeometry.h +FILE: ../../../third_party/skia/src/gpu/ccpr/GrCCStroker.cpp +FILE: ../../../third_party/skia/src/gpu/ccpr/GrCCStroker.h +FILE: ../../../third_party/skia/src/gpu/effects/GrAARectEffect.fp +FILE: ../../../third_party/skia/src/gpu/effects/GrAlphaThresholdFragmentProcessor.fp +FILE: ../../../third_party/skia/src/gpu/effects/GrCircleBlurFragmentProcessor.fp +FILE: ../../../third_party/skia/src/gpu/effects/GrConfigConversionEffect.fp +FILE: ../../../third_party/skia/src/gpu/effects/GrConstColorProcessor.fp +FILE: ../../../third_party/skia/src/gpu/effects/GrLumaColorFilterEffect.fp +FILE: ../../../third_party/skia/src/gpu/effects/GrMagnifierEffect.fp +FILE: ../../../third_party/skia/src/gpu/effects/GrPremulInputFragmentProcessor.fp +FILE: ../../../third_party/skia/src/gpu/effects/GrRRectBlurEffect.fp +FILE: ../../../third_party/skia/src/gpu/effects/GrRectBlurEffect.fp +FILE: ../../../third_party/skia/src/gpu/effects/GrSkSLFP.cpp +FILE: ../../../third_party/skia/src/gpu/effects/GrSkSLFP.h +FILE: ../../../third_party/skia/src/gpu/effects/GrYUVtoRGBEffect.cpp +FILE: ../../../third_party/skia/src/gpu/effects/GrYUVtoRGBEffect.h +FILE: ../../../third_party/skia/src/gpu/effects/generated/GrAARectEffect.cpp +FILE: ../../../third_party/skia/src/gpu/effects/generated/GrAARectEffect.h +FILE: ../../../third_party/skia/src/gpu/effects/generated/GrAlphaThresholdFragmentProcessor.cpp +FILE: ../../../third_party/skia/src/gpu/effects/generated/GrAlphaThresholdFragmentProcessor.h +FILE: ../../../third_party/skia/src/gpu/effects/generated/GrCircleBlurFragmentProcessor.cpp +FILE: ../../../third_party/skia/src/gpu/effects/generated/GrCircleBlurFragmentProcessor.h +FILE: ../../../third_party/skia/src/gpu/effects/generated/GrConfigConversionEffect.cpp +FILE: ../../../third_party/skia/src/gpu/effects/generated/GrConfigConversionEffect.h +FILE: ../../../third_party/skia/src/gpu/effects/generated/GrConstColorProcessor.cpp +FILE: ../../../third_party/skia/src/gpu/effects/generated/GrConstColorProcessor.h +FILE: ../../../third_party/skia/src/gpu/effects/generated/GrLumaColorFilterEffect.cpp +FILE: ../../../third_party/skia/src/gpu/effects/generated/GrLumaColorFilterEffect.h +FILE: ../../../third_party/skia/src/gpu/effects/generated/GrMagnifierEffect.cpp +FILE: ../../../third_party/skia/src/gpu/effects/generated/GrMagnifierEffect.h +FILE: ../../../third_party/skia/src/gpu/effects/generated/GrPremulInputFragmentProcessor.cpp +FILE: ../../../third_party/skia/src/gpu/effects/generated/GrPremulInputFragmentProcessor.h +FILE: ../../../third_party/skia/src/gpu/effects/generated/GrRRectBlurEffect.cpp +FILE: ../../../third_party/skia/src/gpu/effects/generated/GrRRectBlurEffect.h +FILE: ../../../third_party/skia/src/gpu/effects/generated/GrRectBlurEffect.cpp +FILE: ../../../third_party/skia/src/gpu/effects/generated/GrRectBlurEffect.h +FILE: ../../../third_party/skia/src/gpu/geometry/GrQuad.cpp +FILE: ../../../third_party/skia/src/gpu/gradients/GrClampedGradientEffect.fp +FILE: ../../../third_party/skia/src/gpu/gradients/GrDualIntervalGradientColorizer.fp +FILE: ../../../third_party/skia/src/gpu/gradients/GrGradientBitmapCache.cpp +FILE: ../../../third_party/skia/src/gpu/gradients/GrGradientBitmapCache.h +FILE: ../../../third_party/skia/src/gpu/gradients/GrGradientShader.cpp +FILE: ../../../third_party/skia/src/gpu/gradients/GrGradientShader.h +FILE: ../../../third_party/skia/src/gpu/gradients/GrLinearGradientLayout.fp +FILE: ../../../third_party/skia/src/gpu/gradients/GrRadialGradientLayout.fp +FILE: ../../../third_party/skia/src/gpu/gradients/GrSingleIntervalGradientColorizer.fp +FILE: ../../../third_party/skia/src/gpu/gradients/GrSweepGradientLayout.fp +FILE: ../../../third_party/skia/src/gpu/gradients/GrTextureGradientColorizer.fp +FILE: ../../../third_party/skia/src/gpu/gradients/GrTiledGradientEffect.fp +FILE: ../../../third_party/skia/src/gpu/gradients/GrTwoPointConicalGradientLayout.fp +FILE: ../../../third_party/skia/src/gpu/gradients/GrUnrolledBinaryGradientColorizer.fp +FILE: ../../../third_party/skia/src/gpu/gradients/generated/GrClampedGradientEffect.cpp +FILE: ../../../third_party/skia/src/gpu/gradients/generated/GrClampedGradientEffect.h +FILE: ../../../third_party/skia/src/gpu/gradients/generated/GrDualIntervalGradientColorizer.cpp +FILE: ../../../third_party/skia/src/gpu/gradients/generated/GrDualIntervalGradientColorizer.h +FILE: ../../../third_party/skia/src/gpu/gradients/generated/GrLinearGradientLayout.cpp +FILE: ../../../third_party/skia/src/gpu/gradients/generated/GrLinearGradientLayout.h +FILE: ../../../third_party/skia/src/gpu/gradients/generated/GrRadialGradientLayout.cpp +FILE: ../../../third_party/skia/src/gpu/gradients/generated/GrRadialGradientLayout.h +FILE: ../../../third_party/skia/src/gpu/gradients/generated/GrSingleIntervalGradientColorizer.cpp +FILE: ../../../third_party/skia/src/gpu/gradients/generated/GrSingleIntervalGradientColorizer.h +FILE: ../../../third_party/skia/src/gpu/gradients/generated/GrSweepGradientLayout.cpp +FILE: ../../../third_party/skia/src/gpu/gradients/generated/GrSweepGradientLayout.h +FILE: ../../../third_party/skia/src/gpu/gradients/generated/GrTextureGradientColorizer.cpp +FILE: ../../../third_party/skia/src/gpu/gradients/generated/GrTextureGradientColorizer.h +FILE: ../../../third_party/skia/src/gpu/gradients/generated/GrTiledGradientEffect.cpp +FILE: ../../../third_party/skia/src/gpu/gradients/generated/GrTiledGradientEffect.h +FILE: ../../../third_party/skia/src/gpu/gradients/generated/GrTwoPointConicalGradientLayout.cpp +FILE: ../../../third_party/skia/src/gpu/gradients/generated/GrTwoPointConicalGradientLayout.h +FILE: ../../../third_party/skia/src/gpu/gradients/generated/GrUnrolledBinaryGradientColorizer.cpp +FILE: ../../../third_party/skia/src/gpu/gradients/generated/GrUnrolledBinaryGradientColorizer.h +FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlBuffer.h +FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlBuffer.mm +FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlCppUtil.h +FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlDepthStencil.mm +FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlOpsRenderPass.h +FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlOpsRenderPass.mm +FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlPipelineState.h +FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlPipelineState.mm +FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlPipelineStateBuilder.h +FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlPipelineStateBuilder.mm +FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlPipelineStateDataManager.h +FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlPipelineStateDataManager.mm +FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlResourceProvider.h +FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlResourceProvider.mm +FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlSampler.h +FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlSampler.mm +FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlStencilAttachment.h +FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlStencilAttachment.mm +FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlTextureRenderTarget.h +FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlTextureRenderTarget.mm +FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlUniformHandler.h +FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlUniformHandler.mm +FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlVaryingHandler.h +FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlVaryingHandler.mm +FILE: ../../../third_party/skia/src/gpu/ops/GrClearStencilClipOp.cpp +FILE: ../../../third_party/skia/src/gpu/ops/GrDrawableOp.cpp +FILE: ../../../third_party/skia/src/gpu/ops/GrDrawableOp.h +FILE: ../../../third_party/skia/src/gpu/ops/GrFillRRectOp.cpp +FILE: ../../../third_party/skia/src/gpu/ops/GrFillRRectOp.h +FILE: ../../../third_party/skia/src/gpu/ops/GrFillRectOp.cpp +FILE: ../../../third_party/skia/src/gpu/ops/GrFillRectOp.h +FILE: ../../../third_party/skia/src/gpu/ops/GrQuadPerEdgeAA.cpp +FILE: ../../../third_party/skia/src/gpu/ops/GrQuadPerEdgeAA.h +FILE: ../../../third_party/skia/src/gpu/ops/GrStrokeRectOp.cpp +FILE: ../../../third_party/skia/src/gpu/ops/GrStrokeRectOp.h +FILE: ../../../third_party/skia/src/gpu/text/GrAtlasManager.cpp +FILE: ../../../third_party/skia/src/gpu/text/GrAtlasManager.h +FILE: ../../../third_party/skia/src/gpu/text/GrSDFMaskFilter.cpp +FILE: ../../../third_party/skia/src/gpu/text/GrSDFMaskFilter.h +FILE: ../../../third_party/skia/src/gpu/vk/GrVkAMDMemoryAllocator.cpp +FILE: ../../../third_party/skia/src/gpu/vk/GrVkAMDMemoryAllocator.h +FILE: ../../../third_party/skia/src/gpu/vk/GrVkCommandPool.cpp +FILE: ../../../third_party/skia/src/gpu/vk/GrVkCommandPool.h +FILE: ../../../third_party/skia/src/gpu/vk/GrVkImageLayout.h +FILE: ../../../third_party/skia/src/gpu/vk/GrVkSamplerYcbcrConversion.cpp +FILE: ../../../third_party/skia/src/gpu/vk/GrVkSamplerYcbcrConversion.h +FILE: ../../../third_party/skia/src/gpu/vk/GrVkTypesPriv.cpp +FILE: ../../../third_party/skia/src/image/SkImage_GpuBase.cpp +FILE: ../../../third_party/skia/src/image/SkImage_GpuBase.h +FILE: ../../../third_party/skia/src/image/SkImage_GpuYUVA.cpp +FILE: ../../../third_party/skia/src/image/SkImage_GpuYUVA.h +FILE: ../../../third_party/skia/src/image/SkImage_Lazy.h +FILE: ../../../third_party/skia/src/opts/SkBitmapProcState_opts.h +FILE: ../../../third_party/skia/src/opts/SkOpts_hsw.cpp +FILE: ../../../third_party/skia/src/opts/SkRasterPipeline_opts.h +FILE: ../../../third_party/skia/src/pathops/SkPathOpsAsWinding.cpp +FILE: ../../../third_party/skia/src/pathops/SkPathOpsTCurve.h +FILE: ../../../third_party/skia/src/pdf/SkClusterator.cpp +FILE: ../../../third_party/skia/src/pdf/SkClusterator.h +FILE: ../../../third_party/skia/src/pdf/SkPDFTag.cpp +FILE: ../../../third_party/skia/src/pdf/SkPDFTag.h +FILE: ../../../third_party/skia/src/ports/SkFontMgr_fuchsia.cpp +FILE: ../../../third_party/skia/src/sksl/SkSLByteCode.cpp +FILE: ../../../third_party/skia/src/sksl/SkSLCPPUniformCTypes.cpp +FILE: ../../../third_party/skia/src/sksl/SkSLCPPUniformCTypes.h +FILE: ../../../third_party/skia/src/sksl/SkSLPipelineStageCodeGenerator.cpp +FILE: ../../../third_party/skia/src/sksl/ir/SkSLVariableReference.cpp +FILE: ../../../third_party/skia/src/utils/Sk3D.cpp +FILE: ../../../third_party/skia/src/utils/SkAnimCodecPlayer.cpp +FILE: ../../../third_party/skia/src/utils/SkCallableTraits.h +FILE: ../../../third_party/skia/src/utils/SkJSON.cpp +FILE: ../../../third_party/skia/src/utils/SkJSON.h +FILE: ../../../third_party/skia/src/utils/SkTextUtils.cpp +FILE: ../../../third_party/skia/src/utils/mac/SkUniqueCFRef.h +FILE: ../../../third_party/skia/src/utils/win/SkDWriteNTDDI_VERSION.h +---------------------------------------------------------------------------------------------------- +Copyright 2018 Google Inc. 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. + * 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 the copyright holder 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 @@ -3783,15 +3852,14 @@ FILE: ../../../third_party/skia/src/gpu/effects/GrAtlasedShaderHelpers.h FILE: ../../../third_party/skia/src/gpu/effects/GrBlurredEdgeFragmentProcessor.fp FILE: ../../../third_party/skia/src/gpu/effects/GrCircleEffect.fp FILE: ../../../third_party/skia/src/gpu/effects/GrEllipseEffect.fp -FILE: ../../../third_party/skia/src/gpu/effects/GrSimpleTextureEffect.fp +FILE: ../../../third_party/skia/src/gpu/effects/GrTextureEffect.cpp +FILE: ../../../third_party/skia/src/gpu/effects/GrTextureEffect.h FILE: ../../../third_party/skia/src/gpu/effects/generated/GrBlurredEdgeFragmentProcessor.cpp FILE: ../../../third_party/skia/src/gpu/effects/generated/GrBlurredEdgeFragmentProcessor.h FILE: ../../../third_party/skia/src/gpu/effects/generated/GrCircleEffect.cpp FILE: ../../../third_party/skia/src/gpu/effects/generated/GrCircleEffect.h FILE: ../../../third_party/skia/src/gpu/effects/generated/GrEllipseEffect.cpp FILE: ../../../third_party/skia/src/gpu/effects/generated/GrEllipseEffect.h -FILE: ../../../third_party/skia/src/gpu/effects/generated/GrSimpleTextureEffect.cpp -FILE: ../../../third_party/skia/src/gpu/effects/generated/GrSimpleTextureEffect.h FILE: ../../../third_party/skia/src/gpu/gl/GrGLOpsRenderPass.cpp FILE: ../../../third_party/skia/src/gpu/gl/GrGLSemaphore.cpp FILE: ../../../third_party/skia/src/gpu/gl/GrGLSemaphore.h @@ -3817,14 +3885,11 @@ FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlTrampoline.mm FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlUtil.h FILE: ../../../third_party/skia/src/gpu/mtl/GrMtlUtil.mm FILE: ../../../third_party/skia/src/gpu/ops/GrClearOp.cpp -FILE: ../../../third_party/skia/src/gpu/ops/GrDebugMarkerOp.h FILE: ../../../third_party/skia/src/gpu/ops/GrSimpleMeshDrawOpHelper.cpp FILE: ../../../third_party/skia/src/gpu/ops/GrSimpleMeshDrawOpHelper.h FILE: ../../../third_party/skia/src/gpu/ops/GrStencilPathOp.cpp FILE: ../../../third_party/skia/src/gpu/ops/GrTextureOp.cpp FILE: ../../../third_party/skia/src/gpu/ops/GrTextureOp.h -FILE: ../../../third_party/skia/src/gpu/vk/GrVkBufferView.cpp -FILE: ../../../third_party/skia/src/gpu/vk/GrVkBufferView.h FILE: ../../../third_party/skia/src/gpu/vk/GrVkSemaphore.cpp FILE: ../../../third_party/skia/src/gpu/vk/GrVkSemaphore.h FILE: ../../../third_party/skia/src/opts/SkUtils_opts.h @@ -3885,15 +3950,17 @@ 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. + * 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 the copyright holder 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 @@ -4199,7 +4266,6 @@ FILE: ../../../third_party/skia/docs/examples/IPoint_subtract_operator.cpp FILE: ../../../third_party/skia/docs/examples/IPoint_subtractfrom_operator.cpp FILE: ../../../third_party/skia/docs/examples/IPoint_x.cpp FILE: ../../../third_party/skia/docs/examples/IPoint_y.cpp -FILE: ../../../third_party/skia/docs/examples/IRect_EmptyIRect.cpp FILE: ../../../third_party/skia/docs/examples/IRect_Intersects.cpp FILE: ../../../third_party/skia/docs/examples/IRect_MakeEmpty.cpp FILE: ../../../third_party/skia/docs/examples/IRect_MakeLTRB.cpp @@ -4340,12 +4406,9 @@ FILE: ../../../third_party/skia/docs/examples/Matrix_SetAffineIdentity.cpp FILE: ../../../third_party/skia/docs/examples/Matrix_TypeMask.cpp FILE: ../../../third_party/skia/docs/examples/Matrix_array_operator.cpp FILE: ../../../third_party/skia/docs/examples/Matrix_asAffine.cpp -FILE: ../../../third_party/skia/docs/examples/Matrix_cheapEqualTo.cpp FILE: ../../../third_party/skia/docs/examples/Matrix_decomposeScale.cpp FILE: ../../../third_party/skia/docs/examples/Matrix_dirtyMatrixTypeCache.cpp FILE: ../../../third_party/skia/docs/examples/Matrix_dump.cpp -FILE: ../../../third_party/skia/docs/examples/Matrix_equal_operator.cpp -FILE: ../../../third_party/skia/docs/examples/Matrix_fixedStepInX.cpp FILE: ../../../third_party/skia/docs/examples/Matrix_get.cpp FILE: ../../../third_party/skia/docs/examples/Matrix_get9.cpp FILE: ../../../third_party/skia/docs/examples/Matrix_getMaxScale.cpp @@ -4363,7 +4426,6 @@ FILE: ../../../third_party/skia/docs/examples/Matrix_getType.cpp FILE: ../../../third_party/skia/docs/examples/Matrix_hasPerspective.cpp FILE: ../../../third_party/skia/docs/examples/Matrix_invert.cpp FILE: ../../../third_party/skia/docs/examples/Matrix_isFinite.cpp -FILE: ../../../third_party/skia/docs/examples/Matrix_isFixedStepInX.cpp FILE: ../../../third_party/skia/docs/examples/Matrix_isIdentity.cpp FILE: ../../../third_party/skia/docs/examples/Matrix_isScaleTranslate.cpp FILE: ../../../third_party/skia/docs/examples/Matrix_isSimilarity.cpp @@ -4383,7 +4445,6 @@ FILE: ../../../third_party/skia/docs/examples/Matrix_mapVectors.cpp FILE: ../../../third_party/skia/docs/examples/Matrix_mapVectors_2.cpp FILE: ../../../third_party/skia/docs/examples/Matrix_mapXY.cpp FILE: ../../../third_party/skia/docs/examples/Matrix_mapXY_2.cpp -FILE: ../../../third_party/skia/docs/examples/Matrix_notequal_operator.cpp FILE: ../../../third_party/skia/docs/examples/Matrix_postConcat.cpp FILE: ../../../third_party/skia/docs/examples/Matrix_postRotate.cpp FILE: ../../../third_party/skia/docs/examples/Matrix_postRotate_2.cpp @@ -4988,11 +5049,6 @@ FILE: ../../../third_party/skia/docs/examples/Typeface_Methods.cpp FILE: ../../../third_party/skia/docs/examples/Xor.cpp FILE: ../../../third_party/skia/docs/examples/incomplete.cpp FILE: ../../../third_party/skia/experimental/minimal_ios_mtl_skia_app/main.mm -FILE: ../../../third_party/skia/experimental/skottie_ios/SkMetalViewBridge.h -FILE: ../../../third_party/skia/experimental/skottie_ios/SkMetalViewBridge.mm -FILE: ../../../third_party/skia/experimental/skottie_ios/SkottieMtkView.h -FILE: ../../../third_party/skia/experimental/skottie_ios/SkottieMtkView.mm -FILE: ../../../third_party/skia/experimental/skottie_ios/main.mm FILE: ../../../third_party/skia/experimental/xform/SkShape.cpp FILE: ../../../third_party/skia/experimental/xform/SkShape.h FILE: ../../../third_party/skia/experimental/xform/SkXform.cpp @@ -5041,15 +5097,17 @@ 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. + * 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 the copyright holder 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 @@ -5094,15 +5152,17 @@ 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. + * 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 the copyright holder 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 @@ -5129,15 +5189,17 @@ 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. + * 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 the copyright holder 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 @@ -5160,7 +5222,6 @@ FILE: ../../../third_party/skia/fuzz/FuzzEncoders.cpp FILE: ../../../third_party/skia/fuzz/FuzzPolyUtils.cpp FILE: ../../../third_party/skia/modules/canvaskit/canvaskit_bindings.cpp FILE: ../../../third_party/skia/modules/pathkit/pathkit_wasm_bindings.cpp -FILE: ../../../third_party/skia/src/gpu/GrSkSLFPFactoryCache.h FILE: ../../../third_party/skia/src/gpu/ccpr/GrCoverageCountingPathRenderer_none.cpp ---------------------------------------------------------------------------------------------------- Copyright 2018 Google LLC @@ -5169,15 +5230,17 @@ 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. + * 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 the copyright holder 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 @@ -5223,15 +5286,17 @@ 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. + * 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 the copyright holder 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 @@ -5264,15 +5329,17 @@ 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. + * 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 the copyright holder 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 @@ -5299,15 +5366,17 @@ 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. + * 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 the copyright holder 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 @@ -5481,15 +5550,17 @@ 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. + * 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 the copyright holder 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 @@ -5523,15 +5594,17 @@ 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. + * 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 the copyright holder 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 @@ -5575,15 +5648,17 @@ 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. + * 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 the copyright holder 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 @@ -5617,15 +5692,17 @@ 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. + * 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 the copyright holder 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 @@ -5652,15 +5729,17 @@ 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. + * 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 the copyright holder 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 @@ -5695,15 +5774,17 @@ 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. + * 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 the copyright holder 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 @@ -5733,15 +5814,17 @@ 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. + * 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 the copyright holder 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 @@ -5789,15 +5872,17 @@ 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. + * 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 the copyright holder 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 @@ -5863,15 +5948,17 @@ 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. + * 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 the copyright holder 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 @@ -5959,11 +6046,22 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================================================== LIBRARY: skia -ORIGIN: ../../../third_party/skia/samplecode/SampleTextureUpload.cpp + ../../../third_party/skia/LICENSE +ORIGIN: ../../../third_party/skia/modules/canvaskit/canvaskit/LICENSE TYPE: LicenseType.bsd -FILE: ../../../third_party/skia/samplecode/SampleTextureUpload.cpp +FILE: ../../../third_party/skia/modules/canvaskit/canvaskit/NotoSerif-Regular.ttf +FILE: ../../../third_party/skia/modules/canvaskit/canvaskit/Roboto-Regular.ttf +FILE: ../../../third_party/skia/modules/canvaskit/canvaskit/Roboto-Regular.woff +FILE: ../../../third_party/skia/modules/canvaskit/canvaskit/example.html +FILE: ../../../third_party/skia/modules/canvaskit/canvaskit/extra.html +FILE: ../../../third_party/skia/modules/canvaskit/canvaskit/node.example.js +FILE: ../../../third_party/skia/modules/canvaskit/canvaskit/package.json +FILE: ../../../third_party/skia/modules/canvaskit/canvaskit/test.png +FILE: ../../../third_party/skia/modules/pathkit/npm-asmjs/example.html +FILE: ../../../third_party/skia/modules/pathkit/npm-asmjs/package.json +FILE: ../../../third_party/skia/modules/pathkit/npm-wasm/example.html +FILE: ../../../third_party/skia/modules/pathkit/npm-wasm/package.json ---------------------------------------------------------------------------------------------------- -Copyright 2019 Google Inc. and Adobe Inc. +Copyright (c) 2011 Google Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are @@ -5992,6 +6090,83 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================================================== +==================================================================================================== +LIBRARY: skia +ORIGIN: ../../../third_party/skia/modules/skottie/src/Camera.cpp + ../../../third_party/skia/LICENSE +TYPE: LicenseType.bsd +FILE: ../../../third_party/skia/modules/skottie/src/Camera.cpp +FILE: ../../../third_party/skia/modules/skottie/src/Camera.h +FILE: ../../../third_party/skia/modules/skottie/src/Transform.cpp +FILE: ../../../third_party/skia/modules/skottie/src/Transform.h +---------------------------------------------------------------------------------------------------- +Copyright 2020 Google Inc. + +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 the copyright holder 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: skia +ORIGIN: ../../../third_party/skia/samplecode/SampleTextureUpload.cpp + ../../../third_party/skia/LICENSE +TYPE: LicenseType.bsd +FILE: ../../../third_party/skia/samplecode/SampleTextureUpload.cpp +---------------------------------------------------------------------------------------------------- +Copyright 2019 Google Inc. and Adobe Inc. + +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 the copyright holder 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: skia ORIGIN: ../../../third_party/skia/src/codec/SkCodecPriv.h + ../../../third_party/skia/LICENSE @@ -6007,15 +6182,17 @@ 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. + * 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 the copyright holder 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 @@ -6050,15 +6227,17 @@ 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. + * 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 the copyright holder 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 @@ -6073,36 +6252,6 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================================================== -==================================================================================================== -LIBRARY: skia -ORIGIN: ../../../third_party/skia/src/codec/SkGifCodec.cpp -TYPE: LicenseType.bsd -FILE: ../../../third_party/skia/src/codec/SkGifCodec.cpp ----------------------------------------------------------------------------------------------------- -Copyright (C) 2006 Apple Computer, Inc. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. 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. - -THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``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 APPLE COMPUTER, INC. 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: skia ORIGIN: ../../../third_party/skia/src/core/SkBitmapProcState_matrixProcs.cpp + ../../../third_party/skia/LICENSE @@ -6116,15 +6265,17 @@ 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. + * 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 the copyright holder 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 @@ -6161,15 +6312,17 @@ 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. + * 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 the copyright holder 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 @@ -6199,15 +6352,17 @@ 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. + * 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 the copyright holder 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 @@ -6235,15 +6390,17 @@ 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. + * 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 the copyright holder 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 @@ -6270,15 +6427,17 @@ 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. + * 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 the copyright holder 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 @@ -6305,15 +6464,17 @@ 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. + * 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 the copyright holder 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 @@ -6340,15 +6501,17 @@ 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. + * 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 the copyright holder 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 @@ -6376,15 +6539,17 @@ 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. + * 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 the copyright holder 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 @@ -6412,15 +6577,17 @@ 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. + * 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 the copyright holder 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 @@ -6449,15 +6616,17 @@ 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. + * 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 the copyright holder 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 @@ -6486,15 +6655,17 @@ 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. + * 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 the copyright holder 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 @@ -6522,15 +6693,17 @@ 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. + * 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 the copyright holder 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 @@ -6591,4 +6764,4 @@ 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. ==================================================================================================== -Total license count: 52 +Total license count: 54 diff --git a/ci/licenses_golden/licenses_third_party b/ci/licenses_golden/licenses_third_party index a3a74a5cb0e3a..b89da78e9282a 100644 --- a/ci/licenses_golden/licenses_third_party +++ b/ci/licenses_golden/licenses_third_party @@ -1,4 +1,4 @@ -Signature: 10793d772d6c660b5fab347dc5ac6cf2 +Signature: 685bc4373428cffdb439a3d8577b956b UNUSED LICENSES: @@ -7457,7 +7457,10 @@ FILE: ../../../third_party/dart/.mailmap FILE: ../../../third_party/dart/.style.yapf FILE: ../../../third_party/dart/.vpython FILE: ../../../third_party/dart/PATENT_GRANT +FILE: ../../../third_party/dart/benchmarks/IsolateJson/dart/sample.json FILE: ../../../third_party/dart/benchmarks/IsolateSpawn/dart/helloworld.dart +FILE: ../../../third_party/dart/benchmarks/IsolateSpawnMemory/dart/helloworld.dart +FILE: ../../../third_party/dart/benchmarks/SoundSplayTreeSieve/dart/iterable.dart FILE: ../../../third_party/dart/client/idea/.idea/.name FILE: ../../../third_party/dart/client/idea/.idea/inspectionProfiles/Project_Default.xml FILE: ../../../third_party/dart/client/idea/.idea/vcs.xml @@ -7490,7 +7493,6 @@ FILE: ../../../third_party/dart/sdk/lib/vmservice_libraries.json FILE: ../../../third_party/dart/sdk_nnbd/lib/html/html_common/conversions_dart2js.dart FILE: ../../../third_party/dart/sdk_nnbd/lib/html/html_common/html_common.dart FILE: ../../../third_party/dart/sdk_nnbd/lib/libraries.json -FILE: ../../../third_party/dart/sdk_nnbd/lib/libraries_nnbd_mix_hack.json FILE: ../../../third_party/dart/sdk_nnbd/lib/vmservice_libraries.json FILE: ../../../third_party/dart/third_party/7zip.tar.gz.sha1 FILE: ../../../third_party/dart/third_party/clang.tar.gz.sha1 @@ -7542,7 +7544,11 @@ FILE: ../../../third_party/dart/benchmarks/FfiCall/dart/native/native_functions. FILE: ../../../third_party/dart/benchmarks/FfiMemory/dart/FfiMemory.dart FILE: ../../../third_party/dart/benchmarks/FfiStruct/dart/FfiStruct.dart FILE: ../../../third_party/dart/benchmarks/Isolate/dart/Isolate.dart +FILE: ../../../third_party/dart/benchmarks/IsolateJson/dart/IsolateJson.dart FILE: ../../../third_party/dart/benchmarks/IsolateSpawn/dart/IsolateSpawn.dart +FILE: ../../../third_party/dart/benchmarks/IsolateSpawnMemory/dart/IsolateSpawnMemory.dart +FILE: ../../../third_party/dart/benchmarks/SoundSplayTreeSieve/dart/SoundSplayTreeSieve.dart +FILE: ../../../third_party/dart/benchmarks/SoundSplayTreeSieve/dart/sound_splay_tree.dart FILE: ../../../third_party/dart/runtime/bin/abi_version.h FILE: ../../../third_party/dart/runtime/bin/abi_version_in.cc FILE: ../../../third_party/dart/runtime/bin/elf_loader.cc @@ -7551,6 +7557,7 @@ FILE: ../../../third_party/dart/runtime/bin/entrypoints_verification_test_extens FILE: ../../../third_party/dart/runtime/bin/entrypoints_verification_test_extension_dllmain_win.cc FILE: ../../../third_party/dart/runtime/bin/ffi_test/ffi_test_dynamic_library.cc FILE: ../../../third_party/dart/runtime/bin/ffi_test/ffi_test_functions.cc +FILE: ../../../third_party/dart/runtime/bin/ffi_test/ffi_test_functions_vmspecific.cc FILE: ../../../third_party/dart/runtime/bin/ifaddrs-android.cc FILE: ../../../third_party/dart/runtime/bin/ifaddrs-android.h FILE: ../../../third_party/dart/runtime/bin/namespace_fuchsia.h @@ -7615,6 +7622,7 @@ FILE: ../../../third_party/dart/runtime/vm/compiler/backend/sexpression_test.cc FILE: ../../../third_party/dart/runtime/vm/compiler/backend/slot_test.cc FILE: ../../../third_party/dart/runtime/vm/compiler/backend/type_propagator_test.cc FILE: ../../../third_party/dart/runtime/vm/compiler/backend/typed_data_aot_test.cc +FILE: ../../../third_party/dart/runtime/vm/compiler/backend/yield_position_test.cc FILE: ../../../third_party/dart/runtime/vm/compiler/ffi.cc FILE: ../../../third_party/dart/runtime/vm/compiler/ffi.h FILE: ../../../third_party/dart/runtime/vm/compiler/frontend/bytecode_fingerprints.cc @@ -7665,6 +7673,12 @@ FILE: ../../../third_party/dart/runtime/vm/type_testing_stubs_arm64.cc FILE: ../../../third_party/dart/runtime/vm/type_testing_stubs_x64.cc FILE: ../../../third_party/dart/samples/ffi/coordinate.dart FILE: ../../../third_party/dart/samples/ffi/dylib_utils.dart +FILE: ../../../third_party/dart/samples/ffi/resource_management/pool.dart +FILE: ../../../third_party/dart/samples/ffi/resource_management/pool_isolate_shutdown_sample.dart +FILE: ../../../third_party/dart/samples/ffi/resource_management/pool_sample.dart +FILE: ../../../third_party/dart/samples/ffi/resource_management/pool_zoned_sample.dart +FILE: ../../../third_party/dart/samples/ffi/resource_management/resource_management_test.dart +FILE: ../../../third_party/dart/samples/ffi/resource_management/unmanaged_sample.dart FILE: ../../../third_party/dart/samples/ffi/sample_ffi_bitfield.dart FILE: ../../../third_party/dart/samples/ffi/sample_ffi_data.dart FILE: ../../../third_party/dart/samples/ffi/sample_ffi_dynamic_library.dart @@ -7694,6 +7708,7 @@ FILE: ../../../third_party/dart/sdk/lib/ffi/dynamic_library.dart FILE: ../../../third_party/dart/sdk/lib/ffi/ffi.dart FILE: ../../../third_party/dart/sdk/lib/ffi/native_type.dart FILE: ../../../third_party/dart/sdk/lib/ffi/struct.dart +FILE: ../../../third_party/dart/sdk/lib/internal/errors.dart FILE: ../../../third_party/dart/sdk/lib/wasm/wasm.dart FILE: ../../../third_party/dart/sdk_nnbd/lib/_internal/js_runtime/lib/rti.dart FILE: ../../../third_party/dart/sdk_nnbd/lib/_internal/js_runtime/lib/shared/recipe_syntax.dart @@ -7706,6 +7721,7 @@ FILE: ../../../third_party/dart/sdk_nnbd/lib/ffi/dynamic_library.dart FILE: ../../../third_party/dart/sdk_nnbd/lib/ffi/ffi.dart FILE: ../../../third_party/dart/sdk_nnbd/lib/ffi/native_type.dart FILE: ../../../third_party/dart/sdk_nnbd/lib/ffi/struct.dart +FILE: ../../../third_party/dart/sdk_nnbd/lib/internal/errors.dart FILE: ../../../third_party/dart/sdk_nnbd/lib/wasm/wasm.dart ---------------------------------------------------------------------------------------------------- Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file @@ -8180,7 +8196,6 @@ FILE: ../../../third_party/dart/sdk/lib/web_gl/dart2js/web_gl_dart2js.dart FILE: ../../../third_party/dart/sdk/lib/web_sql/dart2js/web_sql_dart2js.dart FILE: ../../../third_party/dart/sdk_nnbd/lib/_http/crypto.dart FILE: ../../../third_party/dart/sdk_nnbd/lib/_http/http_date.dart -FILE: ../../../third_party/dart/sdk_nnbd/lib/_internal/js_dev_runtime/libraries.dart FILE: ../../../third_party/dart/sdk_nnbd/lib/_internal/js_dev_runtime/patch/async_patch.dart FILE: ../../../third_party/dart/sdk_nnbd/lib/_internal/js_dev_runtime/patch/core_patch.dart FILE: ../../../third_party/dart/sdk_nnbd/lib/_internal/js_dev_runtime/patch/isolate_patch.dart @@ -8333,6 +8348,156 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================================================== +==================================================================================================== +LIBRARY: dart +ORIGIN: ../../../third_party/dart/runtime/bin/address_sanitizer.cc + ../../../third_party/dart/LICENSE +TYPE: LicenseType.bsd +FILE: ../../../third_party/dart/runtime/bin/address_sanitizer.cc +FILE: ../../../third_party/dart/runtime/bin/dart_io_api_impl.cc +FILE: ../../../third_party/dart/runtime/bin/observatory_assets_empty.cc +FILE: ../../../third_party/dart/runtime/include/bin/dart_io_api.h +FILE: ../../../third_party/dart/runtime/lib/developer.cc +FILE: ../../../third_party/dart/runtime/lib/timeline.cc +FILE: ../../../third_party/dart/runtime/lib/vmservice.cc +FILE: ../../../third_party/dart/runtime/observatory/lib/allocation_profile.dart +FILE: ../../../third_party/dart/runtime/observatory/lib/cli.dart +FILE: ../../../third_party/dart/runtime/observatory/lib/debugger.dart +FILE: ../../../third_party/dart/runtime/observatory/lib/heap_snapshot.dart +FILE: ../../../third_party/dart/runtime/observatory/lib/sample_profile.dart +FILE: ../../../third_party/dart/runtime/observatory/lib/src/allocation_profile/allocation_profile.dart +FILE: ../../../third_party/dart/runtime/observatory/lib/src/app/analytics.dart +FILE: ../../../third_party/dart/runtime/observatory/lib/src/cli/command.dart +FILE: ../../../third_party/dart/runtime/observatory/lib/src/debugger/debugger.dart +FILE: ../../../third_party/dart/runtime/observatory/lib/src/debugger/debugger_location.dart +FILE: ../../../third_party/dart/runtime/observatory/lib/src/elements/heap_snapshot.dart +FILE: ../../../third_party/dart/runtime/observatory/lib/src/elements/logging.dart +FILE: ../../../third_party/dart/runtime/observatory/lib/src/elements/logging_list.dart +FILE: ../../../third_party/dart/runtime/observatory/lib/src/elements/megamorphiccache_view.dart +FILE: ../../../third_party/dart/runtime/observatory/lib/src/elements/objectpool_view.dart +FILE: ../../../third_party/dart/runtime/observatory/lib/src/elements/persistent_handles.dart +FILE: ../../../third_party/dart/runtime/observatory/lib/src/elements/ports.dart +FILE: ../../../third_party/dart/runtime/observatory/lib/src/elements/timeline_page.dart +FILE: ../../../third_party/dart/runtime/observatory/lib/src/elements/view_footer.dart +FILE: ../../../third_party/dart/runtime/observatory/lib/src/sample_profile/sample_profile.dart +FILE: ../../../third_party/dart/runtime/observatory/web/timeline.js +FILE: ../../../third_party/dart/runtime/vm/atomic_test.cc +FILE: ../../../third_party/dart/runtime/vm/compiler/aot/precompiler.cc +FILE: ../../../third_party/dart/runtime/vm/compiler/aot/precompiler.h +FILE: ../../../third_party/dart/runtime/vm/log.cc +FILE: ../../../third_party/dart/runtime/vm/log.h +FILE: ../../../third_party/dart/runtime/vm/log_test.cc +FILE: ../../../third_party/dart/runtime/vm/os_thread.cc +FILE: ../../../third_party/dart/runtime/vm/profiler_service.cc +FILE: ../../../third_party/dart/runtime/vm/profiler_service.h +FILE: ../../../third_party/dart/runtime/vm/program_visitor.cc +FILE: ../../../third_party/dart/runtime/vm/program_visitor.h +FILE: ../../../third_party/dart/runtime/vm/regexp_assembler_bytecode.cc +FILE: ../../../third_party/dart/runtime/vm/regexp_assembler_bytecode.h +FILE: ../../../third_party/dart/runtime/vm/regexp_assembler_bytecode_inl.h +FILE: ../../../third_party/dart/runtime/vm/regexp_bytecodes.h +FILE: ../../../third_party/dart/runtime/vm/regexp_interpreter.cc +FILE: ../../../third_party/dart/runtime/vm/regexp_interpreter.h +FILE: ../../../third_party/dart/runtime/vm/scope_timer.h +FILE: ../../../third_party/dart/runtime/vm/service_event.cc +FILE: ../../../third_party/dart/runtime/vm/service_event.h +FILE: ../../../third_party/dart/runtime/vm/service_isolate.cc +FILE: ../../../third_party/dart/runtime/vm/source_report.cc +FILE: ../../../third_party/dart/runtime/vm/source_report.h +FILE: ../../../third_party/dart/runtime/vm/source_report_test.cc +FILE: ../../../third_party/dart/runtime/vm/thread.cc +FILE: ../../../third_party/dart/runtime/vm/thread.h +FILE: ../../../third_party/dart/runtime/vm/thread_barrier.h +FILE: ../../../third_party/dart/runtime/vm/thread_barrier_test.cc +FILE: ../../../third_party/dart/runtime/vm/thread_registry.cc +FILE: ../../../third_party/dart/runtime/vm/thread_registry.h +FILE: ../../../third_party/dart/runtime/vm/timeline.cc +FILE: ../../../third_party/dart/runtime/vm/timeline.h +FILE: ../../../third_party/dart/runtime/vm/timeline_analysis.cc +FILE: ../../../third_party/dart/runtime/vm/timeline_analysis.h +FILE: ../../../third_party/dart/runtime/vm/timeline_test.cc +FILE: ../../../third_party/dart/sdk/lib/_internal/js_dev_runtime/lib/js/dart2js/js_dart2js.dart +FILE: ../../../third_party/dart/sdk/lib/_internal/js_dev_runtime/patch/developer_patch.dart +FILE: ../../../third_party/dart/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/classes.dart +FILE: ../../../third_party/dart/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart +FILE: ../../../third_party/dart/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart +FILE: ../../../third_party/dart/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/rtti.dart +FILE: ../../../third_party/dart/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/runtime.dart +FILE: ../../../third_party/dart/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/types.dart +FILE: ../../../third_party/dart/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/utils.dart +FILE: ../../../third_party/dart/sdk/lib/_internal/js_dev_runtime/private/debugger.dart +FILE: ../../../third_party/dart/sdk/lib/_internal/js_dev_runtime/private/js_mirrors.dart +FILE: ../../../third_party/dart/sdk/lib/_internal/js_runtime/lib/developer_patch.dart +FILE: ../../../third_party/dart/sdk/lib/_internal/js_runtime/lib/preambles/d8.js +FILE: ../../../third_party/dart/sdk/lib/_internal/js_runtime/lib/preambles/jsshell.js +FILE: ../../../third_party/dart/sdk/lib/_internal/js_runtime/lib/shared/async_await_error_codes.dart +FILE: ../../../third_party/dart/sdk/lib/_internal/vm/lib/async_patch.dart +FILE: ../../../third_party/dart/sdk/lib/_internal/vm/lib/compact_hash.dart +FILE: ../../../third_party/dart/sdk/lib/_internal/vm/lib/developer.dart +FILE: ../../../third_party/dart/sdk/lib/_internal/vm/lib/timeline.dart +FILE: ../../../third_party/dart/sdk/lib/convert/base64.dart +FILE: ../../../third_party/dart/sdk/lib/developer/developer.dart +FILE: ../../../third_party/dart/sdk/lib/developer/extension.dart +FILE: ../../../third_party/dart/sdk/lib/developer/timeline.dart +FILE: ../../../third_party/dart/sdk/lib/io/io_resource_info.dart +FILE: ../../../third_party/dart/sdk/lib/io/security_context.dart +FILE: ../../../third_party/dart/sdk/lib/vmservice/asset.dart +FILE: ../../../third_party/dart/sdk/lib/vmservice/vmservice.dart +FILE: ../../../third_party/dart/sdk_nnbd/lib/_internal/js_dev_runtime/lib/js/dart2js/js_dart2js.dart +FILE: ../../../third_party/dart/sdk_nnbd/lib/_internal/js_dev_runtime/patch/developer_patch.dart +FILE: ../../../third_party/dart/sdk_nnbd/lib/_internal/js_dev_runtime/private/ddc_runtime/classes.dart +FILE: ../../../third_party/dart/sdk_nnbd/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart +FILE: ../../../third_party/dart/sdk_nnbd/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart +FILE: ../../../third_party/dart/sdk_nnbd/lib/_internal/js_dev_runtime/private/ddc_runtime/rtti.dart +FILE: ../../../third_party/dart/sdk_nnbd/lib/_internal/js_dev_runtime/private/ddc_runtime/runtime.dart +FILE: ../../../third_party/dart/sdk_nnbd/lib/_internal/js_dev_runtime/private/ddc_runtime/types.dart +FILE: ../../../third_party/dart/sdk_nnbd/lib/_internal/js_dev_runtime/private/ddc_runtime/utils.dart +FILE: ../../../third_party/dart/sdk_nnbd/lib/_internal/js_dev_runtime/private/debugger.dart +FILE: ../../../third_party/dart/sdk_nnbd/lib/_internal/js_dev_runtime/private/js_mirrors.dart +FILE: ../../../third_party/dart/sdk_nnbd/lib/_internal/js_runtime/lib/developer_patch.dart +FILE: ../../../third_party/dart/sdk_nnbd/lib/_internal/js_runtime/lib/preambles/d8.js +FILE: ../../../third_party/dart/sdk_nnbd/lib/_internal/js_runtime/lib/preambles/jsshell.js +FILE: ../../../third_party/dart/sdk_nnbd/lib/_internal/js_runtime/lib/shared/async_await_error_codes.dart +FILE: ../../../third_party/dart/sdk_nnbd/lib/_internal/vm/lib/async_patch.dart +FILE: ../../../third_party/dart/sdk_nnbd/lib/_internal/vm/lib/compact_hash.dart +FILE: ../../../third_party/dart/sdk_nnbd/lib/_internal/vm/lib/developer.dart +FILE: ../../../third_party/dart/sdk_nnbd/lib/_internal/vm/lib/timeline.dart +FILE: ../../../third_party/dart/sdk_nnbd/lib/convert/base64.dart +FILE: ../../../third_party/dart/sdk_nnbd/lib/developer/developer.dart +FILE: ../../../third_party/dart/sdk_nnbd/lib/developer/extension.dart +FILE: ../../../third_party/dart/sdk_nnbd/lib/developer/timeline.dart +FILE: ../../../third_party/dart/sdk_nnbd/lib/io/io_resource_info.dart +FILE: ../../../third_party/dart/sdk_nnbd/lib/io/security_context.dart +FILE: ../../../third_party/dart/sdk_nnbd/lib/vmservice/asset.dart +FILE: ../../../third_party/dart/sdk_nnbd/lib/vmservice/vmservice.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 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: dart ORIGIN: ../../../third_party/dart/runtime/bin/cli.cc + ../../../third_party/dart/LICENSE @@ -8593,8 +8758,8 @@ FILE: ../../../third_party/dart/runtime/vm/compiler/frontend/bytecode_flow_graph FILE: ../../../third_party/dart/runtime/vm/compiler/frontend/bytecode_reader.cc FILE: ../../../third_party/dart/runtime/vm/compiler/frontend/bytecode_reader.h FILE: ../../../third_party/dart/runtime/vm/compiler/frontend/bytecode_scope_builder.cc -FILE: ../../../third_party/dart/runtime/vm/compiler/frontend/constant_evaluator.cc -FILE: ../../../third_party/dart/runtime/vm/compiler/frontend/constant_evaluator.h +FILE: ../../../third_party/dart/runtime/vm/compiler/frontend/constant_reader.cc +FILE: ../../../third_party/dart/runtime/vm/compiler/frontend/constant_reader.h FILE: ../../../third_party/dart/runtime/vm/compiler/frontend/kernel_fingerprints.cc FILE: ../../../third_party/dart/runtime/vm/compiler/frontend/kernel_fingerprints.h FILE: ../../../third_party/dart/runtime/vm/compiler/frontend/kernel_translation_helper.cc @@ -9054,158 +9219,6 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================================================== -==================================================================================================== -LIBRARY: dart -ORIGIN: ../../../third_party/dart/runtime/bin/vmservice/loader.dart + ../../../third_party/dart/LICENSE -TYPE: LicenseType.bsd -FILE: ../../../third_party/dart/runtime/bin/address_sanitizer.cc -FILE: ../../../third_party/dart/runtime/bin/builtin_common.cc -FILE: ../../../third_party/dart/runtime/bin/dart_io_api_impl.cc -FILE: ../../../third_party/dart/runtime/bin/observatory_assets_empty.cc -FILE: ../../../third_party/dart/runtime/bin/vmservice/loader.dart -FILE: ../../../third_party/dart/runtime/include/bin/dart_io_api.h -FILE: ../../../third_party/dart/runtime/lib/developer.cc -FILE: ../../../third_party/dart/runtime/lib/timeline.cc -FILE: ../../../third_party/dart/runtime/lib/vmservice.cc -FILE: ../../../third_party/dart/runtime/observatory/lib/allocation_profile.dart -FILE: ../../../third_party/dart/runtime/observatory/lib/cli.dart -FILE: ../../../third_party/dart/runtime/observatory/lib/debugger.dart -FILE: ../../../third_party/dart/runtime/observatory/lib/heap_snapshot.dart -FILE: ../../../third_party/dart/runtime/observatory/lib/sample_profile.dart -FILE: ../../../third_party/dart/runtime/observatory/lib/src/allocation_profile/allocation_profile.dart -FILE: ../../../third_party/dart/runtime/observatory/lib/src/app/analytics.dart -FILE: ../../../third_party/dart/runtime/observatory/lib/src/cli/command.dart -FILE: ../../../third_party/dart/runtime/observatory/lib/src/debugger/debugger.dart -FILE: ../../../third_party/dart/runtime/observatory/lib/src/debugger/debugger_location.dart -FILE: ../../../third_party/dart/runtime/observatory/lib/src/elements/heap_snapshot.dart -FILE: ../../../third_party/dart/runtime/observatory/lib/src/elements/logging.dart -FILE: ../../../third_party/dart/runtime/observatory/lib/src/elements/logging_list.dart -FILE: ../../../third_party/dart/runtime/observatory/lib/src/elements/megamorphiccache_view.dart -FILE: ../../../third_party/dart/runtime/observatory/lib/src/elements/objectpool_view.dart -FILE: ../../../third_party/dart/runtime/observatory/lib/src/elements/persistent_handles.dart -FILE: ../../../third_party/dart/runtime/observatory/lib/src/elements/ports.dart -FILE: ../../../third_party/dart/runtime/observatory/lib/src/elements/timeline_page.dart -FILE: ../../../third_party/dart/runtime/observatory/lib/src/elements/view_footer.dart -FILE: ../../../third_party/dart/runtime/observatory/lib/src/sample_profile/sample_profile.dart -FILE: ../../../third_party/dart/runtime/observatory/web/timeline.js -FILE: ../../../third_party/dart/runtime/vm/atomic_test.cc -FILE: ../../../third_party/dart/runtime/vm/compiler/aot/precompiler.cc -FILE: ../../../third_party/dart/runtime/vm/compiler/aot/precompiler.h -FILE: ../../../third_party/dart/runtime/vm/log.cc -FILE: ../../../third_party/dart/runtime/vm/log.h -FILE: ../../../third_party/dart/runtime/vm/log_test.cc -FILE: ../../../third_party/dart/runtime/vm/os_thread.cc -FILE: ../../../third_party/dart/runtime/vm/profiler_service.cc -FILE: ../../../third_party/dart/runtime/vm/profiler_service.h -FILE: ../../../third_party/dart/runtime/vm/program_visitor.cc -FILE: ../../../third_party/dart/runtime/vm/program_visitor.h -FILE: ../../../third_party/dart/runtime/vm/regexp_assembler_bytecode.cc -FILE: ../../../third_party/dart/runtime/vm/regexp_assembler_bytecode.h -FILE: ../../../third_party/dart/runtime/vm/regexp_assembler_bytecode_inl.h -FILE: ../../../third_party/dart/runtime/vm/regexp_bytecodes.h -FILE: ../../../third_party/dart/runtime/vm/regexp_interpreter.cc -FILE: ../../../third_party/dart/runtime/vm/regexp_interpreter.h -FILE: ../../../third_party/dart/runtime/vm/scope_timer.h -FILE: ../../../third_party/dart/runtime/vm/service_event.cc -FILE: ../../../third_party/dart/runtime/vm/service_event.h -FILE: ../../../third_party/dart/runtime/vm/service_isolate.cc -FILE: ../../../third_party/dart/runtime/vm/source_report.cc -FILE: ../../../third_party/dart/runtime/vm/source_report.h -FILE: ../../../third_party/dart/runtime/vm/source_report_test.cc -FILE: ../../../third_party/dart/runtime/vm/thread.cc -FILE: ../../../third_party/dart/runtime/vm/thread.h -FILE: ../../../third_party/dart/runtime/vm/thread_barrier.h -FILE: ../../../third_party/dart/runtime/vm/thread_barrier_test.cc -FILE: ../../../third_party/dart/runtime/vm/thread_registry.cc -FILE: ../../../third_party/dart/runtime/vm/thread_registry.h -FILE: ../../../third_party/dart/runtime/vm/timeline.cc -FILE: ../../../third_party/dart/runtime/vm/timeline.h -FILE: ../../../third_party/dart/runtime/vm/timeline_analysis.cc -FILE: ../../../third_party/dart/runtime/vm/timeline_analysis.h -FILE: ../../../third_party/dart/runtime/vm/timeline_test.cc -FILE: ../../../third_party/dart/sdk/lib/_internal/js_dev_runtime/lib/js/dart2js/js_dart2js.dart -FILE: ../../../third_party/dart/sdk/lib/_internal/js_dev_runtime/patch/developer_patch.dart -FILE: ../../../third_party/dart/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/classes.dart -FILE: ../../../third_party/dart/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart -FILE: ../../../third_party/dart/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart -FILE: ../../../third_party/dart/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/rtti.dart -FILE: ../../../third_party/dart/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/runtime.dart -FILE: ../../../third_party/dart/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/types.dart -FILE: ../../../third_party/dart/sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/utils.dart -FILE: ../../../third_party/dart/sdk/lib/_internal/js_dev_runtime/private/debugger.dart -FILE: ../../../third_party/dart/sdk/lib/_internal/js_dev_runtime/private/js_mirrors.dart -FILE: ../../../third_party/dart/sdk/lib/_internal/js_runtime/lib/developer_patch.dart -FILE: ../../../third_party/dart/sdk/lib/_internal/js_runtime/lib/preambles/d8.js -FILE: ../../../third_party/dart/sdk/lib/_internal/js_runtime/lib/preambles/jsshell.js -FILE: ../../../third_party/dart/sdk/lib/_internal/js_runtime/lib/shared/async_await_error_codes.dart -FILE: ../../../third_party/dart/sdk/lib/_internal/vm/lib/async_patch.dart -FILE: ../../../third_party/dart/sdk/lib/_internal/vm/lib/compact_hash.dart -FILE: ../../../third_party/dart/sdk/lib/_internal/vm/lib/developer.dart -FILE: ../../../third_party/dart/sdk/lib/_internal/vm/lib/timeline.dart -FILE: ../../../third_party/dart/sdk/lib/convert/base64.dart -FILE: ../../../third_party/dart/sdk/lib/developer/developer.dart -FILE: ../../../third_party/dart/sdk/lib/developer/extension.dart -FILE: ../../../third_party/dart/sdk/lib/developer/timeline.dart -FILE: ../../../third_party/dart/sdk/lib/io/io_resource_info.dart -FILE: ../../../third_party/dart/sdk/lib/io/security_context.dart -FILE: ../../../third_party/dart/sdk/lib/vmservice/asset.dart -FILE: ../../../third_party/dart/sdk/lib/vmservice/vmservice.dart -FILE: ../../../third_party/dart/sdk_nnbd/lib/_internal/js_dev_runtime/lib/js/dart2js/js_dart2js.dart -FILE: ../../../third_party/dart/sdk_nnbd/lib/_internal/js_dev_runtime/patch/developer_patch.dart -FILE: ../../../third_party/dart/sdk_nnbd/lib/_internal/js_dev_runtime/private/ddc_runtime/classes.dart -FILE: ../../../third_party/dart/sdk_nnbd/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart -FILE: ../../../third_party/dart/sdk_nnbd/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart -FILE: ../../../third_party/dart/sdk_nnbd/lib/_internal/js_dev_runtime/private/ddc_runtime/rtti.dart -FILE: ../../../third_party/dart/sdk_nnbd/lib/_internal/js_dev_runtime/private/ddc_runtime/runtime.dart -FILE: ../../../third_party/dart/sdk_nnbd/lib/_internal/js_dev_runtime/private/ddc_runtime/types.dart -FILE: ../../../third_party/dart/sdk_nnbd/lib/_internal/js_dev_runtime/private/ddc_runtime/utils.dart -FILE: ../../../third_party/dart/sdk_nnbd/lib/_internal/js_dev_runtime/private/debugger.dart -FILE: ../../../third_party/dart/sdk_nnbd/lib/_internal/js_dev_runtime/private/js_mirrors.dart -FILE: ../../../third_party/dart/sdk_nnbd/lib/_internal/js_runtime/lib/developer_patch.dart -FILE: ../../../third_party/dart/sdk_nnbd/lib/_internal/js_runtime/lib/preambles/d8.js -FILE: ../../../third_party/dart/sdk_nnbd/lib/_internal/js_runtime/lib/preambles/jsshell.js -FILE: ../../../third_party/dart/sdk_nnbd/lib/_internal/js_runtime/lib/shared/async_await_error_codes.dart -FILE: ../../../third_party/dart/sdk_nnbd/lib/_internal/vm/lib/async_patch.dart -FILE: ../../../third_party/dart/sdk_nnbd/lib/_internal/vm/lib/compact_hash.dart -FILE: ../../../third_party/dart/sdk_nnbd/lib/_internal/vm/lib/developer.dart -FILE: ../../../third_party/dart/sdk_nnbd/lib/_internal/vm/lib/timeline.dart -FILE: ../../../third_party/dart/sdk_nnbd/lib/convert/base64.dart -FILE: ../../../third_party/dart/sdk_nnbd/lib/developer/developer.dart -FILE: ../../../third_party/dart/sdk_nnbd/lib/developer/extension.dart -FILE: ../../../third_party/dart/sdk_nnbd/lib/developer/timeline.dart -FILE: ../../../third_party/dart/sdk_nnbd/lib/io/io_resource_info.dart -FILE: ../../../third_party/dart/sdk_nnbd/lib/io/security_context.dart -FILE: ../../../third_party/dart/sdk_nnbd/lib/vmservice/asset.dart -FILE: ../../../third_party/dart/sdk_nnbd/lib/vmservice/vmservice.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 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: dart ORIGIN: ../../../third_party/dart/runtime/bin/vmservice/server.dart + ../../../third_party/dart/LICENSE @@ -9871,6 +9884,41 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================================================== +==================================================================================================== +LIBRARY: dart +ORIGIN: ../../../third_party/dart/runtime/vm/field_table.cc + ../../../third_party/dart/LICENSE +TYPE: LicenseType.bsd +FILE: ../../../third_party/dart/runtime/vm/field_table.cc +FILE: ../../../third_party/dart/runtime/vm/field_table.h +---------------------------------------------------------------------------------------------------- +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 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: dart ORIGIN: ../../../third_party/dart/sdk/lib/_internal/vm/lib/bigint_patch.dart @@ -23441,4 +23489,4 @@ freely, subject to the following restrictions: misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. ==================================================================================================== -Total license count: 367 +Total license count: 368 diff --git a/flow/BUILD.gn b/flow/BUILD.gn index c2f0c98415a3e..dad0635fa760a 100644 --- a/flow/BUILD.gn +++ b/flow/BUILD.gn @@ -4,8 +4,8 @@ if (is_fuchsia) { import("//build/fuchsia/sdk.gni") + import("$flutter_root/tools/fuchsia/fuchsia_archive.gni") } - import("$flutter_root/testing/testing.gni") source_set("flow") { @@ -28,6 +28,10 @@ source_set("flow") { "layers/color_filter_layer.h", "layers/container_layer.cc", "layers/container_layer.h", + "layers/elevated_container_layer.cc", + "layers/elevated_container_layer.h", + "layers/image_filter_layer.cc", + "layers/image_filter_layer.h", "layers/layer.cc", "layers/layer.h", "layers/layer_tree.cc", @@ -76,6 +80,8 @@ source_set("flow") { sources += [ "layers/child_scene_layer.cc", "layers/child_scene_layer.h", + "layers/fuchsia_system_composited_layer.cc", + "layers/fuchsia_system_composited_layer.h", "scene_update_context.cc", "scene_update_context.h", "view_holder.cc", @@ -102,6 +108,26 @@ test_fixtures("flow_fixtures") { fixtures = [] } +source_set("flow_testing") { + testonly = true + + sources = [ + "testing/layer_test.h", + "testing/mock_layer.cc", + "testing/mock_layer.h", + "testing/mock_texture.cc", + "testing/mock_texture.h", + "testing/skia_gpu_object_layer_test.cc", + "testing/skia_gpu_object_layer_test.h", + ] + + public_deps = [ + ":flow", + "$flutter_root/testing:skia", + "//third_party/googletest:gtest", + ] +} + executable("flow_unittests") { testonly = true @@ -109,22 +135,75 @@ executable("flow_unittests") { "flow_run_all_unittests.cc", "flow_test_utils.cc", "flow_test_utils.h", + "layers/backdrop_filter_layer_unittests.cc", + "layers/clip_path_layer_unittests.cc", + "layers/clip_rect_layer_unittests.cc", + "layers/clip_rrect_layer_unittests.cc", + "layers/color_filter_layer_unittests.cc", + "layers/container_layer_unittests.cc", + "layers/image_filter_layer_unittests.cc", + "layers/layer_tree_unittests.cc", + "layers/opacity_layer_unittests.cc", "layers/performance_overlay_layer_unittests.cc", "layers/physical_shape_layer_unittests.cc", + "layers/picture_layer_unittests.cc", + "layers/platform_view_layer_unittests.cc", + "layers/shader_mask_layer_unittests.cc", + "layers/texture_layer_unittests.cc", + "layers/transform_layer_unittests.cc", "matrix_decomposition_unittests.cc", "mutators_stack_unittests.cc", "raster_cache_unittests.cc", "skia_gpu_object_unittests.cc", + "testing/mock_layer_unittests.cc", + "testing/mock_texture_unittests.cc", "texture_unittests.cc", ] deps = [ ":flow", ":flow_fixtures", + ":flow_testing", "$flutter_root/fml", + "$flutter_root/testing:skia", "$flutter_root/testing:testing_lib", "//third_party/dart/runtime:libdart_jit", # for tracing "//third_party/googletest:gtest", "//third_party/skia", ] } + +if (is_fuchsia) { + fuchsia_archive("flow_tests") { + testonly = true + + deps = [ + ":flow_unittests", + ] + + binary = "flow_unittests" + + libraries = common_libs + + meta_dir = "$flutter_root/testing/fuchsia/meta" + cmx_file = "$meta_dir/fuchsia_test.cmx" + + resources = [ + { + path = rebase_path( + "$flutter_root/testing/resources/performance_overlay_gold_60fps.png") + dest = "flutter/testing/resources/performance_overlay_gold_60fps.png" + }, + { + path = rebase_path( + "$flutter_root/testing/resources/performance_overlay_gold_90fps.png") + dest = "flutter/testing/resources/performance_overlay_gold_90fps.png" + }, + { + path = rebase_path( + "$flutter_root/testing/resources/performance_overlay_gold_120fps.png") + dest = "flutter/testing/resources/performance_overlay_gold_120fps.png" + }, + ] + } +} diff --git a/flow/compositor_context.cc b/flow/compositor_context.cc index 0fafe0f956409..68f308fbf2f57 100644 --- a/flow/compositor_context.cc +++ b/flow/compositor_context.cc @@ -36,10 +36,11 @@ std::unique_ptr CompositorContext::AcquireFrame( ExternalViewEmbedder* view_embedder, const SkMatrix& root_surface_transformation, bool instrumentation_enabled, + bool surface_supports_readback, fml::RefPtr gpu_thread_merger) { return std::make_unique( *this, gr_context, canvas, view_embedder, root_surface_transformation, - instrumentation_enabled, gpu_thread_merger); + instrumentation_enabled, surface_supports_readback, gpu_thread_merger); } CompositorContext::ScopedFrame::ScopedFrame( @@ -49,6 +50,7 @@ CompositorContext::ScopedFrame::ScopedFrame( ExternalViewEmbedder* view_embedder, const SkMatrix& root_surface_transformation, bool instrumentation_enabled, + bool surface_supports_readback, fml::RefPtr gpu_thread_merger) : context_(context), gr_context_(gr_context), @@ -56,6 +58,7 @@ CompositorContext::ScopedFrame::ScopedFrame( view_embedder_(view_embedder), root_surface_transformation_(root_surface_transformation), instrumentation_enabled_(instrumentation_enabled), + surface_supports_readback_(surface_supports_readback), gpu_thread_merger_(gpu_thread_merger) { context_.BeginFrame(*this, instrumentation_enabled_); } @@ -67,7 +70,8 @@ CompositorContext::ScopedFrame::~ScopedFrame() { RasterStatus CompositorContext::ScopedFrame::Raster( flutter::LayerTree& layer_tree, bool ignore_raster_cache) { - layer_tree.Preroll(*this, ignore_raster_cache); + bool root_needs_readback = layer_tree.Preroll(*this, ignore_raster_cache); + bool needs_save_layer = root_needs_readback && !surface_supports_readback(); PostPrerollResult post_preroll_result = PostPrerollResult::kSuccess; if (view_embedder_ && gpu_thread_merger_) { post_preroll_result = view_embedder_->PostPrerollAction(gpu_thread_merger_); @@ -79,9 +83,19 @@ RasterStatus CompositorContext::ScopedFrame::Raster( // Clearing canvas after preroll reduces one render target switch when preroll // paints some raster cache. if (canvas()) { + if (needs_save_layer) { + FML_LOG(INFO) << "Using SaveLayer to protect non-readback surface"; + SkRect bounds = SkRect::Make(layer_tree.frame_size()); + SkPaint paint; + paint.setBlendMode(SkBlendMode::kSrc); + canvas()->saveLayer(&bounds, &paint); + } canvas()->clear(SK_ColorTRANSPARENT); } layer_tree.Paint(*this, ignore_raster_cache); + if (canvas() && needs_save_layer) { + canvas()->restore(); + } return RasterStatus::kSuccess; } diff --git a/flow/compositor_context.h b/flow/compositor_context.h index 2872b5aa16180..6698d3f41a2f1 100644 --- a/flow/compositor_context.h +++ b/flow/compositor_context.h @@ -45,6 +45,7 @@ class CompositorContext { ExternalViewEmbedder* view_embedder, const SkMatrix& root_surface_transformation, bool instrumentation_enabled, + bool surface_supports_readback, fml::RefPtr gpu_thread_merger); virtual ~ScopedFrame(); @@ -59,6 +60,8 @@ class CompositorContext { return root_surface_transformation_; } + bool surface_supports_readback() { return surface_supports_readback_; } + GrContext* gr_context() const { return gr_context_; } virtual RasterStatus Raster(LayerTree& layer_tree, @@ -71,6 +74,7 @@ class CompositorContext { ExternalViewEmbedder* view_embedder_; const SkMatrix& root_surface_transformation_; const bool instrumentation_enabled_; + const bool surface_supports_readback_; fml::RefPtr gpu_thread_merger_; FML_DISALLOW_COPY_AND_ASSIGN(ScopedFrame); @@ -86,6 +90,7 @@ class CompositorContext { ExternalViewEmbedder* view_embedder, const SkMatrix& root_surface_transformation, bool instrumentation_enabled, + bool surface_supports_readback, fml::RefPtr gpu_thread_merger); void OnGrContextCreated(); diff --git a/flow/embedded_views.h b/flow/embedded_views.h index 13c3feefd1e89..030eb88c8a06d 100644 --- a/flow/embedded_views.h +++ b/flow/embedded_views.h @@ -19,6 +19,7 @@ namespace flutter { +// TODO(chinmaygarde): Make these enum names match the style guide. enum MutatorType { clip_rect, clip_rrect, clip_path, transform, opacity }; // Stores mutation information like clipping or transform. @@ -142,6 +143,7 @@ class MutatorsStack { // Returns an iterator pointing to the bottom of the stack. const std::vector>::const_reverse_iterator Bottom() const; + bool is_empty() const { return vector_.empty(); } bool operator==(const MutatorsStack& other) const { if (vector_.size() != other.vector_.size()) { @@ -155,10 +157,26 @@ class MutatorsStack { return true; } + bool operator==(const std::vector& other) const { + if (vector_.size() != other.size()) { + return false; + } + for (size_t i = 0; i < vector_.size(); i++) { + if (*vector_[i] != other[i]) { + return false; + } + } + return true; + } + bool operator!=(const MutatorsStack& other) const { return !operator==(other); } + bool operator!=(const std::vector& other) const { + return !operator==(other); + } + private: std::vector> vector_; }; // MutatorsStack diff --git a/flow/flow_run_all_unittests.cc b/flow/flow_run_all_unittests.cc index 4cf0ba3d7fcdf..39963730172ee 100644 --- a/flow/flow_run_all_unittests.cc +++ b/flow/flow_run_all_unittests.cc @@ -14,6 +14,7 @@ * limitations under the License. */ +#include "flutter/fml/build_config.h" #include "flutter/fml/command_line.h" #include "flutter/fml/logging.h" #include "gtest/gtest.h" @@ -23,8 +24,14 @@ int main(int argc, char** argv) { testing::InitGoogleTest(&argc, argv); fml::CommandLine cmd = fml::CommandLineFromArgcArgv(argc, argv); + +#if defined(OS_FUCHSIA) + flutter::SetGoldenDir(cmd.GetOptionValueWithDefault( + "golden-dir", "/pkg/data/flutter/testing/resources")); +#else flutter::SetGoldenDir( cmd.GetOptionValueWithDefault("golden-dir", "flutter/testing/resources")); +#endif flutter::SetFontFile(cmd.GetOptionValueWithDefault( "font-file", "flutter/third_party/txt/third_party/fonts/Roboto-Regular.ttf")); diff --git a/flow/layers/backdrop_filter_layer.cc b/flow/layers/backdrop_filter_layer.cc index 573db97f191a2..ce86db3deaad7 100644 --- a/flow/layers/backdrop_filter_layer.cc +++ b/flow/layers/backdrop_filter_layer.cc @@ -9,7 +9,12 @@ namespace flutter { BackdropFilterLayer::BackdropFilterLayer(sk_sp filter) : filter_(std::move(filter)) {} -BackdropFilterLayer::~BackdropFilterLayer() = default; +void BackdropFilterLayer::Preroll(PrerollContext* context, + const SkMatrix& matrix) { + Layer::AutoPrerollSaveLayerState save = + Layer::AutoPrerollSaveLayerState::Create(context, true, bool(filter_)); + ContainerLayer::Preroll(context, matrix); +} void BackdropFilterLayer::Paint(PaintContext& context) const { TRACE_EVENT0("flutter", "BackdropFilterLayer::Paint"); diff --git a/flow/layers/backdrop_filter_layer.h b/flow/layers/backdrop_filter_layer.h index ede9ceeef41f8..e3f34252b4be1 100644 --- a/flow/layers/backdrop_filter_layer.h +++ b/flow/layers/backdrop_filter_layer.h @@ -14,7 +14,8 @@ namespace flutter { class BackdropFilterLayer : public ContainerLayer { public: BackdropFilterLayer(sk_sp filter); - ~BackdropFilterLayer() override; + + void Preroll(PrerollContext* context, const SkMatrix& matrix) override; void Paint(PaintContext& context) const override; diff --git a/flow/layers/backdrop_filter_layer_unittests.cc b/flow/layers/backdrop_filter_layer_unittests.cc new file mode 100644 index 0000000000000..50afecbdc7824 --- /dev/null +++ b/flow/layers/backdrop_filter_layer_unittests.cc @@ -0,0 +1,217 @@ +// 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/flow/layers/backdrop_filter_layer.h" + +#include "flutter/flow/testing/layer_test.h" +#include "flutter/flow/testing/mock_layer.h" +#include "flutter/fml/macros.h" +#include "flutter/testing/mock_canvas.h" +#include "third_party/skia/include/core/SkImageFilter.h" +#include "third_party/skia/include/effects/SkImageFilters.h" + +namespace flutter { +namespace testing { + +using BackdropFilterLayerTest = LayerTest; + +#ifndef NDEBUG +TEST_F(BackdropFilterLayerTest, PaintingEmptyLayerDies) { + auto layer = std::make_shared(sk_sp()); + + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(layer->paint_bounds(), kEmptyRect); + EXPECT_FALSE(layer->needs_painting()); + EXPECT_FALSE(layer->needs_system_composite()); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} + +TEST_F(BackdropFilterLayerTest, PaintBeforePrerollDies) { + const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f); + const SkPath child_path = SkPath().addRect(child_bounds); + auto mock_layer = std::make_shared(child_path); + auto layer = std::make_shared(sk_sp()); + layer->Add(mock_layer); + + EXPECT_EQ(layer->paint_bounds(), kEmptyRect); + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} +#endif + +TEST_F(BackdropFilterLayerTest, EmptyFilter) { + const SkMatrix initial_transform = SkMatrix::MakeTrans(0.5f, 1.0f); + const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f); + const SkPath child_path = SkPath().addRect(child_bounds); + const SkPaint child_paint = SkPaint(SkColors::kYellow); + auto mock_layer = std::make_shared(child_path, child_paint); + auto layer = std::make_shared(nullptr); + layer->Add(mock_layer); + + layer->Preroll(preroll_context(), initial_transform); + EXPECT_EQ(layer->paint_bounds(), child_bounds); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_EQ(mock_layer->parent_matrix(), initial_transform); + + layer->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector({MockCanvas::DrawCall{ + 0, MockCanvas::SaveLayerData{child_bounds, SkPaint(), + nullptr, 1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::DrawPathData{child_path, child_paint}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}})); +} + +TEST_F(BackdropFilterLayerTest, SimpleFilter) { + const SkMatrix initial_transform = SkMatrix::MakeTrans(0.5f, 1.0f); + const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f); + const SkPath child_path = SkPath().addRect(child_bounds); + const SkPaint child_paint = SkPaint(SkColors::kYellow); + auto layer_filter = SkImageFilters::Paint(SkPaint(SkColors::kMagenta)); + auto mock_layer = std::make_shared(child_path, child_paint); + auto layer = std::make_shared(layer_filter); + layer->Add(mock_layer); + + layer->Preroll(preroll_context(), initial_transform); + EXPECT_EQ(layer->paint_bounds(), child_bounds); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_EQ(mock_layer->parent_matrix(), initial_transform); + + layer->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector({MockCanvas::DrawCall{ + 0, MockCanvas::SaveLayerData{child_bounds, SkPaint(), + layer_filter, 1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::DrawPathData{child_path, child_paint}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}})); +} + +TEST_F(BackdropFilterLayerTest, MultipleChildren) { + const SkMatrix initial_transform = SkMatrix::MakeTrans(0.5f, 1.0f); + const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 2.5f, 3.5f); + const SkPath child_path1 = SkPath().addRect(child_bounds); + const SkPath child_path2 = + SkPath().addRect(child_bounds.makeOffset(3.0f, 0.0f)); + const SkPaint child_paint1 = SkPaint(SkColors::kYellow); + const SkPaint child_paint2 = SkPaint(SkColors::kCyan); + auto layer_filter = SkImageFilters::Paint(SkPaint(SkColors::kMagenta)); + auto mock_layer1 = std::make_shared(child_path1, child_paint1); + auto mock_layer2 = std::make_shared(child_path2, child_paint2); + auto layer = std::make_shared(layer_filter); + layer->Add(mock_layer1); + layer->Add(mock_layer2); + + SkRect children_bounds = child_path1.getBounds(); + children_bounds.join(child_path2.getBounds()); + layer->Preroll(preroll_context(), initial_transform); + EXPECT_EQ(mock_layer1->paint_bounds(), child_path1.getBounds()); + EXPECT_EQ(mock_layer2->paint_bounds(), child_path2.getBounds()); + EXPECT_EQ(layer->paint_bounds(), children_bounds); + EXPECT_TRUE(mock_layer1->needs_painting()); + EXPECT_TRUE(mock_layer2->needs_painting()); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_EQ(mock_layer1->parent_matrix(), initial_transform); + EXPECT_EQ(mock_layer2->parent_matrix(), initial_transform); + + layer->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector({MockCanvas::DrawCall{ + 0, MockCanvas::SaveLayerData{children_bounds, SkPaint(), + layer_filter, 1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::DrawPathData{child_path1, child_paint1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::DrawPathData{child_path2, child_paint2}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}})); +} + +TEST_F(BackdropFilterLayerTest, Nested) { + const SkMatrix initial_transform = SkMatrix::MakeTrans(0.5f, 1.0f); + const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 2.5f, 3.5f); + const SkPath child_path1 = SkPath().addRect(child_bounds); + const SkPath child_path2 = + SkPath().addRect(child_bounds.makeOffset(3.0f, 0.0f)); + const SkPaint child_paint1 = SkPaint(SkColors::kYellow); + const SkPaint child_paint2 = SkPaint(SkColors::kCyan); + auto layer_filter1 = SkImageFilters::Paint(SkPaint(SkColors::kMagenta)); + auto layer_filter2 = SkImageFilters::Paint(SkPaint(SkColors::kDkGray)); + auto mock_layer1 = std::make_shared(child_path1, child_paint1); + auto mock_layer2 = std::make_shared(child_path2, child_paint2); + auto layer1 = std::make_shared(layer_filter1); + auto layer2 = std::make_shared(layer_filter2); + layer2->Add(mock_layer2); + layer1->Add(mock_layer1); + layer1->Add(layer2); + + SkRect children_bounds = child_path1.getBounds(); + children_bounds.join(child_path2.getBounds()); + layer1->Preroll(preroll_context(), initial_transform); + EXPECT_EQ(mock_layer1->paint_bounds(), child_path1.getBounds()); + EXPECT_EQ(mock_layer2->paint_bounds(), child_path2.getBounds()); + EXPECT_EQ(layer1->paint_bounds(), children_bounds); + EXPECT_EQ(layer2->paint_bounds(), mock_layer2->paint_bounds()); + EXPECT_TRUE(mock_layer1->needs_painting()); + EXPECT_TRUE(mock_layer2->needs_painting()); + EXPECT_TRUE(layer1->needs_painting()); + EXPECT_TRUE(layer2->needs_painting()); + EXPECT_EQ(mock_layer1->parent_matrix(), initial_transform); + EXPECT_EQ(mock_layer2->parent_matrix(), initial_transform); + + layer1->Paint(paint_context()); + EXPECT_EQ(mock_canvas().draw_calls(), + std::vector( + {MockCanvas::DrawCall{ + 0, MockCanvas::SaveLayerData{children_bounds, SkPaint(), + layer_filter1, 1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::DrawPathData{child_path1, child_paint1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::SaveLayerData{child_path2.getBounds(), + SkPaint(), layer_filter2, 2}}, + MockCanvas::DrawCall{ + 2, MockCanvas::DrawPathData{child_path2, child_paint2}}, + MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}})); +} + +TEST_F(BackdropFilterLayerTest, Readback) { + sk_sp no_filter; + auto layer_filter = SkImageFilters::Paint(SkPaint(SkColors::kMagenta)); + auto initial_transform = SkMatrix(); + + // BDF with filter always reads from surface + auto layer1 = std::make_shared(layer_filter); + preroll_context()->surface_needs_readback = false; + layer1->Preroll(preroll_context(), initial_transform); + EXPECT_TRUE(preroll_context()->surface_needs_readback); + + // BDF with no filter does not read from surface itself + auto layer2 = std::make_shared(no_filter); + preroll_context()->surface_needs_readback = false; + layer2->Preroll(preroll_context(), initial_transform); + EXPECT_FALSE(preroll_context()->surface_needs_readback); + + // BDF with no filter does not block prior readback value + preroll_context()->surface_needs_readback = true; + layer2->Preroll(preroll_context(), initial_transform); + EXPECT_TRUE(preroll_context()->surface_needs_readback); + + // BDF with no filter blocks child with readback + auto mock_layer = + std::make_shared(SkPath(), SkPaint(), false, false, true); + layer2->Add(mock_layer); + preroll_context()->surface_needs_readback = false; + layer2->Preroll(preroll_context(), initial_transform); + EXPECT_FALSE(preroll_context()->surface_needs_readback); +} + +} // namespace testing +} // namespace flutter diff --git a/flow/layers/child_scene_layer.cc b/flow/layers/child_scene_layer.cc index f4100be2fee69..10b5a277db575 100644 --- a/flow/layers/child_scene_layer.cc +++ b/flow/layers/child_scene_layer.cc @@ -18,11 +18,27 @@ ChildSceneLayer::ChildSceneLayer(zx_koid_t layer_id, hit_testable_(hit_testable) {} void ChildSceneLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { + TRACE_EVENT0("flutter", "ChildSceneLayer::Preroll"); set_needs_system_composite(true); + + // An alpha "hole punch" is required if the frame behind us is not opaque. + if (!context->is_opaque) { + set_paint_bounds( + SkRect::MakeXYWH(offset_.fX, offset_.fY, size_.fWidth, size_.fHeight)); + } } void ChildSceneLayer::Paint(PaintContext& context) const { - FML_NOTREACHED() << "This layer never needs painting."; + TRACE_EVENT0("flutter", "ChildSceneLayer::Paint"); + FML_DCHECK(needs_painting()); + + // If we are being rendered into our own frame using the system compositor, + // then it is neccesary to "punch a hole" in the canvas/frame behind us so + // that group opacity looks correct. + SkPaint paint; + paint.setColor(SK_ColorTRANSPARENT); + paint.setBlendMode(SkBlendMode::kSrc); + context.leaf_nodes_canvas->drawRect(paint_bounds(), paint); } void ChildSceneLayer::UpdateScene(SceneUpdateContext& context) { diff --git a/flow/layers/clip_path_layer.cc b/flow/layers/clip_path_layer.cc index d08c19b34eeb9..3d420657bf079 100644 --- a/flow/layers/clip_path_layer.cc +++ b/flow/layers/clip_path_layer.cc @@ -17,12 +17,13 @@ ClipPathLayer::ClipPathLayer(const SkPath& clip_path, Clip clip_behavior) FML_DCHECK(clip_behavior != Clip::none); } -ClipPathLayer::~ClipPathLayer() = default; - void ClipPathLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { SkRect previous_cull_rect = context->cull_rect; SkRect clip_path_bounds = clip_path_.getBounds(); - if (context->cull_rect.intersect(clip_path_bounds)) { + children_inside_clip_ = context->cull_rect.intersect(clip_path_bounds); + if (children_inside_clip_) { + Layer::AutoPrerollSaveLayerState save = + Layer::AutoPrerollSaveLayerState::Create(context, UsesSaveLayer()); context->mutators_stack.PushClipPath(clip_path_); SkRect child_paint_bounds = SkRect::MakeEmpty(); PrerollChildren(context, matrix, &child_paint_bounds); @@ -51,15 +52,18 @@ void ClipPathLayer::Paint(PaintContext& context) const { TRACE_EVENT0("flutter", "ClipPathLayer::Paint"); FML_DCHECK(needs_painting()); + if (!children_inside_clip_) + return; + SkAutoCanvasRestore save(context.internal_nodes_canvas, true); context.internal_nodes_canvas->clipPath(clip_path_, clip_behavior_ != Clip::hardEdge); - if (clip_behavior_ == Clip::antiAliasWithSaveLayer) { + if (UsesSaveLayer()) { context.internal_nodes_canvas->saveLayer(paint_bounds(), nullptr); } PaintChildren(context); - if (clip_behavior_ == Clip::antiAliasWithSaveLayer) { + if (UsesSaveLayer()) { context.internal_nodes_canvas->restore(); } } diff --git a/flow/layers/clip_path_layer.h b/flow/layers/clip_path_layer.h index fd4d56f0db7f0..ac7440625bb62 100644 --- a/flow/layers/clip_path_layer.h +++ b/flow/layers/clip_path_layer.h @@ -12,12 +12,15 @@ namespace flutter { class ClipPathLayer : public ContainerLayer { public: ClipPathLayer(const SkPath& clip_path, Clip clip_behavior = Clip::antiAlias); - ~ClipPathLayer() override; void Preroll(PrerollContext* context, const SkMatrix& matrix) override; void Paint(PaintContext& context) const override; + bool UsesSaveLayer() const { + return clip_behavior_ == Clip::antiAliasWithSaveLayer; + } + #if defined(OS_FUCHSIA) void UpdateScene(SceneUpdateContext& context) override; #endif // defined(OS_FUCHSIA) @@ -25,6 +28,7 @@ class ClipPathLayer : public ContainerLayer { private: SkPath clip_path_; Clip clip_behavior_; + bool children_inside_clip_ = false; FML_DISALLOW_COPY_AND_ASSIGN(ClipPathLayer); }; diff --git a/flow/layers/clip_path_layer_unittests.cc b/flow/layers/clip_path_layer_unittests.cc new file mode 100644 index 0000000000000..8c327332baf12 --- /dev/null +++ b/flow/layers/clip_path_layer_unittests.cc @@ -0,0 +1,256 @@ +// 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/flow/layers/clip_path_layer.h" + +#include "flutter/flow/testing/layer_test.h" +#include "flutter/flow/testing/mock_layer.h" +#include "flutter/fml/macros.h" +#include "flutter/testing/mock_canvas.h" + +namespace flutter { +namespace testing { + +using ClipPathLayerTest = LayerTest; + +#ifndef NDEBUG +TEST_F(ClipPathLayerTest, ClipNoneBehaviorDies) { + EXPECT_DEATH_IF_SUPPORTED( + auto clip = std::make_shared(SkPath(), Clip::none), + "clip_behavior != Clip::none"); +} + +TEST_F(ClipPathLayerTest, PaintingEmptyLayerDies) { + auto layer = std::make_shared(SkPath(), Clip::hardEdge); + + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(preroll_context()->cull_rect, kGiantRect); // Untouched + EXPECT_TRUE(preroll_context()->mutators_stack.is_empty()); // Untouched + EXPECT_EQ(layer->paint_bounds(), kEmptyRect); + EXPECT_FALSE(layer->needs_painting()); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} + +TEST_F(ClipPathLayerTest, PaintBeforePrerollDies) { + const SkRect layer_bounds = SkRect::MakeXYWH(0.5, 1.0, 5.0, 6.0); + const SkPath layer_path = SkPath().addRect(layer_bounds); + auto layer = std::make_shared(layer_path, Clip::hardEdge); + EXPECT_EQ(layer->paint_bounds(), kEmptyRect); + EXPECT_FALSE(layer->needs_painting()); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} + +TEST_F(ClipPathLayerTest, PaintingCulledLayerDies) { + const SkMatrix initial_matrix = SkMatrix::MakeTrans(0.5f, 1.0f); + const SkRect child_bounds = SkRect::MakeXYWH(1.0, 2.0, 2.0, 2.0); + const SkRect layer_bounds = SkRect::MakeXYWH(0.5, 1.0, 5.0, 6.0); + const SkPath child_path = SkPath().addRect(child_bounds); + const SkPath layer_path = SkPath().addRect(layer_bounds); + auto mock_layer = std::make_shared(child_path); + auto layer = std::make_shared(layer_path, Clip::hardEdge); + layer->Add(mock_layer); + + preroll_context()->cull_rect = kEmptyRect; // Cull everything + + layer->Preroll(preroll_context(), initial_matrix); + EXPECT_EQ(preroll_context()->cull_rect, kEmptyRect); // Untouched + EXPECT_TRUE(preroll_context()->mutators_stack.is_empty()); // Untouched + EXPECT_EQ(mock_layer->paint_bounds(), kEmptyRect); + EXPECT_EQ(layer->paint_bounds(), kEmptyRect); + EXPECT_FALSE(mock_layer->needs_painting()); + EXPECT_FALSE(layer->needs_painting()); + EXPECT_EQ(mock_layer->parent_cull_rect(), kEmptyRect); + EXPECT_EQ(mock_layer->parent_matrix(), SkMatrix()); + EXPECT_EQ(mock_layer->parent_mutators(), std::vector()); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} +#endif + +TEST_F(ClipPathLayerTest, ChildOutsideBounds) { + const SkMatrix initial_matrix = SkMatrix::MakeTrans(0.5f, 1.0f); + const SkRect cull_bounds = SkRect::MakeXYWH(0.0, 0.0, 2.0, 4.0); + const SkRect child_bounds = SkRect::MakeXYWH(2.5, 5.0, 4.5, 4.0); + const SkRect layer_bounds = SkRect::MakeXYWH(0.5, 1.0, 5.0, 6.0); + const SkPath child_path = SkPath().addRect(child_bounds); + const SkPath layer_path = SkPath().addRect(layer_bounds); + const SkPaint child_paint = SkPaint(SkColors::kYellow); + auto mock_layer = std::make_shared(child_path, child_paint); + auto layer = std::make_shared(layer_path, Clip::hardEdge); + layer->Add(mock_layer); + + SkRect intersect_bounds = layer_bounds; + SkRect child_intersect_bounds = layer_bounds; + intersect_bounds.intersect(cull_bounds); + child_intersect_bounds.intersect(child_bounds); + preroll_context()->cull_rect = cull_bounds; // Cull child + + layer->Preroll(preroll_context(), initial_matrix); + EXPECT_EQ(preroll_context()->cull_rect, cull_bounds); // Untouched + EXPECT_TRUE(preroll_context()->mutators_stack.is_empty()); // Untouched + EXPECT_EQ(mock_layer->paint_bounds(), child_bounds); + EXPECT_EQ(layer->paint_bounds(), child_intersect_bounds); + EXPECT_TRUE(mock_layer->needs_painting()); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_EQ(mock_layer->parent_cull_rect(), intersect_bounds); + EXPECT_EQ(mock_layer->parent_matrix(), initial_matrix); + EXPECT_EQ(mock_layer->parent_mutators(), std::vector({Mutator(layer_path)})); + + layer->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector( + {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::ClipRectData{layer_bounds, SkClipOp::kIntersect, + MockCanvas::kHard_ClipEdgeStyle}}, + MockCanvas::DrawCall{ + 1, MockCanvas::DrawPathData{child_path, child_paint}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}})); +} + +TEST_F(ClipPathLayerTest, FullyContainedChild) { + const SkMatrix initial_matrix = SkMatrix::MakeTrans(0.5f, 1.0f); + const SkRect child_bounds = SkRect::MakeXYWH(1.0, 2.0, 2.0, 2.0); + const SkRect layer_bounds = SkRect::MakeXYWH(0.5, 1.0, 5.0, 6.0); + const SkPath child_path = SkPath().addRect(child_bounds); + const SkPath layer_path = SkPath().addRect(layer_bounds); + const SkPaint child_paint = SkPaint(SkColors::kYellow); + auto mock_layer = std::make_shared(child_path, child_paint); + auto layer = std::make_shared(layer_path, Clip::hardEdge); + layer->Add(mock_layer); + + layer->Preroll(preroll_context(), initial_matrix); + EXPECT_EQ(preroll_context()->cull_rect, kGiantRect); // Untouched + EXPECT_TRUE(preroll_context()->mutators_stack.is_empty()); // Untouched + EXPECT_EQ(mock_layer->paint_bounds(), child_bounds); + EXPECT_EQ(layer->paint_bounds(), mock_layer->paint_bounds()); + EXPECT_TRUE(mock_layer->needs_painting()); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_EQ(mock_layer->parent_cull_rect(), layer_bounds); + EXPECT_EQ(mock_layer->parent_matrix(), initial_matrix); + EXPECT_EQ(mock_layer->parent_mutators(), std::vector({Mutator(layer_path)})); + + layer->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector( + {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::ClipRectData{layer_bounds, SkClipOp::kIntersect, + MockCanvas::kHard_ClipEdgeStyle}}, + MockCanvas::DrawCall{ + 1, MockCanvas::DrawPathData{child_path, child_paint}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}})); +} + +TEST_F(ClipPathLayerTest, PartiallyContainedChild) { + const SkMatrix initial_matrix = SkMatrix::MakeTrans(0.5f, 1.0f); + const SkRect cull_bounds = SkRect::MakeXYWH(0.0, 0.0, 4.0, 5.5); + const SkRect child_bounds = SkRect::MakeXYWH(2.5, 5.0, 4.5, 4.0); + const SkRect layer_bounds = SkRect::MakeXYWH(0.5, 1.0, 5.0, 6.0); + const SkPath child_path = SkPath().addRect(child_bounds); + const SkPath layer_path = SkPath().addRect(layer_bounds); + const SkPaint child_paint = SkPaint(SkColors::kYellow); + auto mock_layer = std::make_shared(child_path, child_paint); + auto layer = std::make_shared(layer_path, Clip::hardEdge); + layer->Add(mock_layer); + + SkRect intersect_bounds = layer_bounds; + SkRect child_intersect_bounds = layer_bounds; + intersect_bounds.intersect(cull_bounds); + child_intersect_bounds.intersect(child_bounds); + preroll_context()->cull_rect = cull_bounds; // Cull child + + layer->Preroll(preroll_context(), initial_matrix); + EXPECT_EQ(preroll_context()->cull_rect, cull_bounds); // Untouched + EXPECT_TRUE(preroll_context()->mutators_stack.is_empty()); // Untouched + EXPECT_EQ(mock_layer->paint_bounds(), child_bounds); + EXPECT_EQ(layer->paint_bounds(), child_intersect_bounds); + EXPECT_TRUE(mock_layer->needs_painting()); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_EQ(mock_layer->parent_cull_rect(), intersect_bounds); + EXPECT_EQ(mock_layer->parent_matrix(), initial_matrix); + EXPECT_EQ(mock_layer->parent_mutators(), std::vector({Mutator(layer_path)})); + + layer->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector( + {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::ClipRectData{layer_bounds, SkClipOp::kIntersect, + MockCanvas::kHard_ClipEdgeStyle}}, + MockCanvas::DrawCall{ + 1, MockCanvas::DrawPathData{child_path, child_paint}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}})); +} + +static bool ReadbackResult(PrerollContext* context, + Clip clip_behavior, + std::shared_ptr child, + bool before) { + const SkMatrix initial_matrix = SkMatrix(); + const SkRect layer_bounds = SkRect::MakeXYWH(0.5, 1.0, 5.0, 6.0); + const SkPath layer_path = SkPath().addRect(layer_bounds); + auto layer = std::make_shared(layer_path, clip_behavior); + if (child != nullptr) { + layer->Add(child); + } + context->surface_needs_readback = before; + layer->Preroll(context, initial_matrix); + return context->surface_needs_readback; +} + +TEST_F(ClipPathLayerTest, Readback) { + PrerollContext* context = preroll_context(); + SkPath path; + SkPaint paint; + + const Clip hard = Clip::hardEdge; + const Clip soft = Clip::antiAlias; + const Clip save_layer = Clip::antiAliasWithSaveLayer; + + std::shared_ptr nochild; + auto reader = std::make_shared(path, paint, false, false, true); + auto nonreader = std::make_shared(path, paint); + + // No children, no prior readback -> no readback after + EXPECT_FALSE(ReadbackResult(context, hard, nochild, false)); + EXPECT_FALSE(ReadbackResult(context, soft, nochild, false)); + EXPECT_FALSE(ReadbackResult(context, save_layer, nochild, false)); + + // No children, prior readback -> readback after + EXPECT_TRUE(ReadbackResult(context, hard, nochild, true)); + EXPECT_TRUE(ReadbackResult(context, soft, nochild, true)); + EXPECT_TRUE(ReadbackResult(context, save_layer, nochild, true)); + + // Non readback child, no prior readback -> no readback after + EXPECT_FALSE(ReadbackResult(context, hard, nonreader, false)); + EXPECT_FALSE(ReadbackResult(context, soft, nonreader, false)); + EXPECT_FALSE(ReadbackResult(context, save_layer, nonreader, false)); + + // Non readback child, prior readback -> readback after + EXPECT_TRUE(ReadbackResult(context, hard, nonreader, true)); + EXPECT_TRUE(ReadbackResult(context, soft, nonreader, true)); + EXPECT_TRUE(ReadbackResult(context, save_layer, nonreader, true)); + + // Readback child, no prior readback -> readback after unless SaveLayer + EXPECT_TRUE(ReadbackResult(context, hard, reader, false)); + EXPECT_TRUE(ReadbackResult(context, soft, reader, false)); + EXPECT_FALSE(ReadbackResult(context, save_layer, reader, false)); + + // Readback child, prior readback -> readback after + EXPECT_TRUE(ReadbackResult(context, hard, reader, true)); + EXPECT_TRUE(ReadbackResult(context, soft, reader, true)); + EXPECT_TRUE(ReadbackResult(context, save_layer, reader, true)); +} + +} // namespace testing +} // namespace flutter diff --git a/flow/layers/clip_rect_layer.cc b/flow/layers/clip_rect_layer.cc index de7590624e408..af6c490f27a7d 100644 --- a/flow/layers/clip_rect_layer.cc +++ b/flow/layers/clip_rect_layer.cc @@ -11,11 +11,12 @@ ClipRectLayer::ClipRectLayer(const SkRect& clip_rect, Clip clip_behavior) FML_DCHECK(clip_behavior != Clip::none); } -ClipRectLayer::~ClipRectLayer() = default; - void ClipRectLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { SkRect previous_cull_rect = context->cull_rect; - if (context->cull_rect.intersect(clip_rect_)) { + children_inside_clip_ = context->cull_rect.intersect(clip_rect_); + if (children_inside_clip_) { + Layer::AutoPrerollSaveLayerState save = + Layer::AutoPrerollSaveLayerState::Create(context, UsesSaveLayer()); context->mutators_stack.PushClipRect(clip_rect_); SkRect child_paint_bounds = SkRect::MakeEmpty(); PrerollChildren(context, matrix, &child_paint_bounds); @@ -44,15 +45,18 @@ void ClipRectLayer::Paint(PaintContext& context) const { TRACE_EVENT0("flutter", "ClipRectLayer::Paint"); FML_DCHECK(needs_painting()); + if (!children_inside_clip_) + return; + SkAutoCanvasRestore save(context.internal_nodes_canvas, true); context.internal_nodes_canvas->clipRect(clip_rect_, clip_behavior_ != Clip::hardEdge); - if (clip_behavior_ == Clip::antiAliasWithSaveLayer) { + if (UsesSaveLayer()) { context.internal_nodes_canvas->saveLayer(clip_rect_, nullptr); } PaintChildren(context); - if (clip_behavior_ == Clip::antiAliasWithSaveLayer) { + if (UsesSaveLayer()) { context.internal_nodes_canvas->restore(); } } diff --git a/flow/layers/clip_rect_layer.h b/flow/layers/clip_rect_layer.h index 76c5a3f01c873..adf43915638a9 100644 --- a/flow/layers/clip_rect_layer.h +++ b/flow/layers/clip_rect_layer.h @@ -12,11 +12,14 @@ namespace flutter { class ClipRectLayer : public ContainerLayer { public: ClipRectLayer(const SkRect& clip_rect, Clip clip_behavior); - ~ClipRectLayer() override; void Preroll(PrerollContext* context, const SkMatrix& matrix) override; void Paint(PaintContext& context) const override; + bool UsesSaveLayer() const { + return clip_behavior_ == Clip::antiAliasWithSaveLayer; + } + #if defined(OS_FUCHSIA) void UpdateScene(SceneUpdateContext& context) override; #endif // defined(OS_FUCHSIA) @@ -24,6 +27,7 @@ class ClipRectLayer : public ContainerLayer { private: SkRect clip_rect_; Clip clip_behavior_; + bool children_inside_clip_ = false; FML_DISALLOW_COPY_AND_ASSIGN(ClipRectLayer); }; diff --git a/flow/layers/clip_rect_layer_unittests.cc b/flow/layers/clip_rect_layer_unittests.cc new file mode 100644 index 0000000000000..b63ca0c3e9eab --- /dev/null +++ b/flow/layers/clip_rect_layer_unittests.cc @@ -0,0 +1,253 @@ +// 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/flow/layers/clip_rect_layer.h" + +#include "flutter/flow/testing/layer_test.h" +#include "flutter/flow/testing/mock_layer.h" +#include "flutter/fml/macros.h" +#include "flutter/testing/mock_canvas.h" + +namespace flutter { +namespace testing { + +using ClipRectLayerTest = LayerTest; + +#ifndef NDEBUG +TEST_F(ClipRectLayerTest, ClipNoneBehaviorDies) { + EXPECT_DEATH_IF_SUPPORTED( + auto clip = std::make_shared(kEmptyRect, Clip::none), + "clip_behavior != Clip::none"); +} + +TEST_F(ClipRectLayerTest, PaintingEmptyLayerDies) { + auto layer = std::make_shared(kEmptyRect, Clip::hardEdge); + + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(preroll_context()->cull_rect, kGiantRect); // Untouched + EXPECT_TRUE(preroll_context()->mutators_stack.is_empty()); // Untouched + EXPECT_EQ(layer->paint_bounds(), kEmptyRect); + EXPECT_FALSE(layer->needs_painting()); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} + +TEST_F(ClipRectLayerTest, PaintBeforePrerollDies) { + const SkRect layer_bounds = SkRect::MakeXYWH(0.5, 1.0, 5.0, 6.0); + auto layer = std::make_shared(layer_bounds, Clip::hardEdge); + EXPECT_EQ(layer->paint_bounds(), kEmptyRect); + EXPECT_FALSE(layer->needs_painting()); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} + +TEST_F(ClipRectLayerTest, PaintingCulledLayerDies) { + const SkMatrix initial_matrix = SkMatrix::MakeTrans(0.5f, 1.0f); + const SkRect child_bounds = SkRect::MakeXYWH(1.0, 2.0, 2.0, 2.0); + const SkRect layer_bounds = SkRect::MakeXYWH(0.5, 1.0, 5.0, 6.0); + const SkPath child_path = SkPath().addRect(child_bounds); + auto mock_layer = std::make_shared(child_path); + auto layer = std::make_shared(layer_bounds, Clip::hardEdge); + layer->Add(mock_layer); + + preroll_context()->cull_rect = kEmptyRect; // Cull everything + + layer->Preroll(preroll_context(), initial_matrix); + EXPECT_EQ(preroll_context()->cull_rect, kEmptyRect); // Untouched + EXPECT_TRUE(preroll_context()->mutators_stack.is_empty()); // Untouched + EXPECT_EQ(mock_layer->paint_bounds(), kEmptyRect); + EXPECT_EQ(layer->paint_bounds(), kEmptyRect); + EXPECT_FALSE(mock_layer->needs_painting()); + EXPECT_FALSE(layer->needs_painting()); + EXPECT_EQ(mock_layer->parent_cull_rect(), kEmptyRect); + EXPECT_EQ(mock_layer->parent_matrix(), SkMatrix()); + EXPECT_EQ(mock_layer->parent_mutators(), std::vector()); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} +#endif + +TEST_F(ClipRectLayerTest, ChildOutsideBounds) { + const SkMatrix initial_matrix = SkMatrix::MakeTrans(0.5f, 1.0f); + const SkRect cull_bounds = SkRect::MakeXYWH(0.0, 0.0, 2.0, 4.0); + const SkRect child_bounds = SkRect::MakeXYWH(2.5, 5.0, 4.5, 4.0); + const SkRect layer_bounds = SkRect::MakeXYWH(0.5, 1.0, 5.0, 6.0); + const SkPath child_path = SkPath().addRect(child_bounds); + const SkPaint child_paint = SkPaint(SkColors::kYellow); + auto mock_layer = std::make_shared(child_path, child_paint); + auto layer = std::make_shared(layer_bounds, Clip::hardEdge); + layer->Add(mock_layer); + + SkRect intersect_bounds = layer_bounds; + SkRect child_intersect_bounds = layer_bounds; + intersect_bounds.intersect(cull_bounds); + child_intersect_bounds.intersect(child_bounds); + preroll_context()->cull_rect = cull_bounds; // Cull child + + layer->Preroll(preroll_context(), initial_matrix); + EXPECT_EQ(preroll_context()->cull_rect, cull_bounds); // Untouched + EXPECT_TRUE(preroll_context()->mutators_stack.is_empty()); // Untouched + EXPECT_EQ(mock_layer->paint_bounds(), child_bounds); + EXPECT_EQ(layer->paint_bounds(), child_intersect_bounds); + EXPECT_TRUE(mock_layer->needs_painting()); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_EQ(mock_layer->parent_cull_rect(), intersect_bounds); + EXPECT_EQ(mock_layer->parent_matrix(), initial_matrix); + EXPECT_EQ(mock_layer->parent_mutators(), + std::vector({Mutator(layer_bounds)})); + + layer->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector( + {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::ClipRectData{layer_bounds, SkClipOp::kIntersect, + MockCanvas::kHard_ClipEdgeStyle}}, + MockCanvas::DrawCall{ + 1, MockCanvas::DrawPathData{child_path, child_paint}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}})); +} + +TEST_F(ClipRectLayerTest, FullyContainedChild) { + const SkMatrix initial_matrix = SkMatrix::MakeTrans(0.5f, 1.0f); + const SkRect child_bounds = SkRect::MakeXYWH(1.0, 2.0, 2.0, 2.0); + const SkRect layer_bounds = SkRect::MakeXYWH(0.5, 1.0, 5.0, 6.0); + const SkPath child_path = SkPath().addRect(child_bounds); + const SkPaint child_paint = SkPaint(SkColors::kYellow); + auto mock_layer = std::make_shared(child_path, child_paint); + auto layer = std::make_shared(layer_bounds, Clip::hardEdge); + layer->Add(mock_layer); + + layer->Preroll(preroll_context(), initial_matrix); + EXPECT_EQ(preroll_context()->cull_rect, kGiantRect); // Untouched + EXPECT_TRUE(preroll_context()->mutators_stack.is_empty()); // Untouched + EXPECT_EQ(mock_layer->paint_bounds(), child_bounds); + EXPECT_EQ(layer->paint_bounds(), mock_layer->paint_bounds()); + EXPECT_TRUE(mock_layer->needs_painting()); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_EQ(mock_layer->parent_cull_rect(), layer_bounds); + EXPECT_EQ(mock_layer->parent_matrix(), initial_matrix); + EXPECT_EQ(mock_layer->parent_mutators(), + std::vector({Mutator(layer_bounds)})); + + layer->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector( + {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::ClipRectData{layer_bounds, SkClipOp::kIntersect, + MockCanvas::kHard_ClipEdgeStyle}}, + MockCanvas::DrawCall{ + 1, MockCanvas::DrawPathData{child_path, child_paint}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}})); +} + +TEST_F(ClipRectLayerTest, PartiallyContainedChild) { + const SkMatrix initial_matrix = SkMatrix::MakeTrans(0.5f, 1.0f); + const SkRect cull_bounds = SkRect::MakeXYWH(0.0, 0.0, 4.0, 5.5); + const SkRect child_bounds = SkRect::MakeXYWH(2.5, 5.0, 4.5, 4.0); + const SkRect layer_bounds = SkRect::MakeXYWH(0.5, 1.0, 5.0, 6.0); + const SkPath child_path = SkPath().addRect(child_bounds); + const SkPaint child_paint = SkPaint(SkColors::kYellow); + auto mock_layer = std::make_shared(child_path, child_paint); + auto layer = std::make_shared(layer_bounds, Clip::hardEdge); + layer->Add(mock_layer); + + SkRect intersect_bounds = layer_bounds; + SkRect child_intersect_bounds = layer_bounds; + intersect_bounds.intersect(cull_bounds); + child_intersect_bounds.intersect(child_bounds); + preroll_context()->cull_rect = cull_bounds; // Cull child + + layer->Preroll(preroll_context(), initial_matrix); + EXPECT_EQ(preroll_context()->cull_rect, cull_bounds); // Untouched + EXPECT_TRUE(preroll_context()->mutators_stack.is_empty()); // Untouched + EXPECT_EQ(mock_layer->paint_bounds(), child_bounds); + EXPECT_EQ(layer->paint_bounds(), child_intersect_bounds); + EXPECT_TRUE(mock_layer->needs_painting()); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_EQ(mock_layer->parent_cull_rect(), intersect_bounds); + EXPECT_EQ(mock_layer->parent_matrix(), initial_matrix); + EXPECT_EQ(mock_layer->parent_mutators(), + std::vector({Mutator(layer_bounds)})); + + layer->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector( + {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::ClipRectData{layer_bounds, SkClipOp::kIntersect, + MockCanvas::kHard_ClipEdgeStyle}}, + MockCanvas::DrawCall{ + 1, MockCanvas::DrawPathData{child_path, child_paint}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}})); +} + +static bool ReadbackResult(PrerollContext* context, + Clip clip_behavior, + std::shared_ptr child, + bool before) { + const SkMatrix initial_matrix = SkMatrix(); + const SkRect layer_bounds = SkRect::MakeXYWH(0.5, 1.0, 5.0, 6.0); + auto layer = std::make_shared(layer_bounds, clip_behavior); + if (child != nullptr) { + layer->Add(child); + } + context->surface_needs_readback = before; + layer->Preroll(context, initial_matrix); + return context->surface_needs_readback; +} + +TEST_F(ClipRectLayerTest, Readback) { + PrerollContext* context = preroll_context(); + SkPath path; + SkPaint paint; + + const Clip hard = Clip::hardEdge; + const Clip soft = Clip::antiAlias; + const Clip save_layer = Clip::antiAliasWithSaveLayer; + + std::shared_ptr nochild; + auto reader = std::make_shared(path, paint, false, false, true); + auto nonreader = std::make_shared(path, paint); + + // No children, no prior readback -> no readback after + EXPECT_FALSE(ReadbackResult(context, hard, nochild, false)); + EXPECT_FALSE(ReadbackResult(context, soft, nochild, false)); + EXPECT_FALSE(ReadbackResult(context, save_layer, nochild, false)); + + // No children, prior readback -> readback after + EXPECT_TRUE(ReadbackResult(context, hard, nochild, true)); + EXPECT_TRUE(ReadbackResult(context, soft, nochild, true)); + EXPECT_TRUE(ReadbackResult(context, save_layer, nochild, true)); + + // Non readback child, no prior readback -> no readback after + EXPECT_FALSE(ReadbackResult(context, hard, nonreader, false)); + EXPECT_FALSE(ReadbackResult(context, soft, nonreader, false)); + EXPECT_FALSE(ReadbackResult(context, save_layer, nonreader, false)); + + // Non readback child, prior readback -> readback after + EXPECT_TRUE(ReadbackResult(context, hard, nonreader, true)); + EXPECT_TRUE(ReadbackResult(context, soft, nonreader, true)); + EXPECT_TRUE(ReadbackResult(context, save_layer, nonreader, true)); + + // Readback child, no prior readback -> readback after unless SaveLayer + EXPECT_TRUE(ReadbackResult(context, hard, reader, false)); + EXPECT_TRUE(ReadbackResult(context, soft, reader, false)); + EXPECT_FALSE(ReadbackResult(context, save_layer, reader, false)); + + // Readback child, prior readback -> readback after + EXPECT_TRUE(ReadbackResult(context, hard, reader, true)); + EXPECT_TRUE(ReadbackResult(context, soft, reader, true)); + EXPECT_TRUE(ReadbackResult(context, save_layer, reader, true)); +} + +} // namespace testing +} // namespace flutter diff --git a/flow/layers/clip_rrect_layer.cc b/flow/layers/clip_rrect_layer.cc index 9899a1658732d..d640b8b47171f 100644 --- a/flow/layers/clip_rrect_layer.cc +++ b/flow/layers/clip_rrect_layer.cc @@ -11,12 +11,13 @@ ClipRRectLayer::ClipRRectLayer(const SkRRect& clip_rrect, Clip clip_behavior) FML_DCHECK(clip_behavior != Clip::none); } -ClipRRectLayer::~ClipRRectLayer() = default; - void ClipRRectLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { SkRect previous_cull_rect = context->cull_rect; SkRect clip_rrect_bounds = clip_rrect_.getBounds(); - if (context->cull_rect.intersect(clip_rrect_bounds)) { + children_inside_clip_ = context->cull_rect.intersect(clip_rrect_bounds); + if (children_inside_clip_) { + Layer::AutoPrerollSaveLayerState save = + Layer::AutoPrerollSaveLayerState::Create(context, UsesSaveLayer()); context->mutators_stack.PushClipRRect(clip_rrect_); SkRect child_paint_bounds = SkRect::MakeEmpty(); PrerollChildren(context, matrix, &child_paint_bounds); @@ -45,15 +46,18 @@ void ClipRRectLayer::Paint(PaintContext& context) const { TRACE_EVENT0("flutter", "ClipRRectLayer::Paint"); FML_DCHECK(needs_painting()); + if (!children_inside_clip_) + return; + SkAutoCanvasRestore save(context.internal_nodes_canvas, true); context.internal_nodes_canvas->clipRRect(clip_rrect_, clip_behavior_ != Clip::hardEdge); - if (clip_behavior_ == Clip::antiAliasWithSaveLayer) { + if (UsesSaveLayer()) { context.internal_nodes_canvas->saveLayer(paint_bounds(), nullptr); } PaintChildren(context); - if (clip_behavior_ == Clip::antiAliasWithSaveLayer) { + if (UsesSaveLayer()) { context.internal_nodes_canvas->restore(); } } diff --git a/flow/layers/clip_rrect_layer.h b/flow/layers/clip_rrect_layer.h index 53f74f30a0776..c1c83ddbcc60b 100644 --- a/flow/layers/clip_rrect_layer.h +++ b/flow/layers/clip_rrect_layer.h @@ -12,12 +12,15 @@ namespace flutter { class ClipRRectLayer : public ContainerLayer { public: ClipRRectLayer(const SkRRect& clip_rrect, Clip clip_behavior); - ~ClipRRectLayer() override; void Preroll(PrerollContext* context, const SkMatrix& matrix) override; void Paint(PaintContext& context) const override; + bool UsesSaveLayer() const { + return clip_behavior_ == Clip::antiAliasWithSaveLayer; + } + #if defined(OS_FUCHSIA) void UpdateScene(SceneUpdateContext& context) override; #endif // defined(OS_FUCHSIA) @@ -25,6 +28,7 @@ class ClipRRectLayer : public ContainerLayer { private: SkRRect clip_rrect_; Clip clip_behavior_; + bool children_inside_clip_ = false; FML_DISALLOW_COPY_AND_ASSIGN(ClipRRectLayer); }; diff --git a/flow/layers/clip_rrect_layer_unittests.cc b/flow/layers/clip_rrect_layer_unittests.cc new file mode 100644 index 0000000000000..57db2140ca175 --- /dev/null +++ b/flow/layers/clip_rrect_layer_unittests.cc @@ -0,0 +1,259 @@ +// 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/flow/layers/clip_rrect_layer.h" + +#include "flutter/flow/testing/layer_test.h" +#include "flutter/flow/testing/mock_layer.h" +#include "flutter/fml/macros.h" +#include "flutter/testing/mock_canvas.h" + +namespace flutter { +namespace testing { + +using ClipRRectLayerTest = LayerTest; + +#ifndef NDEBUG +TEST_F(ClipRRectLayerTest, ClipNoneBehaviorDies) { + const SkRRect layer_rrect = SkRRect::MakeEmpty(); + EXPECT_DEATH_IF_SUPPORTED( + auto clip = std::make_shared(layer_rrect, Clip::none), + "clip_behavior != Clip::none"); +} + +TEST_F(ClipRRectLayerTest, PaintingEmptyLayerDies) { + const SkRRect layer_rrect = SkRRect::MakeEmpty(); + auto layer = std::make_shared(layer_rrect, Clip::hardEdge); + + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(preroll_context()->cull_rect, kGiantRect); // Untouched + EXPECT_TRUE(preroll_context()->mutators_stack.is_empty()); // Untouched + EXPECT_EQ(layer->paint_bounds(), kEmptyRect); + EXPECT_FALSE(layer->needs_painting()); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} + +TEST_F(ClipRRectLayerTest, PaintBeforePreollDies) { + const SkRect layer_bounds = SkRect::MakeXYWH(0.5, 1.0, 5.0, 6.0); + const SkRRect layer_rrect = SkRRect::MakeRect(layer_bounds); + auto layer = std::make_shared(layer_rrect, Clip::hardEdge); + EXPECT_EQ(layer->paint_bounds(), kEmptyRect); + EXPECT_FALSE(layer->needs_painting()); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} + +TEST_F(ClipRRectLayerTest, PaintingCulledLayerDies) { + const SkMatrix initial_matrix = SkMatrix::MakeTrans(0.5f, 1.0f); + const SkRect child_bounds = SkRect::MakeXYWH(1.0, 2.0, 2.0, 2.0); + const SkRect layer_bounds = SkRect::MakeXYWH(0.5, 1.0, 5.0, 6.0); + const SkPath child_path = SkPath().addRect(child_bounds); + const SkRRect layer_rrect = SkRRect::MakeRect(layer_bounds); + const SkPaint child_paint = SkPaint(SkColors::kYellow); + auto mock_layer = std::make_shared(child_path, child_paint); + auto layer = std::make_shared(layer_rrect, Clip::hardEdge); + layer->Add(mock_layer); + + preroll_context()->cull_rect = kEmptyRect; // Cull everything + + layer->Preroll(preroll_context(), initial_matrix); + EXPECT_EQ(preroll_context()->cull_rect, kEmptyRect); // Untouched + EXPECT_TRUE(preroll_context()->mutators_stack.is_empty()); // Untouched + EXPECT_EQ(mock_layer->paint_bounds(), kEmptyRect); + EXPECT_EQ(layer->paint_bounds(), kEmptyRect); + EXPECT_FALSE(mock_layer->needs_painting()); + EXPECT_FALSE(layer->needs_painting()); + EXPECT_EQ(mock_layer->parent_cull_rect(), kEmptyRect); + EXPECT_EQ(mock_layer->parent_matrix(), SkMatrix()); + EXPECT_EQ(mock_layer->parent_mutators(), std::vector()); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} +#endif + +TEST_F(ClipRRectLayerTest, ChildOutsideBounds) { + const SkMatrix initial_matrix = SkMatrix::MakeTrans(0.5f, 1.0f); + const SkRect cull_bounds = SkRect::MakeXYWH(0.0, 0.0, 2.0, 4.0); + const SkRect child_bounds = SkRect::MakeXYWH(2.5, 5.0, 4.5, 4.0); + const SkRect layer_bounds = SkRect::MakeXYWH(0.5, 1.0, 5.0, 6.0); + const SkPath child_path = SkPath().addRect(child_bounds); + const SkRRect layer_rrect = SkRRect::MakeRect(layer_bounds); + const SkPaint child_paint = SkPaint(SkColors::kYellow); + auto mock_layer = std::make_shared(child_path, child_paint); + auto layer = std::make_shared(layer_rrect, Clip::hardEdge); + layer->Add(mock_layer); + + SkRect intersect_bounds = layer_bounds; + SkRect child_intersect_bounds = layer_bounds; + intersect_bounds.intersect(cull_bounds); + child_intersect_bounds.intersect(child_bounds); + preroll_context()->cull_rect = cull_bounds; // Cull child + + layer->Preroll(preroll_context(), initial_matrix); + EXPECT_EQ(preroll_context()->cull_rect, cull_bounds); // Untouched + EXPECT_TRUE(preroll_context()->mutators_stack.is_empty()); // Untouched + EXPECT_EQ(mock_layer->paint_bounds(), child_bounds); + EXPECT_EQ(layer->paint_bounds(), child_intersect_bounds); + EXPECT_TRUE(mock_layer->needs_painting()); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_EQ(mock_layer->parent_cull_rect(), intersect_bounds); + EXPECT_EQ(mock_layer->parent_matrix(), initial_matrix); + EXPECT_EQ(mock_layer->parent_mutators(), std::vector({Mutator(layer_rrect)})); + + layer->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector( + {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::ClipRectData{layer_bounds, SkClipOp::kIntersect, + MockCanvas::kHard_ClipEdgeStyle}}, + MockCanvas::DrawCall{ + 1, MockCanvas::DrawPathData{child_path, child_paint}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}})); +} + +TEST_F(ClipRRectLayerTest, FullyContainedChild) { + const SkMatrix initial_matrix = SkMatrix::MakeTrans(0.5f, 1.0f); + const SkRect child_bounds = SkRect::MakeXYWH(1.0, 2.0, 2.0, 2.0); + const SkRect layer_bounds = SkRect::MakeXYWH(0.5, 1.0, 5.0, 6.0); + const SkPath child_path = SkPath().addRect(child_bounds); + const SkRRect layer_rrect = SkRRect::MakeRect(layer_bounds); + const SkPaint child_paint = SkPaint(SkColors::kYellow); + auto mock_layer = std::make_shared(child_path, child_paint); + auto layer = std::make_shared(layer_rrect, Clip::hardEdge); + layer->Add(mock_layer); + + layer->Preroll(preroll_context(), initial_matrix); + EXPECT_EQ(preroll_context()->cull_rect, kGiantRect); // Untouched + EXPECT_TRUE(preroll_context()->mutators_stack.is_empty()); // Untouched + EXPECT_EQ(mock_layer->paint_bounds(), child_bounds); + EXPECT_EQ(layer->paint_bounds(), mock_layer->paint_bounds()); + EXPECT_TRUE(mock_layer->needs_painting()); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_EQ(mock_layer->parent_cull_rect(), layer_bounds); + EXPECT_EQ(mock_layer->parent_matrix(), initial_matrix); + EXPECT_EQ(mock_layer->parent_mutators(), std::vector({Mutator(layer_rrect)})); + + layer->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector( + {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::ClipRectData{layer_bounds, SkClipOp::kIntersect, + MockCanvas::kHard_ClipEdgeStyle}}, + MockCanvas::DrawCall{ + 1, MockCanvas::DrawPathData{child_path, child_paint}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}})); +} + +TEST_F(ClipRRectLayerTest, PartiallyContainedChild) { + const SkMatrix initial_matrix = SkMatrix::MakeTrans(0.5f, 1.0f); + const SkRect cull_bounds = SkRect::MakeXYWH(0.0, 0.0, 4.0, 5.5); + const SkRect child_bounds = SkRect::MakeXYWH(2.5, 5.0, 4.5, 4.0); + const SkRect layer_bounds = SkRect::MakeXYWH(0.5, 1.0, 5.0, 6.0); + const SkPath child_path = SkPath().addRect(child_bounds); + const SkRRect layer_rrect = SkRRect::MakeRect(layer_bounds); + const SkPaint child_paint = SkPaint(SkColors::kYellow); + auto mock_layer = std::make_shared(child_path, child_paint); + auto layer = std::make_shared(layer_rrect, Clip::hardEdge); + layer->Add(mock_layer); + + SkRect intersect_bounds = layer_bounds; + SkRect child_intersect_bounds = layer_bounds; + intersect_bounds.intersect(cull_bounds); + child_intersect_bounds.intersect(child_bounds); + preroll_context()->cull_rect = cull_bounds; // Cull child + + layer->Preroll(preroll_context(), initial_matrix); + EXPECT_EQ(preroll_context()->cull_rect, cull_bounds); // Untouched + EXPECT_TRUE(preroll_context()->mutators_stack.is_empty()); // Untouched + EXPECT_EQ(mock_layer->paint_bounds(), child_bounds); + EXPECT_EQ(layer->paint_bounds(), child_intersect_bounds); + EXPECT_TRUE(mock_layer->needs_painting()); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_EQ(mock_layer->parent_cull_rect(), intersect_bounds); + EXPECT_EQ(mock_layer->parent_matrix(), initial_matrix); + EXPECT_EQ(mock_layer->parent_mutators(), std::vector({Mutator(layer_rrect)})); + + layer->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector( + {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::ClipRectData{layer_bounds, SkClipOp::kIntersect, + MockCanvas::kHard_ClipEdgeStyle}}, + MockCanvas::DrawCall{ + 1, MockCanvas::DrawPathData{child_path, child_paint}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}})); +} + +static bool ReadbackResult(PrerollContext* context, + Clip clip_behavior, + std::shared_ptr child, + bool before) { + const SkMatrix initial_matrix = SkMatrix(); + const SkRect layer_bounds = SkRect::MakeXYWH(0.5, 1.0, 5.0, 6.0); + const SkRRect layer_rrect = SkRRect::MakeRect(layer_bounds); + auto layer = std::make_shared(layer_rrect, clip_behavior); + if (child != nullptr) { + layer->Add(child); + } + context->surface_needs_readback = before; + layer->Preroll(context, initial_matrix); + return context->surface_needs_readback; +} + +TEST_F(ClipRRectLayerTest, Readback) { + PrerollContext* context = preroll_context(); + SkPath path; + SkPaint paint; + + const Clip hard = Clip::hardEdge; + const Clip soft = Clip::antiAlias; + const Clip save_layer = Clip::antiAliasWithSaveLayer; + + std::shared_ptr nochild; + auto reader = std::make_shared(path, paint, false, false, true); + auto nonreader = std::make_shared(path, paint); + + // No children, no prior readback -> no readback after + EXPECT_FALSE(ReadbackResult(context, hard, nochild, false)); + EXPECT_FALSE(ReadbackResult(context, soft, nochild, false)); + EXPECT_FALSE(ReadbackResult(context, save_layer, nochild, false)); + + // No children, prior readback -> readback after + EXPECT_TRUE(ReadbackResult(context, hard, nochild, true)); + EXPECT_TRUE(ReadbackResult(context, soft, nochild, true)); + EXPECT_TRUE(ReadbackResult(context, save_layer, nochild, true)); + + // Non readback child, no prior readback -> no readback after + EXPECT_FALSE(ReadbackResult(context, hard, nonreader, false)); + EXPECT_FALSE(ReadbackResult(context, soft, nonreader, false)); + EXPECT_FALSE(ReadbackResult(context, save_layer, nonreader, false)); + + // Non readback child, prior readback -> readback after + EXPECT_TRUE(ReadbackResult(context, hard, nonreader, true)); + EXPECT_TRUE(ReadbackResult(context, soft, nonreader, true)); + EXPECT_TRUE(ReadbackResult(context, save_layer, nonreader, true)); + + // Readback child, no prior readback -> readback after unless SaveLayer + EXPECT_TRUE(ReadbackResult(context, hard, reader, false)); + EXPECT_TRUE(ReadbackResult(context, soft, reader, false)); + EXPECT_FALSE(ReadbackResult(context, save_layer, reader, false)); + + // Readback child, prior readback -> readback after + EXPECT_TRUE(ReadbackResult(context, hard, reader, true)); + EXPECT_TRUE(ReadbackResult(context, soft, reader, true)); + EXPECT_TRUE(ReadbackResult(context, save_layer, reader, true)); +} + +} // namespace testing +} // namespace flutter diff --git a/flow/layers/color_filter_layer.cc b/flow/layers/color_filter_layer.cc index f838b0612b2e5..b212720da581d 100644 --- a/flow/layers/color_filter_layer.cc +++ b/flow/layers/color_filter_layer.cc @@ -9,7 +9,12 @@ namespace flutter { ColorFilterLayer::ColorFilterLayer(sk_sp filter) : filter_(std::move(filter)) {} -ColorFilterLayer::~ColorFilterLayer() = default; +void ColorFilterLayer::Preroll(PrerollContext* context, + const SkMatrix& matrix) { + Layer::AutoPrerollSaveLayerState save = + Layer::AutoPrerollSaveLayerState::Create(context); + ContainerLayer::Preroll(context, matrix); +} void ColorFilterLayer::Paint(PaintContext& context) const { TRACE_EVENT0("flutter", "ColorFilterLayer::Paint"); diff --git a/flow/layers/color_filter_layer.h b/flow/layers/color_filter_layer.h index cf1de4cb610fc..989158f704eaf 100644 --- a/flow/layers/color_filter_layer.h +++ b/flow/layers/color_filter_layer.h @@ -14,7 +14,8 @@ namespace flutter { class ColorFilterLayer : public ContainerLayer { public: ColorFilterLayer(sk_sp filter); - ~ColorFilterLayer() override; + + void Preroll(PrerollContext* context, const SkMatrix& matrix) override; void Paint(PaintContext& context) const override; diff --git a/flow/layers/color_filter_layer_unittests.cc b/flow/layers/color_filter_layer_unittests.cc new file mode 100644 index 0000000000000..af1e5788b6e34 --- /dev/null +++ b/flow/layers/color_filter_layer_unittests.cc @@ -0,0 +1,218 @@ +// 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/flow/layers/color_filter_layer.h" + +#include "flutter/flow/testing/layer_test.h" +#include "flutter/flow/testing/mock_layer.h" +#include "flutter/fml/macros.h" +#include "flutter/testing/mock_canvas.h" +#include "third_party/skia/include/core/SkColorFilter.h" +#include "third_party/skia/include/effects/SkColorMatrixFilter.h" + +namespace flutter { +namespace testing { + +using ColorFilterLayerTest = LayerTest; + +#ifndef NDEBUG +TEST_F(ColorFilterLayerTest, PaintingEmptyLayerDies) { + auto layer = std::make_shared(sk_sp()); + + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(layer->paint_bounds(), kEmptyRect); + EXPECT_FALSE(layer->needs_painting()); + EXPECT_FALSE(layer->needs_system_composite()); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} + +TEST_F(ColorFilterLayerTest, PaintBeforePrerollDies) { + const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f); + const SkPath child_path = SkPath().addRect(child_bounds); + auto mock_layer = std::make_shared(child_path); + auto layer = std::make_shared(sk_sp()); + layer->Add(mock_layer); + + EXPECT_EQ(layer->paint_bounds(), kEmptyRect); + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} +#endif + +TEST_F(ColorFilterLayerTest, EmptyFilter) { + const SkMatrix initial_transform = SkMatrix::MakeTrans(0.5f, 1.0f); + const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f); + const SkPath child_path = SkPath().addRect(child_bounds); + const SkPaint child_paint = SkPaint(SkColors::kYellow); + auto mock_layer = std::make_shared(child_path, child_paint); + auto layer = std::make_shared(nullptr); + layer->Add(mock_layer); + + layer->Preroll(preroll_context(), initial_transform); + EXPECT_EQ(layer->paint_bounds(), child_bounds); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_EQ(mock_layer->parent_matrix(), initial_transform); + + SkPaint filter_paint; + filter_paint.setColorFilter(nullptr); + layer->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector({MockCanvas::DrawCall{ + 0, MockCanvas::SaveLayerData{child_bounds, filter_paint, + nullptr, 1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::DrawPathData{child_path, child_paint}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}})); +} + +TEST_F(ColorFilterLayerTest, SimpleFilter) { + const SkMatrix initial_transform = SkMatrix::MakeTrans(0.5f, 1.0f); + const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f); + const SkPath child_path = SkPath().addRect(child_bounds); + const SkPaint child_paint = SkPaint(SkColors::kYellow); + auto layer_filter = + SkColorMatrixFilter::MakeLightingFilter(SK_ColorGREEN, SK_ColorYELLOW); + auto mock_layer = std::make_shared(child_path, child_paint); + auto layer = std::make_shared(layer_filter); + layer->Add(mock_layer); + + layer->Preroll(preroll_context(), initial_transform); + EXPECT_EQ(layer->paint_bounds(), child_bounds); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_EQ(mock_layer->parent_matrix(), initial_transform); + + SkPaint filter_paint; + filter_paint.setColorFilter(layer_filter); + layer->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector({MockCanvas::DrawCall{ + 0, MockCanvas::SaveLayerData{child_bounds, filter_paint, + nullptr, 1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::DrawPathData{child_path, child_paint}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}})); +} + +TEST_F(ColorFilterLayerTest, MultipleChildren) { + const SkMatrix initial_transform = SkMatrix::MakeTrans(0.5f, 1.0f); + const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 2.5f, 3.5f); + const SkPath child_path1 = SkPath().addRect(child_bounds); + const SkPath child_path2 = + SkPath().addRect(child_bounds.makeOffset(3.0f, 0.0f)); + const SkPaint child_paint1 = SkPaint(SkColors::kYellow); + const SkPaint child_paint2 = SkPaint(SkColors::kCyan); + auto layer_filter = + SkColorMatrixFilter::MakeLightingFilter(SK_ColorGREEN, SK_ColorYELLOW); + auto mock_layer1 = std::make_shared(child_path1, child_paint1); + auto mock_layer2 = std::make_shared(child_path2, child_paint2); + auto layer = std::make_shared(layer_filter); + layer->Add(mock_layer1); + layer->Add(mock_layer2); + + SkRect children_bounds = child_path1.getBounds(); + children_bounds.join(child_path2.getBounds()); + layer->Preroll(preroll_context(), initial_transform); + EXPECT_EQ(mock_layer1->paint_bounds(), child_path1.getBounds()); + EXPECT_EQ(mock_layer2->paint_bounds(), child_path2.getBounds()); + EXPECT_EQ(layer->paint_bounds(), children_bounds); + EXPECT_TRUE(mock_layer1->needs_painting()); + EXPECT_TRUE(mock_layer2->needs_painting()); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_EQ(mock_layer1->parent_matrix(), initial_transform); + EXPECT_EQ(mock_layer2->parent_matrix(), initial_transform); + + SkPaint filter_paint; + filter_paint.setColorFilter(layer_filter); + layer->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector({MockCanvas::DrawCall{ + 0, MockCanvas::SaveLayerData{children_bounds, + filter_paint, nullptr, 1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::DrawPathData{child_path1, child_paint1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::DrawPathData{child_path2, child_paint2}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}})); +} + +TEST_F(ColorFilterLayerTest, Nested) { + const SkMatrix initial_transform = SkMatrix::MakeTrans(0.5f, 1.0f); + const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 2.5f, 3.5f); + const SkPath child_path1 = SkPath().addRect(child_bounds); + const SkPath child_path2 = + SkPath().addRect(child_bounds.makeOffset(3.0f, 0.0f)); + const SkPaint child_paint1 = SkPaint(SkColors::kYellow); + const SkPaint child_paint2 = SkPaint(SkColors::kCyan); + auto layer_filter1 = + SkColorMatrixFilter::MakeLightingFilter(SK_ColorGREEN, SK_ColorYELLOW); + auto layer_filter2 = + SkColorMatrixFilter::MakeLightingFilter(SK_ColorMAGENTA, SK_ColorBLUE); + auto mock_layer1 = std::make_shared(child_path1, child_paint1); + auto mock_layer2 = std::make_shared(child_path2, child_paint2); + auto layer1 = std::make_shared(layer_filter1); + auto layer2 = std::make_shared(layer_filter2); + layer2->Add(mock_layer2); + layer1->Add(mock_layer1); + layer1->Add(layer2); + + SkRect children_bounds = child_path1.getBounds(); + children_bounds.join(child_path2.getBounds()); + layer1->Preroll(preroll_context(), initial_transform); + EXPECT_EQ(mock_layer1->paint_bounds(), child_path1.getBounds()); + EXPECT_EQ(mock_layer2->paint_bounds(), child_path2.getBounds()); + EXPECT_EQ(layer1->paint_bounds(), children_bounds); + EXPECT_EQ(layer2->paint_bounds(), mock_layer2->paint_bounds()); + EXPECT_TRUE(mock_layer1->needs_painting()); + EXPECT_TRUE(mock_layer2->needs_painting()); + EXPECT_TRUE(layer1->needs_painting()); + EXPECT_TRUE(layer2->needs_painting()); + EXPECT_EQ(mock_layer1->parent_matrix(), initial_transform); + EXPECT_EQ(mock_layer2->parent_matrix(), initial_transform); + + SkPaint filter_paint1, filter_paint2; + filter_paint1.setColorFilter(layer_filter1); + filter_paint2.setColorFilter(layer_filter2); + layer1->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector({MockCanvas::DrawCall{ + 0, MockCanvas::SaveLayerData{children_bounds, + filter_paint1, nullptr, 1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::DrawPathData{child_path1, child_paint1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::SaveLayerData{child_path2.getBounds(), + filter_paint2, nullptr, 2}}, + MockCanvas::DrawCall{ + 2, MockCanvas::DrawPathData{child_path2, child_paint2}}, + MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}})); +} + +TEST_F(ColorFilterLayerTest, Readback) { + auto layer_filter = SkColorFilters::LinearToSRGBGamma(); + auto initial_transform = SkMatrix(); + + // ColorFilterLayer does not read from surface + auto layer = std::make_shared(layer_filter); + preroll_context()->surface_needs_readback = false; + layer->Preroll(preroll_context(), initial_transform); + EXPECT_FALSE(preroll_context()->surface_needs_readback); + + // ColorFilterLayer blocks child with readback + auto mock_layer = + std::make_shared(SkPath(), SkPaint(), false, false, true); + layer->Add(mock_layer); + preroll_context()->surface_needs_readback = false; + layer->Preroll(preroll_context(), initial_transform); + EXPECT_FALSE(preroll_context()->surface_needs_readback); +} + +} // namespace testing +} // namespace flutter diff --git a/flow/layers/container_layer.cc b/flow/layers/container_layer.cc index 31a5a255afca9..39372f6c84dd8 100644 --- a/flow/layers/container_layer.cc +++ b/flow/layers/container_layer.cc @@ -8,11 +8,8 @@ namespace flutter { ContainerLayer::ContainerLayer() {} -ContainerLayer::~ContainerLayer() = default; - void ContainerLayer::Add(std::shared_ptr layer) { - layer->set_parent(this); - layers_.push_back(std::move(layer)); + layers_.emplace_back(std::move(layer)); } void ContainerLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { @@ -23,17 +20,37 @@ void ContainerLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { set_paint_bounds(child_paint_bounds); } +void ContainerLayer::Paint(PaintContext& context) const { + FML_DCHECK(needs_painting()); + + PaintChildren(context); +} + void ContainerLayer::PrerollChildren(PrerollContext* context, const SkMatrix& child_matrix, SkRect* child_paint_bounds) { + // Platform views have no children, so context->has_platform_view should + // always be false. + FML_DCHECK(!context->has_platform_view); + bool child_has_platform_view = false; for (auto& layer : layers_) { + // Reset context->has_platform_view to false so that layers aren't treated + // as if they have a platform view based on one being previously found in a + // sibling tree. + context->has_platform_view = false; + layer->Preroll(context, child_matrix); if (layer->needs_system_composite()) { set_needs_system_composite(true); } child_paint_bounds->join(layer->paint_bounds()); + + child_has_platform_view = + child_has_platform_view || context->has_platform_view; } + + context->has_platform_view = child_has_platform_view; } void ContainerLayer::PaintChildren(PaintContext& context) const { diff --git a/flow/layers/container_layer.h b/flow/layers/container_layer.h index ef1c03328d1df..df5be5e8b9466 100644 --- a/flow/layers/container_layer.h +++ b/flow/layers/container_layer.h @@ -13,12 +13,11 @@ namespace flutter { class ContainerLayer : public Layer { public: ContainerLayer(); - ~ContainerLayer() override; - void Add(std::shared_ptr layer); + virtual void Add(std::shared_ptr layer); void Preroll(PrerollContext* context, const SkMatrix& matrix) override; - + void Paint(PaintContext& context) const override; #if defined(OS_FUCHSIA) void UpdateScene(SceneUpdateContext& context) override; #endif // defined(OS_FUCHSIA) diff --git a/flow/layers/container_layer_unittests.cc b/flow/layers/container_layer_unittests.cc new file mode 100644 index 0000000000000..c5230fe7e70f3 --- /dev/null +++ b/flow/layers/container_layer_unittests.cc @@ -0,0 +1,202 @@ +// 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/flow/layers/container_layer.h" + +#include "flutter/flow/testing/layer_test.h" +#include "flutter/flow/testing/mock_layer.h" +#include "flutter/fml/macros.h" +#include "flutter/testing/mock_canvas.h" + +namespace flutter { +namespace testing { + +using ContainerLayerTest = LayerTest; + +#ifndef NDEBUG +TEST_F(ContainerLayerTest, LayerWithParentHasPlatformView) { + auto layer = std::make_shared(); + + preroll_context()->has_platform_view = true; + EXPECT_DEATH_IF_SUPPORTED(layer->Preroll(preroll_context(), SkMatrix()), + "!context->has_platform_view"); +} + +TEST_F(ContainerLayerTest, PaintingEmptyLayerDies) { + auto layer = std::make_shared(); + + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(layer->paint_bounds(), SkRect::MakeEmpty()); + EXPECT_FALSE(layer->needs_painting()); + EXPECT_FALSE(layer->needs_system_composite()); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} + +TEST_F(ContainerLayerTest, PaintBeforePreollDies) { + SkPath child_path; + child_path.addRect(5.0f, 6.0f, 20.5f, 21.5f); + auto mock_layer = std::make_shared(child_path); + auto layer = std::make_shared(); + layer->Add(mock_layer); + + EXPECT_EQ(layer->paint_bounds(), SkRect::MakeEmpty()); + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} +#endif + +TEST_F(ContainerLayerTest, Simple) { + SkPath child_path; + child_path.addRect(5.0f, 6.0f, 20.5f, 21.5f); + SkPaint child_paint(SkColors::kGreen); + SkMatrix initial_transform = SkMatrix::MakeTrans(-0.5f, -0.5f); + + auto mock_layer = std::make_shared(child_path, child_paint); + auto layer = std::make_shared(); + layer->Add(mock_layer); + + layer->Preroll(preroll_context(), initial_transform); + EXPECT_FALSE(preroll_context()->has_platform_view); + EXPECT_EQ(mock_layer->paint_bounds(), child_path.getBounds()); + EXPECT_EQ(layer->paint_bounds(), child_path.getBounds()); + EXPECT_TRUE(mock_layer->needs_painting()); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_FALSE(mock_layer->needs_system_composite()); + EXPECT_FALSE(layer->needs_system_composite()); + EXPECT_EQ(mock_layer->parent_matrix(), initial_transform); + EXPECT_EQ(mock_layer->parent_cull_rect(), kGiantRect); + + layer->Paint(paint_context()); + EXPECT_EQ(mock_canvas().draw_calls(), + std::vector({MockCanvas::DrawCall{ + 0, MockCanvas::DrawPathData{child_path, child_paint}}})); +} + +TEST_F(ContainerLayerTest, Multiple) { + SkPath child_path1; + child_path1.addRect(5.0f, 6.0f, 20.5f, 21.5f); + SkPath child_path2; + child_path2.addRect(8.0f, 2.0f, 16.5f, 14.5f); + SkPaint child_paint1(SkColors::kGray); + SkPaint child_paint2(SkColors::kGreen); + SkMatrix initial_transform = SkMatrix::MakeTrans(-0.5f, -0.5f); + + auto mock_layer1 = std::make_shared( + child_path1, child_paint1, true /* fake_has_platform_view */); + auto mock_layer2 = std::make_shared(child_path2, child_paint2); + auto layer = std::make_shared(); + layer->Add(mock_layer1); + layer->Add(mock_layer2); + + SkRect expected_total_bounds = child_path1.getBounds(); + expected_total_bounds.join(child_path2.getBounds()); + layer->Preroll(preroll_context(), initial_transform); + EXPECT_TRUE(preroll_context()->has_platform_view); + EXPECT_EQ(mock_layer1->paint_bounds(), child_path1.getBounds()); + EXPECT_EQ(mock_layer2->paint_bounds(), child_path2.getBounds()); + EXPECT_EQ(layer->paint_bounds(), expected_total_bounds); + EXPECT_TRUE(mock_layer1->needs_painting()); + EXPECT_TRUE(mock_layer2->needs_painting()); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_FALSE(mock_layer1->needs_system_composite()); + EXPECT_FALSE(mock_layer2->needs_system_composite()); + EXPECT_FALSE(layer->needs_system_composite()); + EXPECT_EQ(mock_layer1->parent_matrix(), initial_transform); + EXPECT_EQ(mock_layer2->parent_matrix(), initial_transform); + EXPECT_EQ(mock_layer1->parent_cull_rect(), kGiantRect); + EXPECT_EQ(mock_layer2->parent_cull_rect(), + kGiantRect); // Siblings are independent + + layer->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector({MockCanvas::DrawCall{ + 0, MockCanvas::DrawPathData{child_path1, child_paint1}}, + MockCanvas::DrawCall{0, MockCanvas::DrawPathData{ + child_path2, child_paint2}}})); +} + +TEST_F(ContainerLayerTest, MultipleWithEmpty) { + SkPath child_path1; + child_path1.addRect(5.0f, 6.0f, 20.5f, 21.5f); + SkPaint child_paint1(SkColors::kGray); + SkPaint child_paint2(SkColors::kGreen); + SkMatrix initial_transform = SkMatrix::MakeTrans(-0.5f, -0.5f); + + auto mock_layer1 = std::make_shared(child_path1, child_paint1); + auto mock_layer2 = std::make_shared(SkPath(), child_paint2); + auto layer = std::make_shared(); + layer->Add(mock_layer1); + layer->Add(mock_layer2); + + layer->Preroll(preroll_context(), initial_transform); + EXPECT_FALSE(preroll_context()->has_platform_view); + EXPECT_EQ(mock_layer1->paint_bounds(), child_path1.getBounds()); + EXPECT_EQ(mock_layer2->paint_bounds(), SkPath().getBounds()); + EXPECT_EQ(layer->paint_bounds(), child_path1.getBounds()); + EXPECT_TRUE(mock_layer1->needs_painting()); + EXPECT_FALSE(mock_layer2->needs_painting()); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_FALSE(mock_layer1->needs_system_composite()); + EXPECT_FALSE(mock_layer2->needs_system_composite()); + EXPECT_FALSE(layer->needs_system_composite()); + EXPECT_EQ(mock_layer1->parent_matrix(), initial_transform); + EXPECT_EQ(mock_layer2->parent_matrix(), initial_transform); + EXPECT_EQ(mock_layer1->parent_cull_rect(), kGiantRect); + EXPECT_EQ(mock_layer2->parent_cull_rect(), kGiantRect); + + layer->Paint(paint_context()); + EXPECT_EQ(mock_canvas().draw_calls(), + std::vector({MockCanvas::DrawCall{ + 0, MockCanvas::DrawPathData{child_path1, child_paint1}}})); +} + +TEST_F(ContainerLayerTest, NeedsSystemComposite) { + SkPath child_path1; + child_path1.addRect(5.0f, 6.0f, 20.5f, 21.5f); + SkPath child_path2; + child_path2.addRect(8.0f, 2.0f, 16.5f, 14.5f); + SkPaint child_paint1(SkColors::kGray); + SkPaint child_paint2(SkColors::kGreen); + SkMatrix initial_transform = SkMatrix::MakeTrans(-0.5f, -0.5f); + + auto mock_layer1 = std::make_shared( + child_path1, child_paint1, false /* fake_has_platform_view */, + true /* fake_needs_system_composite */); + auto mock_layer2 = std::make_shared(child_path2, child_paint2); + auto layer = std::make_shared(); + layer->Add(mock_layer1); + layer->Add(mock_layer2); + + SkRect expected_total_bounds = child_path1.getBounds(); + expected_total_bounds.join(child_path2.getBounds()); + layer->Preroll(preroll_context(), initial_transform); + EXPECT_FALSE(preroll_context()->has_platform_view); + EXPECT_EQ(mock_layer1->paint_bounds(), child_path1.getBounds()); + EXPECT_EQ(mock_layer2->paint_bounds(), child_path2.getBounds()); + EXPECT_EQ(layer->paint_bounds(), expected_total_bounds); + EXPECT_TRUE(mock_layer1->needs_painting()); + EXPECT_TRUE(mock_layer2->needs_painting()); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_TRUE(mock_layer1->needs_system_composite()); + EXPECT_FALSE(mock_layer2->needs_system_composite()); + EXPECT_TRUE(layer->needs_system_composite()); + EXPECT_EQ(mock_layer1->parent_matrix(), initial_transform); + EXPECT_EQ(mock_layer2->parent_matrix(), initial_transform); + EXPECT_EQ(mock_layer1->parent_cull_rect(), kGiantRect); + EXPECT_EQ(mock_layer2->parent_cull_rect(), kGiantRect); + + layer->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector({MockCanvas::DrawCall{ + 0, MockCanvas::DrawPathData{child_path1, child_paint1}}, + MockCanvas::DrawCall{0, MockCanvas::DrawPathData{ + child_path2, child_paint2}}})); +} + +} // namespace testing +} // namespace flutter diff --git a/flow/layers/elevated_container_layer.cc b/flow/layers/elevated_container_layer.cc new file mode 100644 index 0000000000000..cd68b06713992 --- /dev/null +++ b/flow/layers/elevated_container_layer.cc @@ -0,0 +1,49 @@ +// 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/flow/layers/elevated_container_layer.h" + +namespace flutter { +namespace { + +float ClampElevation(float elevation, + float parent_elevation, + float max_elevation) { + // TODO(mklim): Deal with bounds overflow more elegantly. We'd like to be + // able to have developers specify the behavior here to alternatives besides + // clamping, like normalization on some arbitrary curve. + float clamped_elevation = elevation; + if (max_elevation > -1 && (parent_elevation + elevation) > max_elevation) { + // Clamp the local z coordinate at our max bound. Take into account the + // parent z position here to fix clamping in cases where the child is + // overflowing because of its parents. + clamped_elevation = max_elevation - parent_elevation; + } + + return clamped_elevation; +} + +} // namespace + +ElevatedContainerLayer::ElevatedContainerLayer(float elevation) + : elevation_(elevation), clamped_elevation_(elevation) {} + +void ElevatedContainerLayer::Preroll(PrerollContext* context, + const SkMatrix& matrix) { + TRACE_EVENT0("flutter", "ElevatedContainerLayer::Preroll"); + + // Track total elevation as we walk the tree, in order to deal with bounds + // overflow in z. + parent_elevation_ = context->total_elevation; + clamped_elevation_ = ClampElevation(elevation_, parent_elevation_, + context->frame_physical_depth); + context->total_elevation += clamped_elevation_; + + ContainerLayer::Preroll(context, matrix); + + // Restore the elevation for our parent. + context->total_elevation = parent_elevation_; +} + +} // namespace flutter diff --git a/flow/layers/elevated_container_layer.h b/flow/layers/elevated_container_layer.h new file mode 100644 index 0000000000000..9c7a8b051f118 --- /dev/null +++ b/flow/layers/elevated_container_layer.h @@ -0,0 +1,34 @@ +// 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. + +#ifndef FLUTTER_FLOW_LAYERS_ELEVATED_CONTAINER_LAYER_H_ +#define FLUTTER_FLOW_LAYERS_ELEVATED_CONTAINER_LAYER_H_ + +#include "flutter/flow/layers/container_layer.h" + +namespace flutter { + +class ElevatedContainerLayer : public ContainerLayer { + public: + ElevatedContainerLayer(float elevation); + ~ElevatedContainerLayer() override = default; + + void Preroll(PrerollContext* context, const SkMatrix& matrix) override; + + float elevation() const { return clamped_elevation_; } + float total_elevation() const { + return parent_elevation_ + clamped_elevation_; + } + + private: + float parent_elevation_ = 0.0f; + float elevation_ = 0.0f; + float clamped_elevation_ = 0.0f; + + FML_DISALLOW_COPY_AND_ASSIGN(ElevatedContainerLayer); +}; + +} // namespace flutter + +#endif // FLUTTER_FLOW_LAYERS_ELEVATED_CONTAINER_LAYER_H_ diff --git a/flow/layers/fuchsia_system_composited_layer.cc b/flow/layers/fuchsia_system_composited_layer.cc new file mode 100644 index 0000000000000..8c4a1b2ae26e3 --- /dev/null +++ b/flow/layers/fuchsia_system_composited_layer.cc @@ -0,0 +1,55 @@ +// 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/flow/layers/fuchsia_system_composited_layer.h" + +namespace flutter { + +FuchsiaSystemCompositedLayer::FuchsiaSystemCompositedLayer(SkColor color, + SkAlpha opacity, + float elevation) + : ElevatedContainerLayer(elevation), color_(color), opacity_(opacity) {} + +void FuchsiaSystemCompositedLayer::Preroll(PrerollContext* context, + const SkMatrix& matrix) { + TRACE_EVENT0("flutter", "FuchsiaSystemCompositedLayer::Preroll"); + + const float parent_is_opaque = context->is_opaque; + context->mutators_stack.PushOpacity(opacity_); + context->is_opaque = parent_is_opaque && (opacity_ == SK_AlphaOPAQUE); + ElevatedContainerLayer::Preroll(context, matrix); + context->is_opaque = parent_is_opaque; + context->mutators_stack.Pop(); +} + +void FuchsiaSystemCompositedLayer::UpdateScene(SceneUpdateContext& context) { + FML_DCHECK(needs_system_composite()); + + // Retained rendering: speedup by reusing a retained entity node if + // possible. When an entity node is reused, no paint layer is added to the + // frame so we won't call Paint. + LayerRasterCacheKey key(unique_id(), context.Matrix()); + if (context.HasRetainedNode(key)) { + TRACE_EVENT_INSTANT0("flutter", "retained layer cache hit"); + const scenic::EntityNode& retained_node = context.GetRetainedNode(key); + FML_DCHECK(context.top_entity()); + FML_DCHECK(retained_node.session() == context.session()); + context.top_entity()->embedder_node().AddChild(retained_node); + return; + } + + TRACE_EVENT_INSTANT0("flutter", "retained cache miss, creating"); + // If we can't find an existing retained surface, create one. + SceneUpdateContext::Frame frame(context, rrect_, color_, opacity_ / 255.0f, + elevation(), this); + for (auto& layer : layers()) { + if (layer->needs_painting()) { + frame.AddPaintLayer(layer.get()); + } + } + + ElevatedContainerLayer::UpdateScene(context); +} + +} // namespace flutter diff --git a/flow/layers/fuchsia_system_composited_layer.h b/flow/layers/fuchsia_system_composited_layer.h new file mode 100644 index 0000000000000..2fe00ee6d550f --- /dev/null +++ b/flow/layers/fuchsia_system_composited_layer.h @@ -0,0 +1,37 @@ +// 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. + +#ifndef FLUTTER_FLOW_LAYERS_FUCHSIA_SYSTEM_COMPOSITED_LAYER_H_ +#define FLUTTER_FLOW_LAYERS_FUCHSIA_SYSTEM_COMPOSITED_LAYER_H_ + +#include "flutter/flow/layers/elevated_container_layer.h" +#include "flutter/flow/scene_update_context.h" + +namespace flutter { + +class FuchsiaSystemCompositedLayer : public ElevatedContainerLayer { + public: + static bool can_system_composite() { return true; } + + FuchsiaSystemCompositedLayer(SkColor color, SkAlpha opacity, float elevation); + + void Preroll(PrerollContext* context, const SkMatrix& matrix) override; + void UpdateScene(SceneUpdateContext& context) override; + + void set_dimensions(SkRRect rrect) { rrect_ = rrect; } + + SkColor color() const { return color_; } + SkAlpha opacity() const { return opacity_; } + + private: + SkRRect rrect_ = SkRRect::MakeEmpty(); + SkColor color_ = SK_ColorTRANSPARENT; + SkAlpha opacity_ = SK_AlphaOPAQUE; + + FML_DISALLOW_COPY_AND_ASSIGN(FuchsiaSystemCompositedLayer); +}; + +} // namespace flutter + +#endif // FLUTTER_FLOW_LAYERS_FUCHSIA_SYSTEM_COMPOSITED_LAYER_H_ diff --git a/flow/layers/image_filter_layer.cc b/flow/layers/image_filter_layer.cc new file mode 100644 index 0000000000000..119fddc181fb8 --- /dev/null +++ b/flow/layers/image_filter_layer.cc @@ -0,0 +1,56 @@ +// 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/flow/layers/image_filter_layer.h" + +namespace flutter { + +ImageFilterLayer::ImageFilterLayer(sk_sp filter) + : filter_(std::move(filter)) {} + +void ImageFilterLayer::Preroll(PrerollContext* context, + const SkMatrix& matrix) { + Layer::AutoPrerollSaveLayerState save = + Layer::AutoPrerollSaveLayerState::Create(context); + ContainerLayer::Preroll(context, matrix); + + if (!context->has_platform_view && context->raster_cache && + SkRect::Intersects(context->cull_rect, paint_bounds())) { + SkMatrix ctm = matrix; +#ifndef SUPPORT_FRACTIONAL_TRANSLATION + ctm = RasterCache::GetIntegralTransCTM(ctm); +#endif + context->raster_cache->Prepare(context, this, ctm); + } +} + +void ImageFilterLayer::Paint(PaintContext& context) const { + TRACE_EVENT0("flutter", "ImageFilterLayer::Paint"); + FML_DCHECK(needs_painting()); + +#ifndef SUPPORT_FRACTIONAL_TRANSLATION + SkAutoCanvasRestore save(context.leaf_nodes_canvas, true); + context.leaf_nodes_canvas->setMatrix(RasterCache::GetIntegralTransCTM( + context.leaf_nodes_canvas->getTotalMatrix())); +#endif + + if (context.raster_cache) { + const SkMatrix& ctm = context.leaf_nodes_canvas->getTotalMatrix(); + RasterCacheResult layer_cache = + context.raster_cache->Get((Layer*)this, ctm); + if (layer_cache.is_valid()) { + layer_cache.draw(*context.leaf_nodes_canvas); + return; + } + } + + SkPaint paint; + paint.setImageFilter(filter_); + + Layer::AutoSaveLayer save_layer = + Layer::AutoSaveLayer::Create(context, paint_bounds(), &paint); + PaintChildren(context); +} + +} // namespace flutter diff --git a/flow/layers/image_filter_layer.h b/flow/layers/image_filter_layer.h new file mode 100644 index 0000000000000..30ec99935ff0a --- /dev/null +++ b/flow/layers/image_filter_layer.h @@ -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. + +#ifndef FLUTTER_FLOW_LAYERS_IMAGE_FILTER_LAYER_H_ +#define FLUTTER_FLOW_LAYERS_IMAGE_FILTER_LAYER_H_ + +#include "flutter/flow/layers/container_layer.h" + +#include "third_party/skia/include/core/SkImageFilter.h" + +namespace flutter { + +class ImageFilterLayer : public ContainerLayer { + public: + ImageFilterLayer(sk_sp filter); + + void Preroll(PrerollContext* context, const SkMatrix& matrix) override; + + void Paint(PaintContext& context) const override; + + private: + sk_sp filter_; + + FML_DISALLOW_COPY_AND_ASSIGN(ImageFilterLayer); +}; + +} // namespace flutter + +#endif // FLUTTER_FLOW_LAYERS_IMAGE_FILTER_LAYER_H_ diff --git a/flow/layers/image_filter_layer_unittests.cc b/flow/layers/image_filter_layer_unittests.cc new file mode 100644 index 0000000000000..63357fbd89ce8 --- /dev/null +++ b/flow/layers/image_filter_layer_unittests.cc @@ -0,0 +1,236 @@ +// 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/flow/layers/image_filter_layer.h" + +#include "flutter/flow/testing/layer_test.h" +#include "flutter/flow/testing/mock_layer.h" +#include "flutter/fml/macros.h" +#include "flutter/testing/mock_canvas.h" +#include "third_party/skia/include/core/SkImageFilter.h" + +namespace flutter { +namespace testing { + +using ImageFilterLayerTest = LayerTest; + +#ifndef NDEBUG +TEST_F(ImageFilterLayerTest, PaintingEmptyLayerDies) { + auto layer = std::make_shared(sk_sp()); + + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(layer->paint_bounds(), kEmptyRect); + EXPECT_FALSE(layer->needs_painting()); + EXPECT_FALSE(layer->needs_system_composite()); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} + +TEST_F(ImageFilterLayerTest, PaintBeforePrerollDies) { + const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f); + const SkPath child_path = SkPath().addRect(child_bounds); + auto mock_layer = std::make_shared(child_path); + auto layer = std::make_shared(sk_sp()); + layer->Add(mock_layer); + + EXPECT_EQ(layer->paint_bounds(), kEmptyRect); + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} +#endif + +TEST_F(ImageFilterLayerTest, EmptyFilter) { + const SkMatrix initial_transform = SkMatrix::MakeTrans(0.5f, 1.0f); + const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f); + const SkPath child_path = SkPath().addRect(child_bounds); + const SkPaint child_paint = SkPaint(SkColors::kYellow); + auto mock_layer = std::make_shared(child_path, child_paint); + auto layer = std::make_shared(nullptr); + layer->Add(mock_layer); + + layer->Preroll(preroll_context(), initial_transform); + EXPECT_EQ(layer->paint_bounds(), child_bounds); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_EQ(mock_layer->parent_matrix(), initial_transform); + + SkPaint filter_paint; + filter_paint.setImageFilter(nullptr); + layer->Paint(paint_context()); + EXPECT_EQ(mock_canvas().draw_calls(), + std::vector({ + MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, + MockCanvas::DrawCall{1, MockCanvas::SetMatrixData{SkMatrix()}}, + MockCanvas::DrawCall{ + 1, MockCanvas::SaveLayerData{child_bounds, filter_paint, + nullptr, 2}}, + MockCanvas::DrawCall{ + 2, MockCanvas::DrawPathData{child_path, child_paint}}, + MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}, + })); +} + +TEST_F(ImageFilterLayerTest, SimpleFilter) { + const SkMatrix initial_transform = SkMatrix::MakeTrans(0.5f, 1.0f); + const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f); + const SkPath child_path = SkPath().addRect(child_bounds); + const SkPaint child_paint = SkPaint(SkColors::kYellow); + auto layer_filter = SkImageFilter::MakeMatrixFilter( + SkMatrix(), SkFilterQuality::kMedium_SkFilterQuality, nullptr); + auto mock_layer = std::make_shared(child_path, child_paint); + auto layer = std::make_shared(layer_filter); + layer->Add(mock_layer); + + layer->Preroll(preroll_context(), initial_transform); + EXPECT_EQ(layer->paint_bounds(), child_bounds); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_EQ(mock_layer->parent_matrix(), initial_transform); + + SkPaint filter_paint; + filter_paint.setImageFilter(layer_filter); + layer->Paint(paint_context()); + EXPECT_EQ(mock_canvas().draw_calls(), + std::vector({ + MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, + MockCanvas::DrawCall{1, MockCanvas::SetMatrixData{SkMatrix()}}, + MockCanvas::DrawCall{ + 1, MockCanvas::SaveLayerData{child_bounds, filter_paint, + nullptr, 2}}, + MockCanvas::DrawCall{ + 2, MockCanvas::DrawPathData{child_path, child_paint}}, + MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}, + })); +} + +TEST_F(ImageFilterLayerTest, MultipleChildren) { + const SkMatrix initial_transform = SkMatrix::MakeTrans(0.5f, 1.0f); + const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 2.5f, 3.5f); + const SkPath child_path1 = SkPath().addRect(child_bounds); + const SkPath child_path2 = + SkPath().addRect(child_bounds.makeOffset(3.0f, 0.0f)); + const SkPaint child_paint1 = SkPaint(SkColors::kYellow); + const SkPaint child_paint2 = SkPaint(SkColors::kCyan); + auto layer_filter = SkImageFilter::MakeMatrixFilter( + SkMatrix(), SkFilterQuality::kMedium_SkFilterQuality, nullptr); + auto mock_layer1 = std::make_shared(child_path1, child_paint1); + auto mock_layer2 = std::make_shared(child_path2, child_paint2); + auto layer = std::make_shared(layer_filter); + layer->Add(mock_layer1); + layer->Add(mock_layer2); + + SkRect children_bounds = child_path1.getBounds(); + children_bounds.join(child_path2.getBounds()); + layer->Preroll(preroll_context(), initial_transform); + EXPECT_EQ(mock_layer1->paint_bounds(), child_path1.getBounds()); + EXPECT_EQ(mock_layer2->paint_bounds(), child_path2.getBounds()); + EXPECT_EQ(layer->paint_bounds(), children_bounds); + EXPECT_TRUE(mock_layer1->needs_painting()); + EXPECT_TRUE(mock_layer2->needs_painting()); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_EQ(mock_layer1->parent_matrix(), initial_transform); + EXPECT_EQ(mock_layer2->parent_matrix(), initial_transform); + + SkPaint filter_paint; + filter_paint.setImageFilter(layer_filter); + layer->Paint(paint_context()); + EXPECT_EQ(mock_canvas().draw_calls(), + std::vector( + {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, + MockCanvas::DrawCall{1, MockCanvas::SetMatrixData{SkMatrix()}}, + MockCanvas::DrawCall{ + 1, MockCanvas::SaveLayerData{children_bounds, filter_paint, + nullptr, 2}}, + MockCanvas::DrawCall{ + 2, MockCanvas::DrawPathData{child_path1, child_paint1}}, + MockCanvas::DrawCall{ + 2, MockCanvas::DrawPathData{child_path2, child_paint2}}, + MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}})); +} + +TEST_F(ImageFilterLayerTest, Nested) { + const SkMatrix initial_transform = SkMatrix::MakeTrans(0.5f, 1.0f); + const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 2.5f, 3.5f); + const SkPath child_path1 = SkPath().addRect(child_bounds); + const SkPath child_path2 = + SkPath().addRect(child_bounds.makeOffset(3.0f, 0.0f)); + const SkPaint child_paint1 = SkPaint(SkColors::kYellow); + const SkPaint child_paint2 = SkPaint(SkColors::kCyan); + auto layer_filter1 = SkImageFilter::MakeMatrixFilter( + SkMatrix(), SkFilterQuality::kMedium_SkFilterQuality, nullptr); + auto layer_filter2 = SkImageFilter::MakeMatrixFilter( + SkMatrix(), SkFilterQuality::kMedium_SkFilterQuality, nullptr); + auto mock_layer1 = std::make_shared(child_path1, child_paint1); + auto mock_layer2 = std::make_shared(child_path2, child_paint2); + auto layer1 = std::make_shared(layer_filter1); + auto layer2 = std::make_shared(layer_filter2); + layer2->Add(mock_layer2); + layer1->Add(mock_layer1); + layer1->Add(layer2); + + SkRect children_bounds = child_path1.getBounds(); + children_bounds.join(child_path2.getBounds()); + layer1->Preroll(preroll_context(), initial_transform); + EXPECT_EQ(mock_layer1->paint_bounds(), child_path1.getBounds()); + EXPECT_EQ(mock_layer2->paint_bounds(), child_path2.getBounds()); + EXPECT_EQ(layer1->paint_bounds(), children_bounds); + EXPECT_EQ(layer2->paint_bounds(), mock_layer2->paint_bounds()); + EXPECT_TRUE(mock_layer1->needs_painting()); + EXPECT_TRUE(mock_layer2->needs_painting()); + EXPECT_TRUE(layer1->needs_painting()); + EXPECT_TRUE(layer2->needs_painting()); + EXPECT_EQ(mock_layer1->parent_matrix(), initial_transform); + EXPECT_EQ(mock_layer2->parent_matrix(), initial_transform); + + SkPaint filter_paint1, filter_paint2; + filter_paint1.setImageFilter(layer_filter1); + filter_paint2.setImageFilter(layer_filter2); + layer1->Paint(paint_context()); + EXPECT_EQ(mock_canvas().draw_calls(), + std::vector({ + MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, + MockCanvas::DrawCall{1, MockCanvas::SetMatrixData{SkMatrix()}}, + MockCanvas::DrawCall{ + 1, MockCanvas::SaveLayerData{children_bounds, filter_paint1, + nullptr, 2}}, + MockCanvas::DrawCall{ + 2, MockCanvas::DrawPathData{child_path1, child_paint1}}, + MockCanvas::DrawCall{2, MockCanvas::SaveData{3}}, + MockCanvas::DrawCall{3, MockCanvas::SetMatrixData{SkMatrix()}}, + MockCanvas::DrawCall{ + 3, MockCanvas::SaveLayerData{child_path2.getBounds(), + filter_paint2, nullptr, 4}}, + MockCanvas::DrawCall{ + 4, MockCanvas::DrawPathData{child_path2, child_paint2}}, + MockCanvas::DrawCall{4, MockCanvas::RestoreData{3}}, + MockCanvas::DrawCall{3, MockCanvas::RestoreData{2}}, + MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}, + })); +} + +TEST_F(ImageFilterLayerTest, Readback) { + auto layer_filter = SkImageFilter::MakeMatrixFilter( + SkMatrix(), SkFilterQuality::kMedium_SkFilterQuality, nullptr); + auto initial_transform = SkMatrix(); + + // ImageFilterLayer does not read from surface + auto layer = std::make_shared(layer_filter); + preroll_context()->surface_needs_readback = false; + layer->Preroll(preroll_context(), initial_transform); + EXPECT_FALSE(preroll_context()->surface_needs_readback); + + // ImageFilterLayer blocks child with readback + auto mock_layer = + std::make_shared(SkPath(), SkPaint(), false, false, true); + layer->Add(mock_layer); + preroll_context()->surface_needs_readback = false; + layer->Preroll(preroll_context(), initial_transform); + EXPECT_FALSE(preroll_context()->surface_needs_readback); +} + +} // namespace testing +} // namespace flutter diff --git a/flow/layers/layer.cc b/flow/layers/layer.cc index b729f582a0a9a..d4bd809d5f2cf 100644 --- a/flow/layers/layer.cc +++ b/flow/layers/layer.cc @@ -10,10 +10,9 @@ namespace flutter { Layer::Layer() - : parent_(nullptr), - needs_system_composite_(false), - paint_bounds_(SkRect::MakeEmpty()), - unique_id_(NextUniqueID()) {} + : paint_bounds_(SkRect::MakeEmpty()), + unique_id_(NextUniqueID()), + needs_system_composite_(false) {} Layer::~Layer() = default; @@ -28,6 +27,34 @@ uint64_t Layer::NextUniqueID() { void Layer::Preroll(PrerollContext* context, const SkMatrix& matrix) {} +Layer::AutoPrerollSaveLayerState::AutoPrerollSaveLayerState( + PrerollContext* preroll_context, + bool save_layer_is_active, + bool layer_itself_performs_readback) + : preroll_context_(preroll_context), + save_layer_is_active_(save_layer_is_active), + layer_itself_performs_readback_(layer_itself_performs_readback) { + if (save_layer_is_active_) { + prev_surface_needs_readback_ = preroll_context_->surface_needs_readback; + preroll_context_->surface_needs_readback = false; + } +} + +Layer::AutoPrerollSaveLayerState Layer::AutoPrerollSaveLayerState::Create( + PrerollContext* preroll_context, + bool save_layer_is_active, + bool layer_itself_performs_readback) { + return Layer::AutoPrerollSaveLayerState(preroll_context, save_layer_is_active, + layer_itself_performs_readback); +} + +Layer::AutoPrerollSaveLayerState::~AutoPrerollSaveLayerState() { + if (save_layer_is_active_) { + preroll_context_->surface_needs_readback = + (prev_surface_needs_readback_ || layer_itself_performs_readback_); + } +} + #if defined(OS_FUCHSIA) void Layer::UpdateScene(SceneUpdateContext& context) {} #endif // defined(OS_FUCHSIA) diff --git a/flow/layers/layer.h b/flow/layers/layer.h index da2dcd8b21ce3..63c7cdb316d7e 100644 --- a/flow/layers/layer.h +++ b/flow/layers/layer.h @@ -42,8 +42,6 @@ static constexpr SkRect kGiantRect = SkRect::MakeLTRB(-1E9F, -1E9F, 1E9F, 1E9F); // This should be an exact copy of the Clip enum in painting.dart. enum Clip { none, hardEdge, antiAlias, antiAliasWithSaveLayer }; -class ContainerLayer; - struct PrerollContext { RasterCache* raster_cache; GrContext* gr_context; @@ -51,13 +49,23 @@ struct PrerollContext { MutatorsStack& mutators_stack; SkColorSpace* dst_color_space; SkRect cull_rect; + bool surface_needs_readback; - // The following allows us to paint in the end of subtree preroll + // These allow us to paint in the end of subtree Preroll. const Stopwatch& raster_time; const Stopwatch& ui_time; TextureRegistry& texture_registry; const bool checkerboard_offscreen_layers; + + // These allow us to make use of the scene metrics during Preroll. + float frame_physical_depth; + float frame_device_pixel_ratio; + + // These allow us to track properties like elevation, opacity, and the + // prescence of a platform view during Preroll. float total_elevation = 0.0f; + bool has_platform_view = false; + bool is_opaque = true; }; // Represents a single composited layer. Created on the UI thread but then @@ -69,6 +77,32 @@ class Layer { virtual void Preroll(PrerollContext* context, const SkMatrix& matrix); + // Used during Preroll by layers that employ a saveLayer to manage the + // PrerollContext settings with values affected by the saveLayer mechanism. + // This object must be created before calling Preroll on the children to + // set up the state for the children and then restore the state upon + // destruction. + class AutoPrerollSaveLayerState { + public: + FML_WARN_UNUSED_RESULT static AutoPrerollSaveLayerState Create( + PrerollContext* preroll_context, + bool save_layer_is_active = true, + bool layer_itself_performs_readback = false); + + ~AutoPrerollSaveLayerState(); + + private: + AutoPrerollSaveLayerState(PrerollContext* preroll_context, + bool save_layer_is_active, + bool layer_itself_performs_readback); + + PrerollContext* preroll_context_; + bool save_layer_is_active_; + bool layer_itself_performs_readback_; + + bool prev_surface_needs_readback_; + }; + struct PaintContext { // When splitting the scene into multiple canvases (e.g when embedding // a platform view on iOS) during the paint traversal we apply the non leaf @@ -89,6 +123,10 @@ class Layer { TextureRegistry& texture_registry; const RasterCache* raster_cache; const bool checkerboard_offscreen_layers; + + // These allow us to make use of the scene metrics during Paint. + float frame_physical_depth; + float frame_device_pixel_ratio; }; // Calls SkCanvas::saveLayer and restores the layer upon destruction. Also @@ -125,10 +163,6 @@ class Layer { virtual void UpdateScene(SceneUpdateContext& context); #endif - ContainerLayer* parent() const { return parent_; } - - void set_parent(ContainerLayer* parent) { parent_ = parent; } - bool needs_system_composite() const { return needs_system_composite_; } void set_needs_system_composite(bool value) { needs_system_composite_ = value; @@ -147,10 +181,9 @@ class Layer { uint64_t unique_id() const { return unique_id_; } private: - ContainerLayer* parent_; - bool needs_system_composite_; SkRect paint_bounds_; uint64_t unique_id_; + bool needs_system_composite_; static uint64_t NextUniqueID(); diff --git a/flow/layers/layer_tree.cc b/flow/layers/layer_tree.cc index b031ebf8cb8bd..dd9e6b4f02b01 100644 --- a/flow/layers/layer_tree.cc +++ b/flow/layers/layer_tree.cc @@ -11,22 +11,30 @@ namespace flutter { -LayerTree::LayerTree() - : frame_size_{}, +LayerTree::LayerTree(const SkISize& frame_size, + float frame_physical_depth, + float frame_device_pixel_ratio) + : frame_size_(frame_size), + frame_physical_depth_(frame_physical_depth), + frame_device_pixel_ratio_(frame_device_pixel_ratio), rasterizer_tracing_threshold_(0), checkerboard_raster_cache_images_(false), checkerboard_offscreen_layers_(false) {} -LayerTree::~LayerTree() = default; - void LayerTree::RecordBuildTime(fml::TimePoint start) { build_start_ = start; build_finish_ = fml::TimePoint::Now(); } -void LayerTree::Preroll(CompositorContext::ScopedFrame& frame, +bool LayerTree::Preroll(CompositorContext::ScopedFrame& frame, bool ignore_raster_cache) { TRACE_EVENT0("flutter", "LayerTree::Preroll"); + + if (!root_layer_) { + FML_LOG(ERROR) << "The scene did not specify any layers."; + return false; + } + SkColorSpace* color_space = frame.canvas() ? frame.canvas()->imageInfo().colorSpace() : nullptr; frame.context().raster_cache().SetCheckboardCacheImages( @@ -39,24 +47,38 @@ void LayerTree::Preroll(CompositorContext::ScopedFrame& frame, stack, color_space, kGiantRect, + false, frame.context().raster_time(), frame.context().ui_time(), frame.context().texture_registry(), - checkerboard_offscreen_layers_}; + checkerboard_offscreen_layers_, + frame_physical_depth_, + frame_device_pixel_ratio_}; root_layer_->Preroll(&context, frame.root_surface_transformation()); + return context.surface_needs_readback; } #if defined(OS_FUCHSIA) void LayerTree::UpdateScene(SceneUpdateContext& context, scenic::ContainerNode& container) { TRACE_EVENT0("flutter", "LayerTree::UpdateScene"); + + // Ensure the context is aware of the view metrics. + context.set_dimensions(frame_size_, frame_physical_depth_, + frame_device_pixel_ratio_); + const auto& metrics = context.metrics(); + FML_DCHECK(metrics->scale_x > 0.0f); + FML_DCHECK(metrics->scale_y > 0.0f); + FML_DCHECK(metrics->scale_z > 0.0f); + SceneUpdateContext::Transform transform(context, // context 1.0f / metrics->scale_x, // X 1.0f / metrics->scale_y, // Y 1.0f / metrics->scale_z // Z ); + SceneUpdateContext::Frame frame( context, SkRRect::MakeRect( @@ -75,6 +97,12 @@ void LayerTree::UpdateScene(SceneUpdateContext& context, void LayerTree::Paint(CompositorContext::ScopedFrame& frame, bool ignore_raster_cache) const { TRACE_EVENT0("flutter", "LayerTree::Paint"); + + if (!root_layer_) { + FML_LOG(ERROR) << "The scene did not specify any layers to paint."; + return; + } + SkISize canvas_size = frame.canvas()->getBaseLayerSize(); SkNWayCanvas internal_nodes_canvas(canvas_size.width(), canvas_size.height()); internal_nodes_canvas.addCanvas(frame.canvas()); @@ -94,7 +122,9 @@ void LayerTree::Paint(CompositorContext::ScopedFrame& frame, frame.context().ui_time(), frame.context().texture_registry(), ignore_raster_cache ? nullptr : &frame.context().raster_cache(), - checkerboard_offscreen_layers_}; + checkerboard_offscreen_layers_, + frame_physical_depth_, + frame_device_pixel_ratio_}; if (root_layer_->needs_painting()) root_layer_->Paint(context); @@ -118,16 +148,19 @@ sk_sp LayerTree::Flatten(const SkRect& bounds) { root_surface_transformation.reset(); PrerollContext preroll_context{ - nullptr, // raster_cache (don't consult the cache) - nullptr, // gr_context (used for the raster cache) - nullptr, // external view embedder - unused_stack, // mutator stack - nullptr, // SkColorSpace* dst_color_space - kGiantRect, // SkRect cull_rect - unused_stopwatch, // frame time (dont care) - unused_stopwatch, // engine time (dont care) - unused_texture_registry, // texture registry (not supported) - false, // checkerboard_offscreen_layers + nullptr, // raster_cache (don't consult the cache) + nullptr, // gr_context (used for the raster cache) + nullptr, // external view embedder + unused_stack, // mutator stack + nullptr, // SkColorSpace* dst_color_space + kGiantRect, // SkRect cull_rect + false, // layer reads from surface + unused_stopwatch, // frame time (dont care) + unused_stopwatch, // engine time (dont care) + unused_texture_registry, // texture registry (not supported) + false, // checkerboard_offscreen_layers + frame_physical_depth_, // maximum depth allowed for rendering + frame_device_pixel_ratio_ // ratio between logical and physical }; SkISize canvas_size = canvas->getBaseLayerSize(); @@ -139,11 +172,13 @@ sk_sp LayerTree::Flatten(const SkRect& bounds) { canvas, // canvas nullptr, nullptr, - unused_stopwatch, // frame time (dont care) - unused_stopwatch, // engine time (dont care) - unused_texture_registry, // texture registry (not supported) - nullptr, // raster cache - false // checkerboard offscreen layers + unused_stopwatch, // frame time (dont care) + unused_stopwatch, // engine time (dont care) + unused_texture_registry, // texture registry (not supported) + nullptr, // raster cache + false, // checkerboard offscreen layers + frame_physical_depth_, // maximum depth allowed for rendering + frame_device_pixel_ratio_ // ratio between logical and physical }; // Even if we don't have a root layer, we still need to create an empty diff --git a/flow/layers/layer_tree.h b/flow/layers/layer_tree.h index 124b8a85dea45..21ba509f09ae6 100644 --- a/flow/layers/layer_tree.h +++ b/flow/layers/layer_tree.h @@ -14,17 +14,23 @@ #include "flutter/fml/macros.h" #include "flutter/fml/time/time_delta.h" #include "third_party/skia/include/core/SkPicture.h" -#include "third_party/skia/include/core/SkSize.h" namespace flutter { class LayerTree { public: - LayerTree(); - - ~LayerTree(); - - void Preroll(CompositorContext::ScopedFrame& frame, + LayerTree(const SkISize& frame_size, + float frame_physical_depth, + float frame_device_pixel_ratio); + + // Perform a preroll pass on the tree and return information about + // the tree that affects rendering this frame. + // + // Returns: + // - a boolean indicating whether or not the top level of the + // layer tree performs any operations that require readback + // from the root surface. + bool Preroll(CompositorContext::ScopedFrame& frame, bool ignore_raster_cache = false); #if defined(OS_FUCHSIA) @@ -44,8 +50,8 @@ class LayerTree { } const SkISize& frame_size() const { return frame_size_; } - - void set_frame_size(const SkISize& frame_size) { frame_size_ = frame_size; } + float frame_physical_depth() const { return frame_physical_depth_; } + float frame_device_pixel_ratio() const { return frame_device_pixel_ratio_; } void RecordBuildTime(fml::TimePoint begin_start); fml::TimePoint build_start() const { return build_start_; } @@ -71,18 +77,15 @@ class LayerTree { checkerboard_offscreen_layers_ = checkerboard; } - void set_device_pixel_ratio(double device_pixel_ratio) { - device_pixel_ratio_ = device_pixel_ratio; - } - - double device_pixel_ratio() const { return device_pixel_ratio_; } + double device_pixel_ratio() const { return frame_device_pixel_ratio_; } private: - SkISize frame_size_ = SkISize::MakeEmpty(); // Physical pixels. - double device_pixel_ratio_ = 1.0; std::shared_ptr root_layer_; fml::TimePoint build_start_; fml::TimePoint build_finish_; + SkISize frame_size_ = SkISize::MakeEmpty(); // Physical pixels. + float frame_physical_depth_; + float frame_device_pixel_ratio_ = 1.0f; // Logical / Physical pixels ratio. uint32_t rasterizer_tracing_threshold_; bool checkerboard_raster_cache_images_; bool checkerboard_offscreen_layers_; diff --git a/flow/layers/layer_tree_unittests.cc b/flow/layers/layer_tree_unittests.cc new file mode 100644 index 0000000000000..9851cebf26d0e --- /dev/null +++ b/flow/layers/layer_tree_unittests.cc @@ -0,0 +1,207 @@ +// 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/flow/layers/layer_tree.h" + +#include "flutter/flow/compositor_context.h" +#include "flutter/flow/layers/container_layer.h" +#include "flutter/flow/testing/mock_layer.h" +#include "flutter/fml/macros.h" +#include "flutter/testing/canvas_test.h" +#include "flutter/testing/mock_canvas.h" +#include "gtest/gtest.h" + +namespace flutter { +namespace testing { + +class LayerTreeTest : public CanvasTest { + public: + LayerTreeTest() + : layer_tree_(SkISize::Make(64, 64), 100.0f, 1.0f), + compositor_context_(fml::kDefaultFrameBudget), + root_transform_(SkMatrix::MakeTrans(1.0f, 1.0f)), + scoped_frame_(compositor_context_.AcquireFrame(nullptr, + &mock_canvas(), + nullptr, + root_transform_, + false, + true, + nullptr)) {} + + LayerTree& layer_tree() { return layer_tree_; } + CompositorContext::ScopedFrame& frame() { return *scoped_frame_.get(); } + const SkMatrix& root_transform() { return root_transform_; } + + private: + LayerTree layer_tree_; + CompositorContext compositor_context_; + SkMatrix root_transform_; + std::unique_ptr scoped_frame_; +}; + +TEST_F(LayerTreeTest, PaintingEmptyLayerDies) { + auto layer = std::make_shared(); + + layer_tree().set_root_layer(layer); + layer_tree().Preroll(frame()); + EXPECT_EQ(layer->paint_bounds(), SkRect::MakeEmpty()); + EXPECT_FALSE(layer->needs_painting()); + + layer_tree().Paint(frame()); +} + +TEST_F(LayerTreeTest, PaintBeforePreollDies) { + const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f); + SkPath child_path; + child_path.addRect(child_bounds); + auto mock_layer = std::make_shared(child_path); + auto layer = std::make_shared(); + layer->Add(mock_layer); + + layer_tree().set_root_layer(layer); + EXPECT_EQ(mock_layer->paint_bounds(), kEmptyRect); + EXPECT_EQ(layer->paint_bounds(), kEmptyRect); + EXPECT_FALSE(mock_layer->needs_painting()); + EXPECT_FALSE(layer->needs_painting()); + + layer_tree().Paint(frame()); + EXPECT_EQ(mock_canvas().draw_calls(), std::vector()); +} + +TEST_F(LayerTreeTest, Simple) { + const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f); + const SkPath child_path = SkPath().addRect(child_bounds); + const SkPaint child_paint = SkPaint(SkColors::kCyan); + auto mock_layer = std::make_shared(child_path, child_paint); + auto layer = std::make_shared(); + layer->Add(mock_layer); + + layer_tree().set_root_layer(layer); + layer_tree().Preroll(frame()); + EXPECT_EQ(mock_layer->paint_bounds(), child_bounds); + EXPECT_EQ(layer->paint_bounds(), mock_layer->paint_bounds()); + EXPECT_TRUE(mock_layer->needs_painting()); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_EQ(mock_layer->parent_matrix(), root_transform()); + + layer_tree().Paint(frame()); + EXPECT_EQ(mock_canvas().draw_calls(), + std::vector({MockCanvas::DrawCall{ + 0, MockCanvas::DrawPathData{child_path, child_paint}}})); +} + +TEST_F(LayerTreeTest, Multiple) { + const SkPath child_path1 = SkPath().addRect(5.0f, 6.0f, 20.5f, 21.5f); + const SkPath child_path2 = SkPath().addRect(8.0f, 2.0f, 16.5f, 14.5f); + const SkPaint child_paint1(SkColors::kGray); + const SkPaint child_paint2(SkColors::kGreen); + auto mock_layer1 = std::make_shared( + child_path1, child_paint1, true /* fake_has_platform_view */); + auto mock_layer2 = std::make_shared(child_path2, child_paint2); + auto layer = std::make_shared(); + layer->Add(mock_layer1); + layer->Add(mock_layer2); + + SkRect expected_total_bounds = child_path1.getBounds(); + expected_total_bounds.join(child_path2.getBounds()); + layer_tree().set_root_layer(layer); + layer_tree().Preroll(frame()); + EXPECT_EQ(mock_layer1->paint_bounds(), child_path1.getBounds()); + EXPECT_EQ(mock_layer2->paint_bounds(), child_path2.getBounds()); + EXPECT_EQ(layer->paint_bounds(), expected_total_bounds); + EXPECT_TRUE(mock_layer1->needs_painting()); + EXPECT_TRUE(mock_layer2->needs_painting()); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_FALSE(mock_layer1->needs_system_composite()); + EXPECT_FALSE(mock_layer2->needs_system_composite()); + EXPECT_FALSE(layer->needs_system_composite()); + EXPECT_EQ(mock_layer1->parent_matrix(), root_transform()); + EXPECT_EQ(mock_layer2->parent_matrix(), root_transform()); + EXPECT_EQ(mock_layer1->parent_cull_rect(), kGiantRect); + EXPECT_EQ(mock_layer2->parent_cull_rect(), + kGiantRect); // Siblings are independent + + layer_tree().Paint(frame()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector({MockCanvas::DrawCall{ + 0, MockCanvas::DrawPathData{child_path1, child_paint1}}, + MockCanvas::DrawCall{0, MockCanvas::DrawPathData{ + child_path2, child_paint2}}})); +} + +TEST_F(LayerTreeTest, MultipleWithEmpty) { + const SkPath child_path1 = SkPath().addRect(5.0f, 6.0f, 20.5f, 21.5f); + const SkPaint child_paint1(SkColors::kGray); + const SkPaint child_paint2(SkColors::kGreen); + auto mock_layer1 = std::make_shared(child_path1, child_paint1); + auto mock_layer2 = std::make_shared(SkPath(), child_paint2); + auto layer = std::make_shared(); + layer->Add(mock_layer1); + layer->Add(mock_layer2); + + layer_tree().set_root_layer(layer); + layer_tree().Preroll(frame()); + EXPECT_EQ(mock_layer1->paint_bounds(), child_path1.getBounds()); + EXPECT_EQ(mock_layer2->paint_bounds(), SkPath().getBounds()); + EXPECT_EQ(layer->paint_bounds(), child_path1.getBounds()); + EXPECT_TRUE(mock_layer1->needs_painting()); + EXPECT_FALSE(mock_layer2->needs_painting()); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_FALSE(mock_layer1->needs_system_composite()); + EXPECT_FALSE(mock_layer2->needs_system_composite()); + EXPECT_FALSE(layer->needs_system_composite()); + EXPECT_EQ(mock_layer1->parent_matrix(), root_transform()); + EXPECT_EQ(mock_layer2->parent_matrix(), root_transform()); + EXPECT_EQ(mock_layer1->parent_cull_rect(), kGiantRect); + EXPECT_EQ(mock_layer2->parent_cull_rect(), kGiantRect); + + layer_tree().Paint(frame()); + EXPECT_EQ(mock_canvas().draw_calls(), + std::vector({MockCanvas::DrawCall{ + 0, MockCanvas::DrawPathData{child_path1, child_paint1}}})); +} + +TEST_F(LayerTreeTest, NeedsSystemComposite) { + const SkPath child_path1 = SkPath().addRect(5.0f, 6.0f, 20.5f, 21.5f); + const SkPath child_path2 = SkPath().addRect(8.0f, 2.0f, 16.5f, 14.5f); + const SkPaint child_paint1(SkColors::kGray); + const SkPaint child_paint2(SkColors::kGreen); + auto mock_layer1 = std::make_shared( + child_path1, child_paint1, false /* fake_has_platform_view */, + true /* fake_needs_system_composite */); + auto mock_layer2 = std::make_shared(child_path2, child_paint2); + auto layer = std::make_shared(); + layer->Add(mock_layer1); + layer->Add(mock_layer2); + + SkRect expected_total_bounds = child_path1.getBounds(); + expected_total_bounds.join(child_path2.getBounds()); + layer_tree().set_root_layer(layer); + layer_tree().Preroll(frame()); + EXPECT_EQ(mock_layer1->paint_bounds(), child_path1.getBounds()); + EXPECT_EQ(mock_layer2->paint_bounds(), child_path2.getBounds()); + EXPECT_EQ(layer->paint_bounds(), expected_total_bounds); + EXPECT_TRUE(mock_layer1->needs_painting()); + EXPECT_TRUE(mock_layer2->needs_painting()); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_TRUE(mock_layer1->needs_system_composite()); + EXPECT_FALSE(mock_layer2->needs_system_composite()); + EXPECT_TRUE(layer->needs_system_composite()); + EXPECT_EQ(mock_layer1->parent_matrix(), root_transform()); + EXPECT_EQ(mock_layer2->parent_matrix(), root_transform()); + EXPECT_EQ(mock_layer1->parent_cull_rect(), kGiantRect); + EXPECT_EQ(mock_layer2->parent_cull_rect(), kGiantRect); + + layer_tree().Paint(frame()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector({MockCanvas::DrawCall{ + 0, MockCanvas::DrawPathData{child_path1, child_paint1}}, + MockCanvas::DrawCall{0, MockCanvas::DrawPathData{ + child_path2, child_paint2}}})); +} + +} // namespace testing +} // namespace flutter diff --git a/flow/layers/opacity_layer.cc b/flow/layers/opacity_layer.cc index 014ee6736d92a..5892fff7792a6 100644 --- a/flow/layers/opacity_layer.cc +++ b/flow/layers/opacity_layer.cc @@ -4,65 +4,102 @@ #include "flutter/flow/layers/opacity_layer.h" -#include "flutter/flow/layers/transform_layer.h" +#include "flutter/fml/trace_event.h" +#include "third_party/skia/include/core/SkPaint.h" namespace flutter { -OpacityLayer::OpacityLayer(int alpha, const SkPoint& offset) - : alpha_(alpha), offset_(offset) {} +// The OpacityLayer has no real "elevation", but we want to avoid Z-fighting +// when using the system compositor. Choose a small but non-zero value for +// this. +constexpr float kOpacityElevationWhenUsingSystemCompositor = 0.01f; -OpacityLayer::~OpacityLayer() = default; +#if !defined(OS_FUCHSIA) +void OpacityLayerBase::Preroll(PrerollContext* context, + const SkMatrix& matrix) { + const float parent_is_opaque = context->is_opaque; -void OpacityLayer::EnsureSingleChild() { - FML_DCHECK(layers().size() > 0); // OpacityLayer should never be a leaf - - if (layers().size() == 1) { - return; - } + context->mutators_stack.PushOpacity(opacity_); + context->is_opaque = parent_is_opaque && (opacity_ == SK_AlphaOPAQUE); + ContainerLayer::Preroll(context, matrix); + context->is_opaque = parent_is_opaque; + context->mutators_stack.Pop(); +} +#endif - // Be careful: SkMatrix's default constructor doesn't initialize the matrix to - // identity. Hence we have to explicitly call SkMatrix::setIdentity. - SkMatrix identity; - identity.setIdentity(); - auto new_child = std::make_shared(identity); +OpacityLayer::OpacityLayer(SkAlpha opacity, const SkPoint& offset) + : OpacityLayerBase(SK_ColorTRANSPARENT, + opacity, + kOpacityElevationWhenUsingSystemCompositor), + offset_(offset) { + // Ensure OpacityLayer has only one direct child. + // + // This is needed to ensure that retained rendering can always be applied to + // save the costly saveLayer. + // + // Any children will be actually added as children of this empty + // ContainerLayer. + ContainerLayer::Add(std::make_shared()); +} - for (auto& child : layers()) { - new_child->Add(child); - } - ClearChildren(); - Add(new_child); +void OpacityLayer::Add(std::shared_ptr layer) { + GetChildContainer()->Add(std::move(layer)); } void OpacityLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { - EnsureSingleChild(); + TRACE_EVENT0("flutter", "OpacityLayer::Preroll"); + + ContainerLayer* container = GetChildContainer(); + FML_DCHECK(!container->layers().empty()); // OpacityLayer can't be a leaf. + + // Factor in the offset during Preroll. |OpacityLayerBase| will handle the + // opacity. SkMatrix child_matrix = matrix; child_matrix.postTranslate(offset_.fX, offset_.fY); context->mutators_stack.PushTransform( SkMatrix::MakeTrans(offset_.fX, offset_.fY)); - context->mutators_stack.PushOpacity(alpha_); - ContainerLayer::Preroll(context, child_matrix); + Layer::AutoPrerollSaveLayerState save = + Layer::AutoPrerollSaveLayerState::Create(context); + OpacityLayerBase::Preroll(context, child_matrix); context->mutators_stack.Pop(); - context->mutators_stack.Pop(); - set_paint_bounds(paint_bounds().makeOffset(offset_.fX, offset_.fY)); - // See |EnsureSingleChild|. - FML_DCHECK(layers().size() == 1); - if (context->view_embedder == nullptr && context->raster_cache && - SkRect::Intersects(context->cull_rect, paint_bounds())) { - Layer* child = layers()[0].get(); - SkMatrix ctm = child_matrix; + + // When using the system compositor, do not include the offset since we are + // rendering as a separate piece of geometry and the offset will be baked into + // that geometry's transform. + if (OpacityLayerBase::can_system_composite() && needs_system_composite()) { + set_dimensions(SkRRect::MakeRect(paint_bounds())); + } else { + set_paint_bounds(paint_bounds().makeOffset(offset_.fX, offset_.fY)); + + if (!context->has_platform_view && context->raster_cache && + SkRect::Intersects(context->cull_rect, paint_bounds())) { + SkMatrix ctm = child_matrix; #ifndef SUPPORT_FRACTIONAL_TRANSLATION - ctm = RasterCache::GetIntegralTransCTM(ctm); + ctm = RasterCache::GetIntegralTransCTM(ctm); #endif - context->raster_cache->Prepare(context, child, ctm); + context->raster_cache->Prepare(context, container, ctm); + } } } +#if defined(OS_FUCHSIA) + +void OpacityLayer::UpdateScene(SceneUpdateContext& context) { + SceneUpdateContext::Transform transform( + context, SkMatrix::MakeTrans(offset_.fX, offset_.fY)); + + // OpacityLayerBase will handle applying the opacity itself. + OpacityLayerBase::UpdateScene(context); +} + +#endif + void OpacityLayer::Paint(PaintContext& context) const { TRACE_EVENT0("flutter", "OpacityLayer::Paint"); FML_DCHECK(needs_painting()); SkPaint paint; - paint.setAlpha(alpha_); + paint.setAlpha(opacity()); SkAutoCanvasRestore save(context.internal_nodes_canvas, true); context.internal_nodes_canvas->translate(offset_.fX, offset_.fY); @@ -72,17 +109,10 @@ void OpacityLayer::Paint(PaintContext& context) const { context.leaf_nodes_canvas->getTotalMatrix())); #endif - // See |EnsureSingleChild|. - FML_DCHECK(layers().size() == 1); - - // Embedded platform views are changing the canvas in the middle of the paint - // traversal. To make sure we paint on the right canvas, when the embedded - // platform views preview is enabled (context.view_embedded is not null) we - // don't use the cache. - if (context.view_embedder == nullptr && context.raster_cache) { + if (context.raster_cache) { + ContainerLayer* container = GetChildContainer(); const SkMatrix& ctm = context.leaf_nodes_canvas->getTotalMatrix(); - RasterCacheResult child_cache = - context.raster_cache->Get(layers()[0].get(), ctm); + RasterCacheResult child_cache = context.raster_cache->Get(container, ctm); if (child_cache.is_valid()) { child_cache.draw(*context.leaf_nodes_canvas, &paint); return; @@ -96,8 +126,7 @@ void OpacityLayer::Paint(PaintContext& context) const { // RasterCache::GetIntegralTransCTM optimization. // // Note that the following lines are only accessible when the raster cache is - // not available (e.g., when we're using the software backend in golden - // tests). + // not available, or when a cache miss occurs. SkRect saveLayerBounds; paint_bounds() .makeOffset(-offset_.fX, -offset_.fY) @@ -105,7 +134,13 @@ void OpacityLayer::Paint(PaintContext& context) const { Layer::AutoSaveLayer save_layer = Layer::AutoSaveLayer::Create(context, saveLayerBounds, &paint); - PaintChildren(context); + OpacityLayerBase::Paint(context); +} + +ContainerLayer* OpacityLayer::GetChildContainer() const { + FML_DCHECK(layers().size() == 1); + + return static_cast(layers()[0].get()); } } // namespace flutter diff --git a/flow/layers/opacity_layer.h b/flow/layers/opacity_layer.h index 795d8841ba6ed..f4951e0557c70 100644 --- a/flow/layers/opacity_layer.h +++ b/flow/layers/opacity_layer.h @@ -5,15 +5,42 @@ #ifndef FLUTTER_FLOW_LAYERS_OPACITY_LAYER_H_ #define FLUTTER_FLOW_LAYERS_OPACITY_LAYER_H_ -#include "flutter/flow/layers/container_layer.h" +#include "flutter/flow/layers/elevated_container_layer.h" +#if defined(OS_FUCHSIA) +#include "flutter/flow/layers/fuchsia_system_composited_layer.h" +#endif namespace flutter { +#if !defined(OS_FUCHSIA) +class OpacityLayerBase : public ContainerLayer { + public: + static bool can_system_composite() { return false; } + + OpacityLayerBase(SkColor color, SkAlpha opacity, float elevation) + : color_(color), opacity_(opacity) {} + + void Preroll(PrerollContext* context, const SkMatrix& matrix) override; + + void set_dimensions(SkRRect rrect) {} + + SkColor color() const { return color_; } + SkAlpha opacity() const { return opacity_; } + float elevation() const { return 0; } + + private: + SkColor color_; + SkAlpha opacity_; +}; +#else +using OpacityLayerBase = FuchsiaSystemCompositedLayer; +#endif + // Don't add an OpacityLayer with no children to the layer tree. Painting an // OpacityLayer is very costly due to the saveLayer call. If there's no child, -// having the OpacityLayer or not has the same effect. In debug_unopt build, the -// |EnsureSingleChild| will assert if there are no children. -class OpacityLayer : public ContainerLayer { +// having the OpacityLayer or not has the same effect. In debug_unopt build, +// |Preroll| will assert if there are no children. +class OpacityLayer : public OpacityLayerBase { public: // An offset is provided here because OpacityLayer.addToScene method in the // Flutter framework can take an optional offset argument. @@ -25,29 +52,20 @@ class OpacityLayer : public ContainerLayer { // the retained rendering inefficient as a small offset change could propagate // to many leaf layers. Therefore we try to capture that offset here to stop // the propagation as repainting the OpacityLayer is expensive. - OpacityLayer(int alpha, const SkPoint& offset); - ~OpacityLayer() override; + OpacityLayer(SkAlpha alpha, const SkPoint& offset); - void Preroll(PrerollContext* context, const SkMatrix& matrix) override; + void Add(std::shared_ptr layer) override; + void Preroll(PrerollContext* context, const SkMatrix& matrix) override; +#if defined(OS_FUCHSIA) + void UpdateScene(SceneUpdateContext& context) override; +#endif void Paint(PaintContext& context) const override; - // TODO(chinmaygarde): Once SCN-139 is addressed, introduce a new node in the - // session scene hierarchy. - private: - int alpha_; - SkPoint offset_; + ContainerLayer* GetChildContainer() const; - // Restructure (if necessary) OpacityLayer to have only one child. - // - // This is needed to ensure that retained rendering can always be applied to - // save the costly saveLayer. - // - // If there are multiple children, this creates a new identity TransformLayer, - // sets all children to be the TransformLayer's children, and sets that - // TransformLayer as the single child of this OpacityLayer. - void EnsureSingleChild(); + SkPoint offset_; FML_DISALLOW_COPY_AND_ASSIGN(OpacityLayer); }; diff --git a/flow/layers/opacity_layer_unittests.cc b/flow/layers/opacity_layer_unittests.cc new file mode 100644 index 0000000000000..f7cc77ffe2ddd --- /dev/null +++ b/flow/layers/opacity_layer_unittests.cc @@ -0,0 +1,331 @@ +// 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/flow/layers/opacity_layer.h" + +#include "flutter/flow/testing/layer_test.h" +#include "flutter/flow/testing/mock_layer.h" +#include "flutter/fml/macros.h" +#include "flutter/testing/mock_canvas.h" + +namespace flutter { +namespace testing { + +using OpacityLayerTest = LayerTest; + +#ifndef NDEBUG +TEST_F(OpacityLayerTest, LeafLayer) { + auto layer = + std::make_shared(SK_AlphaOPAQUE, SkPoint::Make(0.0f, 0.0f)); + + EXPECT_DEATH_IF_SUPPORTED(layer->Preroll(preroll_context(), SkMatrix()), + "\\!container->layers\\(\\)\\.empty\\(\\)"); +} + +TEST_F(OpacityLayerTest, PaintingEmptyLayerDies) { + auto mock_layer = std::make_shared(SkPath()); + auto layer = + std::make_shared(SK_AlphaOPAQUE, SkPoint::Make(0.0f, 0.0f)); + layer->Add(mock_layer); + + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(mock_layer->paint_bounds(), SkPath().getBounds()); + EXPECT_EQ(layer->paint_bounds(), mock_layer->paint_bounds()); + EXPECT_FALSE(mock_layer->needs_painting()); + EXPECT_FALSE(layer->needs_painting()); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} + +TEST_F(OpacityLayerTest, PaintBeforePreollDies) { + SkPath child_path; + child_path.addRect(5.0f, 6.0f, 20.5f, 21.5f); + auto mock_layer = std::make_shared(child_path); + auto layer = + std::make_shared(SK_AlphaOPAQUE, SkPoint::Make(0.0f, 0.0f)); + layer->Add(mock_layer); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} +#endif + +TEST_F(OpacityLayerTest, FullyOpaque) { + const SkPath child_path = SkPath().addRect(SkRect::MakeWH(5.0f, 5.0f)); + const SkPoint layer_offset = SkPoint::Make(0.5f, 1.5f); + const SkMatrix initial_transform = SkMatrix::MakeTrans(0.5f, 0.5f); + const SkMatrix layer_transform = + SkMatrix::MakeTrans(layer_offset.fX, layer_offset.fY); +#ifndef SUPPORT_FRACTIONAL_TRANSLATION + const SkMatrix integral_layer_transform = RasterCache::GetIntegralTransCTM( + SkMatrix::Concat(initial_transform, layer_transform)); +#endif + const SkPaint child_paint = SkPaint(SkColors::kGreen); + const SkRect expected_layer_bounds = + layer_transform.mapRect(child_path.getBounds()); + auto mock_layer = std::make_shared(child_path, child_paint); + auto layer = std::make_shared(SK_AlphaOPAQUE, layer_offset); + layer->Add(mock_layer); + + layer->Preroll(preroll_context(), initial_transform); + EXPECT_EQ(mock_layer->paint_bounds(), child_path.getBounds()); + EXPECT_EQ(layer->paint_bounds(), expected_layer_bounds); + EXPECT_TRUE(mock_layer->needs_painting()); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_EQ(mock_layer->parent_matrix(), + SkMatrix::Concat(initial_transform, layer_transform)); + EXPECT_EQ(mock_layer->parent_mutators(), + std::vector({Mutator(layer_transform), Mutator(SK_AlphaOPAQUE)})); + + const SkPaint opacity_paint = SkPaint(SkColors::kBlack); // A = 1.0f + SkRect opacity_bounds; + expected_layer_bounds.makeOffset(-layer_offset.fX, -layer_offset.fY) + .roundOut(&opacity_bounds); + auto expected_draw_calls = std::vector( + {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, + MockCanvas::DrawCall{1, MockCanvas::ConcatMatrixData{layer_transform}}, +#ifndef SUPPORT_FRACTIONAL_TRANSLATION + MockCanvas::DrawCall{ + 1, MockCanvas::SetMatrixData{integral_layer_transform}}, +#endif + MockCanvas::DrawCall{ + 1, MockCanvas::SaveLayerData{opacity_bounds, opacity_paint, nullptr, + 2}}, + MockCanvas::DrawCall{2, + MockCanvas::DrawPathData{child_path, child_paint}}, + MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}); + layer->Paint(paint_context()); + EXPECT_EQ(mock_canvas().draw_calls(), expected_draw_calls); +} + +TEST_F(OpacityLayerTest, FullyTransparent) { + const SkPath child_path = SkPath().addRect(SkRect::MakeWH(5.0f, 5.0f)); + const SkPoint layer_offset = SkPoint::Make(0.5f, 1.5f); + const SkMatrix initial_transform = SkMatrix::MakeTrans(0.5f, 0.5f); + const SkMatrix layer_transform = + SkMatrix::MakeTrans(layer_offset.fX, layer_offset.fY); +#ifndef SUPPORT_FRACTIONAL_TRANSLATION + const SkMatrix integral_layer_transform = RasterCache::GetIntegralTransCTM( + SkMatrix::Concat(initial_transform, layer_transform)); +#endif + const SkPaint child_paint = SkPaint(SkColors::kGreen); + const SkRect expected_layer_bounds = + layer_transform.mapRect(child_path.getBounds()); + auto mock_layer = std::make_shared(child_path, child_paint); + auto layer = + std::make_shared(SK_AlphaTRANSPARENT, layer_offset); + layer->Add(mock_layer); + + layer->Preroll(preroll_context(), initial_transform); + EXPECT_EQ(mock_layer->paint_bounds(), child_path.getBounds()); + EXPECT_EQ(layer->paint_bounds(), expected_layer_bounds); + EXPECT_TRUE(mock_layer->needs_painting()); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_EQ(mock_layer->parent_matrix(), + SkMatrix::Concat(initial_transform, layer_transform)); + EXPECT_EQ( + mock_layer->parent_mutators(), + std::vector({Mutator(layer_transform), Mutator(SK_AlphaTRANSPARENT)})); + + auto expected_draw_calls = std::vector( + {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, + MockCanvas::DrawCall{1, MockCanvas::ConcatMatrixData{layer_transform}}, +#ifndef SUPPORT_FRACTIONAL_TRANSLATION + MockCanvas::DrawCall{ + 1, MockCanvas::SetMatrixData{integral_layer_transform}}, +#endif + MockCanvas::DrawCall{1, MockCanvas::SaveData{2}}, + MockCanvas::DrawCall{ + 2, MockCanvas::ClipRectData{kEmptyRect, SkClipOp::kIntersect, + MockCanvas::kHard_ClipEdgeStyle}}, + MockCanvas::DrawCall{2, + MockCanvas::DrawPathData{child_path, child_paint}}, + MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}); + layer->Paint(paint_context()); + EXPECT_EQ(mock_canvas().draw_calls(), expected_draw_calls); +} + +TEST_F(OpacityLayerTest, HalfTransparent) { + const SkPath child_path = SkPath().addRect(SkRect::MakeWH(5.0f, 5.0f)); + const SkPoint layer_offset = SkPoint::Make(0.5f, 1.5f); + const SkMatrix initial_transform = SkMatrix::MakeTrans(0.5f, 0.5f); + const SkMatrix layer_transform = + SkMatrix::MakeTrans(layer_offset.fX, layer_offset.fY); +#ifndef SUPPORT_FRACTIONAL_TRANSLATION + const SkMatrix integral_layer_transform = RasterCache::GetIntegralTransCTM( + SkMatrix::Concat(initial_transform, layer_transform)); +#endif + const SkPaint child_paint = SkPaint(SkColors::kGreen); + const SkRect expected_layer_bounds = + layer_transform.mapRect(child_path.getBounds()); + const SkAlpha alpha_half = 255 / 2; + auto mock_layer = std::make_shared(child_path, child_paint); + auto layer = std::make_shared(alpha_half, layer_offset); + layer->Add(mock_layer); + + layer->Preroll(preroll_context(), initial_transform); + EXPECT_EQ(mock_layer->paint_bounds(), child_path.getBounds()); + EXPECT_EQ(layer->paint_bounds(), expected_layer_bounds); + EXPECT_TRUE(mock_layer->needs_painting()); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_EQ(mock_layer->parent_matrix(), + SkMatrix::Concat(initial_transform, layer_transform)); + EXPECT_EQ(mock_layer->parent_mutators(), + std::vector({Mutator(layer_transform), Mutator(alpha_half)})); + + const SkPaint opacity_paint = + SkPaint(SkColor4f::FromColor(SkColorSetA(SK_ColorBLACK, alpha_half))); + SkRect opacity_bounds; + expected_layer_bounds.makeOffset(-layer_offset.fX, -layer_offset.fY) + .roundOut(&opacity_bounds); + auto expected_draw_calls = std::vector( + {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, + MockCanvas::DrawCall{1, MockCanvas::ConcatMatrixData{layer_transform}}, +#ifndef SUPPORT_FRACTIONAL_TRANSLATION + MockCanvas::DrawCall{ + 1, MockCanvas::SetMatrixData{integral_layer_transform}}, +#endif + MockCanvas::DrawCall{ + 1, MockCanvas::SaveLayerData{opacity_bounds, opacity_paint, nullptr, + 2}}, + MockCanvas::DrawCall{2, + MockCanvas::DrawPathData{child_path, child_paint}}, + MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}); + layer->Paint(paint_context()); + EXPECT_EQ(mock_canvas().draw_calls(), expected_draw_calls); +} + +TEST_F(OpacityLayerTest, Nested) { + const SkPath child1_path = SkPath().addRect(SkRect::MakeWH(5.0f, 6.0f)); + const SkPath child2_path = SkPath().addRect(SkRect::MakeWH(2.0f, 7.0f)); + const SkPath child3_path = SkPath().addRect(SkRect::MakeWH(6.0f, 6.0f)); + const SkPoint layer1_offset = SkPoint::Make(0.5f, 1.5f); + const SkPoint layer2_offset = SkPoint::Make(2.5f, 0.5f); + const SkMatrix initial_transform = SkMatrix::MakeTrans(0.5f, 0.5f); + const SkMatrix layer1_transform = + SkMatrix::MakeTrans(layer1_offset.fX, layer1_offset.fY); + const SkMatrix layer2_transform = + SkMatrix::MakeTrans(layer2_offset.fX, layer2_offset.fY); +#ifndef SUPPORT_FRACTIONAL_TRANSLATION + const SkMatrix integral_layer1_transform = RasterCache::GetIntegralTransCTM( + SkMatrix::Concat(initial_transform, layer1_transform)); + const SkMatrix integral_layer2_transform = RasterCache::GetIntegralTransCTM( + SkMatrix::Concat(SkMatrix::Concat(initial_transform, layer1_transform), + layer2_transform)); +#endif + const SkPaint child1_paint = SkPaint(SkColors::kRed); + const SkPaint child2_paint = SkPaint(SkColors::kBlue); + const SkPaint child3_paint = SkPaint(SkColors::kGreen); + const SkAlpha alpha1 = 155; + const SkAlpha alpha2 = 224; + auto mock_layer1 = std::make_shared(child1_path, child1_paint); + auto mock_layer2 = std::make_shared(child2_path, child2_paint); + auto mock_layer3 = std::make_shared(child3_path, child3_paint); + auto layer1 = std::make_shared(alpha1, layer1_offset); + auto layer2 = std::make_shared(alpha2, layer2_offset); + layer2->Add(mock_layer2); + layer1->Add(mock_layer1); + layer1->Add(layer2); + layer1->Add(mock_layer3); // Ensure something is processed after recursion + + const SkRect expected_layer2_bounds = + layer2_transform.mapRect(child2_path.getBounds()); + SkRect expected_layer1_bounds = expected_layer2_bounds; + expected_layer1_bounds.join(child1_path.getBounds()); + expected_layer1_bounds.join(child3_path.getBounds()); + expected_layer1_bounds = layer1_transform.mapRect(expected_layer1_bounds); + layer1->Preroll(preroll_context(), initial_transform); + EXPECT_EQ(mock_layer1->paint_bounds(), child1_path.getBounds()); + EXPECT_EQ(mock_layer2->paint_bounds(), child2_path.getBounds()); + EXPECT_EQ(mock_layer3->paint_bounds(), child3_path.getBounds()); + EXPECT_EQ(layer1->paint_bounds(), expected_layer1_bounds); + EXPECT_EQ(layer2->paint_bounds(), expected_layer2_bounds); + EXPECT_TRUE(mock_layer1->needs_painting()); + EXPECT_TRUE(mock_layer2->needs_painting()); + EXPECT_TRUE(mock_layer3->needs_painting()); + EXPECT_TRUE(layer1->needs_painting()); + EXPECT_TRUE(layer2->needs_painting()); + EXPECT_EQ(mock_layer1->parent_matrix(), + SkMatrix::Concat(initial_transform, layer1_transform)); + // EXPECT_EQ(mock_layer1->parent_mutators(), + // std::vector({Mutator(layer1_transform), Mutator(alpha1)})); + EXPECT_EQ( + mock_layer2->parent_matrix(), + SkMatrix::Concat(SkMatrix::Concat(initial_transform, layer1_transform), + layer2_transform)); + // EXPECT_EQ(mock_layer2->parent_mutators(), + // std::vector({Mutator(layer1_transform), Mutator(alpha1), + // Mutator(layer2_transform), Mutator(alpha2)})); + EXPECT_EQ(mock_layer3->parent_matrix(), + SkMatrix::Concat(initial_transform, layer1_transform)); + // EXPECT_EQ(mock_layer3->parent_mutators(), + // std::vector({Mutator(layer1_transform), Mutator(alpha1)})); + + const SkPaint opacity1_paint = + SkPaint(SkColor4f::FromColor(SkColorSetA(SK_ColorBLACK, alpha1))); + const SkPaint opacity2_paint = + SkPaint(SkColor4f::FromColor(SkColorSetA(SK_ColorBLACK, alpha2))); + SkRect opacity1_bounds, opacity2_bounds; + expected_layer1_bounds.makeOffset(-layer1_offset.fX, -layer1_offset.fY) + .roundOut(&opacity1_bounds); + expected_layer2_bounds.makeOffset(-layer2_offset.fX, -layer2_offset.fY) + .roundOut(&opacity2_bounds); + auto expected_draw_calls = std::vector( + {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, + MockCanvas::DrawCall{1, MockCanvas::ConcatMatrixData{layer1_transform}}, +#ifndef SUPPORT_FRACTIONAL_TRANSLATION + MockCanvas::DrawCall{ + 1, MockCanvas::SetMatrixData{integral_layer1_transform}}, +#endif + MockCanvas::DrawCall{ + 1, MockCanvas::SaveLayerData{opacity1_bounds, opacity1_paint, + nullptr, 2}}, + MockCanvas::DrawCall{ + 2, MockCanvas::DrawPathData{child1_path, child1_paint}}, + MockCanvas::DrawCall{2, MockCanvas::SaveData{3}}, + MockCanvas::DrawCall{3, MockCanvas::ConcatMatrixData{layer2_transform}}, +#ifndef SUPPORT_FRACTIONAL_TRANSLATION + MockCanvas::DrawCall{ + 3, MockCanvas::SetMatrixData{integral_layer2_transform}}, +#endif + MockCanvas::DrawCall{ + 3, MockCanvas::SaveLayerData{opacity2_bounds, opacity2_paint, + nullptr, 4}}, + MockCanvas::DrawCall{ + 4, MockCanvas::DrawPathData{child2_path, child2_paint}}, + MockCanvas::DrawCall{4, MockCanvas::RestoreData{3}}, + MockCanvas::DrawCall{3, MockCanvas::RestoreData{2}}, + MockCanvas::DrawCall{ + 2, MockCanvas::DrawPathData{child3_path, child3_paint}}, + MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}); + layer1->Paint(paint_context()); + EXPECT_EQ(mock_canvas().draw_calls(), expected_draw_calls); +} + +TEST_F(OpacityLayerTest, Readback) { + auto initial_transform = SkMatrix(); + auto layer = std::make_shared(kOpaque_SkAlphaType, SkPoint()); + layer->Add(std::make_shared(SkPath())); + + // OpacityLayer does not read from surface + preroll_context()->surface_needs_readback = false; + layer->Preroll(preroll_context(), initial_transform); + EXPECT_FALSE(preroll_context()->surface_needs_readback); + + // OpacityLayer blocks child with readback + auto mock_layer = + std::make_shared(SkPath(), SkPaint(), false, false, true); + layer->Add(mock_layer); + preroll_context()->surface_needs_readback = false; + layer->Preroll(preroll_context(), initial_transform); + EXPECT_FALSE(preroll_context()->surface_needs_readback); +} + +} // namespace testing +} // namespace flutter diff --git a/flow/layers/performance_overlay_layer.cc b/flow/layers/performance_overlay_layer.cc index ebb279c966866..ef7b6f2c6194c 100644 --- a/flow/layers/performance_overlay_layer.cc +++ b/flow/layers/performance_overlay_layer.cc @@ -8,26 +8,11 @@ #include "flutter/flow/layers/performance_overlay_layer.h" #include "third_party/skia/include/core/SkFont.h" +#include "third_party/skia/include/core/SkTextBlob.h" namespace flutter { namespace { -void DrawStatisticsText(SkCanvas& canvas, - const std::string& string, - int x, - int y, - const std::string& font_path) { - SkFont font; - if (font_path != "") { - font = SkFont(SkTypeface::MakeFromFile(font_path.c_str())); - } - font.setSize(15); - SkPaint paint; - paint.setColor(SK_ColorGRAY); - canvas.drawSimpleText(string.c_str(), string.size(), SkTextEncoding::kUTF8, x, - y, font, paint); -} - void VisualizeStopWatch(SkCanvas& canvas, const Stopwatch& stopwatch, SkScalar x, @@ -47,21 +32,39 @@ void VisualizeStopWatch(SkCanvas& canvas, } if (show_labels) { - double max_ms_per_frame = stopwatch.MaxDelta().ToMillisecondsF(); - double average_ms_per_frame = stopwatch.AverageDelta().ToMillisecondsF(); - std::stringstream stream; - stream.setf(std::ios::fixed | std::ios::showpoint); - stream << std::setprecision(1); - stream << label_prefix << " " - << "max " << max_ms_per_frame << " ms/frame, " - << "avg " << average_ms_per_frame << " ms/frame"; - DrawStatisticsText(canvas, stream.str(), x + label_x, y + height + label_y, - font_path); + auto text = PerformanceOverlayLayer::MakeStatisticsText( + stopwatch, label_prefix, font_path); + SkPaint paint; + paint.setColor(SK_ColorGRAY); + canvas.drawTextBlob(text, x + label_x, y + height + label_y, paint); } } } // namespace +sk_sp PerformanceOverlayLayer::MakeStatisticsText( + const Stopwatch& stopwatch, + const std::string& label_prefix, + const std::string& font_path) { + SkFont font; + if (font_path != "") { + font = SkFont(SkTypeface::MakeFromFile(font_path.c_str())); + } + font.setSize(15); + + double max_ms_per_frame = stopwatch.MaxDelta().ToMillisecondsF(); + double average_ms_per_frame = stopwatch.AverageDelta().ToMillisecondsF(); + std::stringstream stream; + stream.setf(std::ios::fixed | std::ios::showpoint); + stream << std::setprecision(1); + stream << label_prefix << " " + << "max " << max_ms_per_frame << " ms/frame, " + << "avg " << average_ms_per_frame << " ms/frame"; + auto text = stream.str(); + return SkTextBlob::MakeFromText(text.c_str(), text.size(), font, + SkTextEncoding::kUTF8); +} + PerformanceOverlayLayer::PerformanceOverlayLayer(uint64_t options, const char* font_path) : options_(options) { diff --git a/flow/layers/performance_overlay_layer.h b/flow/layers/performance_overlay_layer.h index b5c3370d2055a..b1434a221e688 100644 --- a/flow/layers/performance_overlay_layer.h +++ b/flow/layers/performance_overlay_layer.h @@ -7,9 +7,12 @@ #include +#include "flutter/flow/instrumentation.h" #include "flutter/flow/layers/layer.h" #include "flutter/fml/macros.h" +class SkTextBlob; + namespace flutter { const int kDisplayRasterizerStatistics = 1 << 0; @@ -19,6 +22,10 @@ const int kVisualizeEngineStatistics = 1 << 3; class PerformanceOverlayLayer : public Layer { public: + static sk_sp MakeStatisticsText(const Stopwatch& stopwatch, + const std::string& label_prefix, + const std::string& font_path); + explicit PerformanceOverlayLayer(uint64_t options, const char* font_path = nullptr); diff --git a/flow/layers/performance_overlay_layer_unittests.cc b/flow/layers/performance_overlay_layer_unittests.cc index 605717c870ee3..769f80803a8fa 100644 --- a/flow/layers/performance_overlay_layer_unittests.cc +++ b/flow/layers/performance_overlay_layer_unittests.cc @@ -2,18 +2,29 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "flutter/flow/flow_test_utils.h" #include "flutter/flow/layers/performance_overlay_layer.h" + +#include "flutter/flow/flow_test_utils.h" #include "flutter/flow/raster_cache.h" +#include "flutter/flow/testing/layer_test.h" +#include "flutter/flow/testing/mock_layer.h" #include "flutter/fml/build_config.h" - +#include "flutter/fml/macros.h" +#include "flutter/testing/mock_canvas.h" +#include "gtest/gtest.h" +#include "third_party/skia/include/core/SkData.h" +#include "third_party/skia/include/core/SkSerialProcs.h" #include "third_party/skia/include/core/SkSurface.h" +#include "third_party/skia/include/core/SkTextBlob.h" #include "third_party/skia/include/utils/SkBase64.h" -#include "gtest/gtest.h" - +#include #include +namespace flutter { +namespace testing { +namespace { + // To get the size of kMockedTimes in compile time. template constexpr int size(const T (&array)[N]) noexcept { @@ -77,7 +88,7 @@ static void TestPerformanceOverlayLayerGold(int refresh_rate) { << "Please either set --golden-dir, or make sure that the unit test is " << "run from the right directory (e.g., flutter/engine/src)."; -#if !OS_LINUX +#if !defined(OS_LINUX) GTEST_SKIP() << "Skipping golden tests on non-Linux OSes"; #endif // OS_LINUX const bool golden_data_matches = golden_data->equals(snapshot_data.get()); @@ -97,12 +108,72 @@ static void TestPerformanceOverlayLayerGold(int refresh_rate) { << "Golden file mismatch. Please check " << "the difference between " << golden_file_path << " and " << new_golden_file_path << ", and replace the former " - << "with the latter if the difference looks good.\n\n" + << "with the latter if the difference looks good.\nS\n" << "See also the base64 encoded " << new_golden_file_path << ":\n" << b64_char; } } +} // namespace + +using PerformanceOverlayLayerTest = LayerTest; + +TEST_F(PerformanceOverlayLayerTest, PaintingEmptyLayerDies) { + const uint64_t overlay_opts = kVisualizeRasterizerStatistics; + auto layer = std::make_shared(overlay_opts); + + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(layer->paint_bounds(), SkRect::MakeEmpty()); + EXPECT_FALSE(layer->needs_painting()); + + // Crashes reading a nullptr. + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), ""); +} + +TEST_F(PerformanceOverlayLayerTest, InvalidOptions) { + const SkRect layer_bounds = SkRect::MakeLTRB(0.0f, 0.0f, 64.0f, 64.0f); + const uint64_t overlay_opts = 0; + auto layer = std::make_shared(overlay_opts); + + // TODO(): Note calling code has to call set_paint_bounds right now. Make + // this a constructor parameter and move the set_paint_bounds into Preroll + layer->set_paint_bounds(layer_bounds); + + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(layer->paint_bounds(), layer_bounds); + EXPECT_TRUE(layer->needs_painting()); + + // Nothing is drawn if options are invalid (0). + layer->Paint(paint_context()); + EXPECT_EQ(mock_canvas().draw_calls(), std::vector()); +} + +TEST_F(PerformanceOverlayLayerTest, SimpleRasterizerStatistics) { + const SkRect layer_bounds = SkRect::MakeLTRB(0.0f, 0.0f, 64.0f, 64.0f); + const uint64_t overlay_opts = kDisplayRasterizerStatistics; + auto layer = std::make_shared(overlay_opts); + + // TODO(): Note calling code has to call set_paint_bounds right now. Make + // this a constructor parameter and move the set_paint_bounds into Preroll + layer->set_paint_bounds(layer_bounds); + + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(layer->paint_bounds(), layer_bounds); + EXPECT_TRUE(layer->needs_painting()); + + layer->Paint(paint_context()); + auto overlay_text = PerformanceOverlayLayer::MakeStatisticsText( + paint_context().raster_time, "GPU", ""); + auto overlay_text_data = overlay_text->serialize(SkSerialProcs{}); + SkPaint text_paint; + text_paint.setColor(SK_ColorGRAY); + SkPoint text_position = SkPoint::Make(16.0f, 22.0f); + EXPECT_EQ(mock_canvas().draw_calls(), + std::vector({MockCanvas::DrawCall{ + 0, MockCanvas::DrawTextData{overlay_text_data, text_paint, + text_position}}})); +} + TEST(PerformanceOverlayLayerDefault, Gold) { TestPerformanceOverlayLayerGold(60); } @@ -114,3 +185,6 @@ TEST(PerformanceOverlayLayer90fps, Gold) { TEST(PerformanceOverlayLayer120fps, Gold) { TestPerformanceOverlayLayerGold(120); } + +} // namespace testing +} // namespace flutter diff --git a/flow/layers/physical_shape_layer.cc b/flow/layers/physical_shape_layer.cc index 21c9265ce00dd..524c5a2ac939a 100644 --- a/flow/layers/physical_shape_layer.cc +++ b/flow/layers/physical_shape_layer.cc @@ -14,16 +14,15 @@ const SkScalar kLightRadius = 800; PhysicalShapeLayer::PhysicalShapeLayer(SkColor color, SkColor shadow_color, - SkScalar device_pixel_ratio, - float viewport_depth, float elevation, const SkPath& path, Clip clip_behavior) - : color_(color), +#if !defined(OS_FUCHSIA) + : PhysicalShapeLayerBase(color, elevation), +#else + : PhysicalShapeLayerBase(color, /*opacity=*/1.f, elevation), +#endif shadow_color_(shadow_color), - device_pixel_ratio_(device_pixel_ratio), - viewport_depth_(viewport_depth), - elevation_(elevation), path_(path), isRect_(false), clip_behavior_(clip_behavior) { @@ -46,68 +45,35 @@ PhysicalShapeLayer::PhysicalShapeLayer(SkColor color, // an SkPath. frameRRect_ = SkRRect::MakeRect(path.getBounds()); } -} -PhysicalShapeLayer::~PhysicalShapeLayer() = default; + set_dimensions(frameRRect_); +} void PhysicalShapeLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { - context->total_elevation += elevation_; - total_elevation_ = context->total_elevation; - SkRect child_paint_bounds; - PrerollChildren(context, matrix, &child_paint_bounds); - context->total_elevation -= elevation_; + TRACE_EVENT0("flutter", "PhysicalShapeLayer::Preroll"); - if (elevation_ == 0) { + Layer::AutoPrerollSaveLayerState save = + Layer::AutoPrerollSaveLayerState::Create(context, UsesSaveLayer()); + PhysicalShapeLayerBase::Preroll(context, matrix); + + if (elevation() == 0) { set_paint_bounds(path_.getBounds()); } else { -#if defined(OS_FUCHSIA) - // Let the system compositor draw all shadows for us. - set_needs_system_composite(true); -#else - // Add some margin to the paint bounds to leave space for the shadow. - // We fill this whole region and clip children to it so we don't need to - // join the child paint bounds. - // The offset is calculated as follows: - - // .--- (kLightRadius) - // -------/ (light) - // | / - // | / - // |/ - // |O - // /| (kLightHeight) - // / | - // / | - // / | - // / | - // ------------- (layer) - // /| | - // / | | (elevation) - // A / | |B - // ------------------------------------------------ (canvas) - // --- (extent of shadow) - // - // E = lt } t = (r + w/2)/h - // } => - // r + w/2 = ht } E = (l/h)(r + w/2) - // - // Where: E = extent of shadow - // l = elevation of layer - // r = radius of the light source - // w = width of the layer - // h = light height - // t = tangent of AOB, i.e., multiplier for elevation to extent - SkRect bounds(path_.getBounds()); - // tangent for x - double tx = (kLightRadius * device_pixel_ratio_ + bounds.width() * 0.5) / - kLightHeight; - // tangent for y - double ty = (kLightRadius * device_pixel_ratio_ + bounds.height() * 0.5) / - kLightHeight; - bounds.outset(elevation_ * tx, elevation_ * ty); - set_paint_bounds(bounds); -#endif // defined(OS_FUCHSIA) + if (PhysicalShapeLayerBase::can_system_composite()) { + set_needs_system_composite(true); + return; + } + //#if defined(OS_FUCHSIA) + // // Let the system compositor draw all shadows for us. + // set_needs_system_composite(true); + //#else + // We will draw the shadow in Paint(), so add some margin to the paint + // bounds to leave space for the shadow. We fill this whole region and clip + // children to it so we don't need to join the child paint bounds. + set_paint_bounds(ComputeShadowBounds(path_.getBounds(), elevation(), + context->frame_device_pixel_ratio)); + //#endif // defined(OS_FUCHSIA) } } @@ -115,12 +81,14 @@ void PhysicalShapeLayer::Preroll(PrerollContext* context, void PhysicalShapeLayer::UpdateScene(SceneUpdateContext& context) { FML_DCHECK(needs_system_composite()); + TRACE_EVENT0("flutter", "PhysicalShapeLayer::UpdateScene"); // Retained rendering: speedup by reusing a retained entity node if possible. // When an entity node is reused, no paint layer is added to the frame so we // won't call PhysicalShapeLayer::Paint. LayerRasterCacheKey key(unique_id(), context.Matrix()); if (context.HasRetainedNode(key)) { + TRACE_EVENT_INSTANT0("flutter", "retained layer cache hit"); const scenic::EntityNode& retained_node = context.GetRetainedNode(key); FML_DCHECK(context.top_entity()); FML_DCHECK(retained_node.session() == context.session()); @@ -128,9 +96,11 @@ void PhysicalShapeLayer::UpdateScene(SceneUpdateContext& context) { return; } + TRACE_EVENT_INSTANT0("flutter", "cache miss, creating"); // If we can't find an existing retained surface, create one. - SceneUpdateContext::Frame frame(context, frameRRect_, color_, elevation_, - total_elevation_, viewport_depth_, this); + SceneUpdateContext::Frame frame(context, frameRRect_, color(), opacity(), + elevation(), this); + for (auto& layer : layers()) { if (layer->needs_painting()) { frame.AddPaintLayer(layer.get()); @@ -146,14 +116,14 @@ void PhysicalShapeLayer::Paint(PaintContext& context) const { TRACE_EVENT0("flutter", "PhysicalShapeLayer::Paint"); FML_DCHECK(needs_painting()); - if (elevation_ != 0) { - DrawShadow(context.leaf_nodes_canvas, path_, shadow_color_, elevation_, - SkColorGetA(color_) != 0xff, device_pixel_ratio_); + if (elevation() != 0) { + DrawShadow(context.leaf_nodes_canvas, path_, shadow_color_, elevation(), + SkColorGetA(color()) != 0xff, context.frame_device_pixel_ratio); } // Call drawPath without clip if possible for better performance. SkPaint paint; - paint.setColor(color_); + paint.setColor(color()); paint.setAntiAlias(true); if (clip_behavior_ != Clip::antiAliasWithSaveLayer) { context.leaf_nodes_canvas->drawPath(path_, paint); @@ -175,7 +145,7 @@ void PhysicalShapeLayer::Paint(PaintContext& context) const { break; } - if (clip_behavior_ == Clip::antiAliasWithSaveLayer) { + if (UsesSaveLayer()) { // If we want to avoid the bleeding edge artifact // (https://github.com/flutter/flutter/issues/18057#issue-328003931) // using saveLayer, we have to call drawPaint instead of drawPath as @@ -188,6 +158,50 @@ void PhysicalShapeLayer::Paint(PaintContext& context) const { context.internal_nodes_canvas->restoreToCount(saveCount); } +SkRect PhysicalShapeLayer::ComputeShadowBounds(const SkRect& bounds, + float elevation, + float pixel_ratio) { + // The shadow offset is calculated as follows: + // .--- (kLightRadius) + // -------/ (light) + // | / + // | / + // |/ + // |O + // /| (kLightHeight) + // / | + // / | + // / | + // / | + // ------------- (layer) + // /| | + // / | | (elevation) + // A / | |B + // ------------------------------------------------ (canvas) + // --- (extent of shadow) + // + // E = lt } t = (r + w/2)/h + // } => + // r + w/2 = ht } E = (l/h)(r + w/2) + // + // Where: E = extent of shadow + // l = elevation of layer + // r = radius of the light source + // w = width of the layer + // h = light height + // t = tangent of AOB, i.e., multiplier for elevation to extent + // tangent for x + double tx = + (kLightRadius * pixel_ratio + bounds.width() * 0.5) / kLightHeight; + // tangent for y + double ty = + (kLightRadius * pixel_ratio + bounds.height() * 0.5) / kLightHeight; + SkRect shadow_bounds(bounds); + shadow_bounds.outset(elevation * tx, elevation * ty); + + return shadow_bounds; +} + void PhysicalShapeLayer::DrawShadow(SkCanvas* canvas, const SkPath& path, SkColor color, diff --git a/flow/layers/physical_shape_layer.h b/flow/layers/physical_shape_layer.h index f884fe02fc5bd..34b3c59fe355b 100644 --- a/flow/layers/physical_shape_layer.h +++ b/flow/layers/physical_shape_layer.h @@ -5,21 +5,36 @@ #ifndef FLUTTER_FLOW_LAYERS_PHYSICAL_SHAPE_LAYER_H_ #define FLUTTER_FLOW_LAYERS_PHYSICAL_SHAPE_LAYER_H_ -#include "flutter/flow/layers/container_layer.h" +#include "flutter/flow/layers/elevated_container_layer.h" +#if defined(OS_FUCHSIA) +#include "flutter/flow/layers/fuchsia_system_composited_layer.h" +#endif namespace flutter { -class PhysicalShapeLayer : public ContainerLayer { +#if !defined(OS_FUCHSIA) +class PhysicalShapeLayerBase : public ElevatedContainerLayer { public: - PhysicalShapeLayer(SkColor color, - SkColor shadow_color, - SkScalar device_pixel_ratio, - float viewport_depth, - float elevation, - const SkPath& path, - Clip clip_behavior); - ~PhysicalShapeLayer() override; + static bool can_system_composite() { return false; } + + PhysicalShapeLayerBase(SkColor color, float elevation) + : ElevatedContainerLayer(elevation), color_(color) {} + + void set_dimensions(SkRRect rrect) {} + SkColor color() const { return color_; } + + private: + SkColor color_; +}; +#else +using PhysicalShapeLayerBase = FuchsiaSystemCompositedLayer; +#endif +class PhysicalShapeLayer : public PhysicalShapeLayerBase { + public: + static SkRect ComputeShadowBounds(const SkRect& bounds, + float elevation, + float pixel_ratio); static void DrawShadow(SkCanvas* canvas, const SkPath& path, SkColor color, @@ -27,27 +42,29 @@ class PhysicalShapeLayer : public ContainerLayer { bool transparentOccluder, SkScalar dpr); - void Preroll(PrerollContext* context, const SkMatrix& matrix) override; + PhysicalShapeLayer(SkColor color, + SkColor shadow_color, + float elevation, + const SkPath& path, + Clip clip_behavior); + void Preroll(PrerollContext* context, const SkMatrix& matrix) override; void Paint(PaintContext& context) const override; + bool UsesSaveLayer() const { + return clip_behavior_ == Clip::antiAliasWithSaveLayer; + } + #if defined(OS_FUCHSIA) void UpdateScene(SceneUpdateContext& context) override; #endif // defined(OS_FUCHSIA) private: - SkColor color_; SkColor shadow_color_; - SkScalar device_pixel_ratio_; - float viewport_depth_; - float elevation_ = 0.0f; - float total_elevation_ = 0.0f; SkPath path_; bool isRect_; SkRRect frameRRect_; Clip clip_behavior_; - - friend class PhysicalShapeLayer_TotalElevation_Test; }; } // namespace flutter diff --git a/flow/layers/physical_shape_layer_unittests.cc b/flow/layers/physical_shape_layer_unittests.cc index 972424a2fec6d..d13116c9c44a0 100644 --- a/flow/layers/physical_shape_layer_unittests.cc +++ b/flow/layers/physical_shape_layer_unittests.cc @@ -4,65 +4,291 @@ #include "flutter/flow/layers/physical_shape_layer.h" -#include "gtest/gtest.h" +#include "flutter/flow/testing/layer_test.h" +#include "flutter/flow/testing/mock_layer.h" +#include "flutter/fml/macros.h" +#include "flutter/testing/mock_canvas.h" namespace flutter { +namespace testing { -TEST(PhysicalShapeLayer, TotalElevation) { - std::shared_ptr layers[4]; +using PhysicalShapeLayerTest = LayerTest; - SkColor dummy_color = 0; - SkPath dummy_path; - for (int i = 0; i < 4; i += 1) { - layers[i] = - std::make_shared(dummy_color, dummy_color, - 1.0f, // pixel ratio, - 1.0f, // depth - (float)(i + 1), // elevation - dummy_path, Clip::none); - } +#ifndef NDEBUG +TEST_F(PhysicalShapeLayerTest, PaintingEmptyLayerDies) { + auto layer = + std::make_shared(SK_ColorBLACK, SK_ColorBLACK, + 0.0f, // elevation + SkPath(), Clip::none); - layers[0]->Add(layers[1]); - layers[0]->Add(layers[2]); - layers[2]->Add(layers[3]); + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(layer->paint_bounds(), SkRect::MakeEmpty()); + EXPECT_FALSE(layer->needs_painting()); + EXPECT_FALSE(layer->needs_system_composite()); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} + +TEST_F(PhysicalShapeLayerTest, PaintBeforePreollDies) { + SkPath child_path; + child_path.addRect(5.0f, 6.0f, 20.5f, 21.5f); + auto mock_layer = std::make_shared(child_path, SkPaint()); + auto layer = + std::make_shared(SK_ColorBLACK, SK_ColorBLACK, + 0.0f, // elevation + SkPath(), Clip::none); + layer->Add(mock_layer); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} +#endif + +TEST_F(PhysicalShapeLayerTest, NonEmptyLayer) { + SkPath layer_path; + layer_path.addRect(5.0f, 6.0f, 20.5f, 21.5f); + auto layer = + std::make_shared(SK_ColorGREEN, SK_ColorBLACK, + 0.0f, // elevation + layer_path, Clip::none); + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(layer->paint_bounds(), layer_path.getBounds()); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_FALSE(layer->needs_system_composite()); - const Stopwatch unused_stopwatch; - TextureRegistry unused_texture_registry; - MutatorsStack unused_stack; - PrerollContext preroll_context{ - nullptr, // raster_cache (don't consult the cache) - nullptr, // gr_context (used for the raster cache) - nullptr, // external view embedder - unused_stack, // mutator stack - nullptr, // SkColorSpace* dst_color_space - kGiantRect, // SkRect cull_rect - unused_stopwatch, // frame time (dont care) - unused_stopwatch, // engine time (dont care) - unused_texture_registry, // texture registry (not supported) - false, // checkerboard_offscreen_layers - 0.0f, // total elevation - }; - - SkMatrix identity; - identity.setIdentity(); - - layers[0]->Preroll(&preroll_context, identity); - - // It should look like this: - // layers[0] +1.0f + SkPaint layer_paint; + layer_paint.setColor(SK_ColorGREEN); + layer_paint.setAntiAlias(true); + layer->Paint(paint_context()); + EXPECT_EQ(mock_canvas().draw_calls(), + std::vector({MockCanvas::DrawCall{ + 0, MockCanvas::DrawPathData{layer_path, layer_paint}}})); +} + +TEST_F(PhysicalShapeLayerTest, ChildrenLargerThanPath) { + SkPath layer_path; + layer_path.addRect(5.0f, 6.0f, 20.5f, 21.5f); + SkPath child1_path; + child1_path.addRect(4, 0, 12, 12).close(); + SkPath child2_path; + child2_path.addRect(3, 2, 5, 15).close(); + auto child1 = std::make_shared(SK_ColorRED, SK_ColorBLACK, + 0.0f, // elevation + child1_path, Clip::none); + auto child2 = + std::make_shared(SK_ColorBLUE, SK_ColorBLACK, + 0.0f, // elevation + child2_path, Clip::none); + auto layer = + std::make_shared(SK_ColorGREEN, SK_ColorBLACK, + 0.0f, // elevation + layer_path, Clip::none); + layer->Add(child1); + layer->Add(child2); + + SkRect child_paint_bounds; + layer->Preroll(preroll_context(), SkMatrix()); + child_paint_bounds.join(child1->paint_bounds()); + child_paint_bounds.join(child2->paint_bounds()); + EXPECT_EQ(layer->paint_bounds(), layer_path.getBounds()); + EXPECT_NE(layer->paint_bounds(), child_paint_bounds); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_FALSE(layer->needs_system_composite()); + + SkPaint layer_paint; + layer_paint.setColor(SK_ColorGREEN); + layer_paint.setAntiAlias(true); + SkPaint child1_paint; + child1_paint.setColor(SK_ColorRED); + child1_paint.setAntiAlias(true); + SkPaint child2_paint; + child2_paint.setColor(SK_ColorBLUE); + child2_paint.setAntiAlias(true); + layer->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector({MockCanvas::DrawCall{ + 0, MockCanvas::DrawPathData{layer_path, layer_paint}}, + MockCanvas::DrawCall{ + 0, MockCanvas::DrawPathData{child1_path, child1_paint}}, + MockCanvas::DrawCall{0, MockCanvas::DrawPathData{ + child2_path, child2_paint}}})); +} + +TEST_F(PhysicalShapeLayerTest, ElevationSimple) { + constexpr float initial_elevation = 20.0f; + SkPath layer_path; + layer_path.addRect(0, 0, 8, 8).close(); + auto layer = std::make_shared( + SK_ColorGREEN, SK_ColorBLACK, initial_elevation, layer_path, Clip::none); + + layer->Preroll(preroll_context(), SkMatrix()); + // The Fuchsia system compositor handles all elevated PhysicalShapeLayers and + // their shadows , so we do not do any painting there. +#if defined(OS_FUCHSIA) + EXPECT_EQ(layer->paint_bounds(), kEmptyRect); + EXPECT_FALSE(layer->needs_painting()); + EXPECT_TRUE(layer->needs_system_composite()); +#else + EXPECT_EQ(layer->paint_bounds(), + PhysicalShapeLayer::ComputeShadowBounds(layer_path.getBounds(), + initial_elevation, 1.0f)); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_FALSE(layer->needs_system_composite()); +#endif + EXPECT_EQ(layer->total_elevation(), initial_elevation); + + // The Fuchsia system compositor handles all elevated PhysicalShapeLayers and + // their shadows , so we do not use the direct |Paint()| path there. +#if !defined(OS_FUCHSIA) + SkPaint layer_paint; + layer_paint.setColor(SK_ColorGREEN); + layer_paint.setAntiAlias(true); + layer->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector( + {MockCanvas::DrawCall{0, MockCanvas::DrawShadowData{layer_path}}, + MockCanvas::DrawCall{ + 0, MockCanvas::DrawPathData{layer_path, layer_paint}}})); +#endif +} + +TEST_F(PhysicalShapeLayerTest, ElevationComplex) { + // The layer tree should look like this: + // layers[0] +1.0f = 1.0f // | \ // | \ // | \ - // | layers[2] +3.0f + // | layers[2] +3.0f = 4.0f // | | - // | layers[3] +4.0f + // | layers[3] +4.0f = 8.0f // | // | - // layers[1] + 2.0f - EXPECT_EQ(layers[0]->total_elevation_, 1.0f); - EXPECT_EQ(layers[1]->total_elevation_, 3.0f); - EXPECT_EQ(layers[2]->total_elevation_, 4.0f); - EXPECT_EQ(layers[3]->total_elevation_, 8.0f); + // layers[1] + 2.0f = 3.0f + constexpr float initial_elevations[4] = {1.0f, 2.0f, 3.0f, 4.0f}; + constexpr float total_elevations[4] = {1.0f, 3.0f, 4.0f, 8.0f}; + SkPath layer_path; + layer_path.addRect(0, 0, 80, 80).close(); + + std::shared_ptr layers[4]; + for (int i = 0; i < 4; i += 1) { + layers[i] = std::make_shared( + SK_ColorBLACK, SK_ColorBLACK, initial_elevations[i], layer_path, + Clip::none); + } + layers[0]->Add(layers[1]); + layers[0]->Add(layers[2]); + layers[2]->Add(layers[3]); + + layers[0]->Preroll(preroll_context(), SkMatrix()); + for (int i = 0; i < 4; i += 1) { + // On Fuchsia, the system compositor handles all elevated + // PhysicalShapeLayers and their shadows , so we do not do any painting + // there. +#if defined(OS_FUCHSIA) + EXPECT_EQ(layers[i]->paint_bounds(), kEmptyRect); + EXPECT_FALSE(layers[i]->needs_painting()); + EXPECT_TRUE(layers[i]->needs_system_composite()); +#else + EXPECT_EQ(layers[i]->paint_bounds(), + (PhysicalShapeLayer::ComputeShadowBounds( + layer_path.getBounds(), initial_elevations[i], + 1.0f /* pixel_ratio */))); + EXPECT_TRUE(layers[i]->needs_painting()); + EXPECT_FALSE(layers[i]->needs_system_composite()); +#endif + EXPECT_EQ(layers[i]->total_elevation(), total_elevations[i]); + } + + // The Fuchsia system compositor handles all elevated PhysicalShapeLayers and + // their shadows , so we do not use the direct |Paint()| path there. +#if !defined(OS_FUCHSIA) + SkPaint layer_paint; + layer_paint.setColor(SK_ColorBLACK); + layer_paint.setAntiAlias(true); + layers[0]->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector( + {MockCanvas::DrawCall{0, MockCanvas::DrawShadowData{layer_path}}, + MockCanvas::DrawCall{ + 0, MockCanvas::DrawPathData{layer_path, layer_paint}}, + MockCanvas::DrawCall{0, MockCanvas::DrawShadowData{layer_path}}, + MockCanvas::DrawCall{ + 0, MockCanvas::DrawPathData{layer_path, layer_paint}}, + MockCanvas::DrawCall{0, MockCanvas::DrawShadowData{layer_path}}, + MockCanvas::DrawCall{ + 0, MockCanvas::DrawPathData{layer_path, layer_paint}}, + MockCanvas::DrawCall{0, MockCanvas::DrawShadowData{layer_path}}, + MockCanvas::DrawCall{ + 0, MockCanvas::DrawPathData{layer_path, layer_paint}}})); +#endif +} + +static bool ReadbackResult(PrerollContext* context, + Clip clip_behavior, + std::shared_ptr child, + bool before) { + const SkMatrix initial_matrix = SkMatrix(); + const SkRect layer_bounds = SkRect::MakeXYWH(0.5, 1.0, 5.0, 6.0); + const SkPath layer_path = SkPath().addRect(layer_bounds); + auto layer = + std::make_shared(SK_ColorGREEN, SK_ColorBLACK, + 0.0f, // elevation + layer_path, clip_behavior); + if (child != nullptr) { + layer->Add(child); + } + context->surface_needs_readback = before; + layer->Preroll(context, initial_matrix); + return context->surface_needs_readback; +} + +TEST_F(PhysicalShapeLayerTest, Readback) { + PrerollContext* context = preroll_context(); + SkPath path; + SkPaint paint; + + const Clip hard = Clip::hardEdge; + const Clip soft = Clip::antiAlias; + const Clip save_layer = Clip::antiAliasWithSaveLayer; + + std::shared_ptr nochild; + auto reader = std::make_shared(path, paint, false, false, true); + auto nonreader = std::make_shared(path, paint); + + // No children, no prior readback -> no readback after + EXPECT_FALSE(ReadbackResult(context, hard, nochild, false)); + EXPECT_FALSE(ReadbackResult(context, soft, nochild, false)); + EXPECT_FALSE(ReadbackResult(context, save_layer, nochild, false)); + + // No children, prior readback -> readback after + EXPECT_TRUE(ReadbackResult(context, hard, nochild, true)); + EXPECT_TRUE(ReadbackResult(context, soft, nochild, true)); + EXPECT_TRUE(ReadbackResult(context, save_layer, nochild, true)); + + // Non readback child, no prior readback -> no readback after + EXPECT_FALSE(ReadbackResult(context, hard, nonreader, false)); + EXPECT_FALSE(ReadbackResult(context, soft, nonreader, false)); + EXPECT_FALSE(ReadbackResult(context, save_layer, nonreader, false)); + + // Non readback child, prior readback -> readback after + EXPECT_TRUE(ReadbackResult(context, hard, nonreader, true)); + EXPECT_TRUE(ReadbackResult(context, soft, nonreader, true)); + EXPECT_TRUE(ReadbackResult(context, save_layer, nonreader, true)); + + // Readback child, no prior readback -> readback after unless SaveLayer + EXPECT_TRUE(ReadbackResult(context, hard, reader, false)); + EXPECT_TRUE(ReadbackResult(context, soft, reader, false)); + EXPECT_FALSE(ReadbackResult(context, save_layer, reader, false)); + + // Readback child, prior readback -> readback after + EXPECT_TRUE(ReadbackResult(context, hard, reader, true)); + EXPECT_TRUE(ReadbackResult(context, soft, reader, true)); + EXPECT_TRUE(ReadbackResult(context, save_layer, reader, true)); } +} // namespace testing } // namespace flutter diff --git a/flow/layers/picture_layer.cc b/flow/layers/picture_layer.cc index c4275e76c13cf..230b648f50e80 100644 --- a/flow/layers/picture_layer.cc +++ b/flow/layers/picture_layer.cc @@ -17,8 +17,6 @@ PictureLayer::PictureLayer(const SkPoint& offset, is_complex_(is_complex), will_change_(will_change) {} -PictureLayer::~PictureLayer() = default; - void PictureLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { SkPicture* sk_picture = picture(); diff --git a/flow/layers/picture_layer.h b/flow/layers/picture_layer.h index 9c40cbef37cbd..e733e7455ca6c 100644 --- a/flow/layers/picture_layer.h +++ b/flow/layers/picture_layer.h @@ -19,7 +19,6 @@ class PictureLayer : public Layer { SkiaGPUObject picture, bool is_complex, bool will_change); - ~PictureLayer() override; SkPicture* picture() const { return picture_.get().get(); } diff --git a/flow/layers/picture_layer_unittests.cc b/flow/layers/picture_layer_unittests.cc new file mode 100644 index 0000000000000..687c870eeac66 --- /dev/null +++ b/flow/layers/picture_layer_unittests.cc @@ -0,0 +1,105 @@ +// 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. + +#define FML_USED_ON_EMBEDDER + +#include "flutter/flow/layers/picture_layer.h" + +#include "flutter/flow/testing/skia_gpu_object_layer_test.h" +#include "flutter/fml/macros.h" +#include "flutter/testing/mock_canvas.h" +#include "third_party/skia/include/core/SkPicture.h" + +#ifndef SUPPORT_FRACTIONAL_TRANSLATION +#include "flutter/flow/raster_cache.h" +#endif + +namespace flutter { +namespace testing { + +using PictureLayerTest = SkiaGPUObjectLayerTest; + +#ifndef NDEBUG +TEST_F(PictureLayerTest, PaintBeforePrerollInvalidPictureDies) { + const SkPoint layer_offset = SkPoint::Make(0.0f, 0.0f); + auto layer = std::make_shared( + layer_offset, SkiaGPUObject(), false, false); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "picture_\\.get\\(\\)"); +} + +TEST_F(PictureLayerTest, PaintBeforePreollDies) { + const SkPoint layer_offset = SkPoint::Make(0.0f, 0.0f); + const SkRect picture_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f); + auto mock_picture = SkPicture::MakePlaceholder(picture_bounds); + auto layer = std::make_shared( + layer_offset, SkiaGPUObject(mock_picture, unref_queue()), false, false); + + EXPECT_EQ(layer->paint_bounds(), SkRect::MakeEmpty()); + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} + +TEST_F(PictureLayerTest, PaintingEmptyLayerDies) { + const SkPoint layer_offset = SkPoint::Make(0.0f, 0.0f); + const SkRect picture_bounds = SkRect::MakeEmpty(); + auto mock_picture = SkPicture::MakePlaceholder(picture_bounds); + auto layer = std::make_shared( + layer_offset, SkiaGPUObject(mock_picture, unref_queue()), false, false); + + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(layer->paint_bounds(), SkRect::MakeEmpty()); + EXPECT_FALSE(layer->needs_painting()); + EXPECT_FALSE(layer->needs_system_composite()); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} +#endif + +TEST_F(PictureLayerTest, InvalidPictureDies) { + const SkPoint layer_offset = SkPoint::Make(0.0f, 0.0f); + auto layer = std::make_shared( + layer_offset, SkiaGPUObject(), false, false); + + // Crashes reading a nullptr. + EXPECT_DEATH_IF_SUPPORTED(layer->Preroll(preroll_context(), SkMatrix()), ""); +} + +TEST_F(PictureLayerTest, SimplePicture) { + const SkPoint layer_offset = SkPoint::Make(1.5f, -0.5f); + const SkMatrix layer_offset_matrix = + SkMatrix::MakeTrans(layer_offset.fX, layer_offset.fY); + const SkRect picture_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f); + auto mock_picture = SkPicture::MakePlaceholder(picture_bounds); + auto layer = std::make_shared( + layer_offset, SkiaGPUObject(mock_picture, unref_queue()), false, false); + + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(layer->paint_bounds(), + picture_bounds.makeOffset(layer_offset.fX, layer_offset.fY)); + EXPECT_EQ(layer->picture(), mock_picture.get()); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_FALSE(layer->needs_system_composite()); + + layer->Paint(paint_context()); + auto expected_draw_calls = std::vector( + {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, + MockCanvas::DrawCall{1, + MockCanvas::ConcatMatrixData{layer_offset_matrix}}, +#ifndef SUPPORT_FRACTIONAL_TRANSLATION + MockCanvas::DrawCall{ + 1, MockCanvas::SetMatrixData{RasterCache::GetIntegralTransCTM( + layer_offset_matrix)}}, +#endif + MockCanvas::DrawCall{ + 1, MockCanvas::DrawPictureData{mock_picture->serialize(), SkPaint(), + SkMatrix()}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}); + EXPECT_EQ(mock_canvas().draw_calls(), expected_draw_calls); +} + +} // namespace testing +} // namespace flutter diff --git a/flow/layers/platform_view_layer.cc b/flow/layers/platform_view_layer.cc index 15f9edf9719ee..81541b7a0cde8 100644 --- a/flow/layers/platform_view_layer.cc +++ b/flow/layers/platform_view_layer.cc @@ -11,8 +11,6 @@ PlatformViewLayer::PlatformViewLayer(const SkPoint& offset, int64_t view_id) : offset_(offset), size_(size), view_id_(view_id) {} -PlatformViewLayer::~PlatformViewLayer() = default; - void PlatformViewLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { set_paint_bounds(SkRect::MakeXYWH(offset_.x(), offset_.y(), size_.width(), @@ -23,6 +21,7 @@ void PlatformViewLayer::Preroll(PrerollContext* context, "does not support embedding"; return; } + context->has_platform_view = true; std::unique_ptr params = std::make_unique(); params->offsetPixels = diff --git a/flow/layers/platform_view_layer.h b/flow/layers/platform_view_layer.h index 7ce7ccb58a856..242b3734dd3b1 100644 --- a/flow/layers/platform_view_layer.h +++ b/flow/layers/platform_view_layer.h @@ -14,7 +14,6 @@ namespace flutter { class PlatformViewLayer : public Layer { public: PlatformViewLayer(const SkPoint& offset, const SkSize& size, int64_t view_id); - ~PlatformViewLayer() override; void Preroll(PrerollContext* context, const SkMatrix& matrix) override; void Paint(PaintContext& context) const override; diff --git a/flow/layers/platform_view_layer_unittests.cc b/flow/layers/platform_view_layer_unittests.cc new file mode 100644 index 0000000000000..123f9ab9925f6 --- /dev/null +++ b/flow/layers/platform_view_layer_unittests.cc @@ -0,0 +1,38 @@ +// 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/flow/layers/platform_view_layer.h" + +#include "flutter/flow/testing/layer_test.h" +#include "flutter/flow/testing/mock_layer.h" +#include "flutter/fml/macros.h" +#include "flutter/testing/mock_canvas.h" + +namespace flutter { +namespace testing { + +using PlatformViewLayerTest = LayerTest; + +TEST_F(PlatformViewLayerTest, NullViewEmbedderDoesntPrerollCompositeOrPaint) { + const SkPoint layer_offset = SkPoint::Make(0.0f, 0.0f); + const SkSize layer_size = SkSize::Make(8.0f, 8.0f); + const int64_t view_id = 0; + auto layer = + std::make_shared(layer_offset, layer_size, view_id); + + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_FALSE(preroll_context()->has_platform_view); + EXPECT_EQ(layer->paint_bounds(), + SkRect::MakeSize(layer_size) + .makeOffset(layer_offset.fX, layer_offset.fY)); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_FALSE(layer->needs_system_composite()); + + layer->Paint(paint_context()); + EXPECT_EQ(paint_context().leaf_nodes_canvas, &mock_canvas()); + EXPECT_EQ(mock_canvas().draw_calls(), std::vector()); +} + +} // namespace testing +} // namespace flutter diff --git a/flow/layers/shader_mask_layer.cc b/flow/layers/shader_mask_layer.cc index 36e7b7332aeae..157354f7314b4 100644 --- a/flow/layers/shader_mask_layer.cc +++ b/flow/layers/shader_mask_layer.cc @@ -11,7 +11,11 @@ ShaderMaskLayer::ShaderMaskLayer(sk_sp shader, SkBlendMode blend_mode) : shader_(shader), mask_rect_(mask_rect), blend_mode_(blend_mode) {} -ShaderMaskLayer::~ShaderMaskLayer() = default; +void ShaderMaskLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { + Layer::AutoPrerollSaveLayerState save = + Layer::AutoPrerollSaveLayerState::Create(context); + ContainerLayer::Preroll(context, matrix); +} void ShaderMaskLayer::Paint(PaintContext& context) const { TRACE_EVENT0("flutter", "ShaderMaskLayer::Paint"); diff --git a/flow/layers/shader_mask_layer.h b/flow/layers/shader_mask_layer.h index 01836f4f2fb54..03b90e40d0eff 100644 --- a/flow/layers/shader_mask_layer.h +++ b/flow/layers/shader_mask_layer.h @@ -16,7 +16,8 @@ class ShaderMaskLayer : public ContainerLayer { ShaderMaskLayer(sk_sp shader, const SkRect& mask_rect, SkBlendMode blend_mode); - ~ShaderMaskLayer() override; + + void Preroll(PrerollContext* context, const SkMatrix& matrix) override; void Paint(PaintContext& context) const override; diff --git a/flow/layers/shader_mask_layer_unittests.cc b/flow/layers/shader_mask_layer_unittests.cc new file mode 100644 index 0000000000000..895c6a9360c20 --- /dev/null +++ b/flow/layers/shader_mask_layer_unittests.cc @@ -0,0 +1,279 @@ +// 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/flow/layers/shader_mask_layer.h" + +#include "flutter/flow/testing/layer_test.h" +#include "flutter/flow/testing/mock_layer.h" +#include "flutter/fml/macros.h" +#include "flutter/testing/mock_canvas.h" +#include "third_party/skia/include/core/SkShader.h" +#include "third_party/skia/include/effects/SkPerlinNoiseShader.h" + +namespace flutter { +namespace testing { + +using ShaderMaskLayerTest = LayerTest; + +#ifndef NDEBUG +TEST_F(ShaderMaskLayerTest, PaintingEmptyLayerDies) { + auto layer = + std::make_shared(nullptr, kEmptyRect, SkBlendMode::kSrc); + + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(layer->paint_bounds(), kEmptyRect); + EXPECT_FALSE(layer->needs_painting()); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} + +TEST_F(ShaderMaskLayerTest, PaintBeforePreollDies) { + const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f); + const SkPath child_path = SkPath().addRect(child_bounds); + auto mock_layer = std::make_shared(child_path); + auto layer = + std::make_shared(nullptr, kEmptyRect, SkBlendMode::kSrc); + layer->Add(mock_layer); + + EXPECT_EQ(layer->paint_bounds(), kEmptyRect); + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} +#endif + +TEST_F(ShaderMaskLayerTest, EmptyFilter) { + const SkMatrix initial_transform = SkMatrix::MakeTrans(0.5f, 1.0f); + const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f); + const SkRect layer_bounds = SkRect::MakeLTRB(2.0f, 4.0f, 6.5f, 6.5f); + const SkPath child_path = SkPath().addRect(child_bounds); + const SkPaint child_paint = SkPaint(SkColors::kYellow); + auto mock_layer = std::make_shared(child_path, child_paint); + auto layer = std::make_shared(nullptr, layer_bounds, + SkBlendMode::kSrc); + layer->Add(mock_layer); + + layer->Preroll(preroll_context(), initial_transform); + EXPECT_EQ(mock_layer->paint_bounds(), child_bounds); + EXPECT_EQ(layer->paint_bounds(), child_bounds); + EXPECT_TRUE(mock_layer->needs_painting()); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_EQ(mock_layer->parent_matrix(), initial_transform); + + SkPaint filter_paint; + filter_paint.setBlendMode(SkBlendMode::kSrc); + filter_paint.setShader(nullptr); + layer->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector({MockCanvas::DrawCall{ + 0, MockCanvas::SaveLayerData{child_bounds, SkPaint(), + nullptr, 1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::DrawPathData{child_path, child_paint}}, + MockCanvas::DrawCall{ + 1, MockCanvas::ConcatMatrixData{SkMatrix::MakeTrans( + layer_bounds.fLeft, layer_bounds.fTop)}}, + MockCanvas::DrawCall{ + 1, MockCanvas::DrawRectData{SkRect::MakeWH( + layer_bounds.width(), + layer_bounds.height()), + filter_paint}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}})); +} + +TEST_F(ShaderMaskLayerTest, SimpleFilter) { + const SkMatrix initial_transform = SkMatrix::MakeTrans(0.5f, 1.0f); + const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f); + const SkRect layer_bounds = SkRect::MakeLTRB(2.0f, 4.0f, 6.5f, 6.5f); + const SkPath child_path = SkPath().addRect(child_bounds); + const SkPaint child_paint = SkPaint(SkColors::kYellow); + auto layer_filter = + SkPerlinNoiseShader::MakeImprovedNoise(1.0f, 1.0f, 1, 1.0f); + auto mock_layer = std::make_shared(child_path, child_paint); + auto layer = std::make_shared(layer_filter, layer_bounds, + SkBlendMode::kSrc); + layer->Add(mock_layer); + + layer->Preroll(preroll_context(), initial_transform); + EXPECT_EQ(layer->paint_bounds(), child_bounds); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_EQ(mock_layer->parent_matrix(), initial_transform); + + SkPaint filter_paint; + filter_paint.setBlendMode(SkBlendMode::kSrc); + filter_paint.setShader(layer_filter); + layer->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector({MockCanvas::DrawCall{ + 0, MockCanvas::SaveLayerData{child_bounds, SkPaint(), + nullptr, 1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::DrawPathData{child_path, child_paint}}, + MockCanvas::DrawCall{ + 1, MockCanvas::ConcatMatrixData{SkMatrix::MakeTrans( + layer_bounds.fLeft, layer_bounds.fTop)}}, + MockCanvas::DrawCall{ + 1, MockCanvas::DrawRectData{SkRect::MakeWH( + layer_bounds.width(), + layer_bounds.height()), + filter_paint}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}})); +} + +TEST_F(ShaderMaskLayerTest, MultipleChildren) { + const SkMatrix initial_transform = SkMatrix::MakeTrans(0.5f, 1.0f); + const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f); + const SkRect layer_bounds = SkRect::MakeLTRB(2.0f, 4.0f, 6.5f, 6.5f); + const SkPath child_path1 = SkPath().addRect(child_bounds); + const SkPath child_path2 = + SkPath().addRect(child_bounds.makeOffset(3.0f, 0.0f)); + const SkPaint child_paint1 = SkPaint(SkColors::kYellow); + const SkPaint child_paint2 = SkPaint(SkColors::kCyan); + auto layer_filter = + SkPerlinNoiseShader::MakeImprovedNoise(1.0f, 1.0f, 1, 1.0f); + auto mock_layer1 = std::make_shared(child_path1, child_paint1); + auto mock_layer2 = std::make_shared(child_path2, child_paint2); + auto layer = std::make_shared(layer_filter, layer_bounds, + SkBlendMode::kSrc); + layer->Add(mock_layer1); + layer->Add(mock_layer2); + + SkRect children_bounds = child_path1.getBounds(); + children_bounds.join(child_path2.getBounds()); + layer->Preroll(preroll_context(), initial_transform); + EXPECT_EQ(mock_layer1->paint_bounds(), child_path1.getBounds()); + EXPECT_EQ(mock_layer2->paint_bounds(), child_path2.getBounds()); + EXPECT_EQ(layer->paint_bounds(), children_bounds); + EXPECT_TRUE(mock_layer1->needs_painting()); + EXPECT_TRUE(mock_layer2->needs_painting()); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_EQ(mock_layer1->parent_matrix(), initial_transform); + EXPECT_EQ(mock_layer2->parent_matrix(), initial_transform); + + SkPaint filter_paint; + filter_paint.setBlendMode(SkBlendMode::kSrc); + filter_paint.setShader(layer_filter); + layer->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector({MockCanvas::DrawCall{ + 0, MockCanvas::SaveLayerData{children_bounds, SkPaint(), + nullptr, 1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::DrawPathData{child_path1, child_paint1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::DrawPathData{child_path2, child_paint2}}, + MockCanvas::DrawCall{ + 1, MockCanvas::ConcatMatrixData{SkMatrix::MakeTrans( + layer_bounds.fLeft, layer_bounds.fTop)}}, + MockCanvas::DrawCall{ + 1, MockCanvas::DrawRectData{SkRect::MakeWH( + layer_bounds.width(), + layer_bounds.height()), + filter_paint}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}})); +} + +TEST_F(ShaderMaskLayerTest, Nested) { + const SkMatrix initial_transform = SkMatrix::MakeTrans(0.5f, 1.0f); + const SkRect child_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 7.5f, 8.5f); + const SkRect layer_bounds = SkRect::MakeLTRB(2.0f, 4.0f, 20.5f, 20.5f); + const SkPath child_path1 = SkPath().addRect(child_bounds); + const SkPath child_path2 = + SkPath().addRect(child_bounds.makeOffset(3.0f, 0.0f)); + const SkPaint child_paint1 = SkPaint(SkColors::kYellow); + const SkPaint child_paint2 = SkPaint(SkColors::kCyan); + auto layer_filter1 = + SkPerlinNoiseShader::MakeImprovedNoise(1.0f, 1.0f, 1, 1.0f); + auto layer_filter2 = + SkPerlinNoiseShader::MakeImprovedNoise(2.0f, 2.0f, 2, 2.0f); + auto mock_layer1 = std::make_shared(child_path1, child_paint1); + auto mock_layer2 = std::make_shared(child_path2, child_paint2); + auto layer1 = std::make_shared(layer_filter1, layer_bounds, + SkBlendMode::kSrc); + auto layer2 = std::make_shared(layer_filter2, layer_bounds, + SkBlendMode::kSrc); + layer2->Add(mock_layer2); + layer1->Add(mock_layer1); + layer1->Add(layer2); + + SkRect children_bounds = child_path1.getBounds(); + children_bounds.join(child_path2.getBounds()); + layer1->Preroll(preroll_context(), initial_transform); + EXPECT_EQ(mock_layer1->paint_bounds(), child_path1.getBounds()); + EXPECT_EQ(mock_layer2->paint_bounds(), child_path2.getBounds()); + EXPECT_EQ(layer1->paint_bounds(), children_bounds); + EXPECT_EQ(layer2->paint_bounds(), mock_layer2->paint_bounds()); + EXPECT_TRUE(mock_layer1->needs_painting()); + EXPECT_TRUE(mock_layer2->needs_painting()); + EXPECT_TRUE(layer1->needs_painting()); + EXPECT_TRUE(layer2->needs_painting()); + EXPECT_EQ(mock_layer1->parent_matrix(), initial_transform); + EXPECT_EQ(mock_layer2->parent_matrix(), initial_transform); + + SkPaint filter_paint1, filter_paint2; + filter_paint1.setBlendMode(SkBlendMode::kSrc); + filter_paint2.setBlendMode(SkBlendMode::kSrc); + filter_paint1.setShader(layer_filter1); + filter_paint2.setShader(layer_filter2); + layer1->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector( + {MockCanvas::DrawCall{ + 0, MockCanvas::SaveLayerData{children_bounds, SkPaint(), nullptr, + 1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::DrawPathData{child_path1, child_paint1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::SaveLayerData{child_path2.getBounds(), SkPaint(), + nullptr, 2}}, + MockCanvas::DrawCall{ + 2, MockCanvas::DrawPathData{child_path2, child_paint2}}, + MockCanvas::DrawCall{ + 2, MockCanvas::ConcatMatrixData{SkMatrix::MakeTrans( + layer_bounds.fLeft, layer_bounds.fTop)}}, + MockCanvas::DrawCall{ + 2, + MockCanvas::DrawRectData{ + SkRect::MakeWH(layer_bounds.width(), layer_bounds.height()), + filter_paint2}}, + MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::ConcatMatrixData{SkMatrix::MakeTrans( + layer_bounds.fLeft, layer_bounds.fTop)}}, + MockCanvas::DrawCall{ + 1, + MockCanvas::DrawRectData{ + SkRect::MakeWH(layer_bounds.width(), layer_bounds.height()), + filter_paint1}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}})); +} + +TEST_F(ShaderMaskLayerTest, Readback) { + auto initial_transform = SkMatrix(); + const SkRect layer_bounds = SkRect::MakeLTRB(2.0f, 4.0f, 20.5f, 20.5f); + auto layer_filter = + SkPerlinNoiseShader::MakeImprovedNoise(1.0f, 1.0f, 1, 1.0f); + auto layer = std::make_shared(layer_filter, layer_bounds, + SkBlendMode::kSrc); + + // ShaderMaskLayer does not read from surface + preroll_context()->surface_needs_readback = false; + layer->Preroll(preroll_context(), initial_transform); + EXPECT_FALSE(preroll_context()->surface_needs_readback); + + // ShaderMaskLayer blocks child with readback + auto mock_layer = + std::make_shared(SkPath(), SkPaint(), false, false, true); + layer->Add(mock_layer); + preroll_context()->surface_needs_readback = false; + layer->Preroll(preroll_context(), initial_transform); + EXPECT_FALSE(preroll_context()->surface_needs_readback); +} + +} // namespace testing +} // namespace flutter diff --git a/flow/layers/texture_layer.cc b/flow/layers/texture_layer.cc index c7716dd59bc29..848f69c8a115a 100644 --- a/flow/layers/texture_layer.cc +++ b/flow/layers/texture_layer.cc @@ -14,8 +14,6 @@ TextureLayer::TextureLayer(const SkPoint& offset, bool freeze) : offset_(offset), size_(size), texture_id_(texture_id), freeze_(freeze) {} -TextureLayer::~TextureLayer() = default; - void TextureLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { set_paint_bounds(SkRect::MakeXYWH(offset_.x(), offset_.y(), size_.width(), size_.height())); diff --git a/flow/layers/texture_layer.h b/flow/layers/texture_layer.h index 7c04471afa0c1..20f6c709d6107 100644 --- a/flow/layers/texture_layer.h +++ b/flow/layers/texture_layer.h @@ -17,7 +17,6 @@ class TextureLayer : public Layer { const SkSize& size, int64_t texture_id, bool freeze); - ~TextureLayer() override; void Preroll(PrerollContext* context, const SkMatrix& matrix) override; void Paint(PaintContext& context) const override; diff --git a/flow/layers/texture_layer_unittests.cc b/flow/layers/texture_layer_unittests.cc new file mode 100644 index 0000000000000..9adf273e61122 --- /dev/null +++ b/flow/layers/texture_layer_unittests.cc @@ -0,0 +1,57 @@ +// 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/flow/layers/texture_layer.h" + +#include "flutter/flow/testing/layer_test.h" +#include "flutter/flow/testing/mock_layer.h" +#include "flutter/flow/testing/mock_texture.h" +#include "flutter/fml/macros.h" +#include "flutter/testing/mock_canvas.h" + +namespace flutter { +namespace testing { + +using TextureLayerTest = LayerTest; + +TEST_F(TextureLayerTest, InvalidTexture) { + const SkPoint layer_offset = SkPoint::Make(0.0f, 0.0f); + const SkSize layer_size = SkSize::Make(8.0f, 8.0f); + auto layer = + std::make_shared(layer_offset, layer_size, 0, false); + + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(layer->paint_bounds(), + (SkRect::MakeSize(layer_size) + .makeOffset(layer_offset.fX, layer_offset.fY))); + EXPECT_TRUE(layer->needs_painting()); + + layer->Paint(paint_context()); + EXPECT_EQ(mock_canvas().draw_calls(), std::vector()); +} + +TEST_F(TextureLayerTest, PaintingEmptyLayerDies) { + const SkPoint layer_offset = SkPoint::Make(0.0f, 0.0f); + const SkSize layer_size = SkSize::Make(0.0f, 0.0f); + const int64_t texture_id = 0; + auto mock_texture = std::make_shared(texture_id); + auto layer = std::make_shared(layer_offset, layer_size, + texture_id, false); + + // Ensure the texture is located by the Layer. + preroll_context()->texture_registry.RegisterTexture(mock_texture); + + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(layer->paint_bounds(), kEmptyRect); + EXPECT_FALSE(layer->needs_painting()); + + layer->Paint(paint_context()); + EXPECT_EQ(mock_texture->paint_calls(), + std::vector({MockTexture::PaintCall{ + mock_canvas(), layer->paint_bounds(), false, nullptr}})); + EXPECT_EQ(mock_canvas().draw_calls(), std::vector()); +} + +} // namespace testing +} // namespace flutter diff --git a/flow/layers/transform_layer.cc b/flow/layers/transform_layer.cc index 5a7af132c68f2..9513e8bc0fec6 100644 --- a/flow/layers/transform_layer.cc +++ b/flow/layers/transform_layer.cc @@ -24,8 +24,6 @@ TransformLayer::TransformLayer(const SkMatrix& transform) } } -TransformLayer::~TransformLayer() = default; - void TransformLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { SkMatrix child_matrix; child_matrix.setConcat(matrix, transform_); diff --git a/flow/layers/transform_layer.h b/flow/layers/transform_layer.h index f19a963ced9fe..a21e7d4f10c5b 100644 --- a/flow/layers/transform_layer.h +++ b/flow/layers/transform_layer.h @@ -14,7 +14,6 @@ namespace flutter { class TransformLayer : public ContainerLayer { public: TransformLayer(const SkMatrix& transform); - ~TransformLayer() override; void Preroll(PrerollContext* context, const SkMatrix& matrix) override; diff --git a/flow/layers/transform_layer_unittests.cc b/flow/layers/transform_layer_unittests.cc new file mode 100644 index 0000000000000..b99f35b2c7a25 --- /dev/null +++ b/flow/layers/transform_layer_unittests.cc @@ -0,0 +1,228 @@ +// 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/flow/layers/transform_layer.h" + +#include "flutter/flow/testing/layer_test.h" +#include "flutter/flow/testing/mock_layer.h" +#include "flutter/fml/macros.h" +#include "flutter/testing/mock_canvas.h" + +namespace flutter { +namespace testing { + +using TransformLayerTest = LayerTest; + +#ifndef NDEBUG +TEST_F(TransformLayerTest, PaintingEmptyLayerDies) { + auto layer = std::make_shared(SkMatrix()); // identity + + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(layer->paint_bounds(), SkRect::MakeEmpty()); + EXPECT_FALSE(layer->needs_painting()); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} + +TEST_F(TransformLayerTest, PaintBeforePreollDies) { + SkPath child_path; + child_path.addRect(5.0f, 6.0f, 20.5f, 21.5f); + auto mock_layer = std::make_shared(child_path, SkPaint()); + auto layer = std::make_shared(SkMatrix()); // identity + layer->Add(mock_layer); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} +#endif + +TEST_F(TransformLayerTest, Identity) { + SkPath child_path; + child_path.addRect(5.0f, 6.0f, 20.5f, 21.5f); + SkRect cull_rect = SkRect::MakeXYWH(2.0f, 2.0f, 14.0f, 14.0f); + auto mock_layer = std::make_shared(child_path, SkPaint()); + auto layer = std::make_shared(SkMatrix()); // identity + layer->Add(mock_layer); + + preroll_context()->cull_rect = cull_rect; + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(mock_layer->paint_bounds(), child_path.getBounds()); + EXPECT_EQ(layer->paint_bounds(), mock_layer->paint_bounds()); + EXPECT_TRUE(mock_layer->needs_painting()); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_EQ(mock_layer->parent_matrix(), SkMatrix()); // identity + EXPECT_EQ(mock_layer->parent_cull_rect(), cull_rect); + EXPECT_EQ(mock_layer->parent_mutators(), std::vector({Mutator(SkMatrix())})); + + layer->Paint(paint_context()); + EXPECT_EQ(mock_canvas().draw_calls(), + std::vector({MockCanvas::DrawCall{ + 0, MockCanvas::DrawPathData{child_path, SkPaint()}}})); +} + +TEST_F(TransformLayerTest, Simple) { + SkPath child_path; + child_path.addRect(5.0f, 6.0f, 20.5f, 21.5f); + SkRect cull_rect = SkRect::MakeXYWH(2.0f, 2.0f, 14.0f, 14.0f); + SkMatrix initial_transform = SkMatrix::MakeTrans(-0.5f, -0.5f); + SkMatrix layer_transform = SkMatrix::MakeTrans(2.5f, 2.5f); + SkMatrix inverse_layer_transform; + EXPECT_TRUE(layer_transform.invert(&inverse_layer_transform)); + + auto mock_layer = std::make_shared(child_path, SkPaint()); + auto layer = std::make_shared(layer_transform); + layer->Add(mock_layer); + + preroll_context()->cull_rect = cull_rect; + layer->Preroll(preroll_context(), initial_transform); + EXPECT_EQ(mock_layer->paint_bounds(), child_path.getBounds()); + EXPECT_EQ(layer->paint_bounds(), + layer_transform.mapRect(mock_layer->paint_bounds())); + EXPECT_TRUE(mock_layer->needs_painting()); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_EQ(mock_layer->parent_matrix(), + SkMatrix::Concat(initial_transform, layer_transform)); + EXPECT_EQ(mock_layer->parent_cull_rect(), + inverse_layer_transform.mapRect(cull_rect)); + EXPECT_EQ(mock_layer->parent_mutators(), + std::vector({Mutator(layer_transform)})); + + layer->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector({MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::ConcatMatrixData{layer_transform}}, + MockCanvas::DrawCall{ + 1, MockCanvas::DrawPathData{child_path, SkPaint()}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}})); +} + +TEST_F(TransformLayerTest, Nested) { + SkPath child_path; + child_path.addRect(5.0f, 6.0f, 20.5f, 21.5f); + SkRect cull_rect = SkRect::MakeXYWH(2.0f, 2.0f, 14.0f, 14.0f); + SkMatrix initial_transform = SkMatrix::MakeTrans(-0.5f, -0.5f); + SkMatrix layer1_transform = SkMatrix::MakeTrans(2.5f, 2.5f); + SkMatrix layer2_transform = SkMatrix::MakeTrans(2.5f, 2.5f); + SkMatrix inverse_layer1_transform, inverse_layer2_transform; + EXPECT_TRUE(layer1_transform.invert(&inverse_layer1_transform)); + EXPECT_TRUE(layer2_transform.invert(&inverse_layer2_transform)); + + auto mock_layer = std::make_shared(child_path, SkPaint()); + auto layer1 = std::make_shared(layer1_transform); + auto layer2 = std::make_shared(layer2_transform); + layer1->Add(layer2); + layer2->Add(mock_layer); + + preroll_context()->cull_rect = cull_rect; + layer1->Preroll(preroll_context(), initial_transform); + EXPECT_EQ(mock_layer->paint_bounds(), child_path.getBounds()); + EXPECT_EQ(layer2->paint_bounds(), + layer2_transform.mapRect(mock_layer->paint_bounds())); + EXPECT_EQ(layer1->paint_bounds(), + layer1_transform.mapRect(layer2->paint_bounds())); + EXPECT_TRUE(mock_layer->needs_painting()); + EXPECT_TRUE(layer2->needs_painting()); + EXPECT_TRUE(layer1->needs_painting()); + EXPECT_EQ( + mock_layer->parent_matrix(), + SkMatrix::Concat(SkMatrix::Concat(initial_transform, layer1_transform), + layer2_transform)); + EXPECT_EQ(mock_layer->parent_cull_rect(), + inverse_layer2_transform.mapRect( + inverse_layer1_transform.mapRect(cull_rect))); + EXPECT_EQ( + mock_layer->parent_mutators(), + std::vector({Mutator(layer2_transform), Mutator(layer1_transform)})); + + layer1->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector({MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::ConcatMatrixData{layer1_transform}}, + MockCanvas::DrawCall{1, MockCanvas::SaveData{2}}, + MockCanvas::DrawCall{ + 2, MockCanvas::ConcatMatrixData{layer2_transform}}, + MockCanvas::DrawCall{ + 2, MockCanvas::DrawPathData{child_path, SkPaint()}}, + MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}})); +} + +TEST_F(TransformLayerTest, NestedSeparated) { + SkPath child_path; + child_path.addRect(5.0f, 6.0f, 20.5f, 21.5f); + SkRect cull_rect = SkRect::MakeXYWH(2.0f, 2.0f, 14.0f, 14.0f); + SkMatrix initial_transform = SkMatrix::MakeTrans(-0.5f, -0.5f); + SkMatrix layer1_transform = SkMatrix::MakeTrans(2.5f, 2.5f); + SkMatrix layer2_transform = SkMatrix::MakeTrans(2.5f, 2.5f); + SkMatrix inverse_layer1_transform, inverse_layer2_transform; + EXPECT_TRUE(layer1_transform.invert(&inverse_layer1_transform)); + EXPECT_TRUE(layer2_transform.invert(&inverse_layer2_transform)); + + auto mock_layer1 = + std::make_shared(child_path, SkPaint(SkColors::kBlue)); + auto mock_layer2 = + std::make_shared(child_path, SkPaint(SkColors::kGreen)); + auto layer1 = std::make_shared(layer1_transform); + auto layer2 = std::make_shared(layer2_transform); + layer1->Add(mock_layer1); + layer1->Add(layer2); + layer2->Add(mock_layer2); + + preroll_context()->cull_rect = cull_rect; + layer1->Preroll(preroll_context(), initial_transform); + SkRect expected_layer1_bounds = layer2->paint_bounds(); + expected_layer1_bounds.join(mock_layer1->paint_bounds()); + layer1_transform.mapRect(&expected_layer1_bounds); + EXPECT_EQ(mock_layer2->paint_bounds(), child_path.getBounds()); + EXPECT_EQ(layer2->paint_bounds(), + layer2_transform.mapRect(mock_layer2->paint_bounds())); + EXPECT_EQ(mock_layer1->paint_bounds(), child_path.getBounds()); + EXPECT_EQ(layer1->paint_bounds(), expected_layer1_bounds); + EXPECT_TRUE(mock_layer2->needs_painting()); + EXPECT_TRUE(layer2->needs_painting()); + EXPECT_TRUE(mock_layer1->needs_painting()); + EXPECT_TRUE(layer1->needs_painting()); + EXPECT_EQ(mock_layer1->parent_matrix(), + SkMatrix::Concat(initial_transform, layer1_transform)); + EXPECT_EQ( + mock_layer2->parent_matrix(), + SkMatrix::Concat(SkMatrix::Concat(initial_transform, layer1_transform), + layer2_transform)); + EXPECT_EQ(mock_layer1->parent_cull_rect(), + inverse_layer1_transform.mapRect(cull_rect)); + EXPECT_EQ(mock_layer2->parent_cull_rect(), + inverse_layer2_transform.mapRect( + inverse_layer1_transform.mapRect(cull_rect))); + EXPECT_EQ(mock_layer1->parent_mutators(), + std::vector({Mutator(layer1_transform)})); + EXPECT_EQ( + mock_layer2->parent_mutators(), + std::vector({Mutator(layer2_transform), Mutator(layer1_transform)})); + + layer1->Paint(paint_context()); + EXPECT_EQ( + mock_canvas().draw_calls(), + std::vector({MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::ConcatMatrixData{layer1_transform}}, + MockCanvas::DrawCall{ + 1, MockCanvas::DrawPathData{child_path, + SkPaint(SkColors::kBlue)}}, + MockCanvas::DrawCall{1, MockCanvas::SaveData{2}}, + MockCanvas::DrawCall{ + 2, MockCanvas::ConcatMatrixData{layer2_transform}}, + MockCanvas::DrawCall{ + 2, MockCanvas::DrawPathData{child_path, + SkPaint(SkColors::kGreen)}}, + MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}})); +} + +} // namespace testing +} // namespace flutter diff --git a/flow/matrix_decomposition_unittests.cc b/flow/matrix_decomposition_unittests.cc index cf96025276737..8aa511e4a0a97 100644 --- a/flow/matrix_decomposition_unittests.cc +++ b/flow/matrix_decomposition_unittests.cc @@ -12,6 +12,9 @@ #include "flutter/flow/matrix_decomposition.h" #include "gtest/gtest.h" +namespace flutter { +namespace testing { + TEST(MatrixDecomposition, Rotation) { SkMatrix44 matrix = SkMatrix44::I(); @@ -93,7 +96,8 @@ TEST(MatrixDecomposition, Combination) { } TEST(MatrixDecomposition, ScaleFloatError) { - for (float scale = 0.0001f; scale < 2.0f; scale += 0.000001f) { + constexpr float scale_increment = 0.00001f; + for (float scale = 0.0001f; scale < 2.0f; scale += scale_increment) { SkMatrix44 matrix = SkMatrix44::I(); matrix.setScale(scale, scale, 1.0f); @@ -152,3 +156,6 @@ TEST(MatrixDecomposition, ScaleFloatError) { ASSERT_FLOAT_EQ(0, decomposition3.rotation().fData[1]); ASSERT_FLOAT_EQ(0, decomposition3.rotation().fData[2]); } + +} // namespace testing +} // namespace flutter diff --git a/flow/mutators_stack_unittests.cc b/flow/mutators_stack_unittests.cc index 97cfe9a54a7c7..1d31a81623307 100644 --- a/flow/mutators_stack_unittests.cc +++ b/flow/mutators_stack_unittests.cc @@ -3,6 +3,7 @@ // found in the LICENSE file. #include "flutter/flow/embedded_views.h" + #include "gtest/gtest.h" namespace flutter { diff --git a/flow/raster_cache.cc b/flow/raster_cache.cc index a2831f83e3fdc..a3bbb20b9b05a 100644 --- a/flow/raster_cache.cc +++ b/flow/raster_cache.cc @@ -92,13 +92,14 @@ static bool IsPictureWorthRasterizing(SkPicture* picture, return picture->approximateOpCount() > 5; } +/// @note Procedure doesn't copy all closures. static RasterCacheResult Rasterize( GrContext* context, const SkMatrix& ctm, SkColorSpace* dst_color_space, bool checkerboard, const SkRect& logical_rect, - std::function draw_function) { + const std::function& draw_function) { TRACE_EVENT0("flutter", "RasterCachePopulate"); SkIRect cache_rect = RasterCache::GetDeviceBounds(logical_rect, ctm); @@ -157,27 +158,30 @@ void RasterCache::Prepare(PrerollContext* context, entry.access_count = ClampSize(entry.access_count + 1, 0, access_threshold_); entry.used_this_frame = true; if (!entry.image.is_valid()) { - entry.image = Rasterize(context->gr_context, ctm, context->dst_color_space, - checkerboard_images_, layer->paint_bounds(), - [layer, context](SkCanvas* canvas) { - SkISize canvas_size = canvas->getBaseLayerSize(); - SkNWayCanvas internal_nodes_canvas( - canvas_size.width(), canvas_size.height()); - internal_nodes_canvas.addCanvas(canvas); - Layer::PaintContext paintContext = { - (SkCanvas*)&internal_nodes_canvas, - canvas, - context->gr_context, - nullptr, - context->raster_time, - context->ui_time, - context->texture_registry, - context->raster_cache, - context->checkerboard_offscreen_layers}; - if (layer->needs_painting()) { - layer->Paint(paintContext); - } - }); + entry.image = Rasterize( + context->gr_context, ctm, context->dst_color_space, + checkerboard_images_, layer->paint_bounds(), + [layer, context](SkCanvas* canvas) { + SkISize canvas_size = canvas->getBaseLayerSize(); + SkNWayCanvas internal_nodes_canvas(canvas_size.width(), + canvas_size.height()); + internal_nodes_canvas.addCanvas(canvas); + Layer::PaintContext paintContext = { + (SkCanvas*)&internal_nodes_canvas, + canvas, + context->gr_context, + nullptr, + context->raster_time, + context->ui_time, + context->texture_registry, + context->has_platform_view ? nullptr : context->raster_cache, + context->checkerboard_offscreen_layers, + context->frame_physical_depth, + context->frame_device_pixel_ratio}; + if (layer->needs_painting()) { + layer->Paint(paintContext); + } + }); } } @@ -218,8 +222,8 @@ bool RasterCache::Prepare(GrContext* context, if (!entry.image.is_valid()) { entry.image = RasterizePicture(picture, context, transformation_matrix, dst_color_space, checkerboard_images_); + picture_cached_this_frame_++; } - picture_cached_this_frame_++; return true; } @@ -250,6 +254,10 @@ void RasterCache::Clear() { layer_cache_.clear(); } +size_t RasterCache::GetCachedEntriesCount() const { + return layer_cache_.size() + picture_cache_.size(); +} + void RasterCache::SetCheckboardCacheImages(bool checkerboard) { if (checkerboard_images_ == checkerboard) { return; diff --git a/flow/raster_cache.h b/flow/raster_cache.h index f008f2459cc30..3ea92588e1fd7 100644 --- a/flow/raster_cache.h +++ b/flow/raster_cache.h @@ -100,6 +100,8 @@ class RasterCache { void SetCheckboardCacheImages(bool checkerboard); + size_t GetCachedEntriesCount() const; + private: struct Entry { bool used_this_frame = false; diff --git a/flow/raster_cache_unittests.cc b/flow/raster_cache_unittests.cc index 64f4405ebe5a0..bd83d807875f2 100644 --- a/flow/raster_cache_unittests.cc +++ b/flow/raster_cache_unittests.cc @@ -3,10 +3,15 @@ // found in the LICENSE file. #include "flutter/flow/raster_cache.h" + #include "gtest/gtest.h" #include "third_party/skia/include/core/SkPicture.h" #include "third_party/skia/include/core/SkPictureRecorder.h" +namespace flutter { +namespace testing { +namespace { + sk_sp GetSamplePicture() { SkPictureRecorder recorder; recorder.beginRecording(SkRect::MakeWH(150, 100)); @@ -17,6 +22,8 @@ sk_sp GetSamplePicture() { return recorder.finishRecordingAsPicture(); } +} // namespace + TEST(RasterCache, SimpleInitialization) { flutter::RasterCache cache; ASSERT_TRUE(true); @@ -93,3 +100,6 @@ TEST(RasterCache, SweepsRemoveUnusedFrames) { ASSERT_FALSE(cache.Prepare(NULL, picture.get(), matrix, srgb.get(), true, false)); // 5 } + +} // namespace testing +} // namespace flutter diff --git a/flow/scene_update_context.cc b/flow/scene_update_context.cc index 14190b5b5d631..5ffcc4b272ce9 100644 --- a/flow/scene_update_context.cc +++ b/flow/scene_update_context.cc @@ -7,6 +7,7 @@ #include "flutter/flow/layers/layer.h" #include "flutter/flow/matrix_decomposition.h" #include "flutter/fml/trace_event.h" +#include "include/core/SkColor.h" namespace flutter { @@ -59,18 +60,23 @@ void SceneUpdateContext::CreateFrame(scenic::EntityNode entity_node, scenic::ShapeNode shape_node, const SkRRect& rrect, SkColor color, + float opacity, const SkRect& paint_bounds, std::vector paint_layers, Layer* layer) { - // Frames always clip their children. - SetEntityNodeClipPlanes(entity_node, rrect.getBounds()); - // TODO(SCN-1274): SetClip() will be deleted. - entity_node.SetClip(0u, true /* clip to self */); - // We don't need a shape if the frame is zero size. if (rrect.isEmpty()) return; + SetEntityNodeClipPlanes(entity_node, rrect.getBounds()); + + // isEmpty should account for this, but we are adding these experimental + // checks to validate if this is the root cause for b/144933519. + if (std::isnan(rrect.width()) || std::isnan(rrect.height())) { + FML_LOG(ERROR) << "Invalid RoundedRectangle"; + return; + } + // Add a part which represents the frame's geometry for clipping purposes // and possibly for its texture. // TODO(SCN-137): Need to be able to express the radii as vectors. @@ -95,7 +101,9 @@ void SceneUpdateContext::CreateFrame(scenic::EntityNode entity_node, // Check whether a solid color will suffice. if (paint_layers.empty()) { - SetShapeColor(shape_node, color); + scenic::Material material(session_); + SetMaterialColor(material, color, opacity); + shape_node.SetMaterial(material); return; } @@ -103,43 +111,38 @@ void SceneUpdateContext::CreateFrame(scenic::EntityNode entity_node, const float scale_x = ScaleX(); const float scale_y = ScaleY(); - // Apply a texture to the whole shape. - SetShapeTextureAndColor(shape_node, color, scale_x, scale_y, shape_bounds, - std::move(paint_layers), layer, - std::move(entity_node)); -} - -void SceneUpdateContext::SetShapeTextureAndColor( - scenic::ShapeNode& shape_node, - SkColor color, - SkScalar scale_x, - SkScalar scale_y, - const SkRect& paint_bounds, - std::vector paint_layers, - Layer* layer, - scenic::EntityNode entity_node) { scenic::Image* image = GenerateImageIfNeeded( - color, scale_x, scale_y, paint_bounds, std::move(paint_layers), layer, + color, scale_x, scale_y, shape_bounds, std::move(paint_layers), layer, std::move(entity_node)); if (image != nullptr) { scenic::Material material(session_); + + // The final shape's color is material_color * texture_color. The passed in + // material color was already used as a background when generating the + // texture, so set the model color to |SK_ColorWHITE| in order to allow + // using the texture's color unmodified. + SetMaterialColor(material, SK_ColorWHITE, opacity); material.SetTexture(*image); shape_node.SetMaterial(material); return; } - SetShapeColor(shape_node, color); -} + // No texture was needed, so apply a solid color to the whole shape. + if (SkColorGetA(color) != 0 && opacity != 0.0f) { + scenic::Material material(session_); -void SceneUpdateContext::SetShapeColor(scenic::ShapeNode& shape_node, - SkColor color) { - if (SkColorGetA(color) == 0) + SetMaterialColor(material, color, opacity); + shape_node.SetMaterial(material); return; + } +} - scenic::Material material(session_); +void SceneUpdateContext::SetMaterialColor(scenic::Material& material, + SkColor color, + float opacity) { + const SkAlpha color_alpha = (SkAlpha)(SkColorGetA(color) * opacity); material.SetColor(SkColorGetR(color), SkColorGetG(color), SkColorGetB(color), - SkColorGetA(color)); - shape_node.SetMaterial(material); + color_alpha); } scenic::Image* SceneUpdateContext::GenerateImageIfNeeded( @@ -205,7 +208,9 @@ SceneUpdateContext::ExecutePaintTasks(CompositorContext::ScopedFrame& frame) { frame.context().ui_time(), frame.context().texture_registry(), &frame.context().raster_cache(), - false}; + false, + frame_physical_depth_, + frame_device_pixel_ratio_}; canvas->restoreToCount(1); canvas->save(); canvas->clear(task.background_color); @@ -226,6 +231,7 @@ SceneUpdateContext::Entity::Entity(SceneUpdateContext& context) entity_node_(context.session()) { if (previous_entity_) previous_entity_->embedder_node().AddChild(entity_node_); + context.top_entity_ = this; } @@ -284,43 +290,37 @@ SceneUpdateContext::Transform::~Transform() { context().top_scale_y_ = previous_scale_y_; } -SceneUpdateContext::Shape::Shape(SceneUpdateContext& context) - : Entity(context), shape_node_(context.session()) { - entity_node().AddChild(shape_node_); +SceneUpdateContext::Clip::Clip(SceneUpdateContext& context, + const SkRect& shape_bounds) + : Entity(context) { + SetEntityNodeClipPlanes(entity_node(), shape_bounds); } SceneUpdateContext::Frame::Frame(SceneUpdateContext& context, const SkRRect& rrect, SkColor color, - float local_elevation, - float world_elevation, - float depth, + float opacity, + float elevation, Layer* layer) - : Shape(context), + : Entity(context), + opacity_node_(context.session()), + shape_node_(context.session()), + layer_(layer), rrect_(rrect), - color_(color), paint_bounds_(SkRect::MakeEmpty()), - layer_(layer) { - if (depth > -1 && world_elevation > depth) { - // TODO(mklim): Deal with bounds overflow more elegantly. We'd like to be - // able to have developers specify the behavior here to alternatives besides - // clamping, like normalization on some arbitrary curve. - - // Clamp the local z coordinate at our max bound. Take into account the - // parent z position here to fix clamping in cases where the child is - // overflowing because of its parents. - const float parent_elevation = world_elevation - local_elevation; - local_elevation = depth - parent_elevation; - } - if (local_elevation != 0.0) { - entity_node().SetTranslation(0.f, 0.f, -local_elevation); - } + color_(color), + opacity_(opacity) { + entity_node().SetTranslation(0.f, 0.f, -elevation); + + entity_node().AddChild(shape_node_); + entity_node().AddChild(opacity_node_); + opacity_node_.SetOpacity(opacity_); } SceneUpdateContext::Frame::~Frame() { - context().CreateFrame(std::move(entity_node()), std::move(shape_node()), - rrect_, color_, paint_bounds_, std::move(paint_layers_), - layer_); + context().CreateFrame(std::move(entity_node()), std::move(shape_node_), + rrect_, color_, opacity_, paint_bounds_, + std::move(paint_layers_), layer_); } void SceneUpdateContext::Frame::AddPaintLayer(Layer* layer) { @@ -329,10 +329,4 @@ void SceneUpdateContext::Frame::AddPaintLayer(Layer* layer) { paint_bounds_.join(layer->paint_bounds()); } -SceneUpdateContext::Clip::Clip(SceneUpdateContext& context, - const SkRect& shape_bounds) - : Entity(context) { - SetEntityNodeClipPlanes(entity_node(), shape_bounds); -} - } // namespace flutter diff --git a/flow/scene_update_context.h b/flow/scene_update_context.h index d4a46055967f3..c992fa20bba36 100644 --- a/flow/scene_update_context.h +++ b/flow/scene_update_context.h @@ -37,7 +37,7 @@ class SceneUpdateContext { virtual SkISize GetSize() const = 0; virtual void SignalWritesFinished( - std::function on_writes_committed) = 0; + const std::function& on_writes_committed) = 0; virtual scenic::Image* GetImage() = 0; @@ -89,25 +89,20 @@ class SceneUpdateContext { float scale_x, float scale_y, float scale_z); - virtual ~Transform(); + ~Transform() override; private: float const previous_scale_x_; float const previous_scale_y_; }; - class Shape : public Entity { + class Clip : public Entity { public: - Shape(SceneUpdateContext& context); - virtual ~Shape() = default; - - scenic::ShapeNode& shape_node() { return shape_node_; } - - private: - scenic::ShapeNode shape_node_; + Clip(SceneUpdateContext& context, const SkRect& shape_bounds); + ~Clip() override = default; }; - class Frame : public Shape { + class Frame : public Entity { public: // When layer is not nullptr, the frame is associated with a layer subtree // rooted with that layer. The frame may then create a surface that will be @@ -115,27 +110,25 @@ class SceneUpdateContext { Frame(SceneUpdateContext& context, const SkRRect& rrect, SkColor color, - float local_elevation = 0.0f, - float parent_elevation = 0.0f, - float depth = 0.0f, + float opacity = 1.0f, + float elevation = 0.0f, Layer* layer = nullptr); - virtual ~Frame(); + ~Frame() override; + scenic::ContainerNode& embedder_node() override { return opacity_node_; } void AddPaintLayer(Layer* layer); private: - const SkRRect& rrect_; - SkColor const color_; + scenic::OpacityNodeHACK opacity_node_; + scenic::ShapeNode shape_node_; std::vector paint_layers_; - SkRect paint_bounds_; Layer* layer_; - }; - class Clip : public Entity { - public: - Clip(SceneUpdateContext& context, const SkRect& shape_bounds); - ~Clip() = default; + SkRRect rrect_; + SkRect paint_bounds_; + SkColor color_; + float opacity_; }; SceneUpdateContext(scenic::Session* session, @@ -152,6 +145,17 @@ class SceneUpdateContext { } const fuchsia::ui::gfx::MetricsPtr& metrics() const { return metrics_; } + void set_dimensions(const SkISize& frame_physical_size, + float frame_physical_depth, + float frame_device_pixel_ratio) { + frame_physical_size_ = frame_physical_size; + frame_physical_depth_ = frame_physical_depth; + frame_device_pixel_ratio_ = frame_device_pixel_ratio; + } + const SkISize& frame_size() const { return frame_physical_size_; } + float frame_physical_depth() const { return frame_physical_depth_; } + float frame_device_pixel_ratio() const { return frame_device_pixel_ratio_; } + // TODO(chinmaygarde): This method must submit the surfaces as soon as paint // tasks are done. However, given that there is no support currently for // Vulkan semaphores, we need to submit all the surfaces after an explicit @@ -197,6 +201,7 @@ class SceneUpdateContext { scenic::ShapeNode shape_node, const SkRRect& rrect, SkColor color, + float opacity, const SkRect& paint_bounds, std::vector paint_layers, Layer* layer); @@ -208,7 +213,9 @@ class SceneUpdateContext { std::vector paint_layers, Layer* layer, scenic::EntityNode entity_node); - void SetShapeColor(scenic::ShapeNode& shape_node, SkColor color); + void SetMaterialColor(scenic::Material& material, + SkColor color, + float opacity); scenic::Image* GenerateImageIfNeeded(SkColor color, SkScalar scale_x, SkScalar scale_y, @@ -225,6 +232,10 @@ class SceneUpdateContext { SurfaceProducer* const surface_producer_; fuchsia::ui::gfx::MetricsPtr metrics_; + SkISize frame_physical_size_; + float frame_physical_depth_ = 0.0f; + float frame_device_pixel_ratio_ = + 1.0f; // Ratio between logical and physical pixels. std::vector paint_tasks_; diff --git a/flow/skia_gpu_object.cc b/flow/skia_gpu_object.cc index dd6c6b8436dff..aadb4e3b72f71 100644 --- a/flow/skia_gpu_object.cc +++ b/flow/skia_gpu_object.cc @@ -5,14 +5,17 @@ #include "flutter/flow/skia_gpu_object.h" #include "flutter/fml/message_loop.h" +#include "flutter/fml/trace_event.h" namespace flutter { SkiaUnrefQueue::SkiaUnrefQueue(fml::RefPtr task_runner, - fml::TimeDelta delay) + fml::TimeDelta delay, + fml::WeakPtr context) : task_runner_(std::move(task_runner)), drain_delay_(delay), - drain_pending_(false) {} + drain_pending_(false), + context_(context) {} SkiaUnrefQueue::~SkiaUnrefQueue() { FML_DCHECK(objects_.empty()); @@ -29,6 +32,7 @@ void SkiaUnrefQueue::Unref(SkRefCnt* object) { } void SkiaUnrefQueue::Drain() { + TRACE_EVENT0("flutter", "SkiaUnrefQueue::Drain"); std::deque skia_objects; { std::scoped_lock lock(mutex_); @@ -39,6 +43,10 @@ void SkiaUnrefQueue::Drain() { for (SkRefCnt* skia_object : skia_objects) { skia_object->unref(); } + + if (context_ && skia_objects.size() > 0) { + context_->performDeferredCleanup(std::chrono::milliseconds(0)); + } } } // namespace flutter diff --git a/flow/skia_gpu_object.h b/flow/skia_gpu_object.h index 4c079af96ee95..37850ce0b6cc2 100644 --- a/flow/skia_gpu_object.h +++ b/flow/skia_gpu_object.h @@ -12,6 +12,7 @@ #include "flutter/fml/memory/weak_ptr.h" #include "flutter/fml/task_runner.h" #include "third_party/skia/include/core/SkRefCnt.h" +#include "third_party/skia/include/gpu/GrContext.h" namespace flutter { @@ -34,9 +35,14 @@ class SkiaUnrefQueue : public fml::RefCountedThreadSafe { std::mutex mutex_; std::deque objects_; bool drain_pending_; + fml::WeakPtr context_; + // The `GrContext* context` is only used for signaling Skia to + // performDeferredCleanup. It can be nullptr when such signaling is not needed + // (e.g., in unit tests). SkiaUnrefQueue(fml::RefPtr task_runner, - fml::TimeDelta delay); + fml::TimeDelta delay, + fml::WeakPtr context = {}); ~SkiaUnrefQueue(); @@ -54,14 +60,11 @@ class SkiaGPUObject { using SkiaObjectType = T; SkiaGPUObject() = default; - SkiaGPUObject(sk_sp object, fml::RefPtr queue) : object_(std::move(object)), queue_(std::move(queue)) { - FML_DCHECK(queue_ && object_); + FML_DCHECK(object_); } - SkiaGPUObject(SkiaGPUObject&&) = default; - ~SkiaGPUObject() { reset(); } SkiaGPUObject& operator=(SkiaGPUObject&&) = default; @@ -69,7 +72,7 @@ class SkiaGPUObject { sk_sp get() const { return object_; } void reset() { - if (object_) { + if (object_ && queue_) { queue_->Unref(object_.release()); } queue_ = nullptr; diff --git a/flow/skia_gpu_object_unittests.cc b/flow/skia_gpu_object_unittests.cc index aa259a6909eec..35737708ac5ca 100644 --- a/flow/skia_gpu_object_unittests.cc +++ b/flow/skia_gpu_object_unittests.cc @@ -3,6 +3,7 @@ // found in the LICENSE file. #include "flutter/flow/skia_gpu_object.h" + #include "flutter/fml/message_loop.h" #include "flutter/fml/synchronization/waitable_event.h" #include "flutter/fml/task_runner.h" @@ -10,11 +11,11 @@ #include "gtest/gtest.h" #include "third_party/skia/include/core/SkRefCnt.h" +#include + namespace flutter { namespace testing { -using SkiaGpuObjectTest = flutter::testing::ThreadTest; - class TestSkObject : public SkRefCnt { public: TestSkObject(std::shared_ptr latch, @@ -22,7 +23,9 @@ class TestSkObject : public SkRefCnt { : latch_(latch), dtor_task_queue_id_(dtor_task_queue_id) {} ~TestSkObject() { - *dtor_task_queue_id_ = fml::MessageLoop::GetCurrentTaskQueueId(); + if (dtor_task_queue_id_) { + *dtor_task_queue_id_ = fml::MessageLoop::GetCurrentTaskQueueId(); + } latch_->Signal(); } @@ -31,19 +34,119 @@ class TestSkObject : public SkRefCnt { fml::TaskQueueId* dtor_task_queue_id_; }; -TEST_F(SkiaGpuObjectTest, UnrefQueue) { - fml::RefPtr task_runner = CreateNewThread(); - fml::RefPtr queue = fml::MakeRefCounted( - task_runner, fml::TimeDelta::FromSeconds(0)); +class SkiaGpuObjectTest : public ThreadTest { + public: + SkiaGpuObjectTest() + : unref_task_runner_(CreateNewThread()), + unref_queue_(fml::MakeRefCounted( + unref_task_runner(), + fml::TimeDelta::FromSeconds(0))), + delayed_unref_queue_(fml::MakeRefCounted( + unref_task_runner(), + fml::TimeDelta::FromSeconds(3))) { + // The unref queues must be created in the same thread of the + // unref_task_runner so the queue can access the same-thread-only WeakPtr of + // the GrContext constructed during the creation. + std::promise queuesCreated; + unref_task_runner_->PostTask([this, &queuesCreated]() { + unref_queue_ = fml::MakeRefCounted( + unref_task_runner(), fml::TimeDelta::FromSeconds(0)); + delayed_unref_queue_ = fml::MakeRefCounted( + unref_task_runner(), fml::TimeDelta::FromSeconds(3)); + queuesCreated.set_value(true); + }); + queuesCreated.get_future().wait(); + ::testing::FLAGS_gtest_death_test_style = "threadsafe"; + } + + fml::RefPtr unref_task_runner() { + return unref_task_runner_; + } + fml::RefPtr unref_queue() { return unref_queue_; } + fml::RefPtr delayed_unref_queue() { + return delayed_unref_queue_; + } + + private: + fml::RefPtr unref_task_runner_; + fml::RefPtr unref_queue_; + fml::RefPtr delayed_unref_queue_; +}; +TEST_F(SkiaGpuObjectTest, QueueSimple) { std::shared_ptr latch = std::make_shared(); fml::TaskQueueId dtor_task_queue_id(0); SkRefCnt* ref_object = new TestSkObject(latch, &dtor_task_queue_id); - queue->Unref(ref_object); + unref_queue()->Unref(ref_object); + latch->Wait(); + ASSERT_EQ(dtor_task_queue_id, unref_task_runner()->GetTaskQueueId()); +} + +TEST_F(SkiaGpuObjectTest, ObjectDestructor) { + std::shared_ptr latch = + std::make_shared(); + fml::TaskQueueId dtor_task_queue_id(0); + + { + auto object = sk_make_sp(latch, &dtor_task_queue_id); + SkiaGPUObject sk_object(object, unref_queue()); + ASSERT_EQ(sk_object.get(), object); + ASSERT_EQ(dtor_task_queue_id, 0); + } + + latch->Wait(); + ASSERT_EQ(dtor_task_queue_id, unref_task_runner()->GetTaskQueueId()); +} + +TEST_F(SkiaGpuObjectTest, ObjectReset) { + std::shared_ptr latch = + std::make_shared(); + fml::TaskQueueId dtor_task_queue_id(0); + SkiaGPUObject sk_object( + sk_make_sp(latch, &dtor_task_queue_id), unref_queue()); + + sk_object.reset(); + ASSERT_EQ(sk_object.get(), nullptr); + + latch->Wait(); + ASSERT_EQ(dtor_task_queue_id, unref_task_runner()->GetTaskQueueId()); +} + +TEST_F(SkiaGpuObjectTest, ObjectResetBeforeDestructor) { + std::shared_ptr latch = + std::make_shared(); + fml::TaskQueueId dtor_task_queue_id(0); + + { + auto object = sk_make_sp(latch, &dtor_task_queue_id); + SkiaGPUObject sk_object(object, unref_queue()); + ASSERT_EQ(sk_object.get(), object); + ASSERT_EQ(dtor_task_queue_id, 0); + + sk_object.reset(); + ASSERT_EQ(sk_object.get(), nullptr); + } + + latch->Wait(); + ASSERT_EQ(dtor_task_queue_id, unref_task_runner()->GetTaskQueueId()); +} + +TEST_F(SkiaGpuObjectTest, ObjectResetTwice) { + std::shared_ptr latch = + std::make_shared(); + fml::TaskQueueId dtor_task_queue_id(0); + SkiaGPUObject sk_object( + sk_make_sp(latch, &dtor_task_queue_id), unref_queue()); + + sk_object.reset(); + ASSERT_EQ(sk_object.get(), nullptr); + sk_object.reset(); + ASSERT_EQ(sk_object.get(), nullptr); + latch->Wait(); - ASSERT_EQ(dtor_task_queue_id, task_runner->GetTaskQueueId()); + ASSERT_EQ(dtor_task_queue_id, unref_task_runner()->GetTaskQueueId()); } } // namespace testing diff --git a/flow/testing/layer_test.h b/flow/testing/layer_test.h new file mode 100644 index 0000000000000..593dec1836823 --- /dev/null +++ b/flow/testing/layer_test.h @@ -0,0 +1,80 @@ +// 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. + +#ifndef FLOW_TESTING_LAYER_TEST_H_ +#define FLOW_TESTING_LAYER_TEST_H_ + +#include "flutter/flow/layers/layer.h" + +#include +#include + +#include "flutter/fml/macros.h" +#include "flutter/testing/canvas_test.h" +#include "flutter/testing/mock_canvas.h" +#include "third_party/skia/include/core/SkCanvas.h" +#include "third_party/skia/include/core/SkImageInfo.h" +#include "third_party/skia/include/utils/SkNWayCanvas.h" + +namespace flutter { +namespace testing { + +// This fixture allows generating tests which can |Paint()| and |Preroll()| +// |Layer|'s. +// |LayerTest| is a default implementation based on |::testing::Test|. +// +// |BaseT| should be the base test type, such as |::testing::Test| below. +template +class LayerTestBase : public CanvasTestBase { + using TestT = CanvasTestBase; + + public: + LayerTestBase() + : preroll_context_({ + nullptr, /* raster_cache */ + nullptr, /* gr_context */ + nullptr, /* external_view_embedder */ + mutators_stack_, TestT::mock_canvas().imageInfo().colorSpace(), + kGiantRect, /* cull_rect */ + false, /* layer reads from surface */ + raster_time_, ui_time_, texture_registry_, + false, /* checkerboard_offscreen_layers */ + 100.0f, /* frame_physical_depth */ + 1.0f, /* frame_device_pixel_ratio */ + 0.0f, /* total_elevation */ + false, /* has_platform_view */ + }), + paint_context_({ + TestT::mock_canvas().internal_canvas(), /* internal_nodes_canvas */ + &TestT::mock_canvas(), /* leaf_nodes_canvas */ + nullptr, /* gr_context */ + nullptr, /* external_view_embedder */ + raster_time_, ui_time_, texture_registry_, + nullptr, /* raster_cache */ + false, /* checkerboard_offscreen_layers */ + 100.0f, /* frame_physical_depth */ + 1.0f, /* frame_device_pixel_ratio */ + }) {} + + TextureRegistry& texture_regitry() { return texture_registry_; } + PrerollContext* preroll_context() { return &preroll_context_; } + Layer::PaintContext& paint_context() { return paint_context_; } + + private: + Stopwatch raster_time_; + Stopwatch ui_time_; + MutatorsStack mutators_stack_; + TextureRegistry texture_registry_; + + PrerollContext preroll_context_; + Layer::PaintContext paint_context_; + + FML_DISALLOW_COPY_AND_ASSIGN(LayerTestBase); +}; +using LayerTest = LayerTestBase<::testing::Test>; + +} // namespace testing +} // namespace flutter + +#endif // FLOW_TESTING_LAYER_TEST_H_ diff --git a/flow/testing/mock_layer.cc b/flow/testing/mock_layer.cc new file mode 100644 index 0000000000000..5fe1b98088af1 --- /dev/null +++ b/flow/testing/mock_layer.cc @@ -0,0 +1,43 @@ +// 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/flow/testing/mock_layer.h" + +namespace flutter { +namespace testing { + +MockLayer::MockLayer(SkPath path, + SkPaint paint, + bool fake_has_platform_view, + bool fake_needs_system_composite, + bool fake_reads_surface) + : fake_paint_path_(path), + fake_paint_(paint), + fake_has_platform_view_(fake_has_platform_view), + fake_needs_system_composite_(fake_needs_system_composite), + fake_reads_surface_(fake_reads_surface) {} + +void MockLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { + parent_mutators_ = context->mutators_stack; + parent_matrix_ = matrix; + parent_cull_rect_ = context->cull_rect; + parent_elevation_ = context->total_elevation; + parent_has_platform_view_ = context->has_platform_view; + + context->has_platform_view = fake_has_platform_view_; + set_paint_bounds(fake_paint_path_.getBounds()); + set_needs_system_composite(fake_needs_system_composite_); + if (fake_reads_surface_) { + context->surface_needs_readback = true; + } +} + +void MockLayer::Paint(PaintContext& context) const { + FML_DCHECK(needs_painting()); + + context.leaf_nodes_canvas->drawPath(fake_paint_path_, fake_paint_); +} + +} // namespace testing +} // namespace flutter diff --git a/flow/testing/mock_layer.h b/flow/testing/mock_layer.h new file mode 100644 index 0000000000000..835c3ee9621cc --- /dev/null +++ b/flow/testing/mock_layer.h @@ -0,0 +1,52 @@ +// 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. + +#ifndef FLOW_TESTING_MOCK_LAYER_H_ +#define FLOW_TESTING_MOCK_LAYER_H_ + +#include "flutter/flow/layers/layer.h" + +namespace flutter { +namespace testing { + +// Mock implementation of the |Layer| interface that does nothing but paint +// the specified |path| into the canvas. It records the |PrerollContext| and +// |PaintContext| data passed in by its parent |Layer|, so the test can later +// verify the data against expected values. +class MockLayer : public Layer { + public: + MockLayer(SkPath path, + SkPaint paint = SkPaint(), + bool fake_has_platform_view = false, + bool fake_needs_system_composite = false, + bool fake_reads_surface = false); + + void Preroll(PrerollContext* context, const SkMatrix& matrix) override; + void Paint(PaintContext& context) const override; + + const MutatorsStack& parent_mutators() { return parent_mutators_; } + const SkMatrix& parent_matrix() { return parent_matrix_; } + const SkRect& parent_cull_rect() { return parent_cull_rect_; } + float parent_elevation() { return parent_elevation_; } + bool parent_has_platform_view() { return parent_has_platform_view_; } + + private: + MutatorsStack parent_mutators_; + SkMatrix parent_matrix_; + SkRect parent_cull_rect_ = SkRect::MakeEmpty(); + SkPath fake_paint_path_; + SkPaint fake_paint_; + float parent_elevation_ = 0; + bool parent_has_platform_view_ = false; + bool fake_has_platform_view_ = false; + bool fake_needs_system_composite_ = false; + bool fake_reads_surface_ = false; + + FML_DISALLOW_COPY_AND_ASSIGN(MockLayer); +}; + +} // namespace testing +} // namespace flutter + +#endif // FLOW_TESTING_MOCK_LAYER_H_ diff --git a/flow/testing/mock_layer_unittests.cc b/flow/testing/mock_layer_unittests.cc new file mode 100644 index 0000000000000..d5a215fde5c76 --- /dev/null +++ b/flow/testing/mock_layer_unittests.cc @@ -0,0 +1,87 @@ +// 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/flow/testing/mock_layer.h" + +#include "flutter/flow/testing/layer_test.h" +#include "flutter/fml/macros.h" +#include "flutter/testing/mock_canvas.h" + +namespace flutter { +namespace testing { + +using MockLayerTest = LayerTest; + +#ifndef NDEBUG +TEST_F(MockLayerTest, PaintBeforePreollDies) { + SkPath path = SkPath().addRect(5.0f, 6.0f, 20.5f, 21.5f); + auto layer = std::make_shared(path, SkPaint()); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} + +TEST_F(MockLayerTest, PaintingEmptyLayerDies) { + auto layer = std::make_shared(SkPath(), SkPaint()); + + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(layer->paint_bounds(), SkPath().getBounds()); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(\\)"); +} +#endif + +TEST_F(MockLayerTest, SimpleParams) { + const SkPath path = SkPath().addRect(5.0f, 6.0f, 20.5f, 21.5f); + const SkPaint paint = SkPaint(SkColors::kBlue); + const SkMatrix start_matrix = SkMatrix::MakeTrans(1.0f, 2.0f); + const SkMatrix scale_matrix = SkMatrix::MakeScale(0.5f, 0.5f); + const SkRect cull_rect = SkRect::MakeWH(5.0f, 5.0f); + const float parent_elevation = 5.0f; + const bool parent_has_platform_view = true; + auto layer = std::make_shared(path, paint); + + preroll_context()->mutators_stack.PushTransform(scale_matrix); + preroll_context()->cull_rect = cull_rect; + preroll_context()->total_elevation = parent_elevation; + preroll_context()->has_platform_view = parent_has_platform_view; + layer->Preroll(preroll_context(), start_matrix); + EXPECT_EQ(preroll_context()->has_platform_view, false); + EXPECT_EQ(layer->paint_bounds(), path.getBounds()); + EXPECT_TRUE(layer->needs_painting()); + EXPECT_FALSE(layer->needs_system_composite()); + EXPECT_EQ(layer->parent_mutators(), std::vector{Mutator(scale_matrix)}); + EXPECT_EQ(layer->parent_matrix(), start_matrix); + EXPECT_EQ(layer->parent_cull_rect(), cull_rect); + EXPECT_EQ(layer->parent_elevation(), parent_elevation); + EXPECT_EQ(layer->parent_has_platform_view(), parent_has_platform_view); + + layer->Paint(paint_context()); + EXPECT_EQ(mock_canvas().draw_calls(), + std::vector({MockCanvas::DrawCall{ + 0, MockCanvas::DrawPathData{path, paint}}})); +} + +TEST_F(MockLayerTest, FakePlatformView) { + auto layer = std::make_shared(SkPath(), SkPaint(), + true /* fake_has_platform_view */); + EXPECT_EQ(preroll_context()->has_platform_view, false); + + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(preroll_context()->has_platform_view, true); +} + +TEST_F(MockLayerTest, FakeSystemComposite) { + auto layer = std::make_shared( + SkPath(), SkPaint(), false /* fake_has_platform_view */, + true /* fake_needs_system_composite */); + EXPECT_EQ(layer->needs_system_composite(), false); + + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(layer->needs_system_composite(), true); +} + +} // namespace testing +} // namespace flutter diff --git a/flow/testing/mock_texture.cc b/flow/testing/mock_texture.cc new file mode 100644 index 0000000000000..26e49b764cdaf --- /dev/null +++ b/flow/testing/mock_texture.cc @@ -0,0 +1,31 @@ +// 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/flow/testing/mock_texture.h" + +namespace flutter { +namespace testing { + +MockTexture::MockTexture(int64_t textureId) : Texture(textureId) {} + +void MockTexture::Paint(SkCanvas& canvas, + const SkRect& bounds, + bool freeze, + GrContext* context) { + paint_calls_.emplace_back(PaintCall{canvas, bounds, freeze, context}); +} + +bool operator==(const MockTexture::PaintCall& a, + const MockTexture::PaintCall& b) { + return &a.canvas == &b.canvas && a.bounds == b.bounds && + a.context == b.context && a.freeze == b.freeze; +} + +std::ostream& operator<<(std::ostream& os, const MockTexture::PaintCall& data) { + return os << &data.canvas << " " << data.bounds << " " << data.context << " " + << data.freeze; +} + +} // namespace testing +} // namespace flutter diff --git a/flow/testing/mock_texture.h b/flow/testing/mock_texture.h new file mode 100644 index 0000000000000..c5339ebb77a66 --- /dev/null +++ b/flow/testing/mock_texture.h @@ -0,0 +1,57 @@ +// 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/flow/texture.h" +#include "flutter/testing/assertions_skia.h" + +#include +#include + +namespace flutter { +namespace testing { + +// Mock implementation of the |Texture| interface that does not interact with +// the GPU. It simply records the list of various calls made so the test can +// later verify them against expected data. +class MockTexture : public Texture { + public: + struct PaintCall { + SkCanvas& canvas; + SkRect bounds; + bool freeze; + GrContext* context; + }; + + explicit MockTexture(int64_t textureId); + + // Called from GPU thread. + void Paint(SkCanvas& canvas, + const SkRect& bounds, + bool freeze, + GrContext* context) override; + + void OnGrContextCreated() override { gr_context_created_ = true; } + void OnGrContextDestroyed() override { gr_context_destroyed_ = true; } + void MarkNewFrameAvailable() override {} + void OnTextureUnregistered() override { unregistered_ = true; } + + const std::vector& paint_calls() { return paint_calls_; } + bool gr_context_created() { return gr_context_created_; } + bool gr_context_destroyed() { return gr_context_destroyed_; } + bool unregistered() { return unregistered_; } + + private: + std::vector paint_calls_; + bool gr_context_created_ = false; + bool gr_context_destroyed_ = false; + bool unregistered_ = false; +}; + +extern bool operator==(const MockTexture::PaintCall& a, + const MockTexture::PaintCall& b); +extern std::ostream& operator<<(std::ostream& os, + const MockTexture::PaintCall& data); + +} // namespace testing +} // namespace flutter diff --git a/flow/testing/mock_texture_unittests.cc b/flow/testing/mock_texture_unittests.cc new file mode 100644 index 0000000000000..107eb76307928 --- /dev/null +++ b/flow/testing/mock_texture_unittests.cc @@ -0,0 +1,43 @@ +// 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/flow/testing/mock_texture.h" + +#include "gtest/gtest.h" + +namespace flutter { +namespace testing { + +TEST(MockTextureTest, Callbacks) { + auto texture = std::make_shared(0); + + ASSERT_FALSE(texture->gr_context_created()); + texture->OnGrContextCreated(); + ASSERT_TRUE(texture->gr_context_created()); + + ASSERT_FALSE(texture->gr_context_destroyed()); + texture->OnGrContextDestroyed(); + ASSERT_TRUE(texture->gr_context_destroyed()); + + ASSERT_FALSE(texture->unregistered()); + texture->OnTextureUnregistered(); + ASSERT_TRUE(texture->unregistered()); +} + +TEST(MockTextureTest, PaintCalls) { + SkCanvas canvas; + const SkRect paint_bounds1 = SkRect::MakeWH(1.0f, 1.0f); + const SkRect paint_bounds2 = SkRect::MakeWH(2.0f, 2.0f); + const auto expected_paint_calls = + std::vector{MockTexture::PaintCall{canvas, paint_bounds1, false, nullptr}, + MockTexture::PaintCall{canvas, paint_bounds2, true, nullptr}}; + auto texture = std::make_shared(0); + + texture->Paint(canvas, paint_bounds1, false, nullptr); + texture->Paint(canvas, paint_bounds2, true, nullptr); + EXPECT_EQ(texture->paint_calls(), expected_paint_calls); +} + +} // namespace testing +} // namespace flutter diff --git a/flow/testing/skia_gpu_object_layer_test.cc b/flow/testing/skia_gpu_object_layer_test.cc new file mode 100644 index 0000000000000..1fca8ec8f3b81 --- /dev/null +++ b/flow/testing/skia_gpu_object_layer_test.cc @@ -0,0 +1,18 @@ +// 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/flow/testing/skia_gpu_object_layer_test.h" + +#include "flutter/fml/time/time_delta.h" + +namespace flutter { +namespace testing { + +SkiaGPUObjectLayerTest::SkiaGPUObjectLayerTest() + : unref_queue_(fml::MakeRefCounted( + GetCurrentTaskRunner(), + fml::TimeDelta::FromSeconds(0))) {} + +} // namespace testing +} // namespace flutter diff --git a/flow/testing/skia_gpu_object_layer_test.h b/flow/testing/skia_gpu_object_layer_test.h new file mode 100644 index 0000000000000..d573ac0b41007 --- /dev/null +++ b/flow/testing/skia_gpu_object_layer_test.h @@ -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. + +#ifndef FLOW_TESTING_SKIA_GPU_OBJECT_LAYER_TEST_H_ +#define FLOW_TESTING_SKIA_GPU_OBJECT_LAYER_TEST_H_ + +#include "flutter/flow/skia_gpu_object.h" +#include "flutter/flow/testing/layer_test.h" +#include "flutter/testing/thread_test.h" + +namespace flutter { +namespace testing { + +// This fixture allows generating tests that create |SkiaGPUObject|'s which +// are destroyed on a |SkiaUnrefQueue|. +class SkiaGPUObjectLayerTest : public LayerTestBase { + public: + SkiaGPUObjectLayerTest(); + + fml::RefPtr unref_queue() { return unref_queue_; } + + private: + fml::RefPtr unref_queue_; +}; + +} // namespace testing +} // namespace flutter + +#endif // FLOW_TESTING_SKIA_GPU_OBJECT_LAYER_TEST_H_ diff --git a/flow/texture.cc b/flow/texture.cc index 6f25c6df89593..15c93d360366e 100644 --- a/flow/texture.cc +++ b/flow/texture.cc @@ -6,9 +6,11 @@ namespace flutter { -TextureRegistry::TextureRegistry() = default; +Texture::Texture(int64_t id) : id_(id) {} -TextureRegistry::~TextureRegistry() = default; +Texture::~Texture() = default; + +TextureRegistry::TextureRegistry() = default; void TextureRegistry::RegisterTexture(std::shared_ptr texture) { mapping_[texture->Id()] = texture; @@ -36,8 +38,4 @@ std::shared_ptr TextureRegistry::GetTexture(int64_t id) { return it != mapping_.end() ? it->second : nullptr; } -Texture::Texture(int64_t id) : id_(id) {} - -Texture::~Texture() = default; - } // namespace flutter diff --git a/flow/texture.h b/flow/texture.h index 6e06445884b66..812588d382bb1 100644 --- a/flow/texture.h +++ b/flow/texture.h @@ -14,12 +14,9 @@ namespace flutter { class Texture { - protected: - Texture(int64_t id); - public: - // Called from GPU thread. - virtual ~Texture(); + Texture(int64_t id); // Called from UI or GPU thread. + virtual ~Texture(); // Called from GPU thread. // Called from GPU thread. virtual void Paint(SkCanvas& canvas, @@ -50,7 +47,6 @@ class Texture { class TextureRegistry { public: TextureRegistry(); - ~TextureRegistry(); // Called from GPU thread. void RegisterTexture(std::shared_ptr texture); diff --git a/flow/texture_unittests.cc b/flow/texture_unittests.cc index d292e3965af87..f3eb0fc0931ac 100644 --- a/flow/texture_unittests.cc +++ b/flow/texture_unittests.cc @@ -2,44 +2,97 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "flutter/flow/testing/mock_texture.h" #include "flutter/flow/texture.h" + #include "gtest/gtest.h" namespace flutter { namespace testing { -class MockTexture : public Texture { - public: - MockTexture(int64_t textureId) : Texture(textureId) {} +TEST(TextureRegistryTest, UnregisterTextureCallbackTriggered) { + TextureRegistry registry; + auto mock_texture1 = std::make_shared(0); + auto mock_texture2 = std::make_shared(1); + + registry.RegisterTexture(mock_texture1); + registry.RegisterTexture(mock_texture2); + ASSERT_EQ(registry.GetTexture(0), mock_texture1); + ASSERT_EQ(registry.GetTexture(1), mock_texture2); + ASSERT_FALSE(mock_texture1->unregistered()); + ASSERT_FALSE(mock_texture2->unregistered()); + + registry.UnregisterTexture(0); + ASSERT_EQ(registry.GetTexture(0), nullptr); + ASSERT_TRUE(mock_texture1->unregistered()); + ASSERT_FALSE(mock_texture2->unregistered()); + + registry.UnregisterTexture(1); + ASSERT_EQ(registry.GetTexture(1), nullptr); + ASSERT_TRUE(mock_texture1->unregistered()); + ASSERT_TRUE(mock_texture2->unregistered()); +} + +TEST(TextureRegistryTest, GrContextCallbackTriggered) { + TextureRegistry registry; + auto mock_texture1 = std::make_shared(0); + auto mock_texture2 = std::make_shared(1); + + registry.RegisterTexture(mock_texture1); + registry.RegisterTexture(mock_texture2); + ASSERT_FALSE(mock_texture1->gr_context_created()); + ASSERT_FALSE(mock_texture2->gr_context_created()); + ASSERT_FALSE(mock_texture1->gr_context_destroyed()); + ASSERT_FALSE(mock_texture2->gr_context_destroyed()); - ~MockTexture() override = default; + registry.OnGrContextCreated(); + ASSERT_TRUE(mock_texture1->gr_context_created()); + ASSERT_TRUE(mock_texture2->gr_context_created()); - // Called from GPU thread. - void Paint(SkCanvas& canvas, - const SkRect& bounds, - bool freeze, - GrContext* context) override {} + registry.UnregisterTexture(0); + registry.OnGrContextDestroyed(); + ASSERT_FALSE(mock_texture1->gr_context_destroyed()); + ASSERT_TRUE(mock_texture2->gr_context_created()); +} - void OnGrContextCreated() override {} +TEST(TextureRegistryTest, RegisterTextureTwice) { + TextureRegistry registry; + auto mock_texture1 = std::make_shared(0); + auto mock_texture2 = std::make_shared(0); - void OnGrContextDestroyed() override {} + registry.RegisterTexture(mock_texture1); + ASSERT_EQ(registry.GetTexture(0), mock_texture1); + registry.RegisterTexture(mock_texture2); + ASSERT_EQ(registry.GetTexture(0), mock_texture2); + ASSERT_FALSE(mock_texture1->unregistered()); + ASSERT_FALSE(mock_texture2->unregistered()); + + registry.UnregisterTexture(0); + ASSERT_EQ(registry.GetTexture(0), nullptr); + ASSERT_FALSE(mock_texture1->unregistered()); + ASSERT_TRUE(mock_texture2->unregistered()); +} - void MarkNewFrameAvailable() override {} +TEST(TextureRegistryTest, ReuseSameTextureSlot) { + TextureRegistry registry; + auto mock_texture1 = std::make_shared(0); + auto mock_texture2 = std::make_shared(0); - void OnTextureUnregistered() override { unregistered_ = true; } + registry.RegisterTexture(mock_texture1); + ASSERT_EQ(registry.GetTexture(0), mock_texture1); - bool unregistered() { return unregistered_; } + registry.UnregisterTexture(0); + ASSERT_EQ(registry.GetTexture(0), nullptr); + ASSERT_TRUE(mock_texture1->unregistered()); + ASSERT_FALSE(mock_texture2->unregistered()); - private: - bool unregistered_ = false; -}; + registry.RegisterTexture(mock_texture2); + ASSERT_EQ(registry.GetTexture(0), mock_texture2); -TEST(TextureRegistry, UnregisterTextureCallbackTriggered) { - TextureRegistry textureRegistry; - std::shared_ptr mockTexture = std::make_shared(0); - textureRegistry.RegisterTexture(mockTexture); - textureRegistry.UnregisterTexture(0); - ASSERT_TRUE(mockTexture->unregistered()); + registry.UnregisterTexture(0); + ASSERT_EQ(registry.GetTexture(0), nullptr); + ASSERT_TRUE(mock_texture1->unregistered()); + ASSERT_TRUE(mock_texture2->unregistered()); } } // namespace testing diff --git a/flow/view_holder.cc b/flow/view_holder.cc index 688f56d0987a7..7f8929d933705 100644 --- a/flow/view_holder.cc +++ b/flow/view_holder.cc @@ -51,7 +51,7 @@ namespace flutter { void ViewHolder::Create(zx_koid_t id, fml::RefPtr ui_task_runner, fuchsia::ui::views::ViewHolderToken view_holder_token, - BindCallback on_bind_callback) { + const BindCallback& on_bind_callback) { // This GPU thread contains at least 1 ViewHolder. Initialize the per-thread // bindings. if (tls_view_holder_bindings.get() == nullptr) { @@ -64,7 +64,7 @@ void ViewHolder::Create(zx_koid_t id, auto view_holder = std::make_unique(std::move(ui_task_runner), std::move(view_holder_token), - std::move(on_bind_callback)); + on_bind_callback); bindings->emplace(id, std::move(view_holder)); } @@ -91,10 +91,10 @@ ViewHolder* ViewHolder::FromId(zx_koid_t id) { ViewHolder::ViewHolder(fml::RefPtr ui_task_runner, fuchsia::ui::views::ViewHolderToken view_holder_token, - BindCallback on_bind_callback) + const BindCallback& on_bind_callback) : ui_task_runner_(std::move(ui_task_runner)), pending_view_holder_token_(std::move(view_holder_token)), - pending_bind_callback_(std::move(on_bind_callback)) { + pending_bind_callback_(on_bind_callback) { FML_DCHECK(ui_task_runner_); FML_DCHECK(pending_view_holder_token_.value); } @@ -104,14 +104,11 @@ void ViewHolder::UpdateScene(SceneUpdateContext& context, const SkSize& size, bool hit_testable) { if (pending_view_holder_token_.value) { - opacity_node_ = - std::make_unique(context.session()); entity_node_ = std::make_unique(context.session()); view_holder_ = std::make_unique( context.session(), std::move(pending_view_holder_token_), "Flutter SceneHost"); - opacity_node_->AddChild(*entity_node_); entity_node_->Attach(*view_holder_); ui_task_runner_->PostTask( [bind_callback = std::move(pending_bind_callback_), @@ -119,20 +116,18 @@ void ViewHolder::UpdateScene(SceneUpdateContext& context, bind_callback(view_holder_id); }); } - FML_DCHECK(opacity_node_); + FML_DCHECK(entity_node_); FML_DCHECK(view_holder_); - context.top_entity()->entity_node().AddChild(*opacity_node_); + context.top_entity()->embedder_node().AddChild(*entity_node_); entity_node_->SetTranslation(offset.x(), offset.y(), -0.1f); entity_node_->SetHitTestBehavior( hit_testable ? fuchsia::ui::gfx::HitTestBehavior::kDefault : fuchsia::ui::gfx::HitTestBehavior::kSuppress); - if (has_pending_opacity_) { - opacity_node_->SetOpacity(pending_opacity_); - - has_pending_opacity_ = false; - } if (has_pending_properties_) { + // TODO(dworsham): This should be derived from size and elevation. We + // should be able to Z-limit the view's box but otherwise it uses all of the + // available airspace. view_holder_->SetViewProperties(std::move(pending_properties_)); has_pending_properties_ = false; @@ -151,9 +146,4 @@ void ViewHolder::SetProperties(double width, has_pending_properties_ = true; } -void ViewHolder::SetOpacity(double opacity) { - pending_opacity_ = std::clamp(opacity, 0.0, 1.0); - has_pending_opacity_ = true; -} - } // namespace flutter diff --git a/flow/view_holder.h b/flow/view_holder.h index 9466f644aee23..82d43eba826d4 100644 --- a/flow/view_holder.h +++ b/flow/view_holder.h @@ -34,13 +34,13 @@ class ViewHolder { static void Create(zx_koid_t id, fml::RefPtr ui_task_runner, fuchsia::ui::views::ViewHolderToken view_holder_token, - BindCallback on_bind_callback); + const BindCallback& on_bind_callback); static void Destroy(zx_koid_t id); static ViewHolder* FromId(zx_koid_t id); ViewHolder(fml::RefPtr ui_task_runner, fuchsia::ui::views::ViewHolderToken view_holder_token, - BindCallback on_bind_callback); + const BindCallback& on_bind_callback); ~ViewHolder() = default; // Sets the properties/opacity of the child view by issuing a Scenic command. @@ -51,7 +51,6 @@ class ViewHolder { double insetBottom, double insetLeft, bool focusable); - void SetOpacity(double opacity); // Creates or updates the contained ViewHolder resource using the specified // |SceneUpdateContext|. @@ -63,7 +62,6 @@ class ViewHolder { private: fml::RefPtr ui_task_runner_; - std::unique_ptr opacity_node_; std::unique_ptr entity_node_; std::unique_ptr view_holder_; @@ -71,9 +69,7 @@ class ViewHolder { BindCallback pending_bind_callback_; fuchsia::ui::gfx::ViewProperties pending_properties_; - double pending_opacity_; bool has_pending_properties_ = false; - bool has_pending_opacity_ = false; FML_DISALLOW_COPY_AND_ASSIGN(ViewHolder); }; diff --git a/flutter_frontend_server/BUILD.gn b/flutter_frontend_server/BUILD.gn index 75887f22cec64..06aeb3f73453d 100644 --- a/flutter_frontend_server/BUILD.gn +++ b/flutter_frontend_server/BUILD.gn @@ -2,6 +2,10 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. +import("//third_party/dart/utils/application_snapshot.gni") + +# TODO(kaushikiska@): Leaving this here to make it a soft-transition +# Remove this once we no longer need engine sources in fuchsia/topaz. if (is_fuchsia_host || is_fuchsia) { import("//build/dart/dart_library.gni") import("//build/dart/dart_tool.gni") @@ -42,62 +46,61 @@ if (is_fuchsia_host || is_fuchsia) { ":flutter_frontend_server", ] } -} else { - import("//third_party/dart/utils/application_snapshot.gni") - - frontend_server_files = - exec_script("//third_party/dart/tools/list_dart_files.py", - [ - "absolute", - rebase_path("."), - ], - "list lines") - - frontend_server_files += - exec_script("//third_party/dart/tools/list_dart_files.py", - [ - "absolute", - rebase_path("../../third_party/dart/pkg"), - ], - "list lines") - - application_snapshot("frontend_server") { - main_dart = "bin/starter.dart" - deps = [ - ":package_incremental_compiler", - "$flutter_root/lib/snapshot:kernel_platform_files", - ] - dot_packages = rebase_path(".packages") - flutter_patched_sdk = rebase_path("$root_out_dir/flutter_patched_sdk") - training_args = [ - "--train", - "--sdk-root=$flutter_patched_sdk", - rebase_path(main_dart), - ] +} - inputs = frontend_server_files - } +frontend_server_files = + exec_script("//third_party/dart/tools/list_dart_files.py", + [ + "absolute", + rebase_path("."), + ], + "list lines") - # For flutter/flutter#36738 we make the source files available so that - # we can generate a local frontend_server snapshot in the tools cache. - action("package_incremental_compiler") { - script = "$flutter_root/flutter_frontend_server/package_incremental.py" +frontend_server_files += + exec_script("//third_party/dart/tools/list_dart_files.py", + [ + "absolute", + rebase_path("../../third_party/dart/pkg"), + ], + "list lines") - inputs = frontend_server_files +application_snapshot("frontend_server") { + main_dart = "bin/starter.dart" + deps = [ + ":package_incremental_compiler", + "$flutter_root/lib/snapshot:kernel_platform_files", + ] + dot_packages = rebase_path(".packages") + flutter_patched_sdk = rebase_path("$root_out_dir/flutter_patched_sdk") + training_args = [ + "--train", + "--sdk-root=$flutter_patched_sdk", + rebase_path(main_dart), + ] - outputs = [ - "$root_gen_dir/dart-pkg/flutter_frontend_server/pubspec.yaml", - "$root_gen_dir/dart-pkg/vm/pubspec.yaml", - "$root_gen_dir/dart-pkg/build_integration/pubspec.yaml", - "$root_gen_dir/dart-pkg/front_end/pubspec.yaml", - "$root_gen_dir/dart-pkg/kernel/pubspec.yaml", - "$root_gen_dir/dart-pkg/dev_compiler/pubspec.yaml", - ] + inputs = frontend_server_files +} - args = [ - "--input-root=" + rebase_path("//third_party/dart/pkg"), - "--output-root=" + rebase_path("$root_gen_dir/dart-pkg"), - "--frontend-server=" + rebase_path("$flutter_root"), - ] - } +# For flutter/flutter#36738 we make the source files available so that +# we can generate a local frontend_server snapshot in the tools cache. +action("package_incremental_compiler") { + script = "$flutter_root/flutter_frontend_server/package_incremental.py" + + inputs = frontend_server_files + + outputs = [ + "$root_gen_dir/dart-pkg/flutter_frontend_server/pubspec.yaml", + "$root_gen_dir/dart-pkg/vm/pubspec.yaml", + "$root_gen_dir/dart-pkg/build_integration/pubspec.yaml", + "$root_gen_dir/dart-pkg/front_end/pubspec.yaml", + "$root_gen_dir/dart-pkg/kernel/pubspec.yaml", + "$root_gen_dir/dart-pkg/dev_compiler/pubspec.yaml", + "$root_gen_dir/dart-pkg/frontend_server/pubspec.yaml", + ] + + args = [ + "--input-root=" + rebase_path("//third_party/dart/pkg"), + "--output-root=" + rebase_path("$root_gen_dir/dart-pkg"), + "--frontend-server=" + rebase_path("$flutter_root"), + ] } diff --git a/flutter_frontend_server/package_incremental.py b/flutter_frontend_server/package_incremental.py index a8b42426023b5..8c54f31780bfd 100755 --- a/flutter_frontend_server/package_incremental.py +++ b/flutter_frontend_server/package_incremental.py @@ -16,6 +16,8 @@ "front_end", "dev_compiler", "flutter_frontend_server", + "frontend_server", + "dev_compiler", ] VM_PUBSPEC = r'''name: vm @@ -84,6 +86,19 @@ source_maps: any ''' +FRONTEND_SERVER_PUBSPEC = r'''name: frontend_server +version: 0.0.1 +environment: + sdk: '>=2.2.2 < 3.0.0' +dependencies: + build_integration: any + vm: any + dev_compiler: any + front_end: any + kernel: any + args: any +''' + PUBSPECS = { 'vm': VM_PUBSPEC, 'build_integration': BUILD_INTEGRATION_PUBSPEC, @@ -91,6 +106,7 @@ 'kernel': KERNEL_PUBSPEC, 'front_end': FRONT_END_PUBSPEC, 'dev_compiler': DEV_COMPILER_PUBSPEC, + 'frontend_server': FRONTEND_SERVER_PUBSPEC, } def main(): diff --git a/fml/BUILD.gn b/fml/BUILD.gn index bc66d23626633..8deb8dbdde263 100644 --- a/fml/BUILD.gn +++ b/fml/BUILD.gn @@ -2,7 +2,10 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -import("//build/fuchsia/sdk.gni") +if (is_fuchsia) { + import("//build/fuchsia/sdk.gni") + import("$flutter_root/tools/fuchsia/fuchsia_archive.gni") +} import("$flutter_root/testing/testing.gni") source_set("fml") { @@ -60,6 +63,8 @@ source_set("fml") { "synchronization/semaphore.cc", "synchronization/semaphore.h", "synchronization/shared_mutex.h", + "synchronization/sync_switch.cc", + "synchronization/sync_switch.h", "synchronization/waitable_event.cc", "synchronization/waitable_event.h", "task_runner.cc", @@ -158,12 +163,28 @@ source_set("fml") { } if (is_fuchsia) { - sources += [ "platform/fuchsia/paths_fuchsia.cc" ] + sources += [ + "platform/fuchsia/message_loop_fuchsia.cc", + "platform/fuchsia/message_loop_fuchsia.h", + "platform/fuchsia/paths_fuchsia.cc", + ] if (using_fuchsia_sdk) { - public_deps += [ "$fuchsia_sdk_root/pkg:trace" ] + public_deps += [ + "$fuchsia_sdk_root/pkg:async-cpp", + "$fuchsia_sdk_root/pkg:async-loop-cpp", + "$fuchsia_sdk_root/pkg:async-loop-default", + "$fuchsia_sdk_root/pkg:trace", + "$fuchsia_sdk_root/pkg:zx", + ] } else { - public_deps += [ "//zircon/public/lib/trace" ] + public_deps += [ + "//zircon/public/lib/async-cpp", + "//zircon/public/lib/async-loop-cpp", + "//zircon/public/lib/async-loop-default", + "//zircon/public/lib/trace", + "//zircon/public/lib/zx", + ] } } @@ -199,18 +220,17 @@ executable("fml_unittests") { sources = [ "base32_unittest.cc", "command_line_unittest.cc", - "file_unittest.cc", "gpu_thread_merger_unittests.cc", "memory/ref_counted_unittest.cc", "memory/weak_ptr_unittest.cc", "message_loop_task_queues_merge_unmerge_unittests.cc", "message_loop_task_queues_unittests.cc", - "message_loop_unittests.cc", "message_unittests.cc", "paths_unittests.cc", "platform/darwin/string_range_sanitization_unittests.mm", "synchronization/count_down_latch_unittests.cc", "synchronization/semaphore_unittest.cc", + "synchronization/sync_switch_unittest.cc", "synchronization/waitable_event_unittest.cc", "thread_local_unittests.cc", "thread_unittests.cc", @@ -219,12 +239,35 @@ executable("fml_unittests") { "time/time_unittest.cc", ] + # TODO(gw280): Figure out why these tests don't work currently on Fuchsia + if (!is_fuchsia) { + sources += [ + "file_unittest.cc", + "message_loop_unittests.cc", + ] + } + deps = [ ":fml_fixtures", "$flutter_root/fml", + "$flutter_root/fml/dart", + "$flutter_root/runtime:libdart", "$flutter_root/testing", - "//third_party/dart/runtime:libdart_jit", ] + + if (is_fuchsia && using_fuchsia_sdk) { + libs = [ "${fuchsia_sdk_path}/arch/${target_cpu}/sysroot/lib/libzircon.so" ] + } +} + +if (is_fuchsia) { + fuchsia_test_archive("fml_tests") { + deps = [ + ":fml_unittests", + ] + + binary = "fml_unittests" + } } executable("fml_benchmarks") { diff --git a/fml/closure.h b/fml/closure.h index c67e4d4554f95..ca7f60fdbbd32 100644 --- a/fml/closure.h +++ b/fml/closure.h @@ -31,7 +31,9 @@ using closure = std::function; /// class ScopedCleanupClosure { public: - ScopedCleanupClosure(fml::closure closure) : closure_(closure) {} + ScopedCleanupClosure() = default; + + ScopedCleanupClosure(const fml::closure& closure) : closure_(closure) {} ~ScopedCleanupClosure() { if (closure_) { @@ -39,6 +41,12 @@ class ScopedCleanupClosure { } } + fml::closure SetClosure(const fml::closure& closure) { + auto old_closure = closure_; + closure_ = closure; + return old_closure; + } + fml::closure Release() { fml::closure closure = closure_; closure_ = nullptr; diff --git a/fml/concurrent_message_loop.cc b/fml/concurrent_message_loop.cc index 482ceb479018d..a116356632856 100644 --- a/fml/concurrent_message_loop.cc +++ b/fml/concurrent_message_loop.cc @@ -43,7 +43,7 @@ std::shared_ptr ConcurrentMessageLoop::GetTaskRunner() { return std::make_shared(weak_from_this()); } -void ConcurrentMessageLoop::PostTask(fml::closure task) { +void ConcurrentMessageLoop::PostTask(const fml::closure& task) { if (!task) { return; } @@ -107,7 +107,7 @@ ConcurrentTaskRunner::ConcurrentTaskRunner( ConcurrentTaskRunner::~ConcurrentTaskRunner() = default; -void ConcurrentTaskRunner::PostTask(fml::closure task) { +void ConcurrentTaskRunner::PostTask(const fml::closure& task) { if (!task) { return; } diff --git a/fml/concurrent_message_loop.h b/fml/concurrent_message_loop.h index 1642c58d3632d..a3487b6c119ee 100644 --- a/fml/concurrent_message_loop.h +++ b/fml/concurrent_message_loop.h @@ -44,7 +44,7 @@ class ConcurrentMessageLoop void WorkerMain(); - void PostTask(fml::closure task); + void PostTask(const fml::closure& task); FML_DISALLOW_COPY_AND_ASSIGN(ConcurrentMessageLoop); }; @@ -55,7 +55,7 @@ class ConcurrentTaskRunner { ~ConcurrentTaskRunner(); - void PostTask(fml::closure task); + void PostTask(const fml::closure& task); private: friend ConcurrentMessageLoop; diff --git a/fml/dart/BUILD.gn b/fml/dart/BUILD.gn new file mode 100644 index 0000000000000..c6b3883955e81 --- /dev/null +++ b/fml/dart/BUILD.gn @@ -0,0 +1,19 @@ +# 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. + +# Utilities for working with FML types with the Dart API. Targets that use FML +# as well as Dart must explicitly include this target as FML itself cannot +# depend on Dart. +source_set("dart") { + sources = [ + "dart_converter.cc", + "dart_converter.h", + ] + + public_deps = [ + "$flutter_root/fml", + "$flutter_root/runtime:libdart", + "//third_party/tonic", + ] +} diff --git a/fml/dart/dart_converter.cc b/fml/dart/dart_converter.cc new file mode 100644 index 0000000000000..7e17bd8bb3c30 --- /dev/null +++ b/fml/dart/dart_converter.cc @@ -0,0 +1,11 @@ +// 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/fml/dart/dart_converter.h" + +namespace fml { + +// + +} // namespace fml diff --git a/fml/dart/dart_converter.h b/fml/dart/dart_converter.h new file mode 100644 index 0000000000000..159ec44e25c31 --- /dev/null +++ b/fml/dart/dart_converter.h @@ -0,0 +1,123 @@ +// 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. + +#ifndef FLUTTER_FML_DART_DART_CONVERTER_H_ +#define FLUTTER_FML_DART_DART_CONVERTER_H_ + +#include +#include + +#include "flutter/fml/mapping.h" +#include "third_party/dart/runtime/include/dart_api.h" +#include "third_party/tonic/converter/dart_converter.h" + +namespace tonic { + +using DartConverterMapping = std::unique_ptr; + +template <> +struct DartConverter { + static Dart_Handle ToDart(const DartConverterMapping& val) { + if (!val) { + return Dart_Null(); + } + + auto dart_list_handle = Dart_NewListOf(Dart_CoreType_Int, val->GetSize()); + + if (Dart_IsError(dart_list_handle)) { + FML_LOG(ERROR) << "Error while attempting to allocate a list: " + << Dart_GetError(dart_list_handle); + return dart_list_handle; + } + + if (val->GetSize() == 0) { + // Nothing to copy. Just return the zero sized list. + return dart_list_handle; + } + + auto result = Dart_ListSetAsBytes(dart_list_handle, // list + 0, // offset + val->GetMapping(), // native array, + val->GetSize() // length + ); + + if (Dart_IsError(result)) { + FML_LOG(ERROR) << "Error while attempting to create a Dart list: " + << Dart_GetError(result); + return result; + } + + return dart_list_handle; + } + + static void SetReturnValue(Dart_NativeArguments args, + const DartConverterMapping& val) { + Dart_SetReturnValue(args, ToDart(val)); + } + + static DartConverterMapping FromDart(Dart_Handle dart_list) { + if (Dart_IsNull(dart_list)) { + return nullptr; + } + + if (Dart_IsError(dart_list)) { + FML_LOG(ERROR) << "Cannot convert an error handle to a list: " + << Dart_GetError(dart_list); + return nullptr; + } + + if (!Dart_IsList(dart_list)) { + FML_LOG(ERROR) << "Dart handle was not a list."; + return nullptr; + } + + intptr_t length = 0; + auto handle = Dart_ListLength(dart_list, &length); + + if (Dart_IsError(handle)) { + FML_LOG(ERROR) << "Could not get the length of the Dart list: " + << Dart_GetError(handle); + return nullptr; + } + + if (length == 0) { + // Return a valid zero sized mapping. + return std::make_unique(nullptr, 0); + } + + auto mapping_buffer = ::malloc(length); + + if (!mapping_buffer) { + FML_LOG(ERROR) + << "Out of memory while attempting to allocate a mapping of size: " + << length; + return nullptr; + } + + auto mapping = std::make_unique( + static_cast(mapping_buffer), length, + [](const uint8_t* data, size_t size) { + ::free(const_cast(data)); + }); + + handle = Dart_ListGetAsBytes( + dart_list, // list + 0, // offset + static_cast(mapping_buffer), // native array + length // length + ); + + if (Dart_IsError(handle)) { + FML_LOG(ERROR) << "Could not copy Dart list to native buffer: " + << Dart_GetError(handle); + return nullptr; + } + + return mapping; + } +}; + +} // namespace tonic + +#endif // FLUTTER_FML_DART_DART_CONVERTER_H_ diff --git a/fml/delayed_task.cc b/fml/delayed_task.cc index 569c1d1798fb0..997176d7694e0 100644 --- a/fml/delayed_task.cc +++ b/fml/delayed_task.cc @@ -9,9 +9,9 @@ namespace fml { DelayedTask::DelayedTask(size_t order, - fml::closure task, + const fml::closure& task, fml::TimePoint target_time) - : order_(order), task_(std::move(task)), target_time_(target_time) {} + : order_(order), task_(task), target_time_(target_time) {} DelayedTask::DelayedTask(const DelayedTask& other) = default; diff --git a/fml/delayed_task.h b/fml/delayed_task.h index 1830fbb6ef0d4..20594c3e8a494 100644 --- a/fml/delayed_task.h +++ b/fml/delayed_task.h @@ -14,7 +14,9 @@ namespace fml { class DelayedTask { public: - DelayedTask(size_t order, fml::closure task, fml::TimePoint target_time); + DelayedTask(size_t order, + const fml::closure& task, + fml::TimePoint target_time); DelayedTask(const DelayedTask& other); diff --git a/fml/file.cc b/fml/file.cc index 2f62e6c6f884b..dc31a8cfd86d4 100644 --- a/fml/file.cc +++ b/fml/file.cc @@ -62,7 +62,7 @@ ScopedTemporaryDirectory::~ScopedTemporaryDirectory() { } bool VisitFilesRecursively(const fml::UniqueFD& directory, - FileVisitor visitor) { + const FileVisitor& visitor) { FileVisitor recursive_visitor = [&recursive_visitor, &visitor]( const UniqueFD& directory, const std::string& filename) { diff --git a/fml/file.h b/fml/file.h index ee54354520695..bc82622036f16 100644 --- a/fml/file.h +++ b/fml/file.h @@ -107,7 +107,8 @@ using FileVisitor = std::function; NonOwnedMapping(const uint8_t* data, size_t size, - ReleaseProc release_proc = nullptr); + const ReleaseProc& release_proc = nullptr); ~NonOwnedMapping() override; diff --git a/fml/memory/thread_checker.h b/fml/memory/thread_checker.h index c6314ca62128b..cd972955d30c4 100644 --- a/fml/memory/thread_checker.h +++ b/fml/memory/thread_checker.h @@ -47,7 +47,25 @@ class ThreadChecker final { // Returns true if the current thread is the thread this object was created // on and false otherwise. bool IsCreationThreadCurrent() const { - return !!pthread_equal(pthread_self(), self_); + pthread_t current_thread = pthread_self(); + bool is_creation_thread_current = !!pthread_equal(current_thread, self_); +#ifdef __APPLE__ + // TODO(https://github.com/flutter/flutter/issues/45272): Implement for + // other platforms. + if (!is_creation_thread_current) { + static const int buffer_length = 128; + char expected_thread[buffer_length]; + char actual_thread[buffer_length]; + if (0 == pthread_getname_np(current_thread, actual_thread, + buffer_length) && + 0 == pthread_getname_np(self_, actual_thread, buffer_length)) { + FML_DLOG(ERROR) << "IsCreationThreadCurrent expected thread: '" + << expected_thread << "' actual thread:'" + << actual_thread << "'"; + } + } +#endif // __APPLE__ + return is_creation_thread_current; } private: diff --git a/fml/message_loop.cc b/fml/message_loop.cc index 635dfbf866873..c1043e664ae0a 100644 --- a/fml/message_loop.cc +++ b/fml/message_loop.cc @@ -61,7 +61,7 @@ fml::RefPtr MessageLoop::GetLoopImpl() const { return loop_; } -void MessageLoop::AddTaskObserver(intptr_t key, fml::closure callback) { +void MessageLoop::AddTaskObserver(intptr_t key, const fml::closure& callback) { loop_->AddTaskObserver(key, callback); } diff --git a/fml/message_loop.h b/fml/message_loop.h index 676bb429cd4c5..025f8059ec1aa 100644 --- a/fml/message_loop.h +++ b/fml/message_loop.h @@ -24,7 +24,7 @@ class MessageLoop { void Terminate(); - void AddTaskObserver(intptr_t key, fml::closure callback); + void AddTaskObserver(intptr_t key, const fml::closure& callback); void RemoveTaskObserver(intptr_t key); diff --git a/fml/message_loop_impl.cc b/fml/message_loop_impl.cc index 8a50c6697c4d6..d4c9331e35990 100644 --- a/fml/message_loop_impl.cc +++ b/fml/message_loop_impl.cc @@ -17,6 +17,8 @@ #include "flutter/fml/platform/darwin/message_loop_darwin.h" #elif OS_ANDROID #include "flutter/fml/platform/android/message_loop_android.h" +#elif OS_FUCHSIA +#include "flutter/fml/platform/fuchsia/message_loop_fuchsia.h" #elif OS_LINUX #include "flutter/fml/platform/linux/message_loop_linux.h" #elif OS_WIN @@ -30,6 +32,8 @@ fml::RefPtr MessageLoopImpl::Create() { return fml::MakeRefCounted(); #elif OS_ANDROID return fml::MakeRefCounted(); +#elif OS_FUCHSIA + return fml::MakeRefCounted(); #elif OS_LINUX return fml::MakeRefCounted(); #elif OS_WIN @@ -50,7 +54,8 @@ MessageLoopImpl::~MessageLoopImpl() { task_queue_->Dispose(queue_id_); } -void MessageLoopImpl::PostTask(fml::closure task, fml::TimePoint target_time) { +void MessageLoopImpl::PostTask(const fml::closure& task, + fml::TimePoint target_time) { FML_DCHECK(task != nullptr); FML_DCHECK(task != nullptr); if (terminated_) { @@ -61,7 +66,8 @@ void MessageLoopImpl::PostTask(fml::closure task, fml::TimePoint target_time) { task_queue_->RegisterTask(queue_id_, task, target_time); } -void MessageLoopImpl::AddTaskObserver(intptr_t key, fml::closure callback) { +void MessageLoopImpl::AddTaskObserver(intptr_t key, + const fml::closure& callback) { FML_DCHECK(callback != nullptr); FML_DCHECK(MessageLoop::GetCurrent().GetLoopImpl().get() == this) << "Message loop task observer must be added on the same thread as the " diff --git a/fml/message_loop_impl.h b/fml/message_loop_impl.h index ef98bf5fa4704..055423fad8a85 100644 --- a/fml/message_loop_impl.h +++ b/fml/message_loop_impl.h @@ -34,9 +34,9 @@ class MessageLoopImpl : public Wakeable, virtual void Terminate() = 0; - void PostTask(fml::closure task, fml::TimePoint target_time); + void PostTask(const fml::closure& task, fml::TimePoint target_time); - void AddTaskObserver(intptr_t key, fml::closure callback); + void AddTaskObserver(intptr_t key, const fml::closure& callback); void RemoveTaskObserver(intptr_t key); diff --git a/fml/message_loop_task_queues.cc b/fml/message_loop_task_queues.cc index 5b2c5de20ee5b..0efc33e0414ad 100644 --- a/fml/message_loop_task_queues.cc +++ b/fml/message_loop_task_queues.cc @@ -75,13 +75,13 @@ void MessageLoopTaskQueues::DisposeTasks(TaskQueueId queue_id) { } void MessageLoopTaskQueues::RegisterTask(TaskQueueId queue_id, - fml::closure task, + const fml::closure& task, fml::TimePoint target_time) { std::scoped_lock queue_lock(GetMutex(queue_id)); size_t order = order_++; const auto& queue_entry = queue_entries_[queue_id]; - queue_entry->delayed_tasks.push({order, std::move(task), target_time}); + queue_entry->delayed_tasks.push({order, task, target_time}); TaskQueueId loop_to_wake = queue_id; if (queue_entry->subsumed_by != _kUnmerged) { loop_to_wake = queue_entry->subsumed_by; @@ -157,7 +157,7 @@ size_t MessageLoopTaskQueues::GetNumPendingTasks(TaskQueueId queue_id) const { void MessageLoopTaskQueues::AddTaskObserver(TaskQueueId queue_id, intptr_t key, - fml::closure callback) { + const fml::closure& callback) { std::scoped_lock queue_lock(GetMutex(queue_id)); FML_DCHECK(callback != nullptr) << "Observer callback must be non-null."; diff --git a/fml/message_loop_task_queues.h b/fml/message_loop_task_queues.h index 249e09e685785..67c46991a6f13 100644 --- a/fml/message_loop_task_queues.h +++ b/fml/message_loop_task_queues.h @@ -78,7 +78,7 @@ class MessageLoopTaskQueues // Tasks methods. void RegisterTask(TaskQueueId queue_id, - fml::closure task, + const fml::closure& task, fml::TimePoint target_time); bool HasPendingTasks(TaskQueueId queue_id) const; @@ -93,7 +93,7 @@ class MessageLoopTaskQueues void AddTaskObserver(TaskQueueId queue_id, intptr_t key, - fml::closure callback); + const fml::closure& callback); void RemoveTaskObserver(TaskQueueId queue_id, intptr_t key); diff --git a/fml/platform/fuchsia/message_loop_fuchsia.cc b/fml/platform/fuchsia/message_loop_fuchsia.cc new file mode 100644 index 0000000000000..4e44c913cac46 --- /dev/null +++ b/fml/platform/fuchsia/message_loop_fuchsia.cc @@ -0,0 +1,38 @@ +// 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/fml/platform/fuchsia/message_loop_fuchsia.h" + +#include +#include +#include + +namespace fml { + +MessageLoopFuchsia::MessageLoopFuchsia() + : loop_(&kAsyncLoopConfigAttachToCurrentThread) {} + +MessageLoopFuchsia::~MessageLoopFuchsia() = default; + +void MessageLoopFuchsia::Run() { + loop_.Run(); +} + +void MessageLoopFuchsia::Terminate() { + loop_.Quit(); +} + +void MessageLoopFuchsia::WakeUp(fml::TimePoint time_point) { + fml::TimePoint now = fml::TimePoint::Now(); + zx::duration due_time{0}; + if (time_point > now) { + due_time = zx::nsec((time_point - now).ToNanoseconds()); + } + + auto status = async::PostDelayedTask( + loop_.dispatcher(), [this]() { RunExpiredTasksNow(); }, due_time); + FML_DCHECK(status == ZX_OK); +} + +} // namespace fml diff --git a/fml/platform/fuchsia/message_loop_fuchsia.h b/fml/platform/fuchsia/message_loop_fuchsia.h new file mode 100644 index 0000000000000..f54c587c4a2b3 --- /dev/null +++ b/fml/platform/fuchsia/message_loop_fuchsia.h @@ -0,0 +1,36 @@ +// 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. + +#ifndef FLUTTER_FML_PLATFORM_FUCHSIA_MESSAGE_LOOP_FUCHSIA_H_ +#define FLUTTER_FML_PLATFORM_FUCHSIA_MESSAGE_LOOP_FUCHSIA_H_ + +#include + +#include "flutter/fml/macros.h" +#include "flutter/fml/message_loop_impl.h" + +namespace fml { + +class MessageLoopFuchsia : public MessageLoopImpl { + private: + MessageLoopFuchsia(); + + ~MessageLoopFuchsia() override; + + void Run() override; + + void Terminate() override; + + void WakeUp(fml::TimePoint time_point) override; + + async::Loop loop_; + + FML_FRIEND_MAKE_REF_COUNTED(MessageLoopFuchsia); + FML_FRIEND_REF_COUNTED_THREAD_SAFE(MessageLoopFuchsia); + FML_DISALLOW_COPY_AND_ASSIGN(MessageLoopFuchsia); +}; + +} // namespace fml + +#endif // FLUTTER_FML_PLATFORM_FUCHSIA_MESSAGE_LOOP_FUCHSIA_H_ diff --git a/fml/platform/linux/timerfd.cc b/fml/platform/linux/timerfd.cc index f20ffc7c5ed7b..0dbcb89ad3d25 100644 --- a/fml/platform/linux/timerfd.cc +++ b/fml/platform/linux/timerfd.cc @@ -34,7 +34,13 @@ namespace fml { #endif bool TimerRearm(int fd, fml::TimePoint time_point) { - const uint64_t nano_secs = time_point.ToEpochDelta().ToNanoseconds(); + uint64_t nano_secs = time_point.ToEpochDelta().ToNanoseconds(); + + // "0" will disarm the timer, desired behavior is to immediately + // trigger the timer. + if (nano_secs < 1) { + nano_secs = 1; + } struct itimerspec spec = {}; spec.it_value.tv_sec = (time_t)(nano_secs / NSEC_PER_SEC); diff --git a/fml/platform/posix/file_posix.cc b/fml/platform/posix/file_posix.cc index c275f606d1362..ab64b5cc2de71 100644 --- a/fml/platform/posix/file_posix.cc +++ b/fml/platform/posix/file_posix.cc @@ -222,7 +222,7 @@ bool WriteAtomically(const fml::UniqueFD& base_directory, base_directory.get(), file_name) == 0; } -bool VisitFiles(const fml::UniqueFD& directory, FileVisitor visitor) { +bool VisitFiles(const fml::UniqueFD& directory, const FileVisitor& visitor) { fml::UniqueFD dup_fd(dup(directory.get())); if (!dup_fd.is_valid()) { FML_DLOG(ERROR) << "Can't dup the directory fd. Error: " << strerror(errno); diff --git a/fml/platform/win/file_win.cc b/fml/platform/win/file_win.cc index 42a5958f4d061..a4c74940c3278 100644 --- a/fml/platform/win/file_win.cc +++ b/fml/platform/win/file_win.cc @@ -399,7 +399,7 @@ bool WriteAtomically(const fml::UniqueFD& base_directory, return true; } -bool VisitFiles(const fml::UniqueFD& directory, FileVisitor visitor) { +bool VisitFiles(const fml::UniqueFD& directory, const FileVisitor& visitor) { std::string search_pattern = GetFullHandlePath(directory) + "\\*"; WIN32_FIND_DATA find_file_data; HANDLE find_handle = ::FindFirstFile( diff --git a/fml/synchronization/sync_switch.cc b/fml/synchronization/sync_switch.cc new file mode 100644 index 0000000000000..7ab90db9eb58d --- /dev/null +++ b/fml/synchronization/sync_switch.cc @@ -0,0 +1,39 @@ +// 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/fml/synchronization/sync_switch.h" + +namespace fml { + +SyncSwitch::Handlers& SyncSwitch::Handlers::SetIfTrue( + const std::function& handler) { + true_handler = std::move(handler); + return *this; +} + +SyncSwitch::Handlers& SyncSwitch::Handlers::SetIfFalse( + const std::function& handler) { + false_handler = std::move(handler); + return *this; +} + +SyncSwitch::SyncSwitch() : SyncSwitch(false) {} + +SyncSwitch::SyncSwitch(bool value) : value_(value) {} + +void SyncSwitch::Execute(const SyncSwitch::Handlers& handlers) { + std::scoped_lock guard(mutex_); + if (value_) { + handlers.true_handler(); + } else { + handlers.false_handler(); + } +} + +void SyncSwitch::SetSwitch(bool value) { + std::scoped_lock guard(mutex_); + value_ = value; +} + +} // namespace fml diff --git a/fml/synchronization/sync_switch.h b/fml/synchronization/sync_switch.h new file mode 100644 index 0000000000000..4fd7bfdd49c18 --- /dev/null +++ b/fml/synchronization/sync_switch.h @@ -0,0 +1,67 @@ +// 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. + +#ifndef FLUTTER_FML_SYNCHRONIZATION_SYNC_SWITCH_H_ +#define FLUTTER_FML_SYNCHRONIZATION_SYNC_SWITCH_H_ + +#include +#include +#include + +#include "flutter/fml/macros.h" + +namespace fml { + +/// A threadsafe structure that allows you to switch between 2 different +/// execution paths. +/// +/// Execution and setting the switch is exclusive, i.e. only one will happen +/// at a time. +class SyncSwitch { + public: + /// Represents the 2 code paths available when calling |SyncSwitch::Execute|. + struct Handlers { + /// Sets the handler that will be executed if the |SyncSwitch| is true. + Handlers& SetIfTrue(const std::function& handler); + + /// Sets the handler that will be executed if the |SyncSwitch| is false. + Handlers& SetIfFalse(const std::function& handler); + + std::function true_handler = [] {}; + std::function false_handler = [] {}; + }; + + /// Create a |SyncSwitch| with the false value. + SyncSwitch(); + + /// Create a |SyncSwitch| with the specified value. + /// + /// @param[in] value Default value for the |SyncSwitch|. + SyncSwitch(bool value); + + /// Diverge execution between true and false values of the SyncSwitch. + /// + /// This can be called on any thread. Note that attempting to call + /// |SetSwitch| inside of the handlers will result in a self deadlock. + /// + /// @param[in] handlers Called for the correct value of the |SyncSwitch|. + void Execute(const Handlers& handlers); + + /// Set the value of the SyncSwitch. + /// + /// This can be called on any thread. + /// + /// @param[in] value New value for the |SyncSwitch|. + void SetSwitch(bool value); + + private: + std::mutex mutex_; + bool value_; + + FML_DISALLOW_COPY_AND_ASSIGN(SyncSwitch); +}; + +} // namespace fml + +#endif // FLUTTER_FML_SYNCHRONIZATION_SYNC_SWITCH_H_ diff --git a/fml/synchronization/sync_switch_unittest.cc b/fml/synchronization/sync_switch_unittest.cc new file mode 100644 index 0000000000000..09994496cf907 --- /dev/null +++ b/fml/synchronization/sync_switch_unittest.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/fml/synchronization/sync_switch.h" + +#include "gtest/gtest.h" + +using fml::SyncSwitch; + +TEST(SyncSwitchTest, Basic) { + SyncSwitch syncSwitch; + bool switchValue = false; + syncSwitch.Execute(SyncSwitch::Handlers() + .SetIfTrue([&] { switchValue = true; }) + .SetIfFalse([&] { switchValue = false; })); + EXPECT_FALSE(switchValue); + syncSwitch.SetSwitch(true); + syncSwitch.Execute(SyncSwitch::Handlers() + .SetIfTrue([&] { switchValue = true; }) + .SetIfFalse([&] { switchValue = false; })); + EXPECT_TRUE(switchValue); +} + +TEST(SyncSwitchTest, NoopIfUndefined) { + SyncSwitch syncSwitch; + bool switchValue = false; + syncSwitch.Execute(SyncSwitch::Handlers()); + EXPECT_FALSE(switchValue); +} diff --git a/fml/task_runner.cc b/fml/task_runner.cc index c525332a2aaf9..c50e14a3ca4b9 100644 --- a/fml/task_runner.cc +++ b/fml/task_runner.cc @@ -20,17 +20,18 @@ TaskRunner::TaskRunner(fml::RefPtr loop) TaskRunner::~TaskRunner() = default; -void TaskRunner::PostTask(fml::closure task) { - loop_->PostTask(std::move(task), fml::TimePoint::Now()); +void TaskRunner::PostTask(const fml::closure& task) { + loop_->PostTask(task, fml::TimePoint::Now()); } -void TaskRunner::PostTaskForTime(fml::closure task, +void TaskRunner::PostTaskForTime(const fml::closure& task, fml::TimePoint target_time) { - loop_->PostTask(std::move(task), target_time); + loop_->PostTask(task, target_time); } -void TaskRunner::PostDelayedTask(fml::closure task, fml::TimeDelta delay) { - loop_->PostTask(std::move(task), fml::TimePoint::Now() + delay); +void TaskRunner::PostDelayedTask(const fml::closure& task, + fml::TimeDelta delay) { + loop_->PostTask(task, fml::TimePoint::Now() + delay); } TaskQueueId TaskRunner::GetTaskQueueId() { @@ -62,7 +63,7 @@ bool TaskRunner::RunsTasksOnCurrentThread() { } void TaskRunner::RunNowOrPostTask(fml::RefPtr runner, - fml::closure task) { + const fml::closure& task) { FML_DCHECK(runner); if (runner->RunsTasksOnCurrentThread()) { task(); diff --git a/fml/task_runner.h b/fml/task_runner.h index b7569058af17f..a66f67e13cc12 100644 --- a/fml/task_runner.h +++ b/fml/task_runner.h @@ -20,18 +20,19 @@ class TaskRunner : public fml::RefCountedThreadSafe { public: virtual ~TaskRunner(); - virtual void PostTask(fml::closure task); + virtual void PostTask(const fml::closure& task); - virtual void PostTaskForTime(fml::closure task, fml::TimePoint target_time); + virtual void PostTaskForTime(const fml::closure& task, + fml::TimePoint target_time); - virtual void PostDelayedTask(fml::closure task, fml::TimeDelta delay); + virtual void PostDelayedTask(const fml::closure& task, fml::TimeDelta delay); virtual bool RunsTasksOnCurrentThread(); virtual TaskQueueId GetTaskQueueId(); static void RunNowOrPostTask(fml::RefPtr runner, - fml::closure task); + const fml::closure& task); protected: TaskRunner(fml::RefPtr loop); diff --git a/fml/thread.cc b/fml/thread.cc index b6a4802b48728..4f96fd7e08e9d 100644 --- a/fml/thread.cc +++ b/fml/thread.cc @@ -10,6 +10,8 @@ #if defined(OS_WIN) #include +#elif defined(OS_FUCHSIA) +#include #else #include #endif @@ -85,6 +87,8 @@ void Thread::SetCurrentThreadName(const std::string& name) { reinterpret_cast(&info)); } __except (EXCEPTION_CONTINUE_EXECUTION) { } +#elif OS_FUCHSIA + zx::thread::self()->set_property(ZX_PROP_NAME, name.c_str(), name.size()); #else FML_DLOG(INFO) << "Could not set the thread name to '" << name << "' on this platform."; diff --git a/lib/ui/BUILD.gn b/lib/ui/BUILD.gn index 003efe1cb1b66..967f061f1cc58 100644 --- a/lib/ui/BUILD.gn +++ b/lib/ui/BUILD.gn @@ -101,6 +101,8 @@ source_set("ui") { "window/pointer_data.h", "window/pointer_data_packet.cc", "window/pointer_data_packet.h", + "window/pointer_data_packet_converter.cc", + "window/pointer_data_packet_converter.h", "window/viewport_metrics.cc", "window/viewport_metrics.h", "window/window.cc", @@ -153,6 +155,8 @@ if (current_toolchain == host_toolchain) { fixtures = [ "fixtures/DashInNooglerHat.jpg", "fixtures/Horizontal.jpg", + "fixtures/hello_loop_2.gif", + "fixtures/hello_loop_2.webp", ] } @@ -161,6 +165,7 @@ if (current_toolchain == host_toolchain) { sources = [ "painting/image_decoder_unittests.cc", + "window/pointer_data_packet_converter_unittests.cc", ] deps = [ diff --git a/lib/ui/compositing.dart b/lib/ui/compositing.dart index 232514c0f1805..cf73966e06e20 100644 --- a/lib/ui/compositing.dart +++ b/lib/ui/compositing.dart @@ -19,15 +19,13 @@ class Scene extends NativeFieldWrapperClass2 { @pragma('vm:entry-point') Scene._(); - /// Creates a raster image representation of the current state of the scene. /// This is a slow operation that is performed on a background thread. Future toImage(int width, int height) { - if (width <= 0 || height <= 0) + if (width <= 0 || height <= 0) { throw Exception('Invalid image dimensions.'); - return _futurize( - (_Callback callback) => _toImage(width, height, callback) - ); + } + return _futurize((_Callback callback) => _toImage(width, height, callback)); } String _toImage(int width, int height, _Callback callback) native 'Scene_toImage'; @@ -64,13 +62,12 @@ abstract class _EngineLayerWrapper implements EngineLayer { bool _debugCheckNotUsedAsOldLayer() { assert( - !_debugWasUsedAsOldLayer, - 'Layer $runtimeType was previously used as oldLayer.\n' - 'Once a layer is used as oldLayer, it may not be used again. Instead, ' - 'after calling one of the SceneBuilder.push* methods and passing an oldLayer ' - 'to it, use the layer returned by the method as oldLayer in subsequent ' - 'frames.' - ); + !_debugWasUsedAsOldLayer, + 'Layer $runtimeType was previously used as oldLayer.\n' + 'Once a layer is used as oldLayer, it may not be used again. Instead, ' + 'after calling one of the SceneBuilder.push* methods and passing an oldLayer ' + 'to it, use the layer returned by the method as oldLayer in subsequent ' + 'frames.'); return true; } } @@ -142,6 +139,15 @@ class ColorFilterEngineLayer extends _EngineLayerWrapper { ColorFilterEngineLayer._(EngineLayer nativeLayer) : super._(nativeLayer); } +/// An opaque handle to an image filter engine layer. +/// +/// Instances of this class are created by [SceneBuilder.pushImageFilter]. +/// +/// {@macro dart.ui.sceneBuilder.oldLayerCompatibility} +class ImageFilterEngineLayer extends _EngineLayerWrapper { + ImageFilterEngineLayer._(EngineLayer nativeLayer) : super._(nativeLayer); +} + /// An opaque handle to a backdrop filter engine layer. /// /// Instances of this class are created by [SceneBuilder.pushBackdropFilter]. @@ -179,7 +185,9 @@ class PhysicalShapeEngineLayer extends _EngineLayerWrapper { class SceneBuilder extends NativeFieldWrapperClass2 { /// Creates an empty [SceneBuilder] object. @pragma('vm:entry-point') - SceneBuilder() { _constructor(); } + SceneBuilder() { + _constructor(); + } void _constructor() native 'SceneBuilder_constructor'; // Layers used in this scene. @@ -196,11 +204,10 @@ class SceneBuilder extends NativeFieldWrapperClass2 { } assert( - !_usedLayers.containsKey(layer), - 'Layer ${layer.runtimeType} already used.\n' - 'The layer is already being used as ${_usedLayers[layer]} in this scene.\n' - 'A layer may only be used once in a given scene.' - ); + !_usedLayers.containsKey(layer), + 'Layer ${layer.runtimeType} already used.\n' + 'The layer is already being used as ${_usedLayers[layer]} in this scene.\n' + 'A layer may only be used once in a given scene.'); _usedLayers[layer] = usage; return true; @@ -267,13 +274,17 @@ class SceneBuilder extends NativeFieldWrapperClass2 { /// {@endtemplate} /// /// See [pop] for details about the operation stack. - TransformEngineLayer pushTransform(Float64List matrix4, { TransformEngineLayer oldLayer }) { + TransformEngineLayer pushTransform( + Float64List matrix4, { + TransformEngineLayer oldLayer, + }) { assert(_matrix4IsValid(matrix4)); assert(_debugCheckCanBeUsedAsOldLayer(oldLayer, 'pushTransform')); final TransformEngineLayer layer = TransformEngineLayer._(_pushTransform(matrix4)); assert(_debugPushLayer(layer)); return layer; } + EngineLayer _pushTransform(Float64List matrix4) native 'SceneBuilder_pushTransform'; /// Pushes an offset operation onto the operation stack. @@ -285,12 +296,17 @@ class SceneBuilder extends NativeFieldWrapperClass2 { /// {@macro dart.ui.sceneBuilder.oldLayerVsRetained} /// /// See [pop] for details about the operation stack. - OffsetEngineLayer pushOffset(double dx, double dy, { OffsetEngineLayer oldLayer }) { + OffsetEngineLayer pushOffset( + double dx, + double dy, { + OffsetEngineLayer oldLayer, + }) { assert(_debugCheckCanBeUsedAsOldLayer(oldLayer, 'pushOffset')); final OffsetEngineLayer layer = OffsetEngineLayer._(_pushOffset(dx, dy)); assert(_debugPushLayer(layer)); return layer; } + EngineLayer _pushOffset(double dx, double dy) native 'SceneBuilder_pushOffset'; /// Pushes a rectangular clip operation onto the operation stack. @@ -303,19 +319,22 @@ class SceneBuilder extends NativeFieldWrapperClass2 { /// /// See [pop] for details about the operation stack, and [Clip] for different clip modes. /// By default, the clip will be anti-aliased (clip = [Clip.antiAlias]). - ClipRectEngineLayer pushClipRect(Rect rect, {Clip clipBehavior = Clip.antiAlias, ClipRectEngineLayer oldLayer }) { + ClipRectEngineLayer pushClipRect( + Rect rect, { + Clip clipBehavior = Clip.antiAlias, + ClipRectEngineLayer oldLayer, + }) { assert(clipBehavior != null); assert(clipBehavior != Clip.none); assert(_debugCheckCanBeUsedAsOldLayer(oldLayer, 'pushClipRect')); - final ClipRectEngineLayer layer = ClipRectEngineLayer._(_pushClipRect(rect.left, rect.right, rect.top, rect.bottom, clipBehavior.index)); + final ClipRectEngineLayer layer = ClipRectEngineLayer._( + _pushClipRect(rect.left, rect.right, rect.top, rect.bottom, clipBehavior.index)); assert(_debugPushLayer(layer)); return layer; } - EngineLayer _pushClipRect(double left, - double right, - double top, - double bottom, - int clipBehavior) native 'SceneBuilder_pushClipRect'; + + EngineLayer _pushClipRect(double left, double right, double top, double bottom, int clipBehavior) + native 'SceneBuilder_pushClipRect'; /// Pushes a rounded-rectangular clip operation onto the operation stack. /// @@ -327,15 +346,22 @@ class SceneBuilder extends NativeFieldWrapperClass2 { /// /// See [pop] for details about the operation stack, and [Clip] for different clip modes. /// By default, the clip will be anti-aliased (clip = [Clip.antiAlias]). - ClipRRectEngineLayer pushClipRRect(RRect rrect, {Clip clipBehavior = Clip.antiAlias, ClipRRectEngineLayer oldLayer}) { + ClipRRectEngineLayer pushClipRRect( + RRect rrect, { + Clip clipBehavior = Clip.antiAlias, + ClipRRectEngineLayer oldLayer, + }) { assert(clipBehavior != null); assert(clipBehavior != Clip.none); assert(_debugCheckCanBeUsedAsOldLayer(oldLayer, 'pushClipRRect')); - final ClipRRectEngineLayer layer = ClipRRectEngineLayer._(_pushClipRRect(rrect._value32, clipBehavior.index)); + final ClipRRectEngineLayer layer = + ClipRRectEngineLayer._(_pushClipRRect(rrect._value32, clipBehavior.index)); assert(_debugPushLayer(layer)); return layer; } - EngineLayer _pushClipRRect(Float32List rrect, int clipBehavior) native 'SceneBuilder_pushClipRRect'; + + EngineLayer _pushClipRRect(Float32List rrect, int clipBehavior) + native 'SceneBuilder_pushClipRRect'; /// Pushes a path clip operation onto the operation stack. /// @@ -347,14 +373,20 @@ class SceneBuilder extends NativeFieldWrapperClass2 { /// /// See [pop] for details about the operation stack. See [Clip] for different clip modes. /// By default, the clip will be anti-aliased (clip = [Clip.antiAlias]). - ClipPathEngineLayer pushClipPath(Path path, {Clip clipBehavior = Clip.antiAlias, ClipPathEngineLayer oldLayer}) { + ClipPathEngineLayer pushClipPath( + Path path, { + Clip clipBehavior = Clip.antiAlias, + ClipPathEngineLayer oldLayer, + }) { assert(clipBehavior != null); assert(clipBehavior != Clip.none); assert(_debugCheckCanBeUsedAsOldLayer(oldLayer, 'pushClipPath')); - final ClipPathEngineLayer layer = ClipPathEngineLayer._(_pushClipPath(path, clipBehavior.index)); + final ClipPathEngineLayer layer = + ClipPathEngineLayer._(_pushClipPath(path, clipBehavior.index)); assert(_debugPushLayer(layer)); return layer; } + EngineLayer _pushClipPath(Path path, int clipBehavior) native 'SceneBuilder_pushClipPath'; /// Pushes an opacity operation onto the operation stack. @@ -369,12 +401,18 @@ class SceneBuilder extends NativeFieldWrapperClass2 { /// {@macro dart.ui.sceneBuilder.oldLayerVsRetained} /// /// See [pop] for details about the operation stack. - OpacityEngineLayer pushOpacity(int alpha, {Offset offset = Offset.zero, OpacityEngineLayer oldLayer}) { + OpacityEngineLayer pushOpacity( + int alpha, { + Offset offset = Offset.zero, + OpacityEngineLayer oldLayer, + }) { assert(_debugCheckCanBeUsedAsOldLayer(oldLayer, 'pushOpacity')); - final OpacityEngineLayer layer = OpacityEngineLayer._(_pushOpacity(alpha, offset.dx, offset.dy)); + final OpacityEngineLayer layer = + OpacityEngineLayer._(_pushOpacity(alpha, offset.dx, offset.dy)); assert(_debugPushLayer(layer)); return layer; } + EngineLayer _pushOpacity(int alpha, double dx, double dy) native 'SceneBuilder_pushOpacity'; /// Pushes a color filter operation onto the operation stack. @@ -387,7 +425,10 @@ class SceneBuilder extends NativeFieldWrapperClass2 { /// {@macro dart.ui.sceneBuilder.oldLayerVsRetained} /// /// See [pop] for details about the operation stack. - ColorFilterEngineLayer pushColorFilter(ColorFilter filter, { ColorFilterEngineLayer oldLayer }) { + ColorFilterEngineLayer pushColorFilter( + ColorFilter filter, { + ColorFilterEngineLayer oldLayer, + }) { assert(filter != null); assert(_debugCheckCanBeUsedAsOldLayer(oldLayer, 'pushColorFilter')); final _ColorFilter nativeFilter = filter._toNativeColorFilter(); @@ -396,8 +437,34 @@ class SceneBuilder extends NativeFieldWrapperClass2 { assert(_debugPushLayer(layer)); return layer; } + EngineLayer _pushColorFilter(_ColorFilter filter) native 'SceneBuilder_pushColorFilter'; + /// Pushes an image filter operation onto the operation stack. + /// + /// The given filter is applied to the children's rasterization before compositing them into + /// the scene. + /// + /// {@macro dart.ui.sceneBuilder.oldLayer} + /// + /// {@macro dart.ui.sceneBuilder.oldLayerVsRetained} + /// + /// See [pop] for details about the operation stack. + ImageFilterEngineLayer pushImageFilter( + ImageFilter filter, { + ImageFilterEngineLayer oldLayer, + }) { + assert(filter != null); + assert(_debugCheckCanBeUsedAsOldLayer(oldLayer, 'pushImageFilter')); + final _ImageFilter nativeFilter = filter._toNativeImageFilter(); + assert(nativeFilter != null); + final ImageFilterEngineLayer layer = ImageFilterEngineLayer._(_pushImageFilter(nativeFilter)); + assert(_debugPushLayer(layer)); + return layer; + } + + EngineLayer _pushImageFilter(_ImageFilter filter) native 'SceneBuilder_pushImageFilter'; + /// Pushes a backdrop filter operation onto the operation stack. /// /// The given filter is applied to the current contents of the scene prior to @@ -408,13 +475,18 @@ class SceneBuilder extends NativeFieldWrapperClass2 { /// {@macro dart.ui.sceneBuilder.oldLayerVsRetained} /// /// See [pop] for details about the operation stack. - BackdropFilterEngineLayer pushBackdropFilter(ImageFilter filter, { BackdropFilterEngineLayer oldLayer }) { + BackdropFilterEngineLayer pushBackdropFilter( + ImageFilter filter, { + BackdropFilterEngineLayer oldLayer, + }) { assert(_debugCheckCanBeUsedAsOldLayer(oldLayer, 'pushBackdropFilter')); - final BackdropFilterEngineLayer layer = BackdropFilterEngineLayer._(_pushBackdropFilter(filter)); + final BackdropFilterEngineLayer layer = + BackdropFilterEngineLayer._(_pushBackdropFilter(filter._toNativeImageFilter())); assert(_debugPushLayer(layer)); return layer; } - EngineLayer _pushBackdropFilter(ImageFilter filter) native 'SceneBuilder_pushBackdropFilter'; + + EngineLayer _pushBackdropFilter(_ImageFilter filter) native 'SceneBuilder_pushBackdropFilter'; /// Pushes a shader mask operation onto the operation stack. /// @@ -426,23 +498,26 @@ class SceneBuilder extends NativeFieldWrapperClass2 { /// {@macro dart.ui.sceneBuilder.oldLayerVsRetained} /// /// See [pop] for details about the operation stack. - ShaderMaskEngineLayer pushShaderMask(Shader shader, Rect maskRect, BlendMode blendMode, { ShaderMaskEngineLayer oldLayer }) { + ShaderMaskEngineLayer pushShaderMask( + Shader shader, + Rect maskRect, + BlendMode blendMode, { + ShaderMaskEngineLayer oldLayer, + }) { assert(_debugCheckCanBeUsedAsOldLayer(oldLayer, 'pushShaderMask')); - final ShaderMaskEngineLayer layer = ShaderMaskEngineLayer._(_pushShaderMask(shader, - maskRect.left, - maskRect.right, - maskRect.top, - maskRect.bottom, - blendMode.index)); + final ShaderMaskEngineLayer layer = ShaderMaskEngineLayer._(_pushShaderMask( + shader, maskRect.left, maskRect.right, maskRect.top, maskRect.bottom, blendMode.index)); assert(_debugPushLayer(layer)); return layer; } - EngineLayer _pushShaderMask(Shader shader, - double maskRectLeft, - double maskRectRight, - double maskRectTop, - double maskRectBottom, - int blendMode) native 'SceneBuilder_pushShaderMask'; + + EngineLayer _pushShaderMask( + Shader shader, + double maskRectLeft, + double maskRectRight, + double maskRectTop, + double maskRectBottom, + int blendMode) native 'SceneBuilder_pushShaderMask'; /// Pushes a physical layer operation for an arbitrary shape onto the /// operation stack. @@ -461,14 +536,23 @@ class SceneBuilder extends NativeFieldWrapperClass2 { /// /// See [pop] for details about the operation stack, and [Clip] for different clip modes. // ignore: deprecated_member_use - PhysicalShapeEngineLayer pushPhysicalShape({ Path path, double elevation, Color color, Color shadowColor, Clip clipBehavior = Clip.none, PhysicalShapeEngineLayer oldLayer }) { + PhysicalShapeEngineLayer pushPhysicalShape({ + Path path, + double elevation, + Color color, + Color shadowColor, + Clip clipBehavior = Clip.none, + PhysicalShapeEngineLayer oldLayer, + }) { assert(_debugCheckCanBeUsedAsOldLayer(oldLayer, 'pushPhysicalShape')); - final PhysicalShapeEngineLayer layer = PhysicalShapeEngineLayer._(_pushPhysicalShape(path, elevation, color.value, shadowColor?.value ?? 0xFF000000, clipBehavior.index)); + final PhysicalShapeEngineLayer layer = PhysicalShapeEngineLayer._(_pushPhysicalShape( + path, elevation, color.value, shadowColor?.value ?? 0xFF000000, clipBehavior.index)); assert(_debugPushLayer(layer)); return layer; } - EngineLayer _pushPhysicalShape(Path path, double elevation, int color, int shadowColor, int clipBehavior) native - 'SceneBuilder_pushPhysicalShape'; + + EngineLayer _pushPhysicalShape(Path path, double elevation, int color, int shadowColor, + int clipBehavior) native 'SceneBuilder_pushPhysicalShape'; /// Ends the effect of the most recently pushed operation. /// @@ -482,6 +566,7 @@ class SceneBuilder extends NativeFieldWrapperClass2 { } _pop(); } + void _pop() native 'SceneBuilder_pop'; /// Add a retained engine layer subtree from previous frames. @@ -518,6 +603,7 @@ class SceneBuilder extends NativeFieldWrapperClass2 { final _EngineLayerWrapper wrapper = retainedLayer; _addRetained(wrapper._nativeLayer); } + void _addRetained(EngineLayer retainedLayer) native 'SceneBuilder_addRetained'; /// Adds an object to the scene that displays performance statistics. @@ -545,30 +631,32 @@ class SceneBuilder extends NativeFieldWrapperClass2 { /// for more details. // Values above must match constants in //engine/src/sky/compositor/performance_overlay_layer.h void addPerformanceOverlay(int enabledOptions, Rect bounds) { - _addPerformanceOverlay(enabledOptions, - bounds.left, - bounds.right, - bounds.top, - bounds.bottom); + _addPerformanceOverlay(enabledOptions, bounds.left, bounds.right, bounds.top, bounds.bottom); } - void _addPerformanceOverlay(int enabledOptions, - double left, - double right, - double top, - double bottom) native 'SceneBuilder_addPerformanceOverlay'; + + void _addPerformanceOverlay( + int enabledOptions, + double left, + double right, + double top, + double bottom, + ) native 'SceneBuilder_addPerformanceOverlay'; /// Adds a [Picture] to the scene. /// /// The picture is rasterized at the given offset. - void addPicture(Offset offset, Picture picture, { bool isComplexHint = false, bool willChangeHint = false }) { - int hints = 0; - if (isComplexHint) - hints |= 1; - if (willChangeHint) - hints |= 2; + void addPicture( + Offset offset, + Picture picture, { + bool isComplexHint = false, + bool willChangeHint = false, + }) { + final int hints = (isComplexHint ? 1 : 0) | (willChangeHint ? 2 : 0); _addPicture(offset.dx, offset.dy, picture, hints); } - void _addPicture(double dx, double dy, Picture picture, int hints) native 'SceneBuilder_addPicture'; + + void _addPicture(double dx, double dy, Picture picture, int hints) + native 'SceneBuilder_addPicture'; /// Adds a backend texture to the scene. /// @@ -581,11 +669,19 @@ class SceneBuilder extends NativeFieldWrapperClass2 { /// previous or new size, to workaround this the framework "freezes" the /// texture just before resizing the Android view and un-freezes it when it is /// certain that a frame with the new size is ready. - void addTexture(int textureId, { Offset offset = Offset.zero, double width = 0.0, double height = 0.0 , bool freeze = false}) { + void addTexture( + int textureId, { + Offset offset = Offset.zero, + double width = 0.0, + double height = 0.0, + bool freeze = false, + }) { assert(offset != null, 'Offset argument was null'); _addTexture(offset.dx, offset.dy, width, height, textureId, freeze); } - void _addTexture(double dx, double dy, double width, double height, int textureId, bool freeze) native 'SceneBuilder_addTexture'; + + void _addTexture(double dx, double dy, double width, double height, int textureId, bool freeze) + native 'SceneBuilder_addTexture'; /// Adds a platform view (e.g an iOS UIView) to the scene. /// @@ -603,11 +699,18 @@ class SceneBuilder extends NativeFieldWrapperClass2 { /// With a platform view in the scene, Quartz has to composite the two Flutter surfaces and the /// embedded UIView. In addition to that, on iOS versions greater than 9, the Flutter frames are /// synchronized with the UIView frames adding additional performance overhead. - void addPlatformView(int viewId, { Offset offset = Offset.zero, double width = 0.0, double height = 0.0}) { + void addPlatformView( + int viewId, { + Offset offset = Offset.zero, + double width = 0.0, + double height = 0.0, + }) { assert(offset != null, 'Offset argument was null'); _addPlatformView(offset.dx, offset.dy, width, height, viewId); } - void _addPlatformView(double dx, double dy, double width, double height, int viewId) native 'SceneBuilder_addPlatformView'; + + void _addPlatformView(double dx, double dy, double width, double height, int viewId) + native 'SceneBuilder_addPlatformView'; /// (Fuchsia-only) Adds a scene rendered by another application to the scene /// for this application. @@ -616,21 +719,13 @@ class SceneBuilder extends NativeFieldWrapperClass2 { double width = 0.0, double height = 0.0, SceneHost sceneHost, - bool hitTestable = true + bool hitTestable = true, }) { - _addChildScene(offset.dx, - offset.dy, - width, - height, - sceneHost, - hitTestable); + _addChildScene(offset.dx, offset.dy, width, height, sceneHost, hitTestable); } - void _addChildScene(double dx, - double dy, - double width, - double height, - SceneHost sceneHost, - bool hitTestable) native 'SceneBuilder_addChildScene'; + + void _addChildScene(double dx, double dy, double width, double height, SceneHost sceneHost, + bool hitTestable) native 'SceneBuilder_addChildScene'; /// Sets a threshold after which additional debugging information should be recorded. /// @@ -638,7 +733,8 @@ class SceneBuilder extends NativeFieldWrapperClass2 { /// interested in using this feature, please contact [flutter-dev](https://groups.google.com/forum/#!forum/flutter-dev). /// We'll hopefully be able to figure out how to make this feature more useful /// to you. - void setRasterizerTracingThreshold(int frameInterval) native 'SceneBuilder_setRasterizerTracingThreshold'; + void setRasterizerTracingThreshold(int frameInterval) + native 'SceneBuilder_setRasterizerTracingThreshold'; /// Sets whether the raster cache should checkerboard cached entries. This is /// only useful for debugging purposes. @@ -655,13 +751,15 @@ class SceneBuilder extends NativeFieldWrapperClass2 { /// /// Currently this interface is difficult to use by end-developers. If you're /// interested in using this feature, please contact [flutter-dev](https://groups.google.com/forum/#!forum/flutter-dev). - void setCheckerboardRasterCacheImages(bool checkerboard) native 'SceneBuilder_setCheckerboardRasterCacheImages'; + void setCheckerboardRasterCacheImages(bool checkerboard) + native 'SceneBuilder_setCheckerboardRasterCacheImages'; /// Sets whether the compositor should checkerboard layers that are rendered /// to offscreen bitmaps. /// /// This is only useful for debugging purposes. - void setCheckerboardOffscreenLayers(bool checkerboard) native 'SceneBuilder_setCheckerboardOffscreenLayers'; + void setCheckerboardOffscreenLayers(bool checkerboard) + native 'SceneBuilder_setCheckerboardOffscreenLayers'; /// Finishes building the scene. /// @@ -688,22 +786,21 @@ class SceneHost extends NativeFieldWrapperClass2 { /// /// The SceneHost takes ownership of the provided ViewHolder token. SceneHost( - dynamic viewHolderToken, - void Function() viewConnectedCallback, - void Function() viewDisconnectedCallback, - void Function(bool) viewStateChangedCallback) { - _constructor(viewHolderToken, viewConnectedCallback, viewDisconnectedCallback, viewStateChangedCallback); - } - SceneHost.fromViewHolderToken( - dynamic viewHolderToken, - void Function() viewConnectedCallback, - void Function() viewDisconnectedCallback, - void Function(bool) viewStateChangedCallback) { - _constructor(viewHolderToken, viewConnectedCallback, viewDisconnectedCallback, viewStateChangedCallback); + dynamic viewHolderToken, + void Function() viewConnectedCallback, + void Function() viewDisconnectedCallback, + void Function(bool) viewStateChangedCallback, + ) { + _constructor( + viewHolderToken, viewConnectedCallback, viewDisconnectedCallback, viewStateChangedCallback); } - void _constructor(dynamic viewHolderToken, void Function() viewConnectedCallback, void Function() viewDisconnectedCallback, void Function(bool) viewStateChangedCallback) - native 'SceneHost_constructor'; + void _constructor( + dynamic viewHolderToken, + void Function() viewConnectedCallback, + void Function() viewDisconnectedCallback, + void Function(bool) viewStateChangedCallbac, + ) native 'SceneHost_constructor'; /// Releases the resources associated with the SceneHost. /// @@ -713,15 +810,12 @@ class SceneHost extends NativeFieldWrapperClass2 { /// Set properties on the linked scene. These properties include its bounds, /// as well as whether it can be the target of focus events or not. void setProperties( - double width, - double height, - double insetTop, - double insetRight, - double insetBottom, - double insetLeft, - bool focusable) native 'SceneHost_setProperties'; - - /// Set the opacity of the linked scene. This opacity value is applied only - /// once, when the child scene is composited into our own. - void setOpacity(double opacity) native 'SceneHost_setOpacity'; + double width, + double height, + double insetTop, + double insetRight, + double insetBottom, + double insetLeft, + bool focusable, + ) native 'SceneHost_setProperties'; } diff --git a/lib/ui/compositing/scene.cc b/lib/ui/compositing/scene.cc index fd3e86c7e5176..d02a232ac0433 100644 --- a/lib/ui/compositing/scene.cc +++ b/lib/ui/compositing/scene.cc @@ -7,6 +7,8 @@ #include "flutter/fml/trace_event.h" #include "flutter/lib/ui/painting/image.h" #include "flutter/lib/ui/painting/picture.h" +#include "flutter/lib/ui/ui_dart_state.h" +#include "flutter/lib/ui/window/window.h" #include "third_party/skia/include/core/SkImageInfo.h" #include "third_party/skia/include/core/SkSurface.h" #include "third_party/tonic/converter/dart_converter.h" @@ -36,13 +38,19 @@ fml::RefPtr Scene::create(std::shared_ptr rootLayer, Scene::Scene(std::shared_ptr rootLayer, uint32_t rasterizerTracingThreshold, bool checkerboardRasterCacheImages, - bool checkerboardOffscreenLayers) - : m_layerTree(new flutter::LayerTree()) { - m_layerTree->set_root_layer(std::move(rootLayer)); - m_layerTree->set_rasterizer_tracing_threshold(rasterizerTracingThreshold); - m_layerTree->set_checkerboard_raster_cache_images( + bool checkerboardOffscreenLayers) { + auto viewport_metrics = UIDartState::Current()->window()->viewport_metrics(); + + layer_tree_ = std::make_unique( + SkISize::Make(viewport_metrics.physical_width, + viewport_metrics.physical_height), + static_cast(viewport_metrics.physical_depth), + static_cast(viewport_metrics.device_pixel_ratio)); + layer_tree_->set_root_layer(std::move(rootLayer)); + layer_tree_->set_rasterizer_tracing_threshold(rasterizerTracingThreshold); + layer_tree_->set_checkerboard_raster_cache_images( checkerboardRasterCacheImages); - m_layerTree->set_checkerboard_offscreen_layers(checkerboardOffscreenLayers); + layer_tree_->set_checkerboard_offscreen_layers(checkerboardOffscreenLayers); } Scene::~Scene() {} @@ -56,11 +64,11 @@ Dart_Handle Scene::toImage(uint32_t width, Dart_Handle raw_image_callback) { TRACE_EVENT0("flutter", "Scene::toImage"); - if (!m_layerTree) { + if (!layer_tree_) { return tonic::ToDart("Scene did not contain a layer tree."); } - auto picture = m_layerTree->Flatten(SkRect::MakeWH(width, height)); + auto picture = layer_tree_->Flatten(SkRect::MakeWH(width, height)); if (!picture) { return tonic::ToDart("Could not flatten scene into a layer tree."); } @@ -69,7 +77,7 @@ Dart_Handle Scene::toImage(uint32_t width, } std::unique_ptr Scene::takeLayerTree() { - return std::move(m_layerTree); + return std::move(layer_tree_); } } // namespace flutter diff --git a/lib/ui/compositing/scene.h b/lib/ui/compositing/scene.h index 2d280a534db63..42c972d8d4ee3 100644 --- a/lib/ui/compositing/scene.h +++ b/lib/ui/compositing/scene.h @@ -45,7 +45,7 @@ class Scene : public RefCountedDartWrappable { bool checkerboardRasterCacheImages, bool checkerboardOffscreenLayers); - std::unique_ptr m_layerTree; + std::unique_ptr layer_tree_; }; } // namespace flutter diff --git a/lib/ui/compositing/scene_builder.cc b/lib/ui/compositing/scene_builder.cc index 5b7148803c2c2..467f187400255 100644 --- a/lib/ui/compositing/scene_builder.cc +++ b/lib/ui/compositing/scene_builder.cc @@ -10,6 +10,7 @@ #include "flutter/flow/layers/clip_rrect_layer.h" #include "flutter/flow/layers/color_filter_layer.h" #include "flutter/flow/layers/container_layer.h" +#include "flutter/flow/layers/image_filter_layer.h" #include "flutter/flow/layers/layer.h" #include "flutter/flow/layers/layer_tree.h" #include "flutter/flow/layers/opacity_layer.h" @@ -23,8 +24,6 @@ #include "flutter/fml/build_config.h" #include "flutter/lib/ui/painting/matrix.h" #include "flutter/lib/ui/painting/shader.h" -#include "flutter/lib/ui/ui_dart_state.h" -#include "flutter/lib/ui/window/window.h" #include "third_party/skia/include/core/SkColorFilter.h" #include "third_party/tonic/converter/dart_converter.h" #include "third_party/tonic/dart_args.h" @@ -51,6 +50,7 @@ IMPLEMENT_WRAPPERTYPEINFO(ui, SceneBuilder); V(SceneBuilder, pushClipPath) \ V(SceneBuilder, pushOpacity) \ V(SceneBuilder, pushColorFilter) \ + V(SceneBuilder, pushImageFilter) \ V(SceneBuilder, pushBackdropFilter) \ V(SceneBuilder, pushShaderMask) \ V(SceneBuilder, pushPhysicalShape) \ @@ -80,7 +80,12 @@ void SceneBuilder::RegisterNatives(tonic::DartLibraryNatives* natives) { }); } -SceneBuilder::SceneBuilder() = default; +SceneBuilder::SceneBuilder() { + // Add a ContainerLayer as the root layer, so that AddLayer operations are + // always valid. + PushLayer(std::make_shared()); +} + SceneBuilder::~SceneBuilder() = default; fml::RefPtr SceneBuilder::pushTransform( @@ -149,6 +154,14 @@ fml::RefPtr SceneBuilder::pushColorFilter( return EngineLayer::MakeRetained(layer); } +fml::RefPtr SceneBuilder::pushImageFilter( + const ImageFilter* image_filter) { + auto layer = + std::make_shared(image_filter->filter()); + PushLayer(layer); + return EngineLayer::MakeRetained(layer); +} + fml::RefPtr SceneBuilder::pushBackdropFilter(ImageFilter* filter) { auto layer = std::make_shared(filter->filter()); PushLayer(layer); @@ -176,12 +189,6 @@ fml::RefPtr SceneBuilder::pushPhysicalShape(const CanvasPath* path, int clipBehavior) { auto layer = std::make_shared( static_cast(color), static_cast(shadow_color), - static_cast(UIDartState::Current() - ->window() - ->viewport_metrics() - .device_pixel_ratio), - static_cast( - UIDartState::Current()->window()->viewport_metrics().physical_depth), static_cast(elevation), path->path(), static_cast(clipBehavior)); PushLayer(layer); @@ -189,33 +196,24 @@ fml::RefPtr SceneBuilder::pushPhysicalShape(const CanvasPath* path, } void SceneBuilder::addRetained(fml::RefPtr retainedLayer) { - if (!current_layer_) { - return; - } - current_layer_->Add(retainedLayer->Layer()); + AddLayer(retainedLayer->Layer()); } void SceneBuilder::pop() { - if (!current_layer_) { - return; - } - current_layer_ = current_layer_->parent(); + PopLayer(); } void SceneBuilder::addPicture(double dx, double dy, Picture* picture, int hints) { - if (!current_layer_) { - return; - } SkPoint offset = SkPoint::Make(dx, dy); SkRect pictureRect = picture->picture()->cullRect(); pictureRect.offset(offset.x(), offset.y()); auto layer = std::make_unique( offset, UIDartState::CreateGPUObject(picture->picture()), !!(hints & 1), !!(hints & 2)); - current_layer_->Add(std::move(layer)); + AddLayer(std::move(layer)); } void SceneBuilder::addTexture(double dx, @@ -224,12 +222,9 @@ void SceneBuilder::addTexture(double dx, double height, int64_t textureId, bool freeze) { - if (!current_layer_) { - return; - } auto layer = std::make_unique( SkPoint::Make(dx, dy), SkSize::Make(width, height), textureId, freeze); - current_layer_->Add(std::move(layer)); + AddLayer(std::move(layer)); } void SceneBuilder::addPlatformView(double dx, @@ -237,12 +232,9 @@ void SceneBuilder::addPlatformView(double dx, double width, double height, int64_t viewId) { - if (!current_layer_) { - return; - } auto layer = std::make_unique( SkPoint::Make(dx, dy), SkSize::Make(width, height), viewId); - current_layer_->Add(std::move(layer)); + AddLayer(std::move(layer)); } #if defined(OS_FUCHSIA) @@ -252,13 +244,10 @@ void SceneBuilder::addChildScene(double dx, double height, SceneHost* sceneHost, bool hitTestable) { - if (!current_layer_) { - return; - } auto layer = std::make_unique( sceneHost->id(), SkPoint::Make(dx, dy), SkSize::Make(width, height), hitTestable); - current_layer_->Add(std::move(layer)); + AddLayer(std::move(layer)); } #endif // defined(OS_FUCHSIA) @@ -267,14 +256,11 @@ void SceneBuilder::addPerformanceOverlay(uint64_t enabledOptions, double right, double top, double bottom) { - if (!current_layer_) { - return; - } SkRect rect = SkRect::MakeLTRB(left, top, right, bottom); auto layer = std::make_unique(enabledOptions); layer->set_paint_bounds(rect); - current_layer_->Add(std::move(layer)); + AddLayer(std::move(layer)); } void SceneBuilder::setRasterizerTracingThreshold(uint32_t frameInterval) { @@ -290,29 +276,33 @@ void SceneBuilder::setCheckerboardOffscreenLayers(bool checkerboard) { } fml::RefPtr SceneBuilder::build() { + FML_DCHECK(layer_stack_.size() >= 1); + fml::RefPtr scene = Scene::create( - std::move(root_layer_), rasterizer_tracing_threshold_, + layer_stack_[0], rasterizer_tracing_threshold_, checkerboard_raster_cache_images_, checkerboard_offscreen_layers_); - ClearDartWrapper(); + ClearDartWrapper(); // may delete this object. return scene; } -void SceneBuilder::PushLayer(std::shared_ptr layer) { +void SceneBuilder::AddLayer(std::shared_ptr layer) { FML_DCHECK(layer); - if (!root_layer_) { - root_layer_ = std::move(layer); - current_layer_ = root_layer_.get(); - return; + if (!layer_stack_.empty()) { + layer_stack_.back()->Add(std::move(layer)); } +} - if (!current_layer_) { - return; - } +void SceneBuilder::PushLayer(std::shared_ptr layer) { + AddLayer(layer); + layer_stack_.push_back(std::move(layer)); +} - flutter::ContainerLayer* newLayer = layer.get(); - current_layer_->Add(std::move(layer)); - current_layer_ = newLayer; +void SceneBuilder::PopLayer() { + // We never pop the root layer, so that AddLayer operations are always valid. + if (layer_stack_.size() > 1) { + layer_stack_.pop_back(); + } } } // namespace flutter diff --git a/lib/ui/compositing/scene_builder.h b/lib/ui/compositing/scene_builder.h index d378876d960a2..51ae76e651ce0 100644 --- a/lib/ui/compositing/scene_builder.h +++ b/lib/ui/compositing/scene_builder.h @@ -8,8 +8,9 @@ #include #include -#include +#include +#include "flutter/flow/layers/container_layer.h" #include "flutter/lib/ui/compositing/scene.h" #include "flutter/lib/ui/dart_wrapper.h" #include "flutter/lib/ui/painting/color_filter.h" @@ -35,7 +36,6 @@ class SceneBuilder : public RefCountedDartWrappable { static fml::RefPtr create() { return fml::MakeRefCounted(); } - ~SceneBuilder() override; fml::RefPtr pushTransform(tonic::Float64List& matrix4); @@ -50,6 +50,7 @@ class SceneBuilder : public RefCountedDartWrappable { int clipBehavior); fml::RefPtr pushOpacity(int alpha, double dx = 0, double dy = 0); fml::RefPtr pushColorFilter(const ColorFilter* color_filter); + fml::RefPtr pushImageFilter(const ImageFilter* image_filter); fml::RefPtr pushBackdropFilter(ImageFilter* filter); fml::RefPtr pushShaderMask(Shader* shader, double maskRectLeft, @@ -98,7 +99,6 @@ class SceneBuilder : public RefCountedDartWrappable { #endif void setRasterizerTracingThreshold(uint32_t frameInterval); - void setCheckerboardRasterCacheImages(bool checkerboard); void setCheckerboardOffscreenLayers(bool checkerboard); @@ -109,15 +109,15 @@ class SceneBuilder : public RefCountedDartWrappable { private: SceneBuilder(); - std::shared_ptr root_layer_; - flutter::ContainerLayer* current_layer_ = nullptr; + void AddLayer(std::shared_ptr layer); + void PushLayer(std::shared_ptr layer); + void PopLayer(); + std::vector> layer_stack_; int rasterizer_tracing_threshold_ = 0; bool checkerboard_raster_cache_images_ = false; bool checkerboard_offscreen_layers_ = false; - void PushLayer(std::shared_ptr layer); - FML_DISALLOW_COPY_AND_ASSIGN(SceneBuilder); }; diff --git a/lib/ui/compositing/scene_host.cc b/lib/ui/compositing/scene_host.cc index 889f5cb174351..e4f83cc3ff0e8 100644 --- a/lib/ui/compositing/scene_host.cc +++ b/lib/ui/compositing/scene_host.cc @@ -85,10 +85,9 @@ namespace flutter { IMPLEMENT_WRAPPERTYPEINFO(ui, SceneHost); -#define FOR_EACH_BINDING(V) \ - V(SceneHost, dispose) \ - V(SceneHost, setProperties) \ - V(SceneHost, setOpacity) +#define FOR_EACH_BINDING(V) \ + V(SceneHost, dispose) \ + V(SceneHost, setProperties) FOR_EACH_BINDING(DART_NATIVE_CALLBACK) @@ -205,13 +204,4 @@ void SceneHost::setProperties(double width, }); } -void SceneHost::setOpacity(double opacity) { - gpu_task_runner_->PostTask([id = koid_, opacity]() { - auto* view_holder = flutter::ViewHolder::FromId(id); - FML_DCHECK(view_holder); - - view_holder->SetOpacity(opacity); - }); -} - } // namespace flutter diff --git a/lib/ui/compositing/scene_host.h b/lib/ui/compositing/scene_host.h index 9e47cf49a3645..05c36e3c4f0cf 100644 --- a/lib/ui/compositing/scene_host.h +++ b/lib/ui/compositing/scene_host.h @@ -33,14 +33,11 @@ class SceneHost : public RefCountedDartWrappable { static void OnViewDisconnected(scenic::ResourceId id); static void OnViewStateChanged(scenic::ResourceId id, bool state); - SceneHost(fml::RefPtr viewHolderToken, - Dart_Handle viewConnectedCallback, - Dart_Handle viewDisconnectedCallback, - Dart_Handle viewStateChangedCallback); ~SceneHost() override; zx_koid_t id() const { return koid_; } + // These are visible to Dart. void dispose(); void setProperties(double width, double height, @@ -49,9 +46,13 @@ class SceneHost : public RefCountedDartWrappable { double insetBottom, double insetLeft, bool focusable); - void setOpacity(double opacity); private: + SceneHost(fml::RefPtr viewHolderToken, + Dart_Handle viewConnectedCallback, + Dart_Handle viewDisconnectedCallback, + Dart_Handle viewStateChangedCallback); + fml::RefPtr gpu_task_runner_; tonic::DartPersistentValue view_connected_callback_; tonic::DartPersistentValue view_disconnected_callback_; diff --git a/lib/ui/fixtures/hello_loop_2.gif b/lib/ui/fixtures/hello_loop_2.gif new file mode 100644 index 0000000000000..e9ac36d917d22 Binary files /dev/null and b/lib/ui/fixtures/hello_loop_2.gif differ diff --git a/lib/ui/fixtures/hello_loop_2.webp b/lib/ui/fixtures/hello_loop_2.webp new file mode 100644 index 0000000000000..cc79d776f8a68 Binary files /dev/null and b/lib/ui/fixtures/hello_loop_2.webp differ diff --git a/lib/ui/hooks.dart b/lib/ui/hooks.dart index cee8de37388a3..83ff3a1059cb4 100644 --- a/lib/ui/hooks.dart +++ b/lib/ui/hooks.dart @@ -308,8 +308,9 @@ void _invoke3(void callback(A1 a1, A2 a2, A3 a3), Zone zone, A1 arg1 // If this value changes, update the encoding code in the following files: // // * pointer_data.cc -// * FlutterView.java -const int _kPointerDataFieldCount = 24; +// * pointers.dart +// * AndroidTouchProcessor.java +const int _kPointerDataFieldCount = 28; PointerDataPacket _unpackPointerDataPacket(ByteData packet) { const int kStride = Int64List.bytesPerElement; @@ -325,10 +326,14 @@ PointerDataPacket _unpackPointerDataPacket(ByteData packet) { kind: PointerDeviceKind.values[packet.getInt64(kStride * offset++, _kFakeHostEndian)], signalKind: PointerSignalKind.values[packet.getInt64(kStride * offset++, _kFakeHostEndian)], device: packet.getInt64(kStride * offset++, _kFakeHostEndian), + pointerIdentifier: packet.getInt64(kStride * offset++, _kFakeHostEndian), physicalX: packet.getFloat64(kStride * offset++, _kFakeHostEndian), physicalY: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + physicalDeltaX: packet.getFloat64(kStride * offset++, _kFakeHostEndian), + physicalDeltaY: packet.getFloat64(kStride * offset++, _kFakeHostEndian), buttons: packet.getInt64(kStride * offset++, _kFakeHostEndian), obscured: packet.getInt64(kStride * offset++, _kFakeHostEndian) != 0, + synthesized: packet.getInt64(kStride * offset++, _kFakeHostEndian) != 0, pressure: packet.getFloat64(kStride * offset++, _kFakeHostEndian), pressureMin: packet.getFloat64(kStride * offset++, _kFakeHostEndian), pressureMax: packet.getFloat64(kStride * offset++, _kFakeHostEndian), diff --git a/lib/ui/io_manager.h b/lib/ui/io_manager.h index 200d732f115ad..dfcee970c745b 100644 --- a/lib/ui/io_manager.h +++ b/lib/ui/io_manager.h @@ -7,6 +7,7 @@ #include "flutter/flow/skia_gpu_object.h" #include "flutter/fml/memory/weak_ptr.h" +#include "flutter/fml/synchronization/sync_switch.h" #include "third_party/skia/include/gpu/GrContext.h" namespace flutter { @@ -22,6 +23,8 @@ class IOManager { virtual fml::WeakPtr GetResourceContext() const = 0; virtual fml::RefPtr GetSkiaUnrefQueue() const = 0; + + virtual std::shared_ptr GetIsGpuDisabledSyncSwitch() = 0; }; } // namespace flutter diff --git a/lib/ui/painting.dart b/lib/ui/painting.dart index 0eacb1bca4e07..9c6d3f9df3c95 100644 --- a/lib/ui/painting.dart +++ b/lib/ui/painting.dart @@ -307,6 +307,14 @@ class Color { } } + /// Returns an alpha value representative of the provided [opacity] value. + /// + /// The [opacity] value may not be null. + static int getAlphaFromOpacity(double opacity) { + assert(opacity != null); + return (opacity.clamp(0.0, 1.0) * 255).round(); + } + @override bool operator ==(dynamic other) { if (identical(this, other)) @@ -1058,6 +1066,7 @@ class Paint { static const int _kMaskFilterBlurStyleIndex = 10; static const int _kMaskFilterSigmaIndex = 11; static const int _kInvertColorIndex = 12; + static const int _kDitherIndex = 13; static const int _kIsAntiAliasOffset = _kIsAntiAliasIndex << 2; static const int _kColorOffset = _kColorIndex << 2; @@ -1072,8 +1081,9 @@ class Paint { static const int _kMaskFilterBlurStyleOffset = _kMaskFilterBlurStyleIndex << 2; static const int _kMaskFilterSigmaOffset = _kMaskFilterSigmaIndex << 2; static const int _kInvertColorOffset = _kInvertColorIndex << 2; + static const int _kDitherOffset = _kDitherIndex << 2; // If you add more fields, remember to update _kDataByteCount. - static const int _kDataByteCount = 52; + static const int _kDataByteCount = 56; // Binary format must match the deserialization code in paint.cc. List _objects; @@ -1082,6 +1092,14 @@ class Paint { static const int _kImageFilterIndex = 2; static const int _kObjectCount = 3; // Must be one larger than the largest index. + /// Constructs an empty [Paint] object with all fields initialized to + /// their defaults. + Paint() { + if (enableDithering) { + _dither = true; + } + } + /// Whether to apply anti-aliasing to lines and images drawn on the /// canvas. /// @@ -1379,16 +1397,24 @@ class Paint { /// /// * [MaskFilter], which is used for drawing geometry. ImageFilter get imageFilter { - if (_objects == null) + if (_objects == null || _objects[_kImageFilterIndex] == null) return null; - return _objects[_kImageFilterIndex]; + return _objects[_kImageFilterIndex].creator; } + set imageFilter(ImageFilter value) { - _objects ??= List(_kObjectCount); - _objects[_kImageFilterIndex] = value; + if (value == null) { + if (_objects != null) { + _objects[_kImageFilterIndex] = null; + } + } else { + _objects ??= List(_kObjectCount); + if (_objects[_kImageFilterIndex]?.creator != value) { + _objects[_kImageFilterIndex] = value._toNativeImageFilter(); + } + } } - /// Whether the colors of the image are inverted when drawn. /// /// Inverting the colors of an image applies a new color filter that will @@ -1401,6 +1427,30 @@ class Paint { _data.setInt32(_kInvertColorOffset, value ? 1 : 0, _kFakeHostEndian); } + bool get _dither { + return _data.getInt32(_kDitherOffset, _kFakeHostEndian) == 1; + } + set _dither(bool value) { + _data.setInt32(_kDitherOffset, value ? 1 : 0, _kFakeHostEndian); + } + + /// Whether to dither the output when drawing images. + /// + /// If false, the default value, dithering will be enabled when the input + /// color depth is higher than the output color depth. For example, + /// drawing an RGB8 image onto an RGB565 canvas. + /// + /// This value also controls dithering of [shader]s, which can make + /// gradients appear smoother. + /// + /// Whether or not dithering affects the output is implementation defined. + /// Some implementations may choose to ignore this completely, if they're + /// unable to control dithering. + /// + /// To ensure that dithering is consistently enabled for your entire + /// application, set this to true before invoking any drawing related code. + static bool enableDithering = false; + @override String toString() { final StringBuffer result = StringBuffer(); @@ -1459,6 +1509,8 @@ class Paint { } if (invertColors) result.write('${semicolon}invert: $invertColors'); + if (_dither) + result.write('${semicolon}dither: $_dither'); result.write(')'); return result.toString(); } @@ -2580,8 +2632,6 @@ class ColorFilter { final int _type; // The type of SkColorFilter class to create for Skia. - // These constants must be kept in sync with ColorFilterType in paint.cc. - static const int _TypeNone = 0; // null static const int _TypeMode = 1; // MakeModeFilter static const int _TypeMatrix = 2; // MakeMatrixFilterRowMajor255 static const int _TypeLinearToSrgbGamma = 3; // MakeLinearToSRGBGamma @@ -2651,7 +2701,7 @@ class ColorFilter { /// This is a private class, rather than being the implementation of the public /// ColorFilter, because we want ColorFilter to be const constructible and /// efficiently comparable, so that widgets can check for ColorFilter equality to -// avoid repainting. +/// avoid repainting. class _ColorFilter extends NativeFieldWrapperClass2 { _ColorFilter.mode(this.creator) : assert(creator != null), @@ -2696,15 +2746,112 @@ class _ColorFilter extends NativeFieldWrapperClass2 { /// See also: /// /// * [BackdropFilter], a widget that applies [ImageFilter] to its rendering. +/// * [ImageFiltered], a widget that applies [ImageFilter] to its children. /// * [SceneBuilder.pushBackdropFilter], which is the low-level API for using -/// this class. -class ImageFilter extends NativeFieldWrapperClass2 { +/// this class as a backdrop filter. +/// * [SceneBuilder.pushImageFilter], which is the low-level API for using +/// this class as a child layer filter. +class ImageFilter { + /// Creates an image filter that applies a Gaussian blur. + ImageFilter.blur({ double sigmaX = 0.0, double sigmaY = 0.0 }) + : _data = _makeList(sigmaX, sigmaY), + _filterQuality = null, + _type = _kTypeBlur; + + /// Creates an image filter that applies a matrix transformation. + /// + /// For example, applying a positive scale matrix (see [Matrix4.diagonal3]) + /// when used with [BackdropFilter] would magnify the background image. + ImageFilter.matrix(Float64List matrix4, + { FilterQuality filterQuality = FilterQuality.low }) + : _data = Float64List.fromList(matrix4), + _filterQuality = filterQuality, + _type = _kTypeMatrix { + if (matrix4.length != 16) + throw ArgumentError('"matrix4" must have 16 entries.'); + } + + static Float64List _makeList(double a, double b) { + final Float64List list = Float64List(2); + if (a != null) + list[0] = a; + if (b != null) + list[1] = b; + return list; + } + + final Float64List _data; + final FilterQuality _filterQuality; + final int _type; + _ImageFilter _nativeFilter; + + // The type of SkImageFilter class to create for Skia. + static const int _kTypeBlur = 0; // MakeBlurFilter + static const int _kTypeMatrix = 1; // MakeMatrixFilterRowMajor255 + + @override + bool operator ==(dynamic other) { + if (other is! ImageFilter) { + return false; + } + final ImageFilter typedOther = other; + + if (_type != typedOther._type) { + return false; + } + if (!_listEquals(_data, typedOther._data)) { + return false; + } + + return _filterQuality == typedOther._filterQuality; + } + + _ImageFilter _toNativeImageFilter() => _nativeFilter ??= _makeNativeImageFilter(); + + _ImageFilter _makeNativeImageFilter() { + if (_data == null) { + return null; + } + switch (_type) { + case _kTypeBlur: + return _ImageFilter.blur(this); + case _kTypeMatrix: + return _ImageFilter.matrix(this); + default: + throw StateError('Unknown mode $_type for ImageFilter.'); + } + } + + @override + int get hashCode => hashValues(_filterQuality, hashList(_data), _type); + + @override + String toString() { + switch (_type) { + case _kTypeBlur: + return 'ImageFilter.blur(${_data[0]}, ${_data[1]})'; + case _kTypeMatrix: + return 'ImageFilter.matrix($_data, $_filterQuality)'; + default: + return 'Unknown ImageFilter type. This is an error. If you\'re seeing this, please file an issue at https://github.com/flutter/flutter/issues/new.'; + } + } +} + +/// An [ImageFilter] that is backed by a native SkImageFilter. +/// +/// This is a private class, rather than being the implementation of the public +/// ImageFilter, because we want ImageFilter to be efficiently comparable, so that +/// widgets can check for ImageFilter equality to avoid repainting. +class _ImageFilter extends NativeFieldWrapperClass2 { void _constructor() native 'ImageFilter_constructor'; /// Creates an image filter that applies a Gaussian blur. - ImageFilter.blur({ double sigmaX = 0.0, double sigmaY = 0.0 }) { + _ImageFilter.blur(this.creator) + : assert(creator != null), + assert(creator._type == ImageFilter._kTypeBlur) { _constructor(); - _initBlur(sigmaX, sigmaY); + _initBlur(creator._data[0], creator._data[1]); } void _initBlur(double sigmaX, double sigmaY) native 'ImageFilter_initBlur'; @@ -2712,14 +2859,19 @@ class ImageFilter extends NativeFieldWrapperClass2 { /// /// For example, applying a positive scale matrix (see [Matrix4.diagonal3]) /// when used with [BackdropFilter] would magnify the background image. - ImageFilter.matrix(Float64List matrix4, - { FilterQuality filterQuality = FilterQuality.low }) { - if (matrix4.length != 16) + _ImageFilter.matrix(this.creator) + : assert(creator != null), + assert(creator._type == ImageFilter._kTypeMatrix) { + if (creator._data.length != 16) throw ArgumentError('"matrix4" must have 16 entries.'); _constructor(); - _initMatrix(matrix4, filterQuality.index); + _initMatrix(creator._data, creator._filterQuality.index); } void _initMatrix(Float64List matrix4, int filterQuality) native 'ImageFilter_initMatrix'; + + /// The original Dart object that created the native wrapper, which retains + /// the values used for the filter. + final ImageFilter creator; } /// Base class for objects such as [Gradient] and [ImageShader] which diff --git a/lib/ui/painting/engine_layer.cc b/lib/ui/painting/engine_layer.cc index 7ac9c98497f41..0868b42cc2928 100644 --- a/lib/ui/painting/engine_layer.cc +++ b/lib/ui/painting/engine_layer.cc @@ -4,8 +4,6 @@ #include "flutter/lib/ui/painting/engine_layer.h" -#include "flutter/flow/layers/container_layer.h" - #include "third_party/tonic/converter/dart_converter.h" #include "third_party/tonic/dart_args.h" #include "third_party/tonic/dart_binding_macros.h" diff --git a/lib/ui/painting/engine_layer.h b/lib/ui/painting/engine_layer.h index 1dc565c5193c2..a679ef2fe50f3 100644 --- a/lib/ui/painting/engine_layer.h +++ b/lib/ui/painting/engine_layer.h @@ -7,7 +7,7 @@ #include "flutter/lib/ui/dart_wrapper.h" -#include "flutter/flow/layers/layer.h" +#include "flutter/flow/layers/container_layer.h" namespace tonic { class DartLibraryNatives; diff --git a/lib/ui/painting/image_decoder.cc b/lib/ui/painting/image_decoder.cc index c6a35f073b05c..9902d95fbd894 100644 --- a/lib/ui/painting/image_decoder.cc +++ b/lib/ui/painting/image_decoder.cc @@ -151,8 +151,7 @@ static sk_sp ImageFromCompressedData( static SkiaGPUObject UploadRasterImage( sk_sp image, - fml::WeakPtr context, - fml::RefPtr queue, + fml::WeakPtr io_manager, const fml::tracing::TraceFlow& flow) { TRACE_EVENT0("flutter", __FUNCTION__); flow.Step(__FUNCTION__); @@ -161,7 +160,7 @@ static SkiaGPUObject UploadRasterImage( // the this method. FML_DCHECK(!image->isTextureBacked()); - if (!context || !queue) { + if (!io_manager->GetResourceContext() || !io_manager->GetSkiaUnrefQueue()) { FML_LOG(ERROR) << "Could not acquire context of release queue for texture upload."; return {}; @@ -173,22 +172,40 @@ static SkiaGPUObject UploadRasterImage( return {}; } - auto texture_image = - SkImage::MakeCrossContextFromPixmap(context.get(), // context - pixmap, // pixmap - true, // buildMips, - true // limitToMaxTextureSize - ); - - if (!texture_image) { - FML_LOG(ERROR) << "Could not make x-context image."; - return {}; - } - - return {texture_image, queue}; + SkiaGPUObject result; + io_manager->GetIsGpuDisabledSyncSwitch()->Execute( + fml::SyncSwitch::Handlers() + .SetIfTrue([&result, &pixmap, &image] { + SkSafeRef(image.get()); + sk_sp texture_image = SkImage::MakeFromRaster( + pixmap, + [](const void* pixels, SkImage::ReleaseContext context) { + SkSafeUnref(static_cast(context)); + }, + image.get()); + result = {texture_image, nullptr}; + }) + .SetIfFalse([&result, context = io_manager->GetResourceContext(), + &pixmap, queue = io_manager->GetSkiaUnrefQueue()] { + sk_sp texture_image = SkImage::MakeCrossContextFromPixmap( + context.get(), // context + pixmap, // pixmap + true, // buildMips, + true // limitToMaxTextureSize + ); + if (!texture_image) { + FML_LOG(ERROR) << "Could not make x-context image."; + result = {}; + } else { + result = {texture_image, queue}; + } + })); + + return result; } -void ImageDecoder::Decode(ImageDescriptor descriptor, ImageResult callback) { +void ImageDecoder::Decode(ImageDescriptor descriptor, + const ImageResult& callback) { TRACE_EVENT0("flutter", __FUNCTION__); fml::tracing::TraceFlow flow(__FUNCTION__); @@ -264,9 +281,8 @@ void ImageDecoder::Decode(ImageDescriptor descriptor, ImageResult callback) { return; } - auto uploaded = UploadRasterImage( - std::move(decompressed), io_manager->GetResourceContext(), - io_manager->GetSkiaUnrefQueue(), flow); + auto uploaded = + UploadRasterImage(std::move(decompressed), io_manager, flow); if (!uploaded.get()) { FML_LOG(ERROR) << "Could not upload image to the GPU."; diff --git a/lib/ui/painting/image_decoder.h b/lib/ui/painting/image_decoder.h index 202dff569e1a5..7ffda4ff30017 100644 --- a/lib/ui/painting/image_decoder.h +++ b/lib/ui/painting/image_decoder.h @@ -55,7 +55,7 @@ class ImageDecoder { // concurrently. Texture upload is done on the IO thread and the result // returned back on the UI thread. On error, the texture is null but the // callback is guaranteed to return on the UI thread. - void Decode(ImageDescriptor descriptor, ImageResult result); + void Decode(ImageDescriptor descriptor, const ImageResult& result); fml::WeakPtr GetWeakPtr() const; diff --git a/lib/ui/painting/image_decoder_unittests.cc b/lib/ui/painting/image_decoder_unittests.cc index b4dfece10738a..47e7bafe96052 100644 --- a/lib/ui/painting/image_decoder_unittests.cc +++ b/lib/ui/painting/image_decoder_unittests.cc @@ -9,6 +9,7 @@ #include "flutter/testing/test_gl_surface.h" #include "flutter/testing/testing.h" #include "flutter/testing/thread_test.h" +#include "third_party/skia/include/codec/SkCodec.h" namespace flutter { namespace testing { @@ -29,7 +30,8 @@ class TestIOManager final : public IOManager { task_runner, fml::TimeDelta::FromNanoseconds(0))), runner_(task_runner), - weak_factory_(this) { + weak_factory_(this), + is_gpu_disabled_sync_switch_(std::make_shared()) { FML_CHECK(task_runner->RunsTasksOnCurrentThread()) << "The IO manager must be initialized its primary task runner. The " "test harness may not be setup correctly/safely."; @@ -62,6 +64,14 @@ class TestIOManager final : public IOManager { return unref_queue_; } + // |IOManager| + std::shared_ptr GetIsGpuDisabledSyncSwitch() override { + did_access_is_gpu_disabled_sync_switch_ = true; + return is_gpu_disabled_sync_switch_; + } + + bool did_access_is_gpu_disabled_sync_switch_ = false; + private: TestGLSurface gl_surface_; sk_sp gl_context_; @@ -70,6 +80,7 @@ class TestIOManager final : public IOManager { fml::WeakPtr weak_prototype_; fml::RefPtr runner_; fml::WeakPtrFactory weak_factory_; + std::shared_ptr is_gpu_disabled_sync_switch_; FML_DISALLOW_COPY_AND_ASSIGN(TestIOManager); }; @@ -167,7 +178,7 @@ TEST_F(ImageDecoderFixtureTest, ValidImageResultsInSuccess) { fml::AutoResetWaitableEvent latch; - std::unique_ptr io_manager; + std::unique_ptr io_manager; auto release_io_manager = [&]() { io_manager.reset(); @@ -187,8 +198,10 @@ TEST_F(ImageDecoderFixtureTest, ValidImageResultsInSuccess) { ImageDecoder::ImageResult callback = [&](SkiaGPUObject image) { ASSERT_TRUE(runners.GetUITaskRunner()->RunsTasksOnCurrentThread()); ASSERT_TRUE(image.get()); + EXPECT_TRUE(io_manager->did_access_is_gpu_disabled_sync_switch_); runners.GetIOTaskRunner()->PostTask(release_io_manager); }; + EXPECT_FALSE(io_manager->did_access_is_gpu_disabled_sync_switch_); image_decoder->Decode(std::move(image_descriptor), callback); }; @@ -198,7 +211,6 @@ TEST_F(ImageDecoderFixtureTest, ValidImageResultsInSuccess) { }; runners.GetIOTaskRunner()->PostTask(setup_io_manager_and_decode); - latch.Wait(); } @@ -485,5 +497,27 @@ TEST_F(ImageDecoderFixtureTest, CanResizeWithoutDecode) { latch.Wait(); } +// Verifies https://skia-review.googlesource.com/c/skia/+/259161 is present in +// Flutter. +TEST(ImageDecoderTest, + VerifyCodecRepeatCountsForGifAndWebPAreConsistentWithLoopCounts) { + auto gif_mapping = OpenFixtureAsSkData("hello_loop_2.gif"); + auto webp_mapping = OpenFixtureAsSkData("hello_loop_2.webp"); + + ASSERT_TRUE(gif_mapping); + ASSERT_TRUE(webp_mapping); + + auto gif_codec = SkCodec::MakeFromData(gif_mapping); + auto webp_codec = SkCodec::MakeFromData(webp_mapping); + + ASSERT_TRUE(gif_codec); + ASSERT_TRUE(webp_codec); + + // Both fixtures have a loop count of 2 which should lead to the repeat count + // of 1 + ASSERT_EQ(gif_codec->getRepetitionCount(), 1); + ASSERT_EQ(webp_codec->getRepetitionCount(), 1); +} + } // namespace testing } // namespace flutter diff --git a/lib/ui/painting/image_encoding.cc b/lib/ui/painting/image_encoding.cc index 86bb45bfd33be..cef92a7e2451a 100644 --- a/lib/ui/painting/image_encoding.cc +++ b/lib/ui/painting/image_encoding.cc @@ -51,33 +51,18 @@ void InvokeDataCallback(std::unique_ptr callback, } } -sk_sp ConvertToRasterImageIfNecessary(sk_sp image, - GrContext* context) { - SkPixmap pixmap; - if (image->peekPixels(&pixmap)) { - // This is already a raster image. - return image; - } - - if (sk_sp raster_image = image->makeRasterImage()) { - // The image can be converted to a raster image. - return raster_image; - } - - // Cross-context images do not support makeRasterImage. Convert these images - // by drawing them into a surface. - if (context == nullptr) { - return nullptr; +sk_sp ConvertToRasterUsingResourceContext( + sk_sp image, + GrContext* resource_context) { + sk_sp surface; + SkImageInfo surface_info = SkImageInfo::MakeN32Premul(image->dimensions()); + if (resource_context) { + surface = SkSurface::MakeRenderTarget(resource_context, SkBudgeted::kNo, + surface_info); + } else { + surface = SkSurface::MakeRaster(surface_info); } - TRACE_EVENT0("flutter", __FUNCTION__); - - // Create a GPU surface with the context and then do a device to host copy of - // image contents. - auto surface = SkSurface::MakeRenderTarget( - context, SkBudgeted::kNo, - SkImageInfo::MakeN32Premul(image->dimensions())); - if (surface == nullptr || surface->getCanvas() == nullptr) { FML_LOG(ERROR) << "Could not create a surface to copy the texture into."; return nullptr; @@ -96,6 +81,64 @@ sk_sp ConvertToRasterImageIfNecessary(sk_sp image, return snapshot->makeRasterImage(); } +void ConvertImageToRaster(sk_sp image, + std::function)> encode_task, + fml::RefPtr gpu_task_runner, + fml::RefPtr io_task_runner, + GrContext* resource_context, + fml::WeakPtr snapshot_delegate) { + // Check validity of the image. + if (image == nullptr) { + FML_LOG(ERROR) << "Image was null."; + encode_task(nullptr); + return; + } + + auto dimensions = image->dimensions(); + + if (dimensions.isEmpty()) { + FML_LOG(ERROR) << "Image dimensions were empty."; + encode_task(nullptr); + return; + } + + SkPixmap pixmap; + if (image->peekPixels(&pixmap)) { + // This is already a raster image. + encode_task(image); + return; + } + + if (sk_sp raster_image = image->makeRasterImage()) { + // The image can be converted to a raster image. + encode_task(raster_image); + return; + } + + // Cross-context images do not support makeRasterImage. Convert these images + // by drawing them into a surface. This must be done on the GPU thread + // to prevent concurrent usage of the image on both the IO and GPU threads. + gpu_task_runner->PostTask([image, encode_task = std::move(encode_task), + resource_context, snapshot_delegate, + io_task_runner]() { + sk_sp raster_image = + snapshot_delegate->ConvertToRasterImage(image); + + io_task_runner->PostTask([image, encode_task = std::move(encode_task), + raster_image = std::move(raster_image), + resource_context]() mutable { + if (!raster_image) { + // The rasterizer was unable to render the cross-context image + // (presumably because it does not have a GrContext). In that case, + // convert the image on the IO thread using the resource context. + raster_image = + ConvertToRasterUsingResourceContext(image, resource_context); + } + encode_task(raster_image); + }); + }); +} + sk_sp CopyImageByteData(sk_sp raster_image, SkColorType color_type) { FML_DCHECK(raster_image); @@ -132,28 +175,10 @@ sk_sp CopyImageByteData(sk_sp raster_image, return SkData::MakeWithCopy(pixmap.addr(), pixmap.computeByteSize()); } -sk_sp EncodeImage(sk_sp p_image, - GrContext* context, - ImageByteFormat format) { +sk_sp EncodeImage(sk_sp raster_image, ImageByteFormat format) { TRACE_EVENT0("flutter", __FUNCTION__); - // Check validity of the image. - if (p_image == nullptr) { - FML_LOG(ERROR) << "Image was null."; - return nullptr; - } - - auto dimensions = p_image->dimensions(); - - if (dimensions.isEmpty()) { - FML_LOG(ERROR) << "Image dimensions were empty."; - return nullptr; - } - - auto raster_image = ConvertToRasterImageIfNecessary(p_image, context); - - if (raster_image == nullptr) { - FML_LOG(ERROR) << "Could not create a raster copy of the image."; + if (!raster_image) { return nullptr; } @@ -165,7 +190,7 @@ sk_sp EncodeImage(sk_sp p_image, if (png_image == nullptr) { FML_LOG(ERROR) << "Could not convert raster image to PNG."; return nullptr; - } + }; return png_image; } break; case kRawRGBA: { @@ -181,17 +206,29 @@ sk_sp EncodeImage(sk_sp p_image, } void EncodeImageAndInvokeDataCallback( - std::unique_ptr callback, sk_sp image, - GrContext* context, + std::unique_ptr callback, + ImageByteFormat format, fml::RefPtr ui_task_runner, - ImageByteFormat format) { - sk_sp encoded = EncodeImage(std::move(image), context, format); - - ui_task_runner->PostTask( - fml::MakeCopyable([callback = std::move(callback), encoded]() mutable { + fml::RefPtr gpu_task_runner, + fml::RefPtr io_task_runner, + GrContext* resource_context, + fml::WeakPtr snapshot_delegate) { + auto callback_task = fml::MakeCopyable( + [callback = std::move(callback)](sk_sp encoded) mutable { InvokeDataCallback(std::move(callback), std::move(encoded)); - })); + }); + + auto encode_task = [callback_task = std::move(callback_task), format, + ui_task_runner](sk_sp raster_image) { + sk_sp encoded = EncodeImage(std::move(raster_image), format); + ui_task_runner->PostTask( + [callback_task = std::move(callback_task), + encoded = std::move(encoded)] { callback_task(encoded); }); + }; + + ConvertImageToRaster(std::move(image), encode_task, gpu_task_runner, + io_task_runner, resource_context, snapshot_delegate); } } // namespace @@ -214,13 +251,17 @@ Dart_Handle EncodeImage(CanvasImage* canvas_image, task_runners.GetIOTaskRunner()->PostTask(fml::MakeCopyable( [callback = std::move(callback), image = canvas_image->image(), + image_format, ui_task_runner = task_runners.GetUITaskRunner(), + gpu_task_runner = task_runners.GetGPUTaskRunner(), + io_task_runner = task_runners.GetIOTaskRunner(), io_manager = UIDartState::Current()->GetIOManager(), - ui_task_runner = task_runners.GetUITaskRunner(), - image_format]() mutable { - EncodeImageAndInvokeDataCallback(std::move(callback), std::move(image), - io_manager->GetResourceContext().get(), - std::move(ui_task_runner), - image_format); + snapshot_delegate = + UIDartState::Current()->GetSnapshotDelegate()]() mutable { + EncodeImageAndInvokeDataCallback( + std::move(image), std::move(callback), image_format, + std::move(ui_task_runner), std::move(gpu_task_runner), + std::move(io_task_runner), io_manager->GetResourceContext().get(), + std::move(snapshot_delegate)); })); return Dart_Null(); diff --git a/lib/ui/painting/image_filter.h b/lib/ui/painting/image_filter.h index f95430f2c5460..c44243016b9cc 100644 --- a/lib/ui/painting/image_filter.h +++ b/lib/ui/painting/image_filter.h @@ -26,7 +26,7 @@ class ImageFilter : public RefCountedDartWrappable { void initBlur(double sigma_x, double sigma_y); void initMatrix(const tonic::Float64List& matrix4, int filter_quality); - const sk_sp& filter() { return filter_; } + const sk_sp& filter() const { return filter_; } static void RegisterNatives(tonic::DartLibraryNatives* natives); diff --git a/lib/ui/painting/paint.cc b/lib/ui/painting/paint.cc index 00006c3f989e9..2648ff2f66f41 100644 --- a/lib/ui/painting/paint.cc +++ b/lib/ui/painting/paint.cc @@ -32,7 +32,8 @@ constexpr int kMaskFilterIndex = 9; constexpr int kMaskFilterBlurStyleIndex = 10; constexpr int kMaskFilterSigmaIndex = 11; constexpr int kInvertColorIndex = 12; -constexpr size_t kDataByteCount = 52; // 4 * (last index + 1) +constexpr int kDitherIndex = 13; +constexpr size_t kDataByteCount = 56; // 4 * (last index + 1) // Indices for objects. constexpr int kShaderIndex = 0; @@ -154,6 +155,10 @@ Paint::Paint(Dart_Handle paint_objects, Dart_Handle paint_data) { paint_.setColorFilter(invert_filter); } + if (uint_data[kDitherIndex]) { + paint_.setDither(true); + } + switch (uint_data[kMaskFilterIndex]) { case Null: break; diff --git a/lib/ui/painting/path.cc b/lib/ui/painting/path.cc index facd8d1ce3899..e9f74255b5b9a 100644 --- a/lib/ui/painting/path.cc +++ b/lib/ui/painting/path.cc @@ -71,11 +71,11 @@ CanvasPath::CanvasPath() {} CanvasPath::~CanvasPath() {} int CanvasPath::getFillType() { - return path_.getFillType(); + return static_cast(path_.getFillType()); } void CanvasPath::setFillType(int fill_type) { - path_.setFillType(static_cast(fill_type)); + path_.setFillType(static_cast(fill_type)); } void CanvasPath::moveTo(float x, float y) { @@ -156,9 +156,8 @@ void CanvasPath::arcToPoint(float arcEndX, bool isClockwiseDirection) { const auto arcSize = isLargeArc ? SkPath::ArcSize::kLarge_ArcSize : SkPath::ArcSize::kSmall_ArcSize; - const auto direction = isClockwiseDirection - ? SkPath::Direction::kCW_Direction - : SkPath::Direction::kCCW_Direction; + const auto direction = + isClockwiseDirection ? SkPathDirection::kCW : SkPathDirection::kCCW; path_.arcTo(radiusX, radiusY, xAxisRotation, arcSize, direction, arcEndX, arcEndY); @@ -173,9 +172,8 @@ void CanvasPath::relativeArcToPoint(float arcEndDeltaX, bool isClockwiseDirection) { const auto arcSize = isLargeArc ? SkPath::ArcSize::kLarge_ArcSize : SkPath::ArcSize::kSmall_ArcSize; - const auto direction = isClockwiseDirection - ? SkPath::Direction::kCW_Direction - : SkPath::Direction::kCCW_Direction; + const auto direction = + isClockwiseDirection ? SkPathDirection::kCW : SkPathDirection::kCCW; path_.rArcTo(radiusX, radiusY, xAxisRotation, arcSize, direction, arcEndDeltaX, arcEndDeltaY); } diff --git a/lib/ui/pointer.dart b/lib/ui/pointer.dart index 4879e2f4d7bb7..83247ea6092f1 100644 --- a/lib/ui/pointer.dart +++ b/lib/ui/pointer.dart @@ -75,10 +75,14 @@ class PointerData { this.kind = PointerDeviceKind.touch, this.signalKind, this.device = 0, + this.pointerIdentifier = 0, this.physicalX = 0.0, this.physicalY = 0.0, + this.physicalDeltaX = 0.0, + this.physicalDeltaY = 0.0, this.buttons = 0, this.obscured = false, + this.synthesized = false, this.pressure = 0.0, this.pressureMin = 0.0, this.pressureMax = 0.0, @@ -111,6 +115,12 @@ class PointerData { /// Unique identifier for the pointing device, reused across interactions. final int device; + /// Unique identifier for the pointer. + /// + /// This field changes for each new pointer down event. Framework uses this + /// identifier to determine hit test result. + final int pointerIdentifier; + /// X coordinate of the position of the pointer, in physical pixels in the /// global coordinate space. final double physicalX; @@ -119,6 +129,12 @@ class PointerData { /// global coordinate space. final double physicalY; + /// The distance of pointer movement on X coordinate in physical pixels. + final double physicalDeltaX; + + /// The distance of pointer movement on Y coordinate in physical pixels. + final double physicalDeltaY; + /// Bit field using the *Button constants (primaryMouseButton, /// secondaryStylusButton, etc). For example, if this has the value 6 and the /// [kind] is [PointerDeviceKind.invertedStylus], then this indicates an @@ -130,6 +146,14 @@ class PointerData { /// implemented.) final bool obscured; + /// Set if this pointer data was synthesized by pointer data packet converter. + /// pointer data packet converter will synthesize additional pointer datas if + /// the input sequence of pointer data is illegal. + /// + /// For example, a down pointer data will be synthesized if the converter receives + /// a move pointer data while the pointer is not previously down. + final bool synthesized; + /// The pressure of the touch as a number ranging from 0.0, indicating a touch /// with no discernible pressure, to 1.0, indicating a touch with "normal" /// pressure, and possibly beyond, indicating a stronger touch. For devices @@ -242,9 +266,13 @@ class PointerData { 'kind: $kind, ' 'signalKind: $signalKind, ' 'device: $device, ' + 'pointerIdentifier: $pointerIdentifier, ' 'physicalX: $physicalX, ' 'physicalY: $physicalY, ' + 'physicalDeltaX: $physicalDeltaX, ' + 'physicalDeltaY: $physicalDeltaY, ' 'buttons: $buttons, ' + 'synthesized: $synthesized, ' 'pressure: $pressure, ' 'pressureMin: $pressureMin, ' 'pressureMax: $pressureMax, ' diff --git a/lib/ui/snapshot_delegate.h b/lib/ui/snapshot_delegate.h index 9a771f1219cd8..ad9b8ef1f3612 100644 --- a/lib/ui/snapshot_delegate.h +++ b/lib/ui/snapshot_delegate.h @@ -14,6 +14,8 @@ class SnapshotDelegate { public: virtual sk_sp MakeRasterSnapshot(sk_sp picture, SkISize picture_size) = 0; + + virtual sk_sp ConvertToRasterImage(sk_sp image) = 0; }; } // namespace flutter diff --git a/lib/ui/text.dart b/lib/ui/text.dart index 50a520ec14e4b..6af9893c56243 100644 --- a/lib/ui/text.dart +++ b/lib/ui/text.dart @@ -1406,6 +1406,93 @@ class TextPosition { } } +/// A range of characters in a string of text. +class TextRange { + /// Creates a text range. + /// + /// The [start] and [end] arguments must not be null. Both the [start] and + /// [end] must either be greater than or equal to zero or both exactly -1. + /// + /// The text included in the range includes the character at [start], but not + /// the one at [end]. + /// + /// Instead of creating an empty text range, consider using the [empty] + /// constant. + const TextRange({ + this.start, + this.end, + }) : assert(start != null && start >= -1), + assert(end != null && end >= -1); + + /// A text range that starts and ends at offset. + /// + /// The [offset] argument must be non-null and greater than or equal to -1. + const TextRange.collapsed(int offset) + : assert(offset != null && offset >= -1), + start = offset, + end = offset; + + /// A text range that contains nothing and is not in the text. + static const TextRange empty = TextRange(start: -1, end: -1); + + /// The index of the first character in the range. + /// + /// If [start] and [end] are both -1, the text range is empty. + final int start; + + /// The next index after the characters in this range. + /// + /// If [start] and [end] are both -1, the text range is empty. + final int end; + + /// Whether this range represents a valid position in the text. + bool get isValid => start >= 0 && end >= 0; + + /// Whether this range is empty (but still potentially placed inside the text). + bool get isCollapsed => start == end; + + /// Whether the start of this range precedes the end. + bool get isNormalized => end >= start; + + /// The text before this range. + String textBefore(String text) { + assert(isNormalized); + return text.substring(0, start); + } + + /// The text after this range. + String textAfter(String text) { + assert(isNormalized); + return text.substring(end); + } + + /// The text inside this range. + String textInside(String text) { + assert(isNormalized); + return text.substring(start, end); + } + + @override + bool operator ==(dynamic other) { + if (identical(this, other)) + return true; + if (other is! TextRange) + return false; + final TextRange typedOther = other; + return typedOther.start == start + && typedOther.end == end; + } + + @override + int get hashCode => hashValues( + start.hashCode, + end.hashCode, + ); + + @override + String toString() => 'TextRange(start: $start, end: $end)'; +} + /// Layout constraints for [Paragraph] objects. /// /// Instances of this class are typically used with [Paragraph.layout]. @@ -1512,8 +1599,8 @@ enum BoxHeightStyle { /// Defines various ways to horizontally bound the boxes returned by /// [Paragraph.getBoxesForRange]. enum BoxWidthStyle { - // Provide tight bounding boxes that fit widths to the runs of each line - // independently. + /// Provide tight bounding boxes that fit widths to the runs of each line + /// independently. tight, /// Adds up to two additional boxes as needed at the beginning and/or end @@ -1779,12 +1866,31 @@ class Paragraph extends NativeFieldWrapperClass2 { } List _getPositionForOffset(double dx, double dy) native 'Paragraph_getPositionForOffset'; - /// Returns the [start, end] of the word at the given offset. Characters not - /// part of a word, such as spaces, symbols, and punctuation, have word breaks - /// on both sides. In such cases, this method will return [offset, offset+1]. - /// Word boundaries are defined more precisely in Unicode Standard Annex #29 - /// http://www.unicode.org/reports/tr29/#Word_Boundaries - List getWordBoundary(int offset) native 'Paragraph_getWordBoundary'; + /// Returns the [TextRange] of the word at the given [TextPosition]. + /// + /// Characters not part of a word, such as spaces, symbols, and punctuation, + /// have word breaks on both sides. In such cases, this method will return + /// [offset, offset+1]. Word boundaries are defined more precisely in Unicode + /// Standard Annex #29 http://www.unicode.org/reports/tr29/#Word_Boundaries + TextRange getWordBoundary(TextPosition position) { + final List boundary = _getWordBoundary(position.offset); + return TextRange(start: boundary[0], end: boundary[1]); + } + List _getWordBoundary(int offset) native 'Paragraph_getWordBoundary'; + + /// Returns the [TextRange] of the line at the given [TextPosition]. + /// + /// The newline (if any) is returned as part of the range. + /// + /// Not valid until after layout. + /// + /// This can potentially be expensive, since it needs to compute the line + /// metrics, so use it sparingly. + TextRange getLineBoundary(TextPosition position) { + final List boundary = _getLineBoundary(position.offset); + return TextRange(start: boundary[0], end: boundary[1]); + } + List _getLineBoundary(int offset) native 'Paragraph_getLineBoundary'; // Redirecting the paint function in this way solves some dependency problems // in the C++ code. If we straighten out the C++ dependencies, we can remove diff --git a/lib/ui/text/asset_manager_font_provider.cc b/lib/ui/text/asset_manager_font_provider.cc index fd019f23cc3f2..805376dd3131d 100644 --- a/lib/ui/text/asset_manager_font_provider.cc +++ b/lib/ui/text/asset_manager_font_provider.cc @@ -56,7 +56,8 @@ void AssetManagerFontProvider::RegisterAsset(std::string family_name, if (family_it == registered_families_.end()) { family_names_.push_back(family_name); auto value = std::make_pair( - canonical_name, sk_make_sp(asset_manager_)); + canonical_name, + sk_make_sp(asset_manager_, family_name)); family_it = registered_families_.emplace(value).first; } @@ -64,8 +65,9 @@ void AssetManagerFontProvider::RegisterAsset(std::string family_name, } AssetManagerFontStyleSet::AssetManagerFontStyleSet( - std::shared_ptr asset_manager) - : asset_manager_(asset_manager) {} + std::shared_ptr asset_manager, + std::string family_name) + : asset_manager_(asset_manager), family_name_(family_name) {} AssetManagerFontStyleSet::~AssetManagerFontStyleSet() = default; @@ -78,9 +80,18 @@ int AssetManagerFontStyleSet::count() { } void AssetManagerFontStyleSet::getStyle(int index, - SkFontStyle*, - SkString* style) { - FML_DCHECK(false); + SkFontStyle* style, + SkString* name) { + FML_DCHECK(index < static_cast(assets_.size())); + if (style) { + sk_sp typeface(createTypeface(index)); + if (typeface) { + *style = typeface->fontStyle(); + } + } + if (name) { + *name = family_name_.c_str(); + } } SkTypeface* AssetManagerFontStyleSet::createTypeface(int i) { @@ -112,14 +123,7 @@ SkTypeface* AssetManagerFontStyleSet::createTypeface(int i) { } SkTypeface* AssetManagerFontStyleSet::matchStyle(const SkFontStyle& pattern) { - if (assets_.empty()) - return nullptr; - - for (const TypefaceAsset& asset : assets_) - if (asset.typeface && asset.typeface->fontStyle() == pattern) - return SkRef(asset.typeface.get()); - - return SkRef(assets_[0].typeface.get()); + return matchStyleCSS3(pattern); } AssetManagerFontStyleSet::TypefaceAsset::TypefaceAsset(std::string a) diff --git a/lib/ui/text/asset_manager_font_provider.h b/lib/ui/text/asset_manager_font_provider.h index d24e5acb078c3..ef4d272664203 100644 --- a/lib/ui/text/asset_manager_font_provider.h +++ b/lib/ui/text/asset_manager_font_provider.h @@ -20,7 +20,8 @@ namespace flutter { class AssetManagerFontStyleSet : public SkFontStyleSet { public: - AssetManagerFontStyleSet(std::shared_ptr asset_manager); + AssetManagerFontStyleSet(std::shared_ptr asset_manager, + std::string family_name); ~AssetManagerFontStyleSet() override; @@ -40,6 +41,7 @@ class AssetManagerFontStyleSet : public SkFontStyleSet { private: std::shared_ptr asset_manager_; + std::string family_name_; struct TypefaceAsset { TypefaceAsset(std::string a); diff --git a/lib/ui/text/font_collection.cc b/lib/ui/text/font_collection.cc index 4ef95709cad59..170f30c864f11 100644 --- a/lib/ui/text/font_collection.cc +++ b/lib/ui/text/font_collection.cc @@ -129,17 +129,24 @@ void FontCollection::RegisterFonts( } void FontCollection::RegisterTestFonts() { - sk_sp test_typeface = - SkTypeface::MakeFromStream(GetTestFontData()); + std::vector> test_typefaces; + std::vector> font_data = GetTestFontData(); + for (auto& font : font_data) { + test_typefaces.push_back(SkTypeface::MakeFromStream(std::move(font))); + } std::unique_ptr font_provider = std::make_unique(); - font_provider->RegisterTypeface(std::move(test_typeface), - GetTestFontFamilyName()); + size_t index = 0; + std::vector names = GetTestFontFamilyNames(); + for (sk_sp typeface : test_typefaces) { + font_provider->RegisterTypeface(std::move(typeface), names[index]); + index++; + } - collection_->SetTestFontManager(sk_make_sp( - std::move(font_provider), GetTestFontFamilyName())); + collection_->SetTestFontManager( + sk_make_sp(std::move(font_provider), names)); collection_->DisableFontFallback(); } diff --git a/lib/ui/text/paragraph.cc b/lib/ui/text/paragraph.cc index 0025615e7f8fa..2618c3f08406a 100644 --- a/lib/ui/text/paragraph.cc +++ b/lib/ui/text/paragraph.cc @@ -31,6 +31,7 @@ IMPLEMENT_WRAPPERTYPEINFO(ui, Paragraph); V(Paragraph, layout) \ V(Paragraph, paint) \ V(Paragraph, getWordBoundary) \ + V(Paragraph, getLineBoundary) \ V(Paragraph, getRectsForRange) \ V(Paragraph, getRectsForPlaceholders) \ V(Paragraph, getPositionForOffset) \ @@ -134,6 +135,23 @@ Dart_Handle Paragraph::getWordBoundary(unsigned offset) { return result; } +Dart_Handle Paragraph::getLineBoundary(unsigned offset) { + std::vector metrics = m_paragraph->GetLineMetrics(); + int line_start = -1; + int line_end = -1; + for (txt::LineMetrics& line : metrics) { + if (offset >= line.start_index && offset <= line.end_index) { + line_start = line.start_index; + line_end = line.end_index; + break; + } + } + Dart_Handle result = Dart_NewListOf(Dart_CoreType_Int, 2); + Dart_ListSetAt(result, 0, ToDart(line_start)); + Dart_ListSetAt(result, 1, ToDart(line_end)); + return result; +} + std::vector Paragraph::computeLineMetrics() { std::vector result; std::vector metrics = m_paragraph->GetLineMetrics(); diff --git a/lib/ui/text/paragraph.h b/lib/ui/text/paragraph.h index cdd45335d3e9e..7aea6079378e1 100644 --- a/lib/ui/text/paragraph.h +++ b/lib/ui/text/paragraph.h @@ -49,6 +49,7 @@ class Paragraph : public RefCountedDartWrappable { std::vector getRectsForPlaceholders(); Dart_Handle getPositionForOffset(double dx, double dy); Dart_Handle getWordBoundary(unsigned offset); + Dart_Handle getLineBoundary(unsigned offset); std::vector computeLineMetrics(); size_t GetAllocationSize() override; diff --git a/lib/ui/window.dart b/lib/ui/window.dart index 3bd28a248bbba..dfad860b365bd 100644 --- a/lib/ui/window.dart +++ b/lib/ui/window.dart @@ -69,7 +69,12 @@ enum FramePhase { /// Time-related performance metrics of a frame. /// -/// See [Window.onReportTimings] for how to get this. +/// If you're using the whole Flutter framework, please use +/// [SchedulerBinding.addTimingsCallback] to get this. It's preferred over using +/// [Window.onReportTimings] directly because +/// [SchedulerBinding.addTimingsCallback] allows multiple callbacks. If +/// [SchedulerBinding] is unavailable, then see [Window.onReportTimings] for how +/// to get this. /// /// The metrics in debug mode (`flutter run` without any flags) may be very /// different from those in profile and release modes due to the debug overhead. @@ -171,18 +176,16 @@ enum AppLifecycleState { /// /// When the application is in this state, the engine will not call the /// [Window.onBeginFrame] and [Window.onDrawFrame] callbacks. - /// - /// Android apps in this state should assume that they may enter the - /// [suspending] state at any time. paused, - /// The application will be suspended momentarily. + /// The application is still hosted on a flutter engine but is detached from + /// any host views. /// - /// When the application is in this state, the engine will not call the - /// [Window.onBeginFrame] and [Window.onDrawFrame] callbacks. - /// - /// On iOS, this state is currently unused. - suspending, + /// When the application is in this state, the engine is running without + /// a view. It can either be in the progress of attaching a view when engine + /// was first initializes, or after the view being destroyed due to a Navigator + /// pop. + detached, } /// A representation of distances for each of the four edges of a rectangle, @@ -933,6 +936,10 @@ class Window { /// A callback that is invoked to report the [FrameTiming] of recently /// rasterized frames. /// + /// It's prefered to use [SchedulerBinding.addTimingsCallback] than to use + /// [Window.onReportTimings] directly because + /// [SchedulerBinding.addTimingsCallback] allows multiple callbacks. + /// /// This can be used to see if the application has missed frames (through /// [FrameTiming.buildDuration] and [FrameTiming.rasterDuration]), or high /// latencies (through [FrameTiming.totalSpan]). diff --git a/lib/ui/window/pointer_data.cc b/lib/ui/window/pointer_data.cc index a2d92e8eed36e..a3292d486bab6 100644 --- a/lib/ui/window/pointer_data.cc +++ b/lib/ui/window/pointer_data.cc @@ -8,10 +8,7 @@ namespace flutter { -// If this value changes, update the pointer data unpacking code in hooks.dart. -static constexpr int kPointerDataFieldCount = 24; - -static_assert(sizeof(PointerData) == sizeof(int64_t) * kPointerDataFieldCount, +static_assert(sizeof(PointerData) == kBytesPerField * kPointerDataFieldCount, "PointerData has the wrong size"); void PointerData::Clear() { diff --git a/lib/ui/window/pointer_data.h b/lib/ui/window/pointer_data.h index 05adf0682133a..96a9e7d5dad91 100644 --- a/lib/ui/window/pointer_data.h +++ b/lib/ui/window/pointer_data.h @@ -9,6 +9,9 @@ namespace flutter { +// If this value changes, update the pointer data unpacking code in hooks.dart. +static constexpr int kPointerDataFieldCount = 28; +static constexpr int kBytesPerField = sizeof(int64_t); // Must match the button constants in events.dart. enum PointerButtonMouse : int64_t { kPointerButtonMousePrimary = 1 << 0, @@ -60,10 +63,14 @@ struct alignas(8) PointerData { DeviceKind kind; SignalKind signal_kind; int64_t device; + int64_t pointer_identifier; double physical_x; double physical_y; + double physical_delta_x; + double physical_delta_y; int64_t buttons; int64_t obscured; + int64_t synthesized; double pressure; double pressure_min; double pressure_max; diff --git a/lib/ui/window/pointer_data_packet_converter.cc b/lib/ui/window/pointer_data_packet_converter.cc new file mode 100644 index 0000000000000..c7f643f25417d --- /dev/null +++ b/lib/ui/window/pointer_data_packet_converter.cc @@ -0,0 +1,288 @@ +// 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/lib/ui/window/pointer_data_packet_converter.h" +#include "flutter/fml/logging.h" + +#include + +namespace flutter { + +PointerDataPacketConverter::PointerDataPacketConverter() : pointer_(0) {} + +PointerDataPacketConverter::~PointerDataPacketConverter() = default; + +std::unique_ptr PointerDataPacketConverter::Convert( + std::unique_ptr packet) { + size_t kBytesPerPointerData = kPointerDataFieldCount * kBytesPerField; + auto buffer = packet->data(); + size_t buffer_length = buffer.size(); + + std::vector converted_pointers; + // Converts each pointer data in the buffer and stores it in the + // converted_pointers. + for (size_t i = 0; i < buffer_length / kBytesPerPointerData; i++) { + PointerData pointer_data; + memcpy(&pointer_data, &buffer[i * kBytesPerPointerData], + sizeof(PointerData)); + ConvertPointerData(pointer_data, converted_pointers); + } + + // Writes converted_pointers into converted_packet. + auto converted_packet = + std::make_unique(converted_pointers.size()); + size_t count = 0; + for (auto& converted_pointer : converted_pointers) { + converted_packet->SetPointerData(count++, converted_pointer); + } + + return converted_packet; +} + +void PointerDataPacketConverter::ConvertPointerData( + PointerData pointer_data, + std::vector& converted_pointers) { + if (pointer_data.signal_kind == PointerData::SignalKind::kNone) { + switch (pointer_data.change) { + case PointerData::Change::kCancel: { + // Android's three finger gesture will send a cancel event + // to a non-existing pointer. Drops the cancel if pointer + // is not previously added. + // https://github.com/flutter/flutter/issues/20517 + auto iter = states_.find(pointer_data.device); + if (iter != states_.end()) { + PointerState state = iter->second; + FML_DCHECK(state.isDown); + UpdatePointerIdentifier(pointer_data, state, false); + + if (LocationNeedsUpdate(pointer_data, state)) { + // Synthesizes a move event if the location does not match. + PointerData synthesized_move_event = pointer_data; + synthesized_move_event.change = PointerData::Change::kMove; + synthesized_move_event.synthesized = 1; + + UpdateDeltaAndState(synthesized_move_event, state); + converted_pointers.push_back(synthesized_move_event); + } + + state.isDown = false; + states_[pointer_data.device] = state; + converted_pointers.push_back(pointer_data); + } + break; + } + case PointerData::Change::kAdd: { + FML_DCHECK(states_.find(pointer_data.device) == states_.end()); + EnsurePointerState(pointer_data); + converted_pointers.push_back(pointer_data); + break; + } + case PointerData::Change::kRemove: { + // Makes sure we have an existing pointer + auto iter = states_.find(pointer_data.device); + FML_DCHECK(iter != states_.end()); + PointerState state = iter->second; + + if (state.isDown) { + // Synthesizes cancel event if the pointer is down. + PointerData synthesized_cancel_event = pointer_data; + synthesized_cancel_event.change = PointerData::Change::kCancel; + synthesized_cancel_event.synthesized = 1; + UpdatePointerIdentifier(synthesized_cancel_event, state, false); + + state.isDown = false; + states_[synthesized_cancel_event.device] = state; + converted_pointers.push_back(synthesized_cancel_event); + } + + if (LocationNeedsUpdate(pointer_data, state)) { + // Synthesizes a hover event if the location does not match. + PointerData synthesized_hover_event = pointer_data; + synthesized_hover_event.change = PointerData::Change::kHover; + synthesized_hover_event.synthesized = 1; + + UpdateDeltaAndState(synthesized_hover_event, state); + converted_pointers.push_back(synthesized_hover_event); + } + + states_.erase(pointer_data.device); + converted_pointers.push_back(pointer_data); + break; + } + case PointerData::Change::kHover: { + auto iter = states_.find(pointer_data.device); + PointerState state; + if (iter == states_.end()) { + // Synthesizes add event if the pointer is not previously added. + PointerData synthesized_add_event = pointer_data; + synthesized_add_event.change = PointerData::Change::kAdd; + synthesized_add_event.synthesized = 1; + state = EnsurePointerState(synthesized_add_event); + converted_pointers.push_back(synthesized_add_event); + } else { + state = iter->second; + } + + FML_DCHECK(!state.isDown); + if (LocationNeedsUpdate(pointer_data, state)) { + UpdateDeltaAndState(pointer_data, state); + converted_pointers.push_back(pointer_data); + } + break; + } + case PointerData::Change::kDown: { + auto iter = states_.find(pointer_data.device); + PointerState state; + if (iter == states_.end()) { + // Synthesizes a add event if the pointer is not previously added. + PointerData synthesized_add_event = pointer_data; + synthesized_add_event.change = PointerData::Change::kAdd; + synthesized_add_event.synthesized = 1; + state = EnsurePointerState(synthesized_add_event); + converted_pointers.push_back(synthesized_add_event); + } else { + state = iter->second; + } + + FML_DCHECK(!state.isDown); + if (LocationNeedsUpdate(pointer_data, state)) { + // Synthesizes a hover event if the location does not match. + PointerData synthesized_hover_event = pointer_data; + synthesized_hover_event.change = PointerData::Change::kHover; + synthesized_hover_event.synthesized = 1; + + UpdateDeltaAndState(synthesized_hover_event, state); + converted_pointers.push_back(synthesized_hover_event); + } + + UpdatePointerIdentifier(pointer_data, state, true); + state.isDown = true; + states_[pointer_data.device] = state; + converted_pointers.push_back(pointer_data); + break; + } + case PointerData::Change::kMove: { + // Makes sure we have an existing pointer in down state + auto iter = states_.find(pointer_data.device); + FML_DCHECK(iter != states_.end()); + PointerState state = iter->second; + FML_DCHECK(state.isDown); + + if (LocationNeedsUpdate(pointer_data, state)) { + UpdatePointerIdentifier(pointer_data, state, false); + UpdateDeltaAndState(pointer_data, state); + converted_pointers.push_back(pointer_data); + } + break; + } + case PointerData::Change::kUp: { + // Makes sure we have an existing pointer in down state + auto iter = states_.find(pointer_data.device); + FML_DCHECK(iter != states_.end()); + PointerState state = iter->second; + FML_DCHECK(state.isDown); + + UpdatePointerIdentifier(pointer_data, state, false); + + if (LocationNeedsUpdate(pointer_data, state)) { + // Synthesizes a move event if the location does not match. + PointerData synthesized_move_event = pointer_data; + synthesized_move_event.change = PointerData::Change::kMove; + synthesized_move_event.synthesized = 1; + + UpdateDeltaAndState(synthesized_move_event, state); + converted_pointers.push_back(synthesized_move_event); + } + + state.isDown = false; + states_[pointer_data.device] = state; + converted_pointers.push_back(pointer_data); + break; + } + default: { + converted_pointers.push_back(pointer_data); + break; + } + } + } else { + switch (pointer_data.signal_kind) { + case PointerData::SignalKind::kScroll: { + // Makes sure we have an existing pointer + auto iter = states_.find(pointer_data.device); + FML_DCHECK(iter != states_.end()); + + PointerState state = iter->second; + if (LocationNeedsUpdate(pointer_data, state)) { + if (state.isDown) { + // Synthesizes a move event if the pointer is down. + PointerData synthesized_move_event = pointer_data; + synthesized_move_event.signal_kind = PointerData::SignalKind::kNone; + synthesized_move_event.change = PointerData::Change::kMove; + synthesized_move_event.synthesized = 1; + + UpdateDeltaAndState(synthesized_move_event, state); + converted_pointers.push_back(synthesized_move_event); + } else { + // Synthesizes a hover event if the pointer is up. + PointerData synthesized_hover_event = pointer_data; + synthesized_hover_event.signal_kind = + PointerData::SignalKind::kNone; + synthesized_hover_event.change = PointerData::Change::kHover; + synthesized_hover_event.synthesized = 1; + + UpdateDeltaAndState(synthesized_hover_event, state); + converted_pointers.push_back(synthesized_hover_event); + } + } + + converted_pointers.push_back(pointer_data); + break; + } + default: { + // Ignores unknown signal kind. + break; + } + } + } +} + +PointerState PointerDataPacketConverter::EnsurePointerState( + PointerData pointer_data) { + PointerState state; + state.pointer_identifier = 0; + state.isDown = false; + state.physical_x = pointer_data.physical_x; + state.physical_y = pointer_data.physical_y; + states_[pointer_data.device] = state; + return state; +} + +void PointerDataPacketConverter::UpdateDeltaAndState(PointerData& pointer_data, + PointerState& state) { + pointer_data.physical_delta_x = pointer_data.physical_x - state.physical_x; + pointer_data.physical_delta_y = pointer_data.physical_y - state.physical_y; + state.physical_x = pointer_data.physical_x; + state.physical_y = pointer_data.physical_y; + states_[pointer_data.device] = state; +} + +bool PointerDataPacketConverter::LocationNeedsUpdate( + const PointerData pointer_data, + const PointerState state) { + return state.physical_x != pointer_data.physical_x || + state.physical_y != pointer_data.physical_y; +} + +void PointerDataPacketConverter::UpdatePointerIdentifier( + PointerData& pointer_data, + PointerState& state, + bool start_new_pointer) { + if (start_new_pointer) { + state.pointer_identifier = ++pointer_; + states_[pointer_data.device] = state; + } + pointer_data.pointer_identifier = state.pointer_identifier; +} + +} // namespace flutter diff --git a/lib/ui/window/pointer_data_packet_converter.h b/lib/ui/window/pointer_data_packet_converter.h new file mode 100644 index 0000000000000..29024d07fbd32 --- /dev/null +++ b/lib/ui/window/pointer_data_packet_converter.h @@ -0,0 +1,111 @@ +// 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. + +#ifndef FLUTTER_LIB_UI_WINDOW_POINTER_DATA_PACKET_CONVERTER_H_ +#define FLUTTER_LIB_UI_WINDOW_POINTER_DATA_PACKET_CONVERTER_H_ + +#include + +#include +#include +#include + +#include "flutter/fml/macros.h" +#include "flutter/lib/ui/window/pointer_data_packet.h" + +namespace flutter { + +//------------------------------------------------------------------------------ +/// The current information about a pointer. This struct is used by +/// PointerDataPacketConverter to fill in necesarry information for raw pointer +/// packet sent from embedding. +/// +struct PointerState { + int64_t pointer_identifier; + bool isDown; + double physical_x; + double physical_y; +}; + +//------------------------------------------------------------------------------ +/// Converter to convert the raw pointer data packet from the platforms. +/// +/// Framework requires certain information to process pointer data. e.g. pointer +/// identifier and the delta of pointer moment. The converter keeps track each +/// pointer state and fill in those information appropriately. +/// +/// The converter is also resposible for providing a clean pointer data stream. +/// It will attempt to correct the stream if the it contains illegal pointer +/// transitions. +/// +/// Example 1 Missing Add: +/// +/// Down(position x) -> Up(position x) +/// +/// ###After Conversion### +/// +/// Synthesized_Add(position x) -> Down(position x) -> Up(position x) +/// +/// Example 2 Missing another move: +/// +/// Add(position x) -> Down(position x) -> Move(position y) -> +/// Up(position z) +/// +/// ###After Conversion### +/// +/// Add(position x) -> Down(position x) -> Move(position y) -> +/// Synthesized_Move(position z) -> Up(position z) +/// +/// Platform view is the only client that uses this class to convert all the +/// incoming pointer packet and is responsible for the life cycle of its +/// instance. +/// +class PointerDataPacketConverter { + public: + PointerDataPacketConverter(); + ~PointerDataPacketConverter(); + + //---------------------------------------------------------------------------- + /// @brief Converts pointer data packet into a form that framework + /// understands. The raw pointer data packet from embedding does + /// not have sufficient information and may contain illegal + /// pointer transitions. This method will fill out that + /// information and attempt to correct pointer transitions. + /// + /// @param[in] packet The raw pointer packet sent from + /// embedding. + /// + /// @return A full converted packet with all the required information + /// filled. + /// It may contain synthetic pointer data as the result of + /// converter's attempt to correct illegal pointer transitions. + /// + std::unique_ptr Convert( + std::unique_ptr packet); + + private: + std::map states_; + + int64_t pointer_; + + void ConvertPointerData(PointerData pointer_data, + std::vector& converted_pointers); + + PointerState EnsurePointerState(PointerData pointer_data); + + void UpdateDeltaAndState(PointerData& pointer_data, PointerState& state); + + void UpdatePointerIdentifier(PointerData& pointer_data, + PointerState& state, + bool start_new_pointer); + + bool LocationNeedsUpdate(const PointerData pointer_data, + const PointerState state); + + FML_DISALLOW_COPY_AND_ASSIGN(PointerDataPacketConverter); +}; + +} // namespace flutter + +#endif // FLUTTER_LIB_UI_WINDOW_POINTER_DATA_PACKET_CONVERTER_H_ diff --git a/lib/ui/window/pointer_data_packet_converter_unittests.cc b/lib/ui/window/pointer_data_packet_converter_unittests.cc new file mode 100644 index 0000000000000..d18b7f3e474ed --- /dev/null +++ b/lib/ui/window/pointer_data_packet_converter_unittests.cc @@ -0,0 +1,537 @@ +// 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/lib/ui/window/pointer_data_packet_converter.h" +#include "gtest/gtest.h" + +namespace flutter { +namespace testing { + +void CreateSimulatedPointerData(PointerData& data, + PointerData::Change change, + int64_t device, + double dx, + double dy) { + data.time_stamp = 0; + data.change = change; + data.kind = PointerData::DeviceKind::kTouch; + data.signal_kind = PointerData::SignalKind::kNone; + data.device = device; + data.pointer_identifier = 0; + data.physical_x = dx; + data.physical_y = dy; + data.physical_delta_x = 0.0; + data.physical_delta_y = 0.0; + data.buttons = 0; + data.obscured = 0; + data.synthesized = 0; + data.pressure = 0.0; + data.pressure_min = 0.0; + data.pressure_max = 0.0; + data.distance = 0.0; + data.distance_max = 0.0; + data.size = 0.0; + data.radius_major = 0.0; + data.radius_minor = 0.0; + data.radius_min = 0.0; + data.radius_max = 0.0; + data.orientation = 0.0; + data.tilt = 0.0; + data.platformData = 0; + data.scroll_delta_x = 0.0; + data.scroll_delta_y = 0.0; +} + +void CreateSimulatedMousePointerData(PointerData& data, + PointerData::Change change, + PointerData::SignalKind signal_kind, + int64_t device, + double dx, + double dy, + double scroll_delta_x, + double scroll_delta_y) { + data.time_stamp = 0; + data.change = change; + data.kind = PointerData::DeviceKind::kMouse; + data.signal_kind = signal_kind; + data.device = device; + data.pointer_identifier = 0; + data.physical_x = dx; + data.physical_y = dy; + data.physical_delta_x = 0.0; + data.physical_delta_y = 0.0; + data.buttons = 0; + data.obscured = 0; + data.synthesized = 0; + data.pressure = 0.0; + data.pressure_min = 0.0; + data.pressure_max = 0.0; + data.distance = 0.0; + data.distance_max = 0.0; + data.size = 0.0; + data.radius_major = 0.0; + data.radius_minor = 0.0; + data.radius_min = 0.0; + data.radius_max = 0.0; + data.orientation = 0.0; + data.tilt = 0.0; + data.platformData = 0; + data.scroll_delta_x = scroll_delta_x; + data.scroll_delta_y = scroll_delta_y; +} + +void UnpackPointerPacket(std::vector& output, + std::unique_ptr packet) { + size_t kBytesPerPointerData = kPointerDataFieldCount * kBytesPerField; + auto buffer = packet->data(); + size_t buffer_length = buffer.size(); + + for (size_t i = 0; i < buffer_length / kBytesPerPointerData; i++) { + PointerData pointer_data; + memcpy(&pointer_data, &buffer[i * kBytesPerPointerData], + sizeof(PointerData)); + output.push_back(pointer_data); + } + packet.reset(); +} + +TEST(PointerDataPacketConverterTest, CanConvetPointerDataPacket) { + PointerDataPacketConverter converter; + auto packet = std::make_unique(6); + PointerData data; + CreateSimulatedPointerData(data, PointerData::Change::kAdd, 0, 0.0, 0.0); + packet->SetPointerData(0, data); + CreateSimulatedPointerData(data, PointerData::Change::kHover, 0, 3.0, 0.0); + packet->SetPointerData(1, data); + CreateSimulatedPointerData(data, PointerData::Change::kDown, 0, 3.0, 0.0); + packet->SetPointerData(2, data); + CreateSimulatedPointerData(data, PointerData::Change::kMove, 0, 3.0, 4.0); + packet->SetPointerData(3, data); + CreateSimulatedPointerData(data, PointerData::Change::kUp, 0, 3.0, 4.0); + packet->SetPointerData(4, data); + CreateSimulatedPointerData(data, PointerData::Change::kRemove, 0, 3.0, 4.0); + packet->SetPointerData(5, data); + auto converted_packet = converter.Convert(std::move(packet)); + + std::vector result; + UnpackPointerPacket(result, std::move(converted_packet)); + + ASSERT_EQ(result.size(), (size_t)6); + ASSERT_EQ(result[0].change, PointerData::Change::kAdd); + ASSERT_EQ(result[0].synthesized, 0); + + ASSERT_EQ(result[1].change, PointerData::Change::kHover); + ASSERT_EQ(result[1].synthesized, 0); + ASSERT_EQ(result[1].physical_delta_x, 3.0); + ASSERT_EQ(result[1].physical_delta_y, 0.0); + + ASSERT_EQ(result[2].change, PointerData::Change::kDown); + ASSERT_EQ(result[2].pointer_identifier, 1); + ASSERT_EQ(result[2].synthesized, 0); + + ASSERT_EQ(result[3].change, PointerData::Change::kMove); + ASSERT_EQ(result[3].pointer_identifier, 1); + ASSERT_EQ(result[3].synthesized, 0); + ASSERT_EQ(result[3].physical_delta_x, 0.0); + ASSERT_EQ(result[3].physical_delta_y, 4.0); + + ASSERT_EQ(result[4].change, PointerData::Change::kUp); + ASSERT_EQ(result[4].pointer_identifier, 1); + ASSERT_EQ(result[4].synthesized, 0); + + ASSERT_EQ(result[5].change, PointerData::Change::kRemove); + ASSERT_EQ(result[5].synthesized, 0); +} + +TEST(PointerDataPacketConverterTest, CanSynthesizeDownAndUp) { + PointerDataPacketConverter converter; + auto packet = std::make_unique(4); + PointerData data; + CreateSimulatedPointerData(data, PointerData::Change::kAdd, 0, 0.0, 0.0); + packet->SetPointerData(0, data); + CreateSimulatedPointerData(data, PointerData::Change::kDown, 0, 3.0, 0.0); + packet->SetPointerData(1, data); + CreateSimulatedPointerData(data, PointerData::Change::kUp, 0, 3.0, 4.0); + packet->SetPointerData(2, data); + CreateSimulatedPointerData(data, PointerData::Change::kRemove, 0, 3.0, 4.0); + packet->SetPointerData(3, data); + auto converted_packet = converter.Convert(std::move(packet)); + + std::vector result; + UnpackPointerPacket(result, std::move(converted_packet)); + + ASSERT_EQ(result.size(), (size_t)6); + ASSERT_EQ(result[0].change, PointerData::Change::kAdd); + ASSERT_EQ(result[0].synthesized, 0); + + // A hover should be synthesized. + ASSERT_EQ(result[1].change, PointerData::Change::kHover); + ASSERT_EQ(result[1].synthesized, 1); + ASSERT_EQ(result[1].physical_delta_x, 3.0); + ASSERT_EQ(result[1].physical_delta_y, 0.0); + + ASSERT_EQ(result[2].change, PointerData::Change::kDown); + ASSERT_EQ(result[2].pointer_identifier, 1); + ASSERT_EQ(result[2].synthesized, 0); + + // A move should be synthesized. + ASSERT_EQ(result[3].change, PointerData::Change::kMove); + ASSERT_EQ(result[3].pointer_identifier, 1); + ASSERT_EQ(result[3].synthesized, 1); + ASSERT_EQ(result[3].physical_delta_x, 0.0); + ASSERT_EQ(result[3].physical_delta_y, 4.0); + + ASSERT_EQ(result[4].change, PointerData::Change::kUp); + ASSERT_EQ(result[4].pointer_identifier, 1); + ASSERT_EQ(result[4].synthesized, 0); + + ASSERT_EQ(result[5].change, PointerData::Change::kRemove); + ASSERT_EQ(result[5].synthesized, 0); +} + +TEST(PointerDataPacketConverterTest, CanUpdatePointerIdentifier) { + PointerDataPacketConverter converter; + auto packet = std::make_unique(7); + PointerData data; + CreateSimulatedPointerData(data, PointerData::Change::kAdd, 0, 0.0, 0.0); + packet->SetPointerData(0, data); + CreateSimulatedPointerData(data, PointerData::Change::kDown, 0, 0.0, 0.0); + packet->SetPointerData(1, data); + CreateSimulatedPointerData(data, PointerData::Change::kUp, 0, 0.0, 0.0); + packet->SetPointerData(2, data); + CreateSimulatedPointerData(data, PointerData::Change::kDown, 0, 0.0, 0.0); + packet->SetPointerData(3, data); + CreateSimulatedPointerData(data, PointerData::Change::kMove, 0, 3.0, 0.0); + packet->SetPointerData(4, data); + CreateSimulatedPointerData(data, PointerData::Change::kUp, 0, 3.0, 0.0); + packet->SetPointerData(5, data); + CreateSimulatedPointerData(data, PointerData::Change::kRemove, 0, 3.0, 0.0); + packet->SetPointerData(6, data); + auto converted_packet = converter.Convert(std::move(packet)); + + std::vector result; + UnpackPointerPacket(result, std::move(converted_packet)); + + ASSERT_EQ(result.size(), (size_t)7); + ASSERT_EQ(result[0].change, PointerData::Change::kAdd); + ASSERT_EQ(result[0].synthesized, 0); + + ASSERT_EQ(result[1].change, PointerData::Change::kDown); + ASSERT_EQ(result[1].pointer_identifier, 1); + ASSERT_EQ(result[1].synthesized, 0); + + ASSERT_EQ(result[2].change, PointerData::Change::kUp); + ASSERT_EQ(result[2].pointer_identifier, 1); + ASSERT_EQ(result[2].synthesized, 0); + + // Pointer count increase to 2. + ASSERT_EQ(result[3].change, PointerData::Change::kDown); + ASSERT_EQ(result[3].pointer_identifier, 2); + ASSERT_EQ(result[3].synthesized, 0); + + ASSERT_EQ(result[4].change, PointerData::Change::kMove); + ASSERT_EQ(result[4].pointer_identifier, 2); + ASSERT_EQ(result[4].synthesized, 0); + ASSERT_EQ(result[4].physical_delta_x, 3.0); + ASSERT_EQ(result[4].physical_delta_y, 0.0); + + ASSERT_EQ(result[5].change, PointerData::Change::kUp); + ASSERT_EQ(result[5].pointer_identifier, 2); + ASSERT_EQ(result[5].synthesized, 0); + + ASSERT_EQ(result[6].change, PointerData::Change::kRemove); + ASSERT_EQ(result[6].synthesized, 0); +} + +TEST(PointerDataPacketConverterTest, CanWorkWithDifferentDevices) { + PointerDataPacketConverter converter; + auto packet = std::make_unique(12); + PointerData data; + CreateSimulatedPointerData(data, PointerData::Change::kAdd, 0, 0.0, 0.0); + packet->SetPointerData(0, data); + CreateSimulatedPointerData(data, PointerData::Change::kDown, 0, 0.0, 0.0); + packet->SetPointerData(1, data); + CreateSimulatedPointerData(data, PointerData::Change::kAdd, 1, 0.0, 0.0); + packet->SetPointerData(2, data); + CreateSimulatedPointerData(data, PointerData::Change::kDown, 1, 0.0, 0.0); + packet->SetPointerData(3, data); + CreateSimulatedPointerData(data, PointerData::Change::kUp, 0, 0.0, 0.0); + packet->SetPointerData(4, data); + CreateSimulatedPointerData(data, PointerData::Change::kDown, 0, 0.0, 0.0); + packet->SetPointerData(5, data); + CreateSimulatedPointerData(data, PointerData::Change::kMove, 1, 0.0, 4.0); + packet->SetPointerData(6, data); + CreateSimulatedPointerData(data, PointerData::Change::kMove, 0, 3.0, 0.0); + packet->SetPointerData(7, data); + CreateSimulatedPointerData(data, PointerData::Change::kUp, 1, 0.0, 4.0); + packet->SetPointerData(8, data); + CreateSimulatedPointerData(data, PointerData::Change::kUp, 0, 3.0, 0.0); + packet->SetPointerData(9, data); + CreateSimulatedPointerData(data, PointerData::Change::kRemove, 0, 3.0, 0.0); + packet->SetPointerData(10, data); + CreateSimulatedPointerData(data, PointerData::Change::kRemove, 1, 0.0, 4.0); + packet->SetPointerData(11, data); + auto converted_packet = converter.Convert(std::move(packet)); + + std::vector result; + UnpackPointerPacket(result, std::move(converted_packet)); + + ASSERT_EQ(result.size(), (size_t)12); + ASSERT_EQ(result[0].change, PointerData::Change::kAdd); + ASSERT_EQ(result[0].device, 0); + ASSERT_EQ(result[0].synthesized, 0); + + ASSERT_EQ(result[1].change, PointerData::Change::kDown); + ASSERT_EQ(result[1].device, 0); + ASSERT_EQ(result[1].pointer_identifier, 1); + ASSERT_EQ(result[1].synthesized, 0); + + ASSERT_EQ(result[2].change, PointerData::Change::kAdd); + ASSERT_EQ(result[2].device, 1); + ASSERT_EQ(result[2].synthesized, 0); + + ASSERT_EQ(result[3].change, PointerData::Change::kDown); + ASSERT_EQ(result[3].device, 1); + ASSERT_EQ(result[3].pointer_identifier, 2); + ASSERT_EQ(result[3].synthesized, 0); + + ASSERT_EQ(result[4].change, PointerData::Change::kUp); + ASSERT_EQ(result[4].device, 0); + ASSERT_EQ(result[4].pointer_identifier, 1); + ASSERT_EQ(result[4].synthesized, 0); + + ASSERT_EQ(result[5].change, PointerData::Change::kDown); + ASSERT_EQ(result[5].device, 0); + ASSERT_EQ(result[5].pointer_identifier, 3); + ASSERT_EQ(result[5].synthesized, 0); + + ASSERT_EQ(result[6].change, PointerData::Change::kMove); + ASSERT_EQ(result[6].device, 1); + ASSERT_EQ(result[6].pointer_identifier, 2); + ASSERT_EQ(result[6].synthesized, 0); + ASSERT_EQ(result[6].physical_delta_x, 0.0); + ASSERT_EQ(result[6].physical_delta_y, 4.0); + + ASSERT_EQ(result[7].change, PointerData::Change::kMove); + ASSERT_EQ(result[7].device, 0); + ASSERT_EQ(result[7].pointer_identifier, 3); + ASSERT_EQ(result[7].synthesized, 0); + ASSERT_EQ(result[7].physical_delta_x, 3.0); + ASSERT_EQ(result[7].physical_delta_y, 0.0); + + ASSERT_EQ(result[8].change, PointerData::Change::kUp); + ASSERT_EQ(result[8].device, 1); + ASSERT_EQ(result[8].pointer_identifier, 2); + ASSERT_EQ(result[8].synthesized, 0); + + ASSERT_EQ(result[9].change, PointerData::Change::kUp); + ASSERT_EQ(result[9].device, 0); + ASSERT_EQ(result[9].pointer_identifier, 3); + ASSERT_EQ(result[9].synthesized, 0); + + ASSERT_EQ(result[10].change, PointerData::Change::kRemove); + ASSERT_EQ(result[10].device, 0); + ASSERT_EQ(result[10].synthesized, 0); + + ASSERT_EQ(result[11].change, PointerData::Change::kRemove); + ASSERT_EQ(result[11].device, 1); + ASSERT_EQ(result[11].synthesized, 0); +} + +TEST(PointerDataPacketConverterTest, CanSynthesizeAdd) { + PointerDataPacketConverter converter; + auto packet = std::make_unique(2); + PointerData data; + CreateSimulatedPointerData(data, PointerData::Change::kDown, 0, 330.0, 450.0); + packet->SetPointerData(0, data); + CreateSimulatedPointerData(data, PointerData::Change::kUp, 0, 0.0, 0.0); + packet->SetPointerData(1, data); + auto converted_packet = converter.Convert(std::move(packet)); + + std::vector result; + UnpackPointerPacket(result, std::move(converted_packet)); + + ASSERT_EQ(result.size(), (size_t)4); + // A add should be synthesized. + ASSERT_EQ(result[0].change, PointerData::Change::kAdd); + ASSERT_EQ(result[0].physical_x, 330.0); + ASSERT_EQ(result[0].physical_y, 450.0); + ASSERT_EQ(result[0].synthesized, 1); + + ASSERT_EQ(result[1].change, PointerData::Change::kDown); + ASSERT_EQ(result[1].physical_x, 330.0); + ASSERT_EQ(result[1].physical_y, 450.0); + ASSERT_EQ(result[1].synthesized, 0); + + // A move should be synthesized. + ASSERT_EQ(result[2].change, PointerData::Change::kMove); + ASSERT_EQ(result[2].physical_delta_x, -330.0); + ASSERT_EQ(result[2].physical_delta_y, -450.0); + ASSERT_EQ(result[2].physical_x, 0.0); + ASSERT_EQ(result[2].physical_y, 0.0); + ASSERT_EQ(result[2].synthesized, 1); + + ASSERT_EQ(result[3].change, PointerData::Change::kUp); + ASSERT_EQ(result[3].physical_x, 0.0); + ASSERT_EQ(result[3].physical_y, 0.0); + ASSERT_EQ(result[3].synthesized, 0); +} + +TEST(PointerDataPacketConverterTest, CanHandleThreeFingerGesture) { + // Regression test https://github.com/flutter/flutter/issues/20517. + PointerDataPacketConverter converter; + PointerData data; + std::vector result; + // First finger down. + auto packet = std::make_unique(1); + CreateSimulatedPointerData(data, PointerData::Change::kDown, 0, 0.0, 0.0); + packet->SetPointerData(0, data); + auto converted_packet = converter.Convert(std::move(packet)); + UnpackPointerPacket(result, std::move(converted_packet)); + // Second finger down. + packet = std::make_unique(1); + CreateSimulatedPointerData(data, PointerData::Change::kDown, 1, 33.0, 44.0); + packet->SetPointerData(0, data); + converted_packet = converter.Convert(std::move(packet)); + UnpackPointerPacket(result, std::move(converted_packet)); + // Triggers three cancels. + packet = std::make_unique(3); + CreateSimulatedPointerData(data, PointerData::Change::kCancel, 1, 33.0, 44.0); + packet->SetPointerData(0, data); + CreateSimulatedPointerData(data, PointerData::Change::kCancel, 0, 0.0, 0.0); + packet->SetPointerData(1, data); + CreateSimulatedPointerData(data, PointerData::Change::kCancel, 2, 40.0, 50.0); + packet->SetPointerData(2, data); + converted_packet = converter.Convert(std::move(packet)); + UnpackPointerPacket(result, std::move(converted_packet)); + + ASSERT_EQ(result.size(), (size_t)6); + ASSERT_EQ(result[0].change, PointerData::Change::kAdd); + ASSERT_EQ(result[0].device, 0); + ASSERT_EQ(result[0].physical_x, 0.0); + ASSERT_EQ(result[0].physical_y, 0.0); + ASSERT_EQ(result[0].synthesized, 1); + + ASSERT_EQ(result[1].change, PointerData::Change::kDown); + ASSERT_EQ(result[1].device, 0); + ASSERT_EQ(result[1].physical_x, 0.0); + ASSERT_EQ(result[1].physical_y, 0.0); + ASSERT_EQ(result[1].synthesized, 0); + + ASSERT_EQ(result[2].change, PointerData::Change::kAdd); + ASSERT_EQ(result[2].device, 1); + ASSERT_EQ(result[2].physical_x, 33.0); + ASSERT_EQ(result[2].physical_y, 44.0); + ASSERT_EQ(result[2].synthesized, 1); + + ASSERT_EQ(result[3].change, PointerData::Change::kDown); + ASSERT_EQ(result[3].device, 1); + ASSERT_EQ(result[3].physical_x, 33.0); + ASSERT_EQ(result[3].physical_y, 44.0); + ASSERT_EQ(result[3].synthesized, 0); + + ASSERT_EQ(result[4].change, PointerData::Change::kCancel); + ASSERT_EQ(result[4].device, 1); + ASSERT_EQ(result[4].physical_x, 33.0); + ASSERT_EQ(result[4].physical_y, 44.0); + ASSERT_EQ(result[4].synthesized, 0); + + ASSERT_EQ(result[5].change, PointerData::Change::kCancel); + ASSERT_EQ(result[5].device, 0); + ASSERT_EQ(result[5].physical_x, 0.0); + ASSERT_EQ(result[5].physical_y, 0.0); + ASSERT_EQ(result[5].synthesized, 0); + // Third cancel should be dropped +} + +TEST(PointerDataPacketConverterTest, CanConvetScroll) { + PointerDataPacketConverter converter; + auto packet = std::make_unique(5); + PointerData data; + CreateSimulatedMousePointerData(data, PointerData::Change::kAdd, + PointerData::SignalKind::kNone, 0, 0.0, 0.0, + 0.0, 0.0); + packet->SetPointerData(0, data); + CreateSimulatedMousePointerData(data, PointerData::Change::kAdd, + PointerData::SignalKind::kNone, 1, 0.0, 0.0, + 0.0, 0.0); + packet->SetPointerData(1, data); + CreateSimulatedMousePointerData(data, PointerData::Change::kDown, + PointerData::SignalKind::kNone, 1, 0.0, 0.0, + 0.0, 0.0); + packet->SetPointerData(2, data); + CreateSimulatedMousePointerData(data, PointerData::Change::kHover, + PointerData::SignalKind::kScroll, 0, 34.0, + 34.0, 30.0, 0.0); + packet->SetPointerData(3, data); + CreateSimulatedMousePointerData(data, PointerData::Change::kHover, + PointerData::SignalKind::kScroll, 1, 49.0, + 49.0, 50.0, 0.0); + packet->SetPointerData(4, data); + auto converted_packet = converter.Convert(std::move(packet)); + + std::vector result; + UnpackPointerPacket(result, std::move(converted_packet)); + + ASSERT_EQ(result.size(), (size_t)7); + ASSERT_EQ(result[0].change, PointerData::Change::kAdd); + ASSERT_EQ(result[0].signal_kind, PointerData::SignalKind::kNone); + ASSERT_EQ(result[0].device, 0); + ASSERT_EQ(result[0].physical_x, 0.0); + ASSERT_EQ(result[0].physical_y, 0.0); + ASSERT_EQ(result[0].synthesized, 0); + + ASSERT_EQ(result[1].change, PointerData::Change::kAdd); + ASSERT_EQ(result[1].signal_kind, PointerData::SignalKind::kNone); + ASSERT_EQ(result[1].device, 1); + ASSERT_EQ(result[1].physical_x, 0.0); + ASSERT_EQ(result[1].physical_y, 0.0); + ASSERT_EQ(result[1].synthesized, 0); + + ASSERT_EQ(result[2].change, PointerData::Change::kDown); + ASSERT_EQ(result[2].signal_kind, PointerData::SignalKind::kNone); + ASSERT_EQ(result[2].device, 1); + ASSERT_EQ(result[2].physical_x, 0.0); + ASSERT_EQ(result[2].physical_y, 0.0); + ASSERT_EQ(result[2].synthesized, 0); + + // Converter will synthesize a hover to position. + ASSERT_EQ(result[3].change, PointerData::Change::kHover); + ASSERT_EQ(result[3].signal_kind, PointerData::SignalKind::kNone); + ASSERT_EQ(result[3].device, 0); + ASSERT_EQ(result[3].physical_x, 34.0); + ASSERT_EQ(result[3].physical_y, 34.0); + ASSERT_EQ(result[3].physical_delta_x, 34.0); + ASSERT_EQ(result[3].physical_delta_y, 34.0); + ASSERT_EQ(result[3].synthesized, 1); + + ASSERT_EQ(result[4].change, PointerData::Change::kHover); + ASSERT_EQ(result[4].signal_kind, PointerData::SignalKind::kScroll); + ASSERT_EQ(result[4].device, 0); + ASSERT_EQ(result[4].physical_x, 34.0); + ASSERT_EQ(result[4].physical_y, 34.0); + ASSERT_EQ(result[4].scroll_delta_x, 30.0); + ASSERT_EQ(result[4].scroll_delta_y, 0.0); + + // Converter will synthesize a move to position. + ASSERT_EQ(result[5].change, PointerData::Change::kMove); + ASSERT_EQ(result[5].signal_kind, PointerData::SignalKind::kNone); + ASSERT_EQ(result[5].device, 1); + ASSERT_EQ(result[5].physical_x, 49.0); + ASSERT_EQ(result[5].physical_y, 49.0); + ASSERT_EQ(result[5].physical_delta_x, 49.0); + ASSERT_EQ(result[5].physical_delta_y, 49.0); + ASSERT_EQ(result[5].synthesized, 1); + + ASSERT_EQ(result[6].change, PointerData::Change::kHover); + ASSERT_EQ(result[6].signal_kind, PointerData::SignalKind::kScroll); + ASSERT_EQ(result[6].device, 1); + ASSERT_EQ(result[6].physical_x, 49.0); + ASSERT_EQ(result[6].physical_y, 49.0); + ASSERT_EQ(result[6].scroll_delta_x, 50.0); + ASSERT_EQ(result[6].scroll_delta_y, 0.0); +} + +} // namespace testing +} // namespace flutter diff --git a/lib/ui/window/viewport_metrics.cc b/lib/ui/window/viewport_metrics.cc index f18972992189a..0b6dab6d4c1e0 100644 --- a/lib/ui/window/viewport_metrics.cc +++ b/lib/ui/window/viewport_metrics.cc @@ -4,8 +4,9 @@ #include "flutter/lib/ui/window/viewport_metrics.h" +#include "flutter/fml/logging.h" + namespace flutter { -ViewportMetrics::ViewportMetrics() = default; ViewportMetrics::ViewportMetrics(double p_device_pixel_ratio, double p_physical_width, @@ -39,6 +40,10 @@ ViewportMetrics::ViewportMetrics(double p_device_pixel_ratio, physical_system_gesture_inset_bottom( p_physical_system_gesture_inset_bottom), physical_system_gesture_inset_left(p_physical_system_gesture_inset_left) { + // Ensure we don't have nonsensical dimensions. + FML_DCHECK(physical_width >= 0); + FML_DCHECK(physical_height >= 0); + FML_DCHECK(device_pixel_ratio > 0); } ViewportMetrics::ViewportMetrics(double p_device_pixel_ratio, @@ -68,8 +73,11 @@ ViewportMetrics::ViewportMetrics(double p_device_pixel_ratio, physical_view_inset_bottom(p_physical_view_inset_bottom), physical_view_inset_left(p_physical_view_inset_left), physical_view_inset_front(p_physical_view_inset_front), - physical_view_inset_back(p_physical_view_inset_back) {} - -ViewportMetrics::ViewportMetrics(const ViewportMetrics& other) = default; + physical_view_inset_back(p_physical_view_inset_back) { + // Ensure we don't have nonsensical dimensions. + FML_DCHECK(physical_width >= 0); + FML_DCHECK(physical_height >= 0); + FML_DCHECK(device_pixel_ratio > 0); +} } // namespace flutter diff --git a/lib/ui/window/viewport_metrics.h b/lib/ui/window/viewport_metrics.h index 5ca8b78eaa064..f60adbfcee110 100644 --- a/lib/ui/window/viewport_metrics.h +++ b/lib/ui/window/viewport_metrics.h @@ -16,7 +16,8 @@ namespace flutter { static const double kUnsetDepth = 1.7976931348623157e+308; struct ViewportMetrics { - ViewportMetrics(); + ViewportMetrics() = default; + ViewportMetrics(const ViewportMetrics& other) = default; // Create a 2D ViewportMetrics instance. ViewportMetrics(double p_device_pixel_ratio, @@ -51,8 +52,6 @@ struct ViewportMetrics { double p_physical_view_inset_bottom, double p_physical_view_inset_left); - ViewportMetrics(const ViewportMetrics& other); - double device_pixel_ratio = 1.0; double physical_width = 0; double physical_height = 0; diff --git a/lib/web_ui/build.yaml b/lib/web_ui/build.yaml index 86391d013bb95..af02fd0fb3390 100644 --- a/lib/web_ui/build.yaml +++ b/lib/web_ui/build.yaml @@ -8,4 +8,8 @@ targets: - --no-minify - --enable-asserts generate_for: - - test/**.dart + include: + - test/**.dart + exclude: + - test/**vm_test.dart + diff --git a/lib/web_ui/dart_test.yaml b/lib/web_ui/dart_test_chrome.yaml similarity index 64% rename from lib/web_ui/dart_test.yaml rename to lib/web_ui/dart_test_chrome.yaml index 5715dbe897f2e..a43e4e114d980 100644 --- a/lib/web_ui/dart_test.yaml +++ b/lib/web_ui/dart_test_chrome.yaml @@ -1,11 +1,11 @@ -# Sources of inspiration for this file: +# For more information on test and runner configurations: # -# * https://github.com/dart-lang/angular/blob/master/dev/tool/test/dart_test_repo.yaml -# * https://github.com/dart-lang/angular/blob/master/_tests/dart_test.yaml +# * https://github.com/dart-lang/test/blob/master/pkgs/test/doc/configuration.md#platforms platforms: - chrome - vm + presets: cirrus: override_platforms: diff --git a/lib/web_ui/dart_test_firefox.yaml b/lib/web_ui/dart_test_firefox.yaml new file mode 100644 index 0000000000000..6bfa5925562b0 --- /dev/null +++ b/lib/web_ui/dart_test_firefox.yaml @@ -0,0 +1,7 @@ +# For more information on test and runner configurations: +# +# * https://github.com/dart-lang/test/blob/master/pkgs/test/doc/configuration.md#platforms + +platforms: + - firefox + - vm diff --git a/lib/web_ui/dart_test_safari.yaml b/lib/web_ui/dart_test_safari.yaml new file mode 100644 index 0000000000000..54f2064859b9e --- /dev/null +++ b/lib/web_ui/dart_test_safari.yaml @@ -0,0 +1,7 @@ +# For more information on test and runner configurations: +# +# * https://github.com/dart-lang/test/blob/master/pkgs/test/doc/configuration.md#platforms + +platforms: + - safari + - vm diff --git a/lib/web_ui/dev/README.md b/lib/web_ui/dev/README.md index 8a1ff65f3f058..86e06ccde8f04 100644 --- a/lib/web_ui/dev/README.md +++ b/lib/web_ui/dev/README.md @@ -31,17 +31,36 @@ felt build [-w] -j 100 If you are a Google employee, you can use an internal instance of Goma to parallelize your builds. Because Goma compiles code on remote servers, this option is effective even on low-powered laptops. ## Running web engine tests -To run all tests: +To run all tests on Chrome: + ``` felt test ``` +To run tests on Firefox (this will work only on a Linux device): + +``` +felt test --browser=firefox +``` + +For Chrome and Firefox, the tests run on a version locked on the [browser_lock.yaml](https://github.com/flutter/engine/blob/master/lib/web_ui/dev/browser_lock.yaml). In order to use another version, add the version argument: + +``` +felt test --browser=firefox --firefox-version=70.0.1 +``` + +To run tests on Safari use the following command. It works on MacOS devices and it uses the Safari installed on the OS. Currently there is no option for using another Safari version. + +``` +felt test --browser=safari +``` + To run a single test: ``` felt test test/golden_tests/engine/canvas_golden_test.dart ``` -To debug a test: +To debug a test on Chrome: ``` felt test --debug test/golden_tests/engine/canvas_golden_test.dart ``` diff --git a/lib/web_ui/dev/browser.dart b/lib/web_ui/dev/browser.dart index 8301289acf541..5c7c1b8e63513 100644 --- a/lib/web_ui/dev/browser.dart +++ b/lib/web_ui/dev/browser.dart @@ -10,7 +10,9 @@ import 'package:pedantic/pedantic.dart'; import 'package:stack_trace/stack_trace.dart'; import 'package:typed_data/typed_buffers.dart'; -import 'package:test_api/src/utils.dart'; // ignore: implementation_imports +import 'package:test_api/src/utils.dart'; + +import 'common.dart'; // ignore: unused_import /// An interface for running browser instances. /// diff --git a/lib/web_ui/dev/browser_lock.yaml b/lib/web_ui/dev/browser_lock.yaml index c2da9e63e9fa3..cfc7608d8b061 100644 --- a/lib/web_ui/dev/browser_lock.yaml +++ b/lib/web_ui/dev/browser_lock.yaml @@ -1,7 +1,8 @@ -chrome: - # It seems Chrome can't always release from the same build for all operating - # systems, so we specify per-OS build number. - Linux: 695653 - Mac: 695656 -firefox: - version: 69.0.3 +chrome: + # It seems Chrome can't always release from the same build for all operating + # systems, so we specify per-OS build number. + Linux: 695653 + Mac: 695656 + Win: 695655 +firefox: + version: '71.0' diff --git a/lib/web_ui/dev/build.dart b/lib/web_ui/dev/build.dart index 541710d74d191..86c462e77ec7f 100644 --- a/lib/web_ui/dev/build.dart +++ b/lib/web_ui/dev/build.dart @@ -60,6 +60,8 @@ class BuildCommand extends Command { PipelineWatcher( dir: libPath.absolute, pipeline: buildPipeline, + // Ignore font files that are copied whenever tests run. + ignore: (event) => event.path.endsWith('.ttf'), ).start(); // Return a never-ending future. return Completer().future; @@ -143,10 +145,13 @@ class Pipeline { } } +typedef WatchEventPredicate = bool Function(WatchEvent event); + class PipelineWatcher { PipelineWatcher({ @required this.dir, @required this.pipeline, + this.ignore, }) : watcher = DirectoryWatcher(dir); /// The path of the directory to watch for changes. @@ -158,6 +163,10 @@ class PipelineWatcher { /// Used to watch a directory for any file system changes. final DirectoryWatcher watcher; + /// A callback that determines whether to rerun the pipeline or not for a + /// given [WatchEvent] instance. + final WatchEventPredicate ignore; + void start() { watcher.events.listen(_onEvent); } @@ -166,6 +175,10 @@ class PipelineWatcher { Timer _scheduledPipeline; void _onEvent(WatchEvent event) { + if (ignore != null && ignore(event)) { + return; + } + final String relativePath = path.relative(event.path, from: dir); print('- [${event.type}] ${relativePath}'); diff --git a/lib/web_ui/dev/chrome.dart b/lib/web_ui/dev/chrome.dart index f2d1e3a6e9b63..0eb553b8f863a 100644 --- a/lib/web_ui/dev/chrome.dart +++ b/lib/web_ui/dev/chrome.dart @@ -32,6 +32,8 @@ class Chrome extends Browser { /// Starts a new instance of Chrome open to the given [url], which may be a /// [Uri] or a [String]. factory Chrome(Uri url, {bool debug = false}) { + version = ChromeArgParser.instance.version; + assert(version != null); var remoteDebuggerCompleter = Completer.sync(); return Chrome._(() async { @@ -50,16 +52,20 @@ class Chrome extends Browser { // --disable-gpu // --disallow-non-exact-resource-reuse // --disable-font-subpixel-positioning - final bool isChromeNoSandbox = Platform.environment['CHROME_NO_SANDBOX'] == 'true'; + final bool isChromeNoSandbox = + Platform.environment['CHROME_NO_SANDBOX'] == 'true'; var dir = createTempDir(); var args = [ '--user-data-dir=$dir', url.toString(), - if (!debug) '--headless', - if (isChromeNoSandbox) '--no-sandbox', + if (!debug) + '--headless', + if (isChromeNoSandbox) + '--no-sandbox', '--window-size=$kMaxScreenshotWidth,$kMaxScreenshotHeight', // When headless, this is the actual size of the viewport '--disable-extensions', '--disable-popup-blocking', + // Indicates that the browser is in "browse without sign-in" (Guest session) mode. '--bwsi', '--no-first-run', '--no-default-browser-check', @@ -68,10 +74,11 @@ class Chrome extends Browser { '--remote-debugging-port=$kDevtoolsPort', ]; - final Process process = await Process.start(installation.executable, args); + final Process process = + await Process.start(installation.executable, args); - remoteDebuggerCompleter.complete(getRemoteDebuggerUrl( - Uri.parse('http://localhost:${kDevtoolsPort}'))); + remoteDebuggerCompleter.complete( + getRemoteDebuggerUrl(Uri.parse('http://localhost:${kDevtoolsPort}'))); unawaited(process.exitCode .then((_) => Directory(dir).deleteSync(recursive: true))); diff --git a/lib/web_ui/dev/chrome_installer.dart b/lib/web_ui/dev/chrome_installer.dart index c03872b920e17..5c8dbe399bbb8 100644 --- a/lib/web_ui/dev/chrome_installer.dart +++ b/lib/web_ui/dev/chrome_installer.dart @@ -13,23 +13,42 @@ import 'package:yaml/yaml.dart'; import 'common.dart'; import 'environment.dart'; -void addChromeVersionOption(ArgParser argParser) { - final io.File lockFile = io.File( - path.join(environment.webUiRootDir.path, 'dev', 'browser_lock.yaml')); - final YamlMap lock = loadYaml(lockFile.readAsStringSync()); - final int pinnedChromeVersion = PlatformBinding.instance.getChromeBuild(lock); - - argParser - ..addOption( - 'chrome-version', - defaultsTo: '$pinnedChromeVersion', - help: 'The Chrome version to use while running tests. If the requested ' - 'version has not been installed, it will be downloaded and installed ' - 'automatically. A specific Chrome build version number, such as 695653 ' - 'this use that version of Chrome. Value "latest" will use the latest ' - 'available build of Chrome, installing it if necessary. Value "system" ' - 'will use the manually installed version of Chrome on this computer.', - ); +class ChromeArgParser extends BrowserArgParser { + static final ChromeArgParser _singletonInstance = ChromeArgParser._(); + + /// The [ChromeArgParser] singleton. + static ChromeArgParser get instance => _singletonInstance; + + String _version; + + ChromeArgParser._(); + + @override + void populateOptions(ArgParser argParser) { + final YamlMap browserLock = BrowserLock.instance.configuration; + final int pinnedChromeVersion = + PlatformBinding.instance.getChromeBuild(browserLock); + + argParser + ..addOption( + 'chrome-version', + defaultsTo: '$pinnedChromeVersion', + help: 'The Chrome version to use while running tests. If the requested ' + 'version has not been installed, it will be downloaded and installed ' + 'automatically. A specific Chrome build version number, such as 695653, ' + 'will use that version of Chrome. Value "latest" will use the latest ' + 'available build of Chrome, installing it if necessary. Value "system" ' + 'will use the manually installed version of Chrome on this computer.', + ); + } + + @override + void parseOptions(ArgResults argResults) { + _version = argResults['chrome-version']; + } + + @override + String get version => _version; } /// Returns the installation of Chrome, installing it if necessary. @@ -180,6 +199,7 @@ class ChromeInstaller { if (unzipResult.exitCode != 0) { throw BrowserInstallerException( 'Failed to unzip the downloaded Chrome archive ${downloadedFile.path}.\n' + 'With the version path ${versionDir.path}\n' 'The unzip process exited with code ${unzipResult.exitCode}.'); } diff --git a/lib/web_ui/dev/common.dart b/lib/web_ui/dev/common.dart index e96872da283c5..17bf4d8010a48 100644 --- a/lib/web_ui/dev/common.dart +++ b/lib/web_ui/dev/common.dart @@ -4,10 +4,13 @@ import 'dart:io' as io; +import 'package:args/args.dart'; import 'package:meta/meta.dart'; import 'package:path/path.dart' as path; import 'package:yaml/yaml.dart'; +import 'environment.dart'; + /// The port number for debugging. const int kDevtoolsPort = 12345; const int kMaxScreenshotWidth = 1024; @@ -30,6 +33,8 @@ abstract class PlatformBinding { _instance = _LinuxBinding(); } else if (io.Platform.isMacOS) { _instance = _MacBinding(); + } else if (io.Platform.isWindows) { + _instance = _WindowsBinding(); } else { throw '${io.Platform.operatingSystem} is not supported'; } @@ -45,11 +50,44 @@ abstract class PlatformBinding { String getChromeExecutablePath(io.Directory versionDir); String getFirefoxExecutablePath(io.Directory versionDir); String getFirefoxLatestVersionUrl(); + String getSafariSystemExecutablePath(); } const String _kBaseDownloadUrl = 'https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o'; +class _WindowsBinding implements PlatformBinding { + @override + int getChromeBuild(YamlMap browserLock) { + final YamlMap chromeMap = browserLock['chrome']; + return chromeMap['Win']; + } + + @override + String getChromeDownloadUrl(String version) => + 'https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/Win%2F${version}%2Fchrome-win32.zip?alt=media'; + + @override + String getChromeExecutablePath(io.Directory versionDir) => + path.join(versionDir.path, 'chrome-win32', 'chrome'); + + @override + String getFirefoxDownloadUrl(String version) => + 'https://download-installer.cdn.mozilla.net/pub/firefox/releases/${version}/win64/en-US/firefox-${version}.exe'; + + @override + String getFirefoxExecutablePath(io.Directory versionDir) => + path.join(versionDir.path, 'firefox', 'firefox'); + + @override + String getFirefoxLatestVersionUrl() => + 'https://download.mozilla.org/?product=firefox-latest&os=win&lang=en-US'; + + @override + String getSafariSystemExecutablePath() => + throw UnsupportedError('Safari is not supported on Windows'); +} + class _LinuxBinding implements PlatformBinding { @override int getChromeBuild(YamlMap browserLock) { @@ -76,6 +114,10 @@ class _LinuxBinding implements PlatformBinding { @override String getFirefoxLatestVersionUrl() => 'https://download.mozilla.org/?product=firefox-latest&os=linux64&lang=en-US'; + + @override + String getSafariSystemExecutablePath() => + throw UnsupportedError('Safari is not supported on Linux'); } class _MacBinding implements PlatformBinding { @@ -109,6 +151,10 @@ class _MacBinding implements PlatformBinding { @override String getFirefoxLatestVersionUrl() => 'https://download.mozilla.org/?product=firefox-latest&os=osx&lang=en-US'; + + @override + String getSafariSystemExecutablePath() => + '/Applications/Safari.app/Contents/MacOS/Safari'; } class BrowserInstallation { @@ -124,6 +170,36 @@ class BrowserInstallation { final String executable; } +abstract class BrowserArgParser { + const BrowserArgParser(); + + /// Populate options specific to a browser to the [ArgParser]. + void populateOptions(ArgParser argParser); + + /// Populate browser with results of the arguments passed. + void parseOptions(ArgResults argResults); + + String get version; +} + +/// Provides access to the contents of the `browser_lock.yaml` file. +class BrowserLock { + /// Initializes the [BrowserLock] singleton. + static final BrowserLock _singletonInstance = BrowserLock._(); + + /// The [Keyboard] singleton. + static BrowserLock get instance => _singletonInstance; + + YamlMap _configuration = YamlMap(); + YamlMap get configuration => _configuration; + + BrowserLock._() { + final io.File lockFile = io.File( + path.join(environment.webUiRootDir.path, 'dev', 'browser_lock.yaml')); + this._configuration = loadYaml(lockFile.readAsStringSync()); + } +} + /// A string sink that swallows all input. class DevNull implements StringSink { @override diff --git a/lib/web_ui/dev/felt.dart b/lib/web_ui/dev/felt.dart index 1830c051b9ef6..33034b3e8b0e2 100644 --- a/lib/web_ui/dev/felt.dart +++ b/lib/web_ui/dev/felt.dart @@ -51,8 +51,12 @@ void _listenToShutdownSignals() { print('Received SIGINT. Shutting down.'); io.exit(1); }); - io.ProcessSignal.sigterm.watch().listen((_) { - print('Received SIGTERM. Shutting down.'); - io.exit(1); - }); + // SIGTERM signals are not generated under Windows. + // See https://docs.microsoft.com/en-us/previous-versions/xdkz3x12(v%3Dvs.140) + if (!io.Platform.isWindows) { + io.ProcessSignal.sigterm.watch().listen((_) { + print('Received SIGTERM. Shutting down.'); + io.exit(1); + }); + } } diff --git a/lib/web_ui/dev/firefox.dart b/lib/web_ui/dev/firefox.dart index e1a43301eb128..787daf6befc8a 100644 --- a/lib/web_ui/dev/firefox.dart +++ b/lib/web_ui/dev/firefox.dart @@ -32,6 +32,8 @@ class Firefox extends Browser { /// Starts a new instance of Firefox open to the given [url], which may be a /// [Uri] or a [String]. factory Firefox(Uri url, {bool debug = false}) { + version = FirefoxArgParser.instance.version; + assert(version != null); var remoteDebuggerCompleter = Completer.sync(); return Firefox._(() async { @@ -46,18 +48,19 @@ class Firefox extends Browser { var dir = createTempDir(); var args = [ url.toString(), - if (!debug) '--headless', - '-width $kMaxScreenshotWidth' + '--headless', + '-width $kMaxScreenshotWidth', '-height $kMaxScreenshotHeight', '-new-window', '-new-instance', '--start-debugger-server $kDevtoolsPort', ]; - final Process process = await Process.start(installation.executable, args); + final Process process = + await Process.start(installation.executable, args); - remoteDebuggerCompleter.complete(getRemoteDebuggerUrl( - Uri.parse('http://localhost:$kDevtoolsPort'))); + remoteDebuggerCompleter.complete( + getRemoteDebuggerUrl(Uri.parse('http://localhost:$kDevtoolsPort'))); unawaited(process.exitCode .then((_) => Directory(dir).deleteSync(recursive: true))); diff --git a/lib/web_ui/dev/firefox_installer.dart b/lib/web_ui/dev/firefox_installer.dart index 641b9ad638591..1554f7b584eea 100644 --- a/lib/web_ui/dev/firefox_installer.dart +++ b/lib/web_ui/dev/firefox_installer.dart @@ -3,13 +3,51 @@ // found in the LICENSE file. import 'dart:io' as io; +import 'package:args/args.dart'; import 'package:http/http.dart'; import 'package:meta/meta.dart'; import 'package:path/path.dart' as path; +import 'package:yaml/yaml.dart'; import 'common.dart'; import 'environment.dart'; +class FirefoxArgParser extends BrowserArgParser { + static final FirefoxArgParser _singletonInstance = FirefoxArgParser._(); + + /// The [FirefoxArgParser] singleton. + static FirefoxArgParser get instance => _singletonInstance; + + String _version; + + FirefoxArgParser._(); + + @override + void populateOptions(ArgParser argParser) { + final YamlMap browserLock = BrowserLock.instance.configuration; + String firefoxVersion = browserLock['firefox']['version']; + + argParser + ..addOption( + 'firefox-version', + defaultsTo: '$firefoxVersion', + help: 'The Firefox version to use while running tests. If the requested ' + 'version has not been installed, it will be downloaded and installed ' + 'automatically. Value "latest" will use the latest ' + 'stable build of Firefox, installing it if necessary. Value "system" ' + 'will use the manually installed version of Firefox on this computer.', + ); + } + + @override + void parseOptions(ArgResults argResults) { + _version = argResults['firefox-version']; + } + + @override + String get version => _version; +} + /// Returns the installation of Firefox, installing it if necessary. /// /// If [requestedVersion] is null, uses the version specified on the @@ -106,7 +144,7 @@ class FirefoxInstaller { /// HTTP client used to download Firefox. final Client client = Client(); - /// Root directory that contains Chrome versions. + /// Root directory that contains Firefox versions. final io.Directory firefoxInstallationDir; /// Installation directory for Firefox of the requested [version]. diff --git a/lib/web_ui/dev/goldens_lock.yaml b/lib/web_ui/dev/goldens_lock.yaml index e82417f92861e..c9a03622d779a 100644 --- a/lib/web_ui/dev/goldens_lock.yaml +++ b/lib/web_ui/dev/goldens_lock.yaml @@ -1,2 +1,2 @@ repository: https://github.com/flutter/goldens.git -revision: 7935a97f89a6af5ae5182b2b5e59debda0189984 \ No newline at end of file +revision: c0032eeb9f9f064234991b8b5ddc15f714a53cf5 diff --git a/lib/web_ui/dev/safari.dart b/lib/web_ui/dev/safari.dart new file mode 100644 index 0000000000000..f96bd7cbf7772 --- /dev/null +++ b/lib/web_ui/dev/safari.dart @@ -0,0 +1,67 @@ +// 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:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'environment.dart'; + +import 'package:path/path.dart' as path; +import 'package:pedantic/pedantic.dart'; + +import 'package:test_core/src/util/io.dart'; // ignore: implementation_imports + +import 'browser.dart'; +import 'safari_installation.dart'; +import 'common.dart'; + +/// A class for running an instance of Safari. +/// +/// Most of the communication with the browser is expected to happen via HTTP, +/// so this exposes a bare-bones API. The browser starts as soon as the class is +/// constructed, and is killed when [close] is called. +/// +/// Any errors starting or running the process are reported through [onExit]. +class Safari extends Browser { + @override + final name = 'Safari'; + + static String version; + + /// Starts a new instance of Safari open to the given [url], which may be a + /// [Uri] or a [String]. + factory Safari(Uri url, {bool debug = false}) { + version = SafariArgParser.instance.version; + + assert(version != null); + return Safari._(() async { + // TODO(nurhan): Configure info log for LUCI. + final BrowserInstallation installation = await getOrInstallSafari( + version, + infoLog: DevNull(), + ); + + // Safari will only open files (not general URLs) via the command-line + // API, so we create a dummy file to redirect it to the page we actually + // want it to load. + final Directory redirectDir = Directory( + path.join(environment.webUiDartToolDir.path), + ); + final redirect = path.join(redirectDir.path, 'redirect.html'); + File(redirect).writeAsStringSync( + ''); + + var process = + await Process.start(installation.executable, [redirect] /* args */); + + unawaited(process.exitCode + .then((_) => File(redirect).deleteSync(recursive: true))); + + return process; + }); + } + + Safari._(Future startBrowser()) : super(startBrowser); +} diff --git a/lib/web_ui/dev/safari_installation.dart b/lib/web_ui/dev/safari_installation.dart new file mode 100644 index 0000000000000..2f651d72d2202 --- /dev/null +++ b/lib/web_ui/dev/safari_installation.dart @@ -0,0 +1,79 @@ +// 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:async'; +import 'dart:io' as io; + +import 'package:args/args.dart'; + +import 'common.dart'; + +class SafariArgParser extends BrowserArgParser { + static final SafariArgParser _singletonInstance = SafariArgParser._(); + + /// The [SafariArgParser] singleton. + static SafariArgParser get instance => _singletonInstance; + + String _version; + + SafariArgParser._(); + + @override + void populateOptions(ArgParser argParser) { + argParser + ..addOption( + 'safari-version', + defaultsTo: 'system', + help: 'The Safari version to use while running tests. The Safari ' + 'browser installed on the system is used as the only option now.' + 'Soon we will add support for using different versions using the ' + 'tech previews.', + ); + } + + @override + void parseOptions(ArgResults argResults) { + _version = argResults['safari-version']; + assert(_version == 'system'); + } + + @override + String get version => _version; +} + +/// Returns the installation of Safari. +/// +/// Currently uses the Safari version installed on the operating system. +/// +/// Latest Safari version for Catalina, Mojave, High Siera is 13. +/// +/// Latest Safari version for Sierra is 12. +// TODO(nurhan): user latest version to download and install the latest +// technology preview. +Future getOrInstallSafari( + String requestedVersion, { + StringSink infoLog, +}) async { + + // These tests are aimed to run only on MacOs machines local or on LUCI. + if (!io.Platform.isMacOS) { + throw UnimplementedError('Safari on ${io.Platform.operatingSystem} is' + ' not supported. Safari is only supported on MacOS.'); + } + + infoLog ??= io.stdout; + + if (requestedVersion == 'system') { + // Since Safari is included in MacOS, always assume there will be one on the + // system. + infoLog.writeln('Using the system version that is already installed.'); + return BrowserInstallation( + version: 'system', + executable: PlatformBinding.instance.getSafariSystemExecutablePath(), + ); + } else { + infoLog.writeln('Unsupported version $requestedVersion.'); + throw UnimplementedError(); + } +} diff --git a/lib/web_ui/dev/supported_browsers.dart b/lib/web_ui/dev/supported_browsers.dart new file mode 100644 index 0000000000000..5581e1ba82eb2 --- /dev/null +++ b/lib/web_ui/dev/supported_browsers.dart @@ -0,0 +1,61 @@ +// 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_api/src/backend/runtime.dart'; + +import 'browser.dart'; +import 'chrome.dart'; +import 'chrome_installer.dart'; +import 'common.dart'; +import 'environment.dart'; +import 'firefox.dart'; +import 'firefox_installer.dart'; +import 'safari.dart'; +import 'safari_installation.dart'; + +/// Utilities for browsers, that tests are supported. +/// +/// Extending [Browser] is not enough for supporting test. +/// +/// Each new browser should be added to the [Runtime] map, to the [getBrowser] +/// method. +/// +/// One should also implement [BrowserArgParser] and add it to the [argParsers]. +class SupportedBrowsers { + final List argParsers = + List.of([ChromeArgParser.instance, FirefoxArgParser.instance, SafariArgParser.instance]); + + final List supportedBrowserNames = ['chrome', 'firefox', 'safari']; + + final Map supportedBrowsersToRuntimes = { + 'chrome': Runtime.chrome, + 'firefox': Runtime.firefox, + 'safari': Runtime.safari, + }; + + final Map browserToConfiguration = { + 'chrome': '--configuration=${environment.webUiRootDir.path}/dart_test_chrome.yaml', + 'firefox': '--configuration=${environment.webUiRootDir.path}/dart_test_firefox.yaml', + 'safari': '--configuration=${environment.webUiRootDir.path}/dart_test_safari.yaml', + }; + + static final SupportedBrowsers _singletonInstance = SupportedBrowsers._(); + + /// The [SupportedBrowsers] singleton. + static SupportedBrowsers get instance => _singletonInstance; + + SupportedBrowsers._(); + + Browser getBrowser(Runtime runtime, Uri url, {bool debug = false}) { + if (runtime == Runtime.chrome) { + return Chrome(url, debug: debug); + } else if (runtime == Runtime.firefox) { + return Firefox(url, debug: debug); + } else if (runtime == Runtime.safari) { + return Safari(url, debug: debug); + } else { + throw new UnsupportedError('The browser type not supported in tests'); + } + } +} diff --git a/lib/web_ui/dev/test_platform.dart b/lib/web_ui/dev/test_platform.dart index bf1b5f95614d0..e9f5281710c99 100644 --- a/lib/web_ui/dev/test_platform.dart +++ b/lib/web_ui/dev/test_platform.dart @@ -38,19 +38,22 @@ import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart' as wip; import 'browser.dart'; -import 'chrome.dart'; import 'common.dart'; import 'environment.dart' as env; import 'goldens.dart'; +import 'supported_browsers.dart'; class BrowserPlatform extends PlatformPlugin { /// Starts the server. /// /// [root] is the root directory that the server should serve. It defaults to /// the working directory. - static Future start({String root, bool doUpdateScreenshotGoldens: false}) async { + static Future start(String name, + {String root, bool doUpdateScreenshotGoldens: false}) async { + assert(SupportedBrowsers.instance.supportedBrowserNames.contains(name)); var server = shelf_io.IOServer(await HttpMultiServer.loopback(0)); return BrowserPlatform._( + name, server, Configuration.current, p.fromUri(await Isolate.resolvePackageUri( @@ -66,6 +69,9 @@ class BrowserPlatform extends PlatformPlugin { /// The underlying server. final shelf.Server _server; + /// Name for the running browser. Not final on purpose can be mutated later. + String browserName; + /// A randomly-generated secret. /// /// This is used to ensure that other users on the same system can't snoop @@ -97,9 +103,11 @@ class BrowserPlatform extends PlatformPlugin { /// Whether to update screenshot golden files. final bool doUpdateScreenshotGoldens; - BrowserPlatform._(this._server, Configuration config, String faviconPath, + BrowserPlatform._( + String name, this._server, Configuration config, String faviconPath, {String root, this.doUpdateScreenshotGoldens}) - : _config = config, + : this.browserName = name, + _config = config, _root = root == null ? p.current : root, _http = config.pubServeUrl == null ? null : HttpClient() { var cascade = shelf.Cascade().add(_webSocketHandler.handler); @@ -109,7 +117,6 @@ class BrowserPlatform extends PlatformPlugin { final String staticFilePath = config.suiteDefaults.precompiledPath ?? _root; cascade = cascade - .add(_screeshotHandler) .add(packagesDirHandler()) .add(_jsHandler.handler) .add(createStaticHandler(staticFilePath, @@ -117,6 +124,10 @@ class BrowserPlatform extends PlatformPlugin { serveFilesOutsidePath: config.suiteDefaults.precompiledPath != null)) .add(_wrapperHandler); + // Screenshot tests are only enabled in chrome for now. + if (name == 'chrome') { + cascade = cascade.add(_screeshotHandler); + } } var pipeline = shelf.Pipeline() @@ -130,6 +141,10 @@ class BrowserPlatform extends PlatformPlugin { } Future _screeshotHandler(shelf.Request request) async { + if (browserName != 'chrome') { + throw Exception('Screenshots tests are only available in Chrome.'); + } + if (!request.requestedUri.path.endsWith('/screenshot')) { return shelf.Response.notFound( 'This request is not handled by the screenshot handler'); @@ -141,11 +156,14 @@ class BrowserPlatform extends PlatformPlugin { final bool write = requestData['write']; final double maxDiffRate = requestData['maxdiffrate']; final Map region = requestData['region']; - final String result = await _diffScreenshot(filename, write, maxDiffRate ?? kMaxDiffRateFailure, region); + final String result = await _diffScreenshot( + filename, write, maxDiffRate ?? kMaxDiffRateFailure, region); return shelf.Response.ok(json.encode(result)); } - Future _diffScreenshot(String filename, bool write, double maxDiffRateFailure, [ Map region ]) async { + Future _diffScreenshot( + String filename, bool write, double maxDiffRateFailure, + [Map region]) async { if (doUpdateScreenshotGoldens) { write = true; } @@ -195,7 +213,8 @@ To automatically create this file call matchGoldenFile('$filename', write: true) 'y': region['y'], 'width': region['width'], 'height': region['height'], - 'scale': 1, // This is NOT the DPI of the page, instead it's the "zoom level". + 'scale': + 1, // This is NOT the DPI of the page, instead it's the "zoom level". }, }; } @@ -208,8 +227,8 @@ To automatically create this file call matchGoldenFile('$filename', write: true) 'deviceScaleFactor': 1, 'mobile': false, }); - final wip.WipResponse response = - await wipConnection.sendCommand('Page.captureScreenshot', captureScreenshotParameters); + final wip.WipResponse response = await wipConnection.sendCommand( + 'Page.captureScreenshot', captureScreenshotParameters); // Compare screenshots final Image screenshot = decodePng(base64.decode(response.result['data'])); @@ -226,31 +245,37 @@ To automatically create this file call matchGoldenFile('$filename', write: true) } } - ImageDiff diff = ImageDiff(golden: decodeNamedImage(file.readAsBytesSync(), filename), other: screenshot); + ImageDiff diff = ImageDiff( + golden: decodeNamedImage(file.readAsBytesSync(), filename), + other: screenshot); - if (diff.rate > 0) { // Images are different, so produce some debug info + if (diff.rate > 0) { + // Images are different, so produce some debug info final String testResultsPath = isCirrus - ? p.join( - Platform.environment['CIRRUS_WORKING_DIR'], - 'test_results', - ) - : p.join( - env.environment.webUiDartToolDir.path, - 'test_results', - ); + ? p.join( + Platform.environment['CIRRUS_WORKING_DIR'], + 'test_results', + ) + : p.join( + env.environment.webUiDartToolDir.path, + 'test_results', + ); Directory(testResultsPath).createSync(recursive: true); final String basename = p.basenameWithoutExtension(file.path); - final File actualFile = File(p.join(testResultsPath, '$basename.actual.png')); + final File actualFile = + File(p.join(testResultsPath, '$basename.actual.png')); actualFile.writeAsBytesSync(encodePng(screenshot), flush: true); final File diffFile = File(p.join(testResultsPath, '$basename.diff.png')); - diffFile.writeAsBytesSync(encodePng(diff.diff), flush:true); + diffFile.writeAsBytesSync(encodePng(diff.diff), flush: true); - final File expectedFile = File(p.join(testResultsPath, '$basename.expected.png')); + final File expectedFile = + File(p.join(testResultsPath, '$basename.expected.png')); file.copySync(expectedFile.path); - final File reportFile = File(p.join(testResultsPath, '$basename.report.html')); + final File reportFile = + File(p.join(testResultsPath, '$basename.report.html')); reportFile.writeAsStringSync(''' Golden file $filename did not match the image generated by the test. @@ -275,16 +300,19 @@ Golden file $filename did not match the image generated by the test. '''); final StringBuffer message = StringBuffer(); - message.writeln('Golden file $filename did not match the image generated by the test.'); + message.writeln( + 'Golden file $filename did not match the image generated by the test.'); message.writeln(getPrintableDiffFilesInfo(diff.rate, maxDiffRateFailure)); - message.writeln('You can view the test report in your browser by opening:'); + message + .writeln('You can view the test report in your browser by opening:'); // Cirrus cannot serve HTML pages generated by build jobs, so we // archive all the files so that they can be downloaded and inspected // locally. if (isCirrus) { final String taskId = Platform.environment['CIRRUS_TASK_ID']; - final String baseArtifactsUrl = 'https://api.cirrus-ci.com/v1/artifact/task/$taskId/web_engine_test/test_results'; + final String baseArtifactsUrl = + 'https://api.cirrus-ci.com/v1/artifact/task/$taskId/web_engine_test/test_results'; final String cirrusReportUrl = '$baseArtifactsUrl/$basename.report.zip'; message.writeln(cirrusReportUrl); @@ -304,7 +332,8 @@ Golden file $filename did not match the image generated by the test. message.writeln(localReportPath); } - message.writeln('To update the golden file call matchGoldenFile(\'$filename\', write: true).'); + message.writeln( + 'To update the golden file call matchGoldenFile(\'$filename\', write: true).'); message.writeln('Golden file: ${expectedFile.path}'); message.writeln('Actual file: ${actualFile.path}'); @@ -649,8 +678,9 @@ class BrowserManager { /// Starts the browser identified by [browser] using [settings] and has it load [url]. /// /// If [debug] is true, starts the browser in debug mode. - static Browser _newBrowser(Uri url, Runtime browser, {bool debug = false}) { - return Chrome(url, debug: debug); + static Browser _newBrowser(Uri url, Runtime browser, + {bool debug = false}) { + return SupportedBrowsers.instance.getBrowser(browser, url, debug: debug); } /// Creates a new BrowserManager that communicates with [browser] over @@ -740,7 +770,7 @@ class BrowserManager { final String mapPath = p.join( env.environment.webUiRootDir.path, 'build', - '$path.js.map', + '$path.browser_test.dart.js.map', ); final JSStackTraceMapper mapper = JSStackTraceMapper( await File(mapPath).readAsString(), diff --git a/lib/web_ui/dev/test_runner.dart b/lib/web_ui/dev/test_runner.dart index 842b4e4e7adfd..d21aa65fd2a70 100644 --- a/lib/web_ui/dev/test_runner.dart +++ b/lib/web_ui/dev/test_runner.dart @@ -14,8 +14,7 @@ import 'package:test_api/src/backend/runtime.dart'; // ignore: implementation_im import 'package:test_core/src/executable.dart' as test; // ignore: implementation_imports -import 'chrome.dart'; -import 'chrome_installer.dart'; +import 'supported_browsers.dart'; import 'test_platform.dart'; import 'environment.dart'; import 'utils.dart'; @@ -32,12 +31,20 @@ class TestCommand extends Command { ..addFlag( 'update-screenshot-goldens', defaultsTo: false, - help: 'When running screenshot tests writes them to the file system into ' + help: + 'When running screenshot tests writes them to the file system into ' '.dart_tool/goldens. Use this option to bulk-update all screenshots, ' 'for example, when a new browser version affects pixels.', + ) + ..addOption( + 'browser', + defaultsTo: 'chrome', + help: 'An option to choose a browser to run the tests. Tests only work ' + ' on Chrome for now.', ); - addChromeVersionOption(argParser); + SupportedBrowsers.instance.argParsers + .forEach((t) => t.populateOptions(argParser)); } @override @@ -48,7 +55,8 @@ class TestCommand extends Command { @override Future run() async { - Chrome.version = chromeVersion; + SupportedBrowsers.instance + ..argParsers.forEach((t) => t.parseOptions(argResults)); _copyTestFontsIntoWebUi(); await _buildHostPage(); @@ -73,8 +81,9 @@ class TestCommand extends Command { /// Paths to targets to run, e.g. a single test. List get targets => argResults.rest; - /// See [ChromeInstallerCommand.chromeVersion]. - String get chromeVersion => argResults['chrome-version']; + String get browser => argResults['browser']; + + bool get isChrome => argResults['browser'] == 'chrome'; /// When running screenshot tests writes them to the file system into /// ".dart_tool/goldens". @@ -91,54 +100,74 @@ class TestCommand extends Command { 'test', )); - // Separate screenshot tests from unit-tests. Screenshot tests must run - // one at a time. Otherwise, they will end up screenshotting each other. - // This is not an issue for unit-tests. - final FilePath failureSmokeTestPath = FilePath.fromWebUi( - 'test/golden_tests/golden_failure_smoke_test.dart', - ); - final List screenshotTestFiles = []; - final List unitTestFiles = []; - - for (io.File testFile - in testDir.listSync(recursive: true).whereType()) { - final FilePath testFilePath = FilePath.fromCwd(testFile.path); - if (!testFilePath.absolute.endsWith('_test.dart')) { - // Not a test file at all. Skip. - continue; - } - if (testFilePath == failureSmokeTestPath) { - // A smoke test that fails on purpose. Skip. - continue; + // Screenshot tests and smoke tests only run in Chrome. + if (isChrome) { + // Separate screenshot tests from unit-tests. Screenshot tests must run + // one at a time. Otherwise, they will end up screenshotting each other. + // This is not an issue for unit-tests. + final FilePath failureSmokeTestPath = FilePath.fromWebUi( + 'test/golden_tests/golden_failure_smoke_test.dart', + ); + final List screenshotTestFiles = []; + final List unitTestFiles = []; + + for (io.File testFile + in testDir.listSync(recursive: true).whereType()) { + final FilePath testFilePath = FilePath.fromCwd(testFile.path); + if (!testFilePath.absolute.endsWith('_test.dart')) { + // Not a test file at all. Skip. + continue; + } + if (testFilePath == failureSmokeTestPath) { + // A smoke test that fails on purpose. Skip. + continue; + } + + if (path.split(testFilePath.relativeToWebUi).contains('golden_tests')) { + screenshotTestFiles.add(testFilePath); + } else { + unitTestFiles.add(testFilePath); + } } - if (path.split(testFilePath.relativeToWebUi).contains('golden_tests')) { - screenshotTestFiles.add(testFilePath); - } else { - unitTestFiles.add(testFilePath); + + // This test returns a non-zero exit code on purpose. Run it separately. + if (io.Platform.environment['CIRRUS_CI'] != 'true') { + await _runTestBatch( + [failureSmokeTestPath], + concurrency: 1, + expectFailure: true, + ); + _checkExitCode(); } - } - // This test returns a non-zero exit code on purpose. Run it separately. - if (io.Platform.environment['CIRRUS_CI'] != 'true') { - await _runTestBatch( - [failureSmokeTestPath], - concurrency: 1, - expectFailure: true, - ); + // Run all unit-tests as a single batch. + await _runTestBatch(unitTestFiles, concurrency: 10, expectFailure: false); _checkExitCode(); - } - - // Run all unit-tests as a single batch. - await _runTestBatch(unitTestFiles, concurrency: 10, expectFailure: false); - _checkExitCode(); - // Run screenshot tests one at a time. - for (FilePath testFilePath in screenshotTestFiles) { - await _runTestBatch( - [testFilePath], - concurrency: 1, - expectFailure: false, - ); + // Run screenshot tests one at a time. + for (FilePath testFilePath in screenshotTestFiles) { + await _runTestBatch( + [testFilePath], + concurrency: 1, + expectFailure: false, + ); + _checkExitCode(); + } + } else { + final List unitTestFiles = []; + for (io.File testFile + in testDir.listSync(recursive: true).whereType()) { + final FilePath testFilePath = FilePath.fromCwd(testFile.path); + if (!testFilePath.absolute.endsWith('_test.dart')) { + // Not a test file at all. Skip. + continue; + } + if(!path.split(testFilePath.relativeToWebUi).contains('golden_tests')) { + unitTestFiles.add(testFilePath); + } + } + // Run all unit-tests as a single batch. + await _runTestBatch(unitTestFiles, concurrency: 10, expectFailure: false); _checkExitCode(); } } @@ -161,7 +190,8 @@ class TestCommand extends Command { '$hostDartPath.js.timestamp', )); - final String timestamp = hostDartFile.statSync().modified.millisecondsSinceEpoch.toString(); + final String timestamp = + hostDartFile.statSync().modified.millisecondsSinceEpoch.toString(); if (timestampFile.existsSync()) { final String lastBuildTimestamp = timestampFile.readAsStringSync(); if (lastBuildTimestamp == timestamp) { @@ -195,7 +225,7 @@ class TestCommand extends Command { timestampFile.writeAsStringSync(timestamp); } - Future _buildTests({ List targets }) async { + Future _buildTests({List targets}) async { final int exitCode = await runProcess( environment.pubExecutable, [ @@ -206,11 +236,10 @@ class TestCommand extends Command { '-o', 'build', if (targets != null) - for (FilePath path in targets) - ...[ - '--build-filter=${path.relativeToWebUi}.js', - '--build-filter=${path.relativeToWebUi}.browser_test.dart.js', - ], + for (FilePath path in targets) ...[ + '--build-filter=${path.relativeToWebUi}.js', + '--build-filter=${path.relativeToWebUi}.browser_test.dart.js', + ], ], workingDirectory: environment.webUiRootDir.path, ); @@ -234,13 +263,18 @@ class TestCommand extends Command { ...['-r', 'compact'], '--concurrency=$concurrency', if (isDebug) '--pause-after-load', - '--platform=chrome', + '--platform=$browser', '--precompiled=${environment.webUiRootDir.path}/build', + SupportedBrowsers.instance.browserToConfiguration[browser], '--', ...testFiles.map((f) => f.relativeToWebUi).toList(), ]; - hack.registerPlatformPlugin([Runtime.chrome], () { + + hack.registerPlatformPlugin([ + SupportedBrowsers.instance.supportedBrowsersToRuntimes[browser] + ], () { return BrowserPlatform.start( + browser, root: io.Directory.current.path, // It doesn't make sense to update a screenshot for a test that is expected to fail. doUpdateScreenshotGoldens: !expectFailure && doUpdateScreenshotGoldens, @@ -280,7 +314,8 @@ void _copyTestFontsIntoWebUi() { for (String fontFile in _kTestFonts) { final io.File sourceTtf = io.File(path.join(fontsPath, fontFile)); - final String destinationTtfPath = path.join(environment.webUiRootDir.path, 'lib', 'assets', fontFile); + final String destinationTtfPath = + path.join(environment.webUiRootDir.path, 'lib', 'assets', fontFile); sourceTtf.copySync(destinationTtfPath); } } diff --git a/lib/web_ui/lib/src/engine.dart b/lib/web_ui/lib/src/engine.dart index 0579928cfdd9d..f000505c5e953 100644 --- a/lib/web_ui/lib/src/engine.dart +++ b/lib/web_ui/lib/src/engine.dart @@ -5,7 +5,7 @@ library engine; import 'dart:async'; -import 'dart:collection' show ListBase; +import 'dart:collection' show ListBase, IterableBase; import 'dart:convert' hide Codec; import 'dart:developer' as developer; import 'dart:html' as html; @@ -23,25 +23,32 @@ part 'engine/assets.dart'; part 'engine/bitmap_canvas.dart'; part 'engine/browser_detection.dart'; part 'engine/browser_location.dart'; +part 'engine/canvas_pool.dart'; part 'engine/color_filter.dart'; part 'engine/compositor/canvas.dart'; +part 'engine/compositor/canvas_kit_canvas.dart'; part 'engine/compositor/color_filter.dart'; +part 'engine/compositor/embedded_views.dart'; part 'engine/compositor/engine_delegate.dart'; part 'engine/compositor/fonts.dart'; part 'engine/compositor/image.dart'; +part 'engine/compositor/image_filter.dart'; part 'engine/compositor/initialization.dart'; part 'engine/compositor/layer.dart'; part 'engine/compositor/layer_scene_builder.dart'; part 'engine/compositor/layer_tree.dart'; +part 'engine/compositor/n_way_canvas.dart'; part 'engine/compositor/path.dart'; +part 'engine/compositor/painting.dart'; +part 'engine/compositor/path_metrics.dart'; part 'engine/compositor/picture.dart'; part 'engine/compositor/picture_recorder.dart'; part 'engine/compositor/platform_message.dart'; part 'engine/compositor/raster_cache.dart'; part 'engine/compositor/rasterizer.dart'; -part 'engine/compositor/recording_canvas.dart'; part 'engine/compositor/runtime_delegate.dart'; part 'engine/compositor/surface.dart'; +part 'engine/compositor/text.dart'; part 'engine/compositor/util.dart'; part 'engine/compositor/vertices.dart'; part 'engine/compositor/viewport_metrics.dart'; @@ -55,10 +62,11 @@ part 'engine/html_image_codec.dart'; part 'engine/keyboard.dart'; part 'engine/onscreen_logging.dart'; part 'engine/path_to_svg.dart'; +part 'engine/picture.dart'; part 'engine/platform_views.dart'; part 'engine/plugins.dart'; part 'engine/pointer_binding.dart'; -part 'engine/recording_canvas.dart'; +part 'engine/pointer_converter.dart'; part 'engine/render_vertices.dart'; part 'engine/rrect_renderer.dart'; part 'engine/semantics/accessibility.dart'; @@ -69,6 +77,7 @@ part 'engine/semantics/label_and_value.dart'; part 'engine/semantics/live_region.dart'; part 'engine/semantics/scrollable.dart'; part 'engine/semantics/semantics.dart'; +part 'engine/semantics/semantics_helper.dart'; part 'engine/semantics/tappable.dart'; part 'engine/semantics/text_field.dart'; part 'engine/services/buffers.dart'; @@ -80,10 +89,14 @@ part 'engine/shadow.dart'; part 'engine/surface/backdrop_filter.dart'; part 'engine/surface/clip.dart'; part 'engine/surface/debug_canvas_reuse_overlay.dart'; +part 'engine/surface/image_filter.dart'; part 'engine/surface/offset.dart'; part 'engine/surface/opacity.dart'; +part 'engine/surface/painting.dart'; +part 'engine/surface/path_metrics.dart'; part 'engine/surface/picture.dart'; part 'engine/surface/platform_view.dart'; +part 'engine/surface/recording_canvas.dart'; part 'engine/surface/scene.dart'; part 'engine/surface/scene_builder.dart'; part 'engine/surface/surface.dart'; diff --git a/lib/web_ui/lib/src/engine/bitmap_canvas.dart b/lib/web_ui/lib/src/engine/bitmap_canvas.dart index 65de6c351296d..682eca3297bde 100644 --- a/lib/web_ui/lib/src/engine/bitmap_canvas.dart +++ b/lib/web_ui/lib/src/engine/bitmap_canvas.dart @@ -5,7 +5,7 @@ part of engine; /// A raw HTML canvas that is directly written to. -class BitmapCanvas extends EngineCanvas with SaveStackTracking { +class BitmapCanvas extends EngineCanvas { /// The rectangle positioned relative to the parent layer's coordinate /// system's origin, within which this canvas paints. /// @@ -14,6 +14,14 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { set bounds(ui.Rect newValue) { assert(newValue != null); _bounds = newValue; + final int newCanvasPositionX = _bounds.left.floor() - kPaddingPixels; + final int newCanvasPositionY = _bounds.top.floor() - kPaddingPixels; + if (_canvasPositionX != newCanvasPositionX || + _canvasPositionY != newCanvasPositionY) { + _canvasPositionX = newCanvasPositionX; + _canvasPositionY = newCanvasPositionY; + _updateRootElementTransform(); + } } ui.Rect _bounds; @@ -25,8 +33,7 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { @override final html.Element rootElement = html.Element.tag('flt-canvas'); - html.CanvasElement _canvas; - html.CanvasRenderingContext2D _ctx; + final _CanvasPool _canvasPool; /// The size of the paint [bounds]. ui.Size get size => _bounds.size; @@ -43,22 +50,20 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { /// /// These pixels are different from the logical CSS pixels. Here a pixel /// literally means 1 point with a RGBA color. - int get widthInBitmapPixels => _widthInBitmapPixels; - int _widthInBitmapPixels; + final int _widthInBitmapPixels; /// The number of pixels along the width of the bitmap that the canvas element /// renders into. /// /// These pixels are different from the logical CSS pixels. Here a pixel /// literally means 1 point with a RGBA color. - int get heightInBitmapPixels => _heightInBitmapPixels; - int _heightInBitmapPixels; + final int _heightInBitmapPixels; /// The number of pixels in the bitmap that the canvas element renders into. /// /// These pixels are different from the logical CSS pixels. Here a pixel /// literally means 1 point with a RGBA color. - int get bitmapPixelCount => widthInBitmapPixels * heightInBitmapPixels; + int get bitmapPixelCount => _widthInBitmapPixels * _heightInBitmapPixels; int _saveCount = 0; @@ -66,11 +71,19 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { /// was created. final double _devicePixelRatio = html.window.devicePixelRatio; - // Cached current filter, fill and stroke style to reduce updates to - // CanvasRenderingContext2D that are slow even when resetting to null. - String _prevFilter = 'none'; - Object _prevFillStyle; - Object _prevStrokeStyle; + // Compensation for [_initializeViewport] snapping canvas position to 1 pixel. + int _canvasPositionX, _canvasPositionY; + + // Indicates the instructions following drawImage or drawParagraph that + // a child element was created to paint. + // TODO(flutter_web): When childElements are created by + // drawImage/drawParagraph commands, compositing order is not correctly + // handled when we interleave these with other paint commands. + // To solve this, recording canvas will have to check the paint queue + // and send a hint to EngineCanvas that additional canvas layers need + // to be used to composite correctly. In practice this is very rare + // with Widgets but CustomPainter(s) can hit this code path. + bool _childOverdraw = false; /// Allocates a canvas with enough memory to paint a picture within the given /// [bounds]. @@ -78,51 +91,62 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { /// This canvas can be reused by pictures with different paint bounds as long /// as the [Rect.size] of the bounds fully fit within the size used to /// initialize this canvas. - BitmapCanvas(this._bounds) : assert(_bounds != null) { + BitmapCanvas(this._bounds) + : assert(_bounds != null), + _widthInBitmapPixels = _widthToPhysical(_bounds.width), + _heightInBitmapPixels = _heightToPhysical(_bounds.height), + _canvasPool = _CanvasPool(_widthToPhysical(_bounds.width), + _heightToPhysical(_bounds.height)) { rootElement.style.position = 'absolute'; - // Adds one extra pixel to the requested size. This is to compensate for // _initializeViewport() snapping canvas position to 1 pixel, causing // painting to overflow by at most 1 pixel. + _canvasPositionX = _bounds.left.floor() - kPaddingPixels; + _canvasPositionY = _bounds.top.floor() - kPaddingPixels; + _updateRootElementTransform(); + _canvasPool.allocateCanvas(rootElement); + _setupInitialTransform(); + } - _widthInBitmapPixels = _widthToPhysical(_bounds.width); - _heightInBitmapPixels = _heightToPhysical(_bounds.height); - - // Compute the final CSS canvas size given the actual pixel count we - // allocated. This is done for the following reasons: + void _updateRootElementTransform() { + // Flutter emits paint operations positioned relative to the parent layer's + // coordinate system. However, canvas' coordinate system's origin is always + // in the top-left corner of the canvas. We therefore need to inject an + // initial translation so the paint operations are positioned as expected. // - // * To satisfy the invariant: pixel size = css size * device pixel ratio. - // * To make sure that when we scale the canvas by devicePixelRatio (see - // _initializeViewport below) the pixels line up. - final double cssWidth = _widthInBitmapPixels / html.window.devicePixelRatio; - final double cssHeight = - _heightInBitmapPixels / html.window.devicePixelRatio; - - _canvas = html.CanvasElement( - width: _widthInBitmapPixels, - height: _heightInBitmapPixels, + // The flooring of the value is to ensure that canvas' top-left corner + // lands on the physical pixel. TODO: !This is not accurate if there are + // transforms higher up in the stack. + rootElement.style.transform = + 'translate(${_canvasPositionX}px, ${_canvasPositionY}px)'; + } + + void _setupInitialTransform() { + final double canvasPositionCorrectionX = _bounds.left - + BitmapCanvas.kPaddingPixels - + _canvasPositionX.toDouble(); + final double canvasPositionCorrectionY = + _bounds.top - BitmapCanvas.kPaddingPixels - _canvasPositionY.toDouble(); + // This compensates for the translate on the `rootElement`. + _canvasPool.initialTransform = ui.Offset( + -_bounds.left + canvasPositionCorrectionX + BitmapCanvas.kPaddingPixels, + -_bounds.top + canvasPositionCorrectionY + BitmapCanvas.kPaddingPixels, ); - _canvas.style - ..position = 'absolute' - ..width = '${cssWidth}px' - ..height = '${cssHeight}px'; - _ctx = _canvas.context2D; - rootElement.append(_canvas); - _initializeViewport(); } - int _widthToPhysical(double width) { + static int _widthToPhysical(double width) { final double boundsWidth = width + 1; return (boundsWidth * html.window.devicePixelRatio).ceil() + 2 * kPaddingPixels; } - int _heightToPhysical(double height) { + static int _heightToPhysical(double height) { final double boundsHeight = height + 1; return (boundsHeight * html.window.devicePixelRatio).ceil() + 2 * kPaddingPixels; } + // Used by picture to assess if canvas is large enough to reuse as is. bool doesFitBounds(ui.Rect newBounds) { assert(newBounds != null); return _widthInBitmapPixels >= _widthToPhysical(newBounds.width) && @@ -131,47 +155,20 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { @override void dispose() { - super.dispose(); - // Webkit has a threshold for the amount of canvas pixels an app can - // allocate. Even though our canvases are being garbage-collected as - // expected when we don't need them, Webkit keeps track of their sizes - // towards the threshold. Setting width and height to zero tricks Webkit - // into thinking that this canvas has a zero size so it doesn't count it - // towards the threshold. - if (browserEngine == BrowserEngine.webkit) { - _canvas.width = _canvas.height = 0; - } + _canvasPool.dispose(); } /// Prepare to reuse this canvas by clearing it's current contents. @override void clear() { - super.clear(); + _canvasPool.clear(); final int len = _children.length; for (int i = 0; i < len; i++) { _children[i].remove(); } _children.clear(); _cachedLastStyle = null; - // Restore to the state where we have only applied the scaling. - if (_ctx != null) { - _ctx.restore(); - _ctx.clearRect(0, 0, _widthInBitmapPixels, _heightInBitmapPixels); - try { - _ctx.font = ''; - } catch (e) { - // Firefox may explode here: - // https://bugzilla.mozilla.org/show_bug.cgi?id=941146 - if (!_isNsErrorFailureException(e)) { - rethrow; - } - } - _initializeViewport(); - } - if (_canvas != null) { - _canvas.style.transformOrigin = ''; - _canvas.style.transform = ''; - } + _setupInitialTransform(); } /// Checks whether this [BitmapCanvas] can still be recycled and reused. @@ -186,128 +183,41 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { return _devicePixelRatio == html.window.devicePixelRatio; } - /// Configures the canvas such that its coordinate system follows the scene's - /// coordinate system, and the pixel ratio is applied such that CSS pixels are - /// translated to bitmap pixels. - void _initializeViewport() { - // Save the canvas state with top-level transforms so we can undo - // any clips later when we reuse the canvas. - _ctx.save(); - - // We always start with identity transform because the surrounding transform - // is applied on the DOM elements. - _ctx.setTransform(1, 0, 0, 1, 0, 0); - - // This scale makes sure that 1 CSS pixel is translated to the correct - // number of bitmap pixels. - _ctx.scale(html.window.devicePixelRatio, html.window.devicePixelRatio); - - // Flutter emits paint operations positioned relative to the parent layer's - // coordinate system. However, canvas' coordinate system's origin is always - // in the top-left corner of the canvas. We therefore need to inject an - // initial translation so the paint operations are positioned as expected. - - // The flooring of the value is to ensure that canvas' top-left corner - // lands on the physical pixel. - final int canvasPositionX = _bounds.left.floor() - kPaddingPixels; - final int canvasPositionY = _bounds.top.floor() - kPaddingPixels; - final double canvasPositionCorrectionX = - _bounds.left - kPaddingPixels - canvasPositionX.toDouble(); - final double canvasPositionCorrectionY = - _bounds.top - kPaddingPixels - canvasPositionY.toDouble(); - - rootElement.style.transform = - 'translate(${canvasPositionX}px, ${canvasPositionY}px)'; - - // This compensates for the translate on the `rootElement`. - translate( - -_bounds.left + canvasPositionCorrectionX + kPaddingPixels, - -_bounds.top + canvasPositionCorrectionY + kPaddingPixels, - ); + /// Returns a data URI containing a representation of the image in this + /// canvas. + String toDataUrl() { + return _canvasPool.toDataUrl(); } - /// The `` element used by this bitmap canvas. - html.CanvasElement get canvas => _canvas; - - /// The 2D context of the `` element used by this bitmap canvas. - html.CanvasRenderingContext2D get ctx => _ctx; - /// Sets the global paint styles to correspond to [paint]. - void _applyPaint(ui.PaintData paint) { - ctx.globalCompositeOperation = - _stringForBlendMode(paint.blendMode) ?? 'source-over'; - ctx.lineWidth = paint.strokeWidth ?? 1.0; - final ui.StrokeCap cap = paint.strokeCap; - if (cap != null) { - ctx.lineCap = _stringForStrokeCap(cap); - } else { - ctx.lineCap = 'butt'; - } - final ui.StrokeJoin join = paint.strokeJoin; - if (join != null) { - ctx.lineJoin = _stringForStrokeJoin(join); - } else { - ctx.lineJoin = 'miter'; - } + void _applyPaint(SurfacePaintData paint) { + ContextStateHandle contextHandle = _canvasPool.contextHandle; + contextHandle + ..lineWidth = paint.strokeWidth ?? 1.0 + ..blendMode = paint.blendMode + ..strokeCap = paint.strokeCap + ..strokeJoin = paint.strokeJoin + ..filter = _maskFilterToCss(paint.maskFilter); + if (paint.shader != null) { final EngineGradient engineShader = paint.shader; - final Object paintStyle = engineShader.createPaintStyle(ctx); - _setFillAndStrokeStyle(paintStyle, paintStyle); + final Object paintStyle = + engineShader.createPaintStyle(_canvasPool.context); + contextHandle.fillStyle = paintStyle; + contextHandle.strokeStyle = paintStyle; } else if (paint.color != null) { final String colorString = paint.color.toCssString(); - _setFillAndStrokeStyle(colorString, colorString); - } - if (paint.maskFilter != null) { - _setFilter('blur(${paint.maskFilter.webOnlySigma}px)'); - } - } - - void _strokeOrFill(ui.PaintData paint, {bool resetPaint = true}) { - switch (paint.style) { - case ui.PaintingStyle.stroke: - ctx.stroke(); - break; - case ui.PaintingStyle.fill: - default: - ctx.fill(); - break; - } - if (resetPaint) { - _resetPaint(); - } - } - - /// Resets the paint styles that were set due to a previous paint command. - /// - /// For example, if a previous paint commands has a blur filter, we need to - /// undo that filter here. - /// - /// This needs to be called after [_applyPaint]. - void _resetPaint() { - _setFilter('none'); - _setFillAndStrokeStyle(null, null); - } - - void _setFilter(String value) { - if (_prevFilter != value) { - _prevFilter = ctx.filter = value; - } - } - - void _setFillAndStrokeStyle(Object fillStyle, Object strokeStyle) { - final html.CanvasRenderingContext2D _ctx = ctx; - if (!identical(_prevFillStyle, fillStyle)) { - _prevFillStyle = _ctx.fillStyle = fillStyle; - } - if (!identical(_prevStrokeStyle, strokeStyle)) { - _prevStrokeStyle = _ctx.strokeStyle = strokeStyle; + contextHandle.fillStyle = colorString; + contextHandle.strokeStyle = colorString; + } else { + contextHandle.fillStyle = ''; + contextHandle.strokeStyle = ''; } } @override int save() { - super.save(); - ctx.save(); + _canvasPool.save(); return _saveCount++; } @@ -317,8 +227,7 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { @override void restore() { - super.restore(); - ctx.restore(); + _canvasPool.restore(); _saveCount--; _cachedLastStyle = null; } @@ -330,275 +239,207 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { assert(_saveCount >= count); final int restores = _saveCount - count; for (int i = 0; i < restores; i++) { - ctx.restore(); + _canvasPool.restore(); } _saveCount = count; } @override void translate(double dx, double dy) { - super.translate(dx, dy); - ctx.translate(dx, dy); + _canvasPool.translate(dx, dy); } @override void scale(double sx, double sy) { - super.scale(sx, sy); - ctx.scale(sx, sy); + _canvasPool.scale(sx, sy); } @override void rotate(double radians) { - super.rotate(radians); - ctx.rotate(radians); + _canvasPool.rotate(radians); } @override void skew(double sx, double sy) { - super.skew(sx, sy); - ctx.transform(1, sy, sx, 1, 0, 0); - // | | | | | | - // | | | | | f - vertical translation - // | | | | e - horizontal translation - // | | | d - vertical scaling - // | | c - horizontal skewing - // | b - vertical skewing - // a - horizontal scaling - // - // Source: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/transform + _canvasPool.skew(sx, sy); } @override void transform(Float64List matrix4) { - super.transform(matrix4); - - // Canvas2D transform API: - // - // ctx.transform(a, b, c, d, e, f); - // - // In 3x3 matrix form assuming vector representation of (x, y, 1): - // - // a c e - // b d f - // 0 0 1 - // - // This translates to 4x4 matrix with vector representation of (x, y, z, 1) - // as: - // - // a c 0 e - // b d 0 f - // 0 0 1 0 - // 0 0 0 1 - // - // This matrix is sufficient to represent 2D rotates, translates, scales, - // and skews. - _ctx.transform( - matrix4[0], - matrix4[1], - matrix4[4], - matrix4[5], - matrix4[12], - matrix4[13], - ); + _canvasPool.transform(matrix4); } @override void clipRect(ui.Rect rect) { - super.clipRect(rect); - ctx.beginPath(); - ctx.rect(rect.left, rect.top, rect.width, rect.height); - ctx.clip(); + _canvasPool.clipRect(rect); } @override void clipRRect(ui.RRect rrect) { - super.clipRRect(rrect); - final ui.Path path = ui.Path()..addRRect(rrect); - _runPath(path); - ctx.clip(); + _canvasPool.clipRRect(rrect); } @override void clipPath(ui.Path path) { - super.clipPath(path); - _runPath(path); - ctx.clip(); + _canvasPool.clipPath(path); } @override void drawColor(ui.Color color, ui.BlendMode blendMode) { - ctx.globalCompositeOperation = _stringForBlendMode(blendMode); - - // Fill a virtually infinite rect with the color. - // - // We can't use (0, 0, width, height) because the current transform can - // cause it to not fill the entire clip. - ctx.fillRect(-10000, -10000, 20000, 20000); + _canvasPool.drawColor(color, blendMode); } @override - void drawLine(ui.Offset p1, ui.Offset p2, ui.PaintData paint) { + void drawLine(ui.Offset p1, ui.Offset p2, SurfacePaintData paint) { _applyPaint(paint); - ctx.beginPath(); - ctx.moveTo(p1.dx, p1.dy); - ctx.lineTo(p2.dx, p2.dy); - ctx.stroke(); - _resetPaint(); + _canvasPool.strokeLine(p1, p2); } @override - void drawPaint(ui.PaintData paint) { + void drawPaint(SurfacePaintData paint) { _applyPaint(paint); - ctx.beginPath(); - - // Fill a virtually infinite rect with the color. - // - // We can't use (0, 0, width, height) because the current transform can - // cause it to not fill the entire clip. - ctx.fillRect(-10000, -10000, 20000, 20000); - _resetPaint(); + _canvasPool.fill(); } @override - void drawRect(ui.Rect rect, ui.PaintData paint) { + void drawRect(ui.Rect rect, SurfacePaintData paint) { _applyPaint(paint); - ctx.beginPath(); - ctx.rect(rect.left, rect.top, rect.width, rect.height); - _strokeOrFill(paint); + _canvasPool.drawRect(rect, paint.style); } @override - void drawRRect(ui.RRect rrect, ui.PaintData paint) { + void drawRRect(ui.RRect rrect, SurfacePaintData paint) { _applyPaint(paint); - _RRectToCanvasRenderer(ctx).render(rrect); - _strokeOrFill(paint); + _canvasPool.drawRRect(rrect, paint.style); } @override - void drawDRRect(ui.RRect outer, ui.RRect inner, ui.PaintData paint) { + void drawDRRect(ui.RRect outer, ui.RRect inner, SurfacePaintData paint) { _applyPaint(paint); - _RRectRenderer renderer = _RRectToCanvasRenderer(ctx); - renderer.render(outer); - renderer.render(inner, startNewPath: false, reverse: true); - _strokeOrFill(paint); + _canvasPool.drawDRRect(outer, inner, paint.style); } @override - void drawOval(ui.Rect rect, ui.PaintData paint) { + void drawOval(ui.Rect rect, SurfacePaintData paint) { _applyPaint(paint); - ctx.beginPath(); - ctx.ellipse(rect.center.dx, rect.center.dy, rect.width / 2, rect.height / 2, - 0, 0, 2.0 * math.pi, false); - _strokeOrFill(paint); + _canvasPool.drawOval(rect, paint.style); } @override - void drawCircle(ui.Offset c, double radius, ui.PaintData paint) { + void drawCircle(ui.Offset c, double radius, SurfacePaintData paint) { _applyPaint(paint); - ctx.beginPath(); - ctx.ellipse(c.dx, c.dy, radius, radius, 0, 0, 2.0 * math.pi, false); - _strokeOrFill(paint); + _canvasPool.drawCircle(c, radius, paint.style); } @override - void drawPath(ui.Path path, ui.PaintData paint) { + void drawPath(ui.Path path, SurfacePaintData paint) { _applyPaint(paint); - _runPath(path); - _strokeOrFill(paint); + _canvasPool.drawPath(path, paint.style); } @override void drawShadow(ui.Path path, ui.Color color, double elevation, bool transparentOccluder) { - final List shadows = - ElevationShadow.computeCanvasShadows(elevation, color); - if (shadows.isNotEmpty) { - for (final CanvasShadow shadow in shadows) { - // TODO(het): Shadows with transparent occluders are not supported - // on webkit since filter is unsupported. - if (transparentOccluder && browserEngine != BrowserEngine.webkit) { - // We paint shadows using a path and a mask filter instead of the - // built-in shadow* properties. This is because the color alpha of the - // paint is added to the shadow. The effect we're looking for is to just - // paint the shadow without the path itself, but if we use a non-zero - // alpha for the paint the path is painted in addition to the shadow, - // which is undesirable. - final ui.Paint paint = ui.Paint() - ..color = shadow.color - ..style = ui.PaintingStyle.fill - ..strokeWidth = 0.0 - ..maskFilter = ui.MaskFilter.blur(ui.BlurStyle.normal, shadow.blur); - _ctx.save(); - _ctx.translate(shadow.offsetX, shadow.offsetY); - final ui.PaintData paintData = paint.webOnlyPaintData; - _applyPaint(paintData); - _runPath(path); - _strokeOrFill(paintData, resetPaint: false); - _ctx.restore(); - } else { - // TODO(het): We fill the path with this paint, then later we clip - // by the same path and fill it with a fully opaque color (we know - // the color is fully opaque because `transparentOccluder` is false. - // However, due to anti-aliasing of the clip, a few pixels of the - // path we are about to paint may still be visible after we fill with - // the opaque occluder. For that reason, we fill with the shadow color, - // and set the shadow color to fully opaque. This way, the visible - // pixels are less opaque and less noticeable. - final ui.Paint paint = ui.Paint() - ..color = shadow.color - ..style = ui.PaintingStyle.fill - ..strokeWidth = 0.0; - _ctx.save(); - final ui.PaintData paintData = paint.webOnlyPaintData; - _applyPaint(paintData); - _ctx.shadowBlur = shadow.blur; - _ctx.shadowColor = shadow.color.withAlpha(0xff).toCssString(); - _ctx.shadowOffsetX = shadow.offsetX; - _ctx.shadowOffsetY = shadow.offsetY; - _runPath(path); - _strokeOrFill(paintData, resetPaint: false); - _ctx.restore(); - } - } - _resetPaint(); - } + _canvasPool.drawShadow(path, color, elevation, transparentOccluder); } @override - void drawImage(ui.Image image, ui.Offset p, ui.PaintData paint) { - _applyPaint(paint); + void drawImage(ui.Image image, ui.Offset p, SurfacePaintData paint) { + //_applyPaint(paint); final HtmlImage htmlImage = image; - final html.Element imgElement = htmlImage.imgElement.clone(true); - imgElement.style - ..position = 'absolute' - ..transform = 'translate(${p.dx}px, ${p.dy}px)'; - rootElement.append(imgElement); + final html.ImageElement imgElement = htmlImage.cloneImageElement(); + String blendMode = _stringForBlendMode(paint.blendMode); + imgElement.style.mixBlendMode = blendMode; + _drawImage(imgElement, p); + _childOverdraw = true; + _canvasPool.allocateExtraCanvas(); + } + + void _drawImage(html.ImageElement imgElement, ui.Offset p) { + if (_canvasPool.isClipped) { + final List clipElements = _clipContent( + _canvasPool._clipStack, imgElement, p, _canvasPool.currentTransform); + for (html.Element clipElement in clipElements) { + rootElement.append(clipElement); + _children.add(clipElement); + } + } else { + final String cssTransform = matrix4ToCssTransform3d( + transformWithOffset(_canvasPool.currentTransform, p)); + imgElement.style + ..transformOrigin = '0 0 0' + ..transform = cssTransform; + rootElement.append(imgElement); + _children.add(imgElement); + } } @override void drawImageRect( - ui.Image image, ui.Rect src, ui.Rect dst, ui.PaintData paint) { - // TODO(het): Check if the src rect is the entire image, and if so just - // append the imgElement and set it's height and width. + ui.Image image, ui.Rect src, ui.Rect dst, SurfacePaintData paint) { final HtmlImage htmlImage = image; - ctx.drawImageScaledFromSource( - htmlImage.imgElement, - src.left, - src.top, - src.width, - src.height, - dst.left, - dst.top, - dst.width, - dst.height, - ); + final bool requiresClipping = src.left != 0 || + src.top != 0 || + src.width != image.width || + src.height != image.height; + if (dst.width == image.width && + dst.height == image.height && + !requiresClipping) { + drawImage(image, dst.topLeft, paint); + } else { + _applyPaint(paint); + final html.Element imgElement = htmlImage.cloneImageElement(); + final ui.BlendMode blendMode = paint.blendMode; + imgElement.style.mixBlendMode = _stringForBlendMode(blendMode); + if (requiresClipping) { + save(); + clipRect(dst); + } + double targetLeft = dst.left; + double targetTop = dst.top; + if (requiresClipping) { + if (src.width != image.width) { + double leftMargin = -src.left * (dst.width / src.width); + targetLeft += leftMargin; + } + if (src.height != image.height) { + double topMargin = -src.top * (dst.height / src.height); + targetTop += topMargin; + } + } + _drawImage(imgElement, ui.Offset(targetLeft, targetTop)); + // To scale set width / height on destination image. + // For clipping we need to scale according to + // clipped-width/full image width and shift it according to left/top of + // source rectangle. + double targetWidth = dst.width; + double targetHeight = dst.height; + if (requiresClipping) { + targetWidth *= image.width / src.width; + targetHeight *= image.height / src.height; + } + final html.CssStyleDeclaration imageStyle = imgElement.style; + imageStyle + ..width = '${targetWidth.toStringAsFixed(2)}px' + ..height = '${targetHeight.toStringAsFixed(2)}px'; + if (requiresClipping) { + restore(); + } + _canvasPool.allocateExtraCanvas(); + } + _childOverdraw = true; } void _drawTextLine( - ParagraphGeometricStyle style, String line, double x, double y) { + ParagraphGeometricStyle style, + EngineLineMetrics line, + double x, + double y, + ) { + html.CanvasRenderingContext2D ctx = _canvasPool.context; final double letterSpacing = style.letterSpacing; if (letterSpacing == null || letterSpacing == 0.0) { - ctx.fillText(line, x, y); + ctx.fillText(line.text, x, y); } else { // When letter-spacing is set, we go through a more expensive code path // that renders each character separately with the correct spacing @@ -610,9 +451,9 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { // would put 5px before each letter and 5px after it, but on the web, we // put no spacing before the letter and 10px after it. This is how the DOM // does it. - final int len = line.length; + final int len = line.text.length; for (int i = 0; i < len; i++) { - final String char = line[i]; + final String char = line.text[i]; ctx.fillText(char, x, y); x += letterSpacing + ctx.measureText(char).width; } @@ -622,15 +463,13 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { @override void drawParagraph(EngineParagraph paragraph, ui.Offset offset) { assert(paragraph._isLaidOut); - + html.CanvasRenderingContext2D ctx = _canvasPool.context; final ParagraphGeometricStyle style = paragraph._geometricStyle; - if (paragraph._drawOnCanvas) { - final List lines = - paragraph._lines ?? [paragraph._plainText]; + if (paragraph._drawOnCanvas && _childOverdraw == false) { + final List lines = paragraph._measurementResult.lines; - final ui.PaintData backgroundPaint = - paragraph._background?.webOnlyPaintData; + final SurfacePaintData backgroundPaint = paragraph._background?.paintData; if (backgroundPaint != null) { final ui.Rect rect = ui.Rect.fromLTWH( offset.dx, offset.dy, paragraph.width, paragraph.height); @@ -641,7 +480,7 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { ctx.font = style.cssFontString; _cachedLastStyle = style; } - _applyPaint(paragraph._paint.webOnlyPaintData); + _applyPaint(paragraph._paint.paintData); final double x = offset.dx + paragraph._alignOffset; double y = offset.dy + paragraph.alphabeticBaseline; @@ -650,23 +489,25 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { _drawTextLine(style, lines[i], x, y); y += paragraph._lineHeight; } - _resetPaint(); return; } final html.Element paragraphElement = _drawParagraphElement(paragraph, offset); - if (isClipped) { - final List clipElements = - _clipContent(_clipStack, paragraphElement, offset, currentTransform); + if (_canvasPool.isClipped) { + final List clipElements = _clipContent( + _canvasPool._clipStack, + paragraphElement, + offset, + _canvasPool.currentTransform); for (html.Element clipElement in clipElements) { rootElement.append(clipElement); _children.add(clipElement); } } else { - final String cssTransform = - matrix4ToCssTransform(transformWithOffset(currentTransform, offset)); + final String cssTransform = matrix4ToCssTransform3d( + transformWithOffset(_canvasPool.currentTransform, offset)); paragraphElement.style ..transformOrigin = '0 0 0' ..transform = cssTransform; @@ -677,7 +518,8 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { /// Paints the [picture] into this canvas. void drawPicture(ui.Picture picture) { - picture.recordingCanvas.apply(this); + final EnginePicture enginePicture = picture; + enginePicture.recordingCanvas.apply(this); } /// Draws vertices on a gl context. @@ -694,7 +536,7 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { /// and use a SkTriColorShader to render. @override void drawVertices( - ui.Vertices vertices, ui.BlendMode blendMode, ui.PaintData paint) { + ui.Vertices vertices, ui.BlendMode blendMode, SurfacePaintData paint) { // TODO(flutter_web): Implement shaders for [Paint.shader] and // blendMode. https://github.com/flutter/flutter/issues/40096 // Move rendering to OffscreenCanvas so that transform is preserved @@ -703,6 +545,7 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { 'Linear/Radial/SweepGradient and ImageShader not supported yet'); final Int32List colors = vertices.colors; final ui.VertexMode mode = vertices.mode; + html.CanvasRenderingContext2D ctx = _canvasPool.context; if (colors == null) { final Float32List positions = mode == ui.VertexMode.triangles ? vertices.positions @@ -710,69 +553,21 @@ class BitmapCanvas extends EngineCanvas with SaveStackTracking { // Draw hairline for vertices if no vertex colors are specified. save(); final ui.Color color = paint.color ?? ui.Color(0xFF000000); - _setFillAndStrokeStyle('', color.toCssString()); - _glRenderer.drawHairline(_ctx, positions); + _canvasPool.contextHandle + ..fillStyle = null + ..strokeStyle = color.toCssString(); + _glRenderer.drawHairline(ctx, positions); restore(); return; } - _glRenderer.drawVertices(_ctx, _widthInBitmapPixels, _heightInBitmapPixels, - currentTransform, vertices, blendMode, paint); - } - - /// 'Runs' the given [path] by applying all of its commands to the canvas. - void _runPath(ui.Path path) { - ctx.beginPath(); - for (Subpath subpath in path.subpaths) { - for (PathCommand command in subpath.commands) { - switch (command.type) { - case PathCommandTypes.bezierCurveTo: - final BezierCurveTo curve = command; - ctx.bezierCurveTo( - curve.x1, curve.y1, curve.x2, curve.y2, curve.x3, curve.y3); - break; - case PathCommandTypes.close: - ctx.closePath(); - break; - case PathCommandTypes.ellipse: - final Ellipse ellipse = command; - ctx.ellipse( - ellipse.x, - ellipse.y, - ellipse.radiusX, - ellipse.radiusY, - ellipse.rotation, - ellipse.startAngle, - ellipse.endAngle, - ellipse.anticlockwise); - break; - case PathCommandTypes.lineTo: - final LineTo lineTo = command; - ctx.lineTo(lineTo.x, lineTo.y); - break; - case PathCommandTypes.moveTo: - final MoveTo moveTo = command; - ctx.moveTo(moveTo.x, moveTo.y); - break; - case PathCommandTypes.rRect: - final RRectCommand rrectCommand = command; - _RRectToCanvasRenderer(ctx) - .render(rrectCommand.rrect, startNewPath: false); - break; - case PathCommandTypes.rect: - final RectCommand rectCommand = command; - ctx.rect(rectCommand.x, rectCommand.y, rectCommand.width, - rectCommand.height); - break; - case PathCommandTypes.quadraticCurveTo: - final QuadraticCurveTo quadraticCurveTo = command; - ctx.quadraticCurveTo(quadraticCurveTo.x1, quadraticCurveTo.y1, - quadraticCurveTo.x2, quadraticCurveTo.y2); - break; - default: - throw UnimplementedError('Unknown path command $command'); - } - } - } + _glRenderer.drawVertices(ctx, _widthInBitmapPixels, _heightInBitmapPixels, + _canvasPool.currentTransform, vertices, blendMode, paint); + } + + @override + void endOfPaint() { + assert(_saveCount == 0); + _canvasPool.endOfPaint(); } } @@ -900,7 +695,7 @@ List _clipContent(List<_SaveClipEntry> clipStack, ..translate(clipOffsetX, clipOffsetY); curElement.style ..overflow = 'hidden' - ..transform = matrix4ToCssTransform(newClipTransform) + ..transform = matrix4ToCssTransform3d(newClipTransform) ..transformOrigin = '0 0 0' ..width = '${rect.right - clipOffsetX}px' ..height = '${rect.bottom - clipOffsetY}px'; @@ -916,7 +711,7 @@ List _clipContent(List<_SaveClipEntry> clipStack, curElement.style ..borderRadius = borderRadius ..overflow = 'hidden' - ..transform = matrix4ToCssTransform(newClipTransform) + ..transform = matrix4ToCssTransform3d(newClipTransform) ..transformOrigin = '0 0 0' ..width = '${roundRect.right - clipOffsetX}px' ..height = '${roundRect.bottom - clipOffsetY}px'; @@ -954,6 +749,11 @@ List _clipContent(List<_SaveClipEntry> clipStack, String _cssTransformAtOffset( Matrix4 transform, double offsetX, double offsetY) { - return matrix4ToCssTransform( + return matrix4ToCssTransform3d( transformWithOffset(transform, ui.Offset(offsetX, offsetY))); } + +String _maskFilterToCss(ui.MaskFilter maskFilter) { + if (maskFilter == null) return 'none'; + return 'blur(${maskFilter.webOnlySigma}px)'; +} diff --git a/lib/web_ui/lib/src/engine/browser_detection.dart b/lib/web_ui/lib/src/engine/browser_detection.dart index 9cdc61edbd49c..3a4c1a4cbc305 100644 --- a/lib/web_ui/lib/src/engine/browser_detection.dart +++ b/lib/web_ui/lib/src/engine/browser_detection.dart @@ -16,6 +16,12 @@ enum BrowserEngine { /// The engine that powers Firefox. firefox, + /// The engine that powers Edge. + edge, + + /// The engine that powers Internet Explorer 11. + ie11, + /// We were unable to detect the current browser engine. unknown, } @@ -23,18 +29,36 @@ enum BrowserEngine { /// Lazily initialized current browser engine. BrowserEngine _browserEngine; +/// Override the value of [browserEngine]. +/// +/// Setting this to `null` lets [browserEngine] detect the browser that the +/// app is running on. +/// +/// This is intended to be used for testing and debugging only. +BrowserEngine debugBrowserEngineOverride; + /// Returns the [BrowserEngine] used by the current browser. /// /// This is used to implement browser-specific behavior. -BrowserEngine get browserEngine => _browserEngine ??= _detectBrowserEngine(); +BrowserEngine get browserEngine { + if (debugBrowserEngineOverride != null) { + return debugBrowserEngineOverride; + } + return _browserEngine ??= _detectBrowserEngine(); +} BrowserEngine _detectBrowserEngine() { final String vendor = html.window.navigator.vendor; + final String agent = html.window.navigator.userAgent.toLowerCase(); if (vendor == 'Google Inc.') { return BrowserEngine.blink; } else if (vendor == 'Apple Computer, Inc.') { return BrowserEngine.webkit; - } else if (vendor == '') { + } else if (agent.contains('edge/')) { + return BrowserEngine.edge; + } else if (agent.contains('trident/7.0')) { + return BrowserEngine.ie11; + } else if (vendor == '' && agent.contains('firefox')) { // An empty string means firefox: // https://developer.mozilla.org/en-US/docs/Web/API/Navigator/vendor return BrowserEngine.firefox; @@ -42,7 +66,6 @@ BrowserEngine _detectBrowserEngine() { // Assume blink otherwise, but issue a warning. print('WARNING: failed to detect current browser engine.'); - return BrowserEngine.unknown; } @@ -115,3 +138,19 @@ OperatingSystem _detectOperatingSystem() { return OperatingSystem.unknown; } } + +/// List of Operating Systems we know to be working on laptops/desktops. +/// +/// These devices tend to behave differently on many core issues such as events, +/// screen readers, input devices. +const Set _desktopOperatingSystems = { + OperatingSystem.macOs, + OperatingSystem.linux, + OperatingSystem.windows, +}; + +/// A flag to check if the current operating system is a laptop/desktop +/// operating system. +/// +/// See [_desktopOperatingSystems]. +bool get isDesktop => _desktopOperatingSystems.contains(operatingSystem); diff --git a/lib/web_ui/lib/src/engine/canvas_pool.dart b/lib/web_ui/lib/src/engine/canvas_pool.dart new file mode 100644 index 0000000000000..e3586a4b6bf04 --- /dev/null +++ b/lib/web_ui/lib/src/engine/canvas_pool.dart @@ -0,0 +1,767 @@ +// 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. + +part of engine; + +/// Allocates and caches 0 or more canvas(s) for [BitmapCanvas]. +/// +/// [BitmapCanvas] signals allocation of first canvas using allocateCanvas. +/// When a painting command such as drawImage or drawParagraph requires +/// multiple canvases for correct compositing, it calls allocateExtraCanvas and +/// adds the canvas(s) to a [_pool] of active canvas(s). +/// +/// To make sure transformations and clips are preserved correctly when a new +/// canvas is allocated, [_CanvasPool] replays the current stack on the newly +/// allocated canvas. It also maintains a [_saveContextCount] so that +/// the context stack can be reinitialized to default when reused in the future. +/// +/// On a subsequent repaint, when a Picture determines that a [BitmapCanvas] +/// can be reused, [_CanvasPool] will move canvas(s) from pool to reusablePool +/// to prevent reallocation. +class _CanvasPool extends _SaveStackTracking { + html.CanvasRenderingContext2D _context; + ContextStateHandle _contextHandle; + final int _widthInBitmapPixels, _heightInBitmapPixels; + // List of canvases that have been allocated and used in this paint cycle. + List _pool; + // List of canvases available to reuse from prior paint cycle. + List _reusablePool; + // Current canvas element or null if marked for lazy allocation. + html.CanvasElement _canvas; + html.HtmlElement _rootElement; + int _saveContextCount = 0; + + _CanvasPool(this._widthInBitmapPixels, this._heightInBitmapPixels); + + html.CanvasRenderingContext2D get context { + if (_canvas == null) { + _createCanvas(); + assert(_context != null); + assert(_canvas != null); + } + return _context; + } + + ContextStateHandle get contextHandle { + if (_canvas == null) { + _createCanvas(); + assert(_context != null); + assert(_canvas != null); + } + return _contextHandle; + } + + // Allocating extra canvas items. Save current canvas so we can dispose + // and replay the clip/transform stack on top of new canvas. + void allocateExtraCanvas() { + assert(_rootElement != null); + // Place clean copy of current canvas with context stack restored and paint + // reset into pool. + if (_canvas != null) { + _restoreContextSave(); + _contextHandle.reset(); + _pool ??= []; + _pool.add(_canvas); + _canvas = null; + _context = null; + _contextHandle = null; + } + } + + void allocateCanvas(html.HtmlElement rootElement) { + _rootElement = rootElement; + } + + void _createCanvas() { + bool requiresClearRect = false; + if (_reusablePool != null && _reusablePool.isNotEmpty) { + _canvas = _reusablePool.removeAt(0); + requiresClearRect = true; + } else { + // Compute the final CSS canvas size given the actual pixel count we + // allocated. This is done for the following reasons: + // + // * To satisfy the invariant: pixel size = css size * device pixel ratio. + // * To make sure that when we scale the canvas by devicePixelRatio (see + // _initializeViewport below) the pixels line up. + final double cssWidth = + _widthInBitmapPixels / html.window.devicePixelRatio; + final double cssHeight = + _heightInBitmapPixels / html.window.devicePixelRatio; + _canvas = html.CanvasElement( + width: _widthInBitmapPixels, + height: _heightInBitmapPixels, + ); + _canvas.style + ..position = 'absolute' + ..width = '${cssWidth}px' + ..height = '${cssHeight}px'; + } + _rootElement.append(_canvas); + _context = _canvas.context2D; + _contextHandle = ContextStateHandle(_context); + _initializeViewport(); + if (requiresClearRect) { + // Now that the context is reset, clear old contents. + _context.clearRect(0, 0, _widthInBitmapPixels, _heightInBitmapPixels); + } + _replayClipStack(); + } + + @override + void clear() { + super.clear(); + + if (_canvas != null) { + // Restore to the state where we have only applied the scaling. + html.CanvasRenderingContext2D ctx = _context; + if (ctx != null) { + try { + ctx.font = ''; + } catch (e) { + // Firefox may explode here: + // https://bugzilla.mozilla.org/show_bug.cgi?id=941146 + if (!_isNsErrorFailureException(e)) { + rethrow; + } + } + } + } + reuse(); + resetTransform(); + } + + set initialTransform(ui.Offset transform) { + translate(transform.dx, transform.dy); + } + + int _replaySingleSaveEntry( + int clipDepth, Matrix4 transform, List<_SaveClipEntry> clipStack) { + final html.CanvasRenderingContext2D ctx = _context; + if (!transform.isIdentity()) { + ctx.setTransform(transform[0], transform[1], transform[4], transform[5], + transform[12], transform[13]); + } + if (clipStack != null) { + for (int clipCount = clipStack.length; + clipDepth < clipCount; + clipDepth++) { + _SaveClipEntry clipEntry = clipStack[clipDepth]; + if (clipEntry.rect != null) { + _clipRect(ctx, clipEntry.rect); + } else if (clipEntry.rrect != null) { + _clipRRect(ctx, clipEntry.rrect); + } else if (clipEntry.path != null) { + _runPath(ctx, clipEntry.path); + ctx.clip(); + } + } + } + return clipDepth; + } + + void _replayClipStack() { + // Replay save/clip stack on this canvas now. + html.CanvasRenderingContext2D ctx = _context; + int clipDepth = 0; + for (int saveStackIndex = 0, len = _saveStack.length; + saveStackIndex < len; + saveStackIndex++) { + _SaveStackEntry saveEntry = _saveStack[saveStackIndex]; + clipDepth = _replaySingleSaveEntry( + clipDepth, saveEntry.transform, saveEntry.clipStack); + ctx.save(); + ++_saveContextCount; + } + _replaySingleSaveEntry(clipDepth, _currentTransform, _clipStack); + } + + // Marks this pool for reuse. + void reuse() { + if (_canvas != null) { + _restoreContextSave(); + _contextHandle.reset(); + _pool ??= []; + _pool.add(_canvas); + _context = null; + _contextHandle = null; + } + _reusablePool = _pool; + _pool = null; + _canvas = null; + _context = null; + _contextHandle = null; + } + + void endOfPaint() { + if (_reusablePool != null) { + for (html.CanvasElement e in _reusablePool) { + e.remove(); + } + _reusablePool = null; + } + _restoreContextSave(); + } + + void _restoreContextSave() { + while (_saveContextCount != 0) { + _context.restore(); + --_saveContextCount; + } + } + + /// Configures the canvas such that its coordinate system follows the scene's + /// coordinate system, and the pixel ratio is applied such that CSS pixels are + /// translated to bitmap pixels. + void _initializeViewport() { + html.CanvasRenderingContext2D ctx = context; + // Save the canvas state with top-level transforms so we can undo + // any clips later when we reuse the canvas. + ctx.save(); + ++_saveContextCount; + + // We always start with identity transform because the surrounding transform + // is applied on the DOM elements. + ctx.setTransform(1, 0, 0, 1, 0, 0); + + // This scale makes sure that 1 CSS pixel is translated to the correct + // number of bitmap pixels. + ctx.scale(html.window.devicePixelRatio, html.window.devicePixelRatio); + } + + void resetTransform() { + if (_canvas != null) { + _canvas.style.transformOrigin = ''; + _canvas.style.transform = ''; + } + } + + // Returns a data URI containing a representation of the image in this + // canvas. + String toDataUrl() => _canvas.toDataUrl(); + + @override + void save() { + super.save(); + if (_canvas != null) { + context.save(); + ++_saveContextCount; + } + } + + @override + void restore() { + super.restore(); + if (_canvas != null) { + context.restore(); + contextHandle.reset(); + --_saveContextCount; + } + } + + @override + void translate(double dx, double dy) { + super.translate(dx, dy); + if (_canvas != null) { + context.translate(dx, dy); + } + } + + @override + void scale(double sx, double sy) { + super.scale(sx, sy); + if (_canvas != null) { + context.scale(sx, sy); + } + } + + @override + void rotate(double radians) { + super.rotate(radians); + if (_canvas != null) { + context.rotate(radians); + } + } + + @override + void skew(double sx, double sy) { + super.skew(sx, sy); + if (_canvas != null) { + context.transform(1, sy, sx, 1, 0, 0); + // | | | | | | + // | | | | | f - vertical translation + // | | | | e - horizontal translation + // | | | d - vertical scaling + // | | c - horizontal skewing + // | b - vertical skewing + // a - horizontal scaling + // + // Source: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/transform + } + } + + @override + void transform(Float64List matrix4) { + super.transform(matrix4); + // Canvas2D transform API: + // + // ctx.transform(a, b, c, d, e, f); + // + // In 3x3 matrix form assuming vector representation of (x, y, 1): + // + // a c e + // b d f + // 0 0 1 + // + // This translates to 4x4 matrix with vector representation of (x, y, z, 1) + // as: + // + // a c 0 e + // b d 0 f + // 0 0 1 0 + // 0 0 0 1 + // + // This matrix is sufficient to represent 2D rotates, translates, scales, + // and skews. + if (_canvas != null) { + context.transform(matrix4[0], matrix4[1], matrix4[4], matrix4[5], + matrix4[12], matrix4[13]); + } + } + + void clipRect(ui.Rect rect) { + super.clipRect(rect); + if (_canvas != null) { + _clipRect(context, rect); + } + } + + void _clipRect(html.CanvasRenderingContext2D ctx, ui.Rect rect) { + ctx.beginPath(); + ctx.rect(rect.left, rect.top, rect.width, rect.height); + ctx.clip(); + } + + void clipRRect(ui.RRect rrect) { + super.clipRRect(rrect); + if (_canvas != null) { + _clipRRect(context, rrect); + } + } + + void _clipRRect(html.CanvasRenderingContext2D ctx, ui.RRect rrect) { + final ui.Path path = ui.Path()..addRRect(rrect); + _runPath(ctx, path); + ctx.clip(); + } + + void clipPath(ui.Path path) { + super.clipPath(path); + if (_canvas != null) { + html.CanvasRenderingContext2D ctx = context; + _runPath(ctx, path); + ctx.clip(); + } + } + + void drawColor(ui.Color color, ui.BlendMode blendMode) { + html.CanvasRenderingContext2D ctx = context; + contextHandle.blendMode = blendMode; + contextHandle.fillStyle = color.toCssString(); + contextHandle.strokeStyle = ''; + ctx.beginPath(); + // Fill a virtually infinite rect with the color. + // + // We can't use (0, 0, width, height) because the current transform can + // cause it to not fill the entire clip. + ctx.fillRect(-10000, -10000, 20000, 20000); + } + + // Fill a virtually infinite rect with the color. + void fill() { + html.CanvasRenderingContext2D ctx = context; + ctx.beginPath(); + // We can't use (0, 0, width, height) because the current transform can + // cause it to not fill the entire clip. + ctx.fillRect(-10000, -10000, 20000, 20000); + } + + void strokeLine(ui.Offset p1, ui.Offset p2) { + html.CanvasRenderingContext2D ctx = context; + ctx.beginPath(); + ctx.moveTo(p1.dx, p1.dy); + ctx.lineTo(p2.dx, p2.dy); + ctx.stroke(); + } + + /// 'Runs' the given [path] by applying all of its commands to the canvas. + void _runPath(html.CanvasRenderingContext2D ctx, SurfacePath path) { + ctx.beginPath(); + for (Subpath subpath in path.subpaths) { + for (PathCommand command in subpath.commands) { + switch (command.type) { + case PathCommandTypes.bezierCurveTo: + final BezierCurveTo curve = command; + ctx.bezierCurveTo( + curve.x1, curve.y1, curve.x2, curve.y2, curve.x3, curve.y3); + break; + case PathCommandTypes.close: + ctx.closePath(); + break; + case PathCommandTypes.ellipse: + final Ellipse ellipse = command; + ctx.ellipse( + ellipse.x, + ellipse.y, + ellipse.radiusX, + ellipse.radiusY, + ellipse.rotation, + ellipse.startAngle, + ellipse.endAngle, + ellipse.anticlockwise); + break; + case PathCommandTypes.lineTo: + final LineTo lineTo = command; + ctx.lineTo(lineTo.x, lineTo.y); + break; + case PathCommandTypes.moveTo: + final MoveTo moveTo = command; + ctx.moveTo(moveTo.x, moveTo.y); + break; + case PathCommandTypes.rRect: + final RRectCommand rrectCommand = command; + _RRectToCanvasRenderer(ctx) + .render(rrectCommand.rrect, startNewPath: false); + break; + case PathCommandTypes.rect: + final RectCommand rectCommand = command; + ctx.rect(rectCommand.x, rectCommand.y, rectCommand.width, + rectCommand.height); + break; + case PathCommandTypes.quadraticCurveTo: + final QuadraticCurveTo quadraticCurveTo = command; + ctx.quadraticCurveTo(quadraticCurveTo.x1, quadraticCurveTo.y1, + quadraticCurveTo.x2, quadraticCurveTo.y2); + break; + default: + throw UnimplementedError('Unknown path command $command'); + } + } + } + } + + void drawRect(ui.Rect rect, ui.PaintingStyle style) { + context.beginPath(); + context.rect(rect.left, rect.top, rect.width, rect.height); + contextHandle.paint(style); + } + + void drawRRect(ui.RRect roundRect, ui.PaintingStyle style) { + _RRectToCanvasRenderer(context).render(roundRect); + contextHandle.paint(style); + } + + void drawDRRect(ui.RRect outer, ui.RRect inner, ui.PaintingStyle style) { + _RRectRenderer renderer = _RRectToCanvasRenderer(context); + renderer.render(outer); + renderer.render(inner, startNewPath: false, reverse: true); + contextHandle.paint(style); + } + + void drawOval(ui.Rect rect, ui.PaintingStyle style) { + context.beginPath(); + context.ellipse(rect.center.dx, rect.center.dy, rect.width / 2, + rect.height / 2, 0, 0, 2.0 * math.pi, false); + contextHandle.paint(style); + } + + void drawCircle(ui.Offset c, double radius, ui.PaintingStyle style) { + context.beginPath(); + context.ellipse(c.dx, c.dy, radius, radius, 0, 0, 2.0 * math.pi, false); + contextHandle.paint(style); + } + + void drawPath(ui.Path path, ui.PaintingStyle style) { + _runPath(context, path); + contextHandle.paint(style); + } + + void drawShadow(ui.Path path, ui.Color color, double elevation, + bool transparentOccluder) { + final List shadows = + ElevationShadow.computeCanvasShadows(elevation, color); + if (shadows.isNotEmpty) { + for (final CanvasShadow shadow in shadows) { + // TODO(het): Shadows with transparent occluders are not supported + // on webkit since filter is unsupported. + if (transparentOccluder && browserEngine != BrowserEngine.webkit) { + // We paint shadows using a path and a mask filter instead of the + // built-in shadow* properties. This is because the color alpha of the + // paint is added to the shadow. The effect we're looking for is to just + // paint the shadow without the path itself, but if we use a non-zero + // alpha for the paint the path is painted in addition to the shadow, + // which is undesirable. + context.save(); + context.translate(shadow.offsetX, shadow.offsetY); + context.filter = _maskFilterToCss( + ui.MaskFilter.blur(ui.BlurStyle.normal, shadow.blur)); + context.strokeStyle = ''; + context.fillStyle = shadow.color.toCssString(); + _runPath(context, path); + context.fill(); + context.restore(); + } else { + // TODO(het): We fill the path with this paint, then later we clip + // by the same path and fill it with a fully opaque color (we know + // the color is fully opaque because `transparentOccluder` is false. + // However, due to anti-aliasing of the clip, a few pixels of the + // path we are about to paint may still be visible after we fill with + // the opaque occluder. For that reason, we fill with the shadow color, + // and set the shadow color to fully opaque. This way, the visible + // pixels are less opaque and less noticeable. + context.save(); + context.filter = 'none'; + context.strokeStyle = ''; + context.fillStyle = shadow.color.toCssString(); + context.shadowBlur = shadow.blur; + context.shadowColor = shadow.color.withAlpha(0xff).toCssString(); + context.shadowOffsetX = shadow.offsetX; + context.shadowOffsetY = shadow.offsetY; + _runPath(context, path); + context.fill(); + context.restore(); + } + } + } + } + + void dispose() { + // Webkit has a threshold for the amount of canvas pixels an app can + // allocate. Even though our canvases are being garbage-collected as + // expected when we don't need them, Webkit keeps track of their sizes + // towards the threshold. Setting width and height to zero tricks Webkit + // into thinking that this canvas has a zero size so it doesn't count it + // towards the threshold. + if (browserEngine == BrowserEngine.webkit && _canvas != null) { + _canvas.width = _canvas.height = 0; + } + _clearPool(); + } + + void _clearPool() { + if (_pool != null) { + for (html.CanvasElement c in _pool) { + if (browserEngine == BrowserEngine.webkit) { + c.width = c.height = 0; + } + c.remove(); + } + } + _pool = null; + } +} + +// Optimizes applying paint parameters to html canvas. +// +// See https://www.w3.org/TR/2dcontext/ for defaults used in this class +// to initialize current values. +// +class ContextStateHandle { + html.CanvasRenderingContext2D context; + ContextStateHandle(this.context); + ui.BlendMode _currentBlendMode = ui.BlendMode.srcOver; + ui.StrokeCap _currentStrokeCap = ui.StrokeCap.butt; + ui.StrokeJoin _currentStrokeJoin = ui.StrokeJoin.miter; + // Fill style and stroke style are Object since they can have a String or + // shader object such as a gradient. + Object _currentFillStyle; + Object _currentStrokeStyle; + double _currentLineWidth = 1.0; + String _currentFilter = 'none'; + + set blendMode(ui.BlendMode blendMode) { + if (blendMode != _currentBlendMode) { + _currentBlendMode = blendMode; + context.globalCompositeOperation = + _stringForBlendMode(blendMode) ?? 'source-over'; + } + } + + set strokeCap(ui.StrokeCap strokeCap) { + strokeCap ??= ui.StrokeCap.butt; + if (strokeCap != _currentStrokeCap) { + _currentStrokeCap = strokeCap; + context.lineCap = _stringForStrokeCap(strokeCap); + } + } + + set lineWidth(double lineWidth) { + if (lineWidth != _currentLineWidth) { + _currentLineWidth = lineWidth; + context.lineWidth = lineWidth; + } + } + + set strokeJoin(ui.StrokeJoin strokeJoin) { + strokeJoin ??= ui.StrokeJoin.miter; + if (strokeJoin != _currentStrokeJoin) { + _currentStrokeJoin = strokeJoin; + context.lineJoin = _stringForStrokeJoin(strokeJoin); + } + } + + set fillStyle(Object colorOrGradient) { + if (!identical(colorOrGradient, _currentFillStyle)) { + _currentFillStyle = colorOrGradient; + context.fillStyle = colorOrGradient; + } + } + + set strokeStyle(Object colorOrGradient) { + if (!identical(colorOrGradient, _currentStrokeStyle)) { + _currentStrokeStyle = colorOrGradient; + context.strokeStyle = colorOrGradient; + } + } + + set filter(String filter) { + if (_currentFilter != filter) { + _currentFilter = filter; + context.filter = filter; + } + } + + void paint(ui.PaintingStyle style) { + if (style == ui.PaintingStyle.stroke) { + context.stroke(); + } else { + context.fill(); + } + } + + void reset() { + context.fillStyle = ''; + // Read back fillStyle/strokeStyle values from context so that input such + // as rgba(0, 0, 0, 0) is correctly compared and doesn't cause diff on + // setter. + _currentFillStyle = context.fillStyle; + context.strokeStyle = ''; + _currentStrokeStyle = context.strokeStyle; + context.filter = 'none'; + _currentFilter = 'none'; + context.globalCompositeOperation = 'source-over'; + _currentBlendMode = ui.BlendMode.srcOver; + context.lineWidth = 1.0; + _currentLineWidth = 1.0; + context.lineCap = 'butt'; + _currentStrokeCap = ui.StrokeCap.butt; + context.lineJoin = 'miter'; + _currentStrokeJoin = ui.StrokeJoin.miter; + } +} + +/// Provides save stack tracking functionality to implementations of +/// [EngineCanvas]. +class _SaveStackTracking { + // !Warning: this vector should not be mutated. + static final Vector3 _unitZ = Vector3(0.0, 0.0, 1.0); + + final List<_SaveStackEntry> _saveStack = <_SaveStackEntry>[]; + + /// The stack that maintains clipping operations used when text is painted + /// onto bitmap canvas but is composited as separate element. + List<_SaveClipEntry> _clipStack; + + /// Returns whether there are active clipping regions on the canvas. + bool get isClipped => _clipStack != null; + + /// Empties the save stack and the element stack, and resets the transform + /// and clip parameters. + @mustCallSuper + void clear() { + _saveStack.clear(); + _clipStack = null; + _currentTransform = Matrix4.identity(); + } + + /// The current transformation matrix. + Matrix4 get currentTransform => _currentTransform; + Matrix4 _currentTransform = Matrix4.identity(); + + /// Saves current clip and transform on the save stack. + @mustCallSuper + void save() { + _saveStack.add(_SaveStackEntry( + transform: _currentTransform.clone(), + clipStack: + _clipStack == null ? null : List<_SaveClipEntry>.from(_clipStack), + )); + } + + /// Restores current clip and transform from the save stack. + @mustCallSuper + void restore() { + if (_saveStack.isEmpty) { + return; + } + final _SaveStackEntry entry = _saveStack.removeLast(); + _currentTransform = entry.transform; + _clipStack = entry.clipStack; + } + + /// Multiplies the [currentTransform] matrix by a translation. + @mustCallSuper + void translate(double dx, double dy) { + _currentTransform.translate(dx, dy); + } + + /// Scales the [currentTransform] matrix. + @mustCallSuper + void scale(double sx, double sy) { + _currentTransform.scale(sx, sy); + } + + /// Rotates the [currentTransform] matrix. + @mustCallSuper + void rotate(double radians) { + _currentTransform.rotate(_unitZ, radians); + } + + /// Skews the [currentTransform] matrix. + @mustCallSuper + void skew(double sx, double sy) { + final Matrix4 skewMatrix = Matrix4.identity(); + final Float64List storage = skewMatrix.storage; + storage[1] = sy; + storage[4] = sx; + _currentTransform.multiply(skewMatrix); + } + + /// Multiplies the [currentTransform] matrix by another matrix. + @mustCallSuper + void transform(Float64List matrix4) { + _currentTransform.multiply(Matrix4.fromFloat64List(matrix4)); + } + + /// Adds a rectangle to clipping stack. + @mustCallSuper + void clipRect(ui.Rect rect) { + _clipStack ??= <_SaveClipEntry>[]; + _clipStack.add(_SaveClipEntry.rect(rect, _currentTransform.clone())); + } + + /// Adds a round rectangle to clipping stack. + @mustCallSuper + void clipRRect(ui.RRect rrect) { + _clipStack ??= <_SaveClipEntry>[]; + _clipStack.add(_SaveClipEntry.rrect(rrect, _currentTransform.clone())); + } + + /// Adds a path to clipping stack. + @mustCallSuper + void clipPath(ui.Path path) { + _clipStack ??= <_SaveClipEntry>[]; + _clipStack.add(_SaveClipEntry.path(path, _currentTransform.clone())); + } +} diff --git a/lib/web_ui/lib/src/engine/color_filter.dart b/lib/web_ui/lib/src/engine/color_filter.dart index 48e4b8bb60863..c6dcf8e1a9088 100644 --- a/lib/web_ui/lib/src/engine/color_filter.dart +++ b/lib/web_ui/lib/src/engine/color_filter.dart @@ -114,8 +114,6 @@ class EngineColorFilter implements ui.ColorFilter { final int _type; // The type of SkColorFilter class to create for Skia. - // These constants must be kept in sync with ColorFilterType in paint.cc. - static const int _TypeNone = 0; // null static const int _TypeMode = 1; // MakeModeFilter static const int _TypeMatrix = 2; // MakeMatrixFilterRowMajor255 static const int _TypeLinearToSrgbGamma = 3; // MakeLinearToSRGBGamma diff --git a/lib/web_ui/lib/src/engine/compositor/canvas.dart b/lib/web_ui/lib/src/engine/compositor/canvas.dart index cba6d481dcafb..65548560cb255 100644 --- a/lib/web_ui/lib/src/engine/compositor/canvas.dart +++ b/lib/web_ui/lib/src/engine/compositor/canvas.dart @@ -4,78 +4,179 @@ part of engine; -/// An actual [SkCanvas] which can receive raw drawing commands. -/// -/// In order for the drawing commands to be flushed to the associated HTML -/// canvas, you must call `flush()` on the canvas's `SkSurface`. -/// -/// Although this class is backed by an `SkCanvas` and can in theory perform -/// arbitrary drawing operations, this class is only used in the final -/// compositing by the layers, and arbitrary drawings are done in a -/// [ui.Picture] which uses a Skia recording canvas. This class receives -/// drawing calls from the various `Layer` classes, e.g. [ClipRectLayer] and -/// so only exposes a subset of the drawing operations that can be performed -/// on a canvas. +/// A Dart wrapper around Skia's SKCanvas. class SkCanvas { final js.JsObject skCanvas; - final html.CanvasElement htmlCanvas; - final js.JsObject skSurface; - final ui.Size size; - SkCanvas(this.skCanvas, this.htmlCanvas, this.skSurface, this.size); + SkCanvas(this.skCanvas); - int save() { - return skCanvas.callMethod('save'); + int get saveCount => skCanvas.callMethod('getSaveCount'); + + void clear(ui.Color color) { + skCanvas.callMethod('clear', [color.value]); } - int saveLayer(ui.Rect bounds, ui.Paint paint) { - return skCanvas.callMethod( - 'saveLayer', [makeSkRect(bounds), makeSkPaint(paint)]); + void clipPath(ui.Path path, bool doAntiAlias) { + final SkPath skPath = path; + final js.JsObject intersectClipOp = canvasKit['ClipOp']['Intersect']; + skCanvas.callMethod('clipPath', [ + skPath._skPath, + intersectClipOp, + doAntiAlias, + ]); } - void restore() { - skCanvas.callMethod('restore'); + void clipRRect(ui.RRect rrect, bool doAntiAlias) { + final js.JsObject intersectClipOp = canvasKit['ClipOp']['Intersect']; + skCanvas.callMethod('clipRRect', [ + makeSkRRect(rrect), + intersectClipOp, + doAntiAlias, + ]); } - void restoreToCount(int count) { - skCanvas.callMethod('restoreToCount', [count]); + void clipRect(ui.Rect rect, ui.ClipOp clipOp, bool doAntiAlias) { + js.JsObject skClipOp; + switch (clipOp) { + case ui.ClipOp.difference: + skClipOp = canvasKit['ClipOp']['Difference']; + break; + case ui.ClipOp.intersect: + skClipOp = canvasKit['ClipOp']['Intersect']; + break; + } + + skCanvas.callMethod( + 'clipRect', [makeSkRect(rect), skClipOp, doAntiAlias]); } - void clear() { - skCanvas.callMethod('clear', [0xffffffff]); + void drawArc( + ui.Rect oval, + double startAngle, + double sweepAngle, + bool useCenter, + SkPaint paint, + ) { + const double toDegrees = 180 / math.pi; + skCanvas.callMethod('drawArc', [ + makeSkRect(oval), + startAngle * toDegrees, + sweepAngle * toDegrees, + useCenter, + paint.skiaObject, + ]); } - void translate(double dx, double dy) { - skCanvas.callMethod('translate', [dx, dy]); + void drawAtlasRaw( + SkPaint paint, + ui.Image atlas, + Float32List rstTransforms, + Float32List rects, + Int32List colors, + ui.BlendMode blendMode, + ) { + final SkImage skAtlas = atlas; + skCanvas.callMethod('drawAtlas', [ + skAtlas.skImage, + rects, + rstTransforms, + paint.skiaObject, + makeSkBlendMode(blendMode), + colors, + ]); } - void transform(Float64List matrix) { - skCanvas.callMethod('concat', >[makeSkMatrix(matrix)]); + void drawCircle(ui.Offset c, double radius, SkPaint paint) { + skCanvas.callMethod('drawCircle', [ + c.dx, + c.dy, + radius, + paint.skiaObject, + ]); } - void clipPath(ui.Path path, {bool doAntiAlias = true}) { - final SkPath skPath = path; - final js.JsObject intersectClipOp = canvasKit['ClipOp']['Intersect']; - skCanvas.callMethod('clipPath', [ - skPath._skPath, - intersectClipOp, - doAntiAlias, + void drawColor(ui.Color color, ui.BlendMode blendMode) { + skCanvas.callMethod('drawColor', [ + color.value, + makeSkBlendMode(blendMode), ]); } - void clipRect(ui.Rect rect, {bool doAntiAlias = true}) { - final js.JsObject intersectClipOp = canvasKit['ClipOp']['Intersect']; - skCanvas.callMethod('clipRect', [ + void drawDRRect(ui.RRect outer, ui.RRect inner, SkPaint paint) { + skCanvas.callMethod('drawDRRect', [ + makeSkRRect(outer), + makeSkRRect(inner), + paint.skiaObject, + ]); + } + + void drawImage(ui.Image image, ui.Offset offset, SkPaint paint) { + final SkImage skImage = image; + skCanvas.callMethod('drawImage', [ + skImage.skImage, + offset.dx, + offset.dy, + paint.skiaObject, + ]); + } + + void drawImageRect(ui.Image image, ui.Rect src, ui.Rect dst, SkPaint paint) { + final SkImage skImage = image; + skCanvas.callMethod('drawImageRect', [ + skImage.skImage, + makeSkRect(src), + makeSkRect(dst), + paint.skiaObject, + false, + ]); + } + + void drawImageNine( + ui.Image image, ui.Rect center, ui.Rect dst, SkPaint paint) { + final SkImage skImage = image; + skCanvas.callMethod('drawImageNine', [ + skImage.skImage, + makeSkRect(center), + makeSkRect(dst), + paint.skiaObject, + ]); + } + + void drawLine(ui.Offset p1, ui.Offset p2, SkPaint paint) { + skCanvas.callMethod('drawLine', [ + p1.dx, + p1.dy, + p2.dx, + p2.dy, + paint.skiaObject, + ]); + } + + void drawOval(ui.Rect rect, SkPaint paint) { + skCanvas.callMethod('drawOval', [ makeSkRect(rect), - intersectClipOp, - doAntiAlias, + paint.skiaObject, ]); } - void clipRRect(ui.RRect rrect) { - final SkPath skPath = SkPath(); - skPath.addRRect(rrect); - clipPath(skPath); + void drawPaint(SkPaint paint) { + skCanvas.callMethod('drawPaint', [paint.skiaObject]); + } + + void drawParagraph(ui.Paragraph paragraph, ui.Offset offset) { + final SkParagraph skParagraph = paragraph; + skCanvas.callMethod('drawParagraph', [ + skParagraph.skParagraph, + offset.dx, + offset.dy, + ]); + } + + void drawPath(ui.Path path, SkPaint paint) { + final js.JsObject skPaint = paint.skiaObject; + final SkPath enginePath = path; + final js.JsObject skPath = enginePath._skPath; + skCanvas.callMethod('drawPath', [skPath, skPaint]); } void drawPicture(ui.Picture picture) { @@ -83,18 +184,101 @@ class SkCanvas { skCanvas.callMethod('drawPicture', [skPicture.skPicture]); } - void drawPath(ui.Path path, ui.Paint paint) { - final SkPath skPath = path; - skCanvas.callMethod( - 'drawPath', [skPath._skPath, makeSkPaint(paint)]); + void drawPoints(SkPaint paint, ui.PointMode pointMode, Float32List points) { + skCanvas.callMethod('drawPoints', [ + makeSkPointMode(pointMode), + points, + paint.skiaObject, + ]); + } + + void drawRRect(ui.RRect rrect, SkPaint paint) { + skCanvas.callMethod('drawRRect', [ + makeSkRRect(rrect), + paint.skiaObject, + ]); } - void drawPaint(ui.Paint paint) { - skCanvas.callMethod('drawPaint', [makeSkPaint(paint)]); + void drawRect(ui.Rect rect, SkPaint paint) { + final js.JsObject skRect = makeSkRect(rect); + final js.JsObject skPaint = paint.skiaObject; + skCanvas.callMethod('drawRect', [skRect, skPaint]); } void drawShadow(ui.Path path, ui.Color color, double elevation, bool transparentOccluder) { - drawSkShadow(skCanvas, path, color, elevation, transparentOccluder); + drawSkShadow(skCanvas, path, color, elevation, transparentOccluder, + ui.window.devicePixelRatio); + } + + void drawVertices( + ui.Vertices vertices, ui.BlendMode blendMode, SkPaint paint) { + SkVertices skVertices = vertices; + skCanvas.callMethod('drawVertices', [ + skVertices.skVertices, + makeSkBlendMode(blendMode), + paint.skiaObject + ]); + } + + void restore() { + skCanvas.callMethod('restore'); + } + + void restoreToCount(int count) { + skCanvas.callMethod('restoreToCount', [count]); + } + + void rotate(double radians) { + skCanvas + .callMethod('rotate', [radians * 180.0 / math.pi, 0.0, 0.0]); + } + + int save() { + return skCanvas.callMethod('save'); + } + + void saveLayer(ui.Rect bounds, SkPaint paint) { + skCanvas.callMethod('saveLayer', [ + makeSkRect(bounds), + paint.skiaObject, + ]); + } + + void saveLayerWithoutBounds(SkPaint paint) { + skCanvas.callMethod('saveLayer', [null, paint.skiaObject]); + } + + void saveLayerWithFilter(ui.Rect bounds, ui.ImageFilter filter) { + final SkImageFilter skImageFilter = filter; + return skCanvas.callMethod( + 'saveLayer', + [ + null, + skImageFilter.skImageFilter, + 0, + makeSkRect(bounds), + ], + ); + } + + void scale(double sx, double sy) { + skCanvas.callMethod('scale', [sx, sy]); + } + + void skew(double sx, double sy) { + skCanvas.callMethod('skew', [sx, sy]); + } + + void transform(Float64List matrix4) { + skCanvas.callMethod('concat', >[makeSkMatrix(matrix4)]); + } + + void translate(double dx, double dy) { + skCanvas.callMethod('translate', [dx, dy]); + } + + void flush() { + skCanvas.callMethod('flush'); } } diff --git a/lib/web_ui/lib/src/engine/compositor/canvas_kit_canvas.dart b/lib/web_ui/lib/src/engine/compositor/canvas_kit_canvas.dart new file mode 100644 index 0000000000000..4765ca301055d --- /dev/null +++ b/lib/web_ui/lib/src/engine/compositor/canvas_kit_canvas.dart @@ -0,0 +1,449 @@ +// 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. + +part of engine; + +/// An implementation of [ui.Canvas] that is backed by a CanvasKit canvas. +class CanvasKitCanvas implements ui.Canvas { + final SkCanvas _canvas; + + factory CanvasKitCanvas(ui.PictureRecorder recorder, [ui.Rect cullRect]) { + assert(recorder != null); + if (recorder.isRecording) { + throw ArgumentError( + '"recorder" must not already be associated with another Canvas.'); + } + cullRect ??= ui.Rect.largest; + final SkPictureRecorder skRecorder = recorder; + return CanvasKitCanvas._(skRecorder.beginRecording(cullRect)); + } + + CanvasKitCanvas._(this._canvas); + + @override + void save() { + _canvas.save(); + } + + @override + void saveLayer(ui.Rect bounds, ui.Paint paint) { + assert(paint != null); + if (bounds == null) { + _saveLayerWithoutBounds(paint); + } else { + assert(rectIsValid(bounds)); + _saveLayer(bounds, paint); + } + } + + void _saveLayerWithoutBounds(ui.Paint paint) { + _canvas.saveLayerWithoutBounds(paint); + } + + void _saveLayer(ui.Rect bounds, ui.Paint paint) { + _canvas.saveLayer(bounds, paint); + } + + @override + void restore() { + _canvas.restore(); + } + + @override + int getSaveCount() { + return _canvas.saveCount; + } + + @override + void translate(double dx, double dy) { + _canvas.translate(dx, dy); + } + + @override + void scale(double sx, [double sy]) => _scale(sx, sy ?? sx); + + void _scale(double sx, double sy) { + _canvas.scale(sx, sy); + } + + @override + void rotate(double radians) { + _canvas.rotate(radians); + } + + @override + void skew(double sx, double sy) { + _canvas.skew(sx, sy); + } + + @override + void transform(Float64List matrix4) { + assert(matrix4 != null); + if (matrix4.length != 16) { + throw ArgumentError('"matrix4" must have 16 entries.'); + } + _transform(matrix4); + } + + void _transform(Float64List matrix4) { + _canvas.transform(matrix4); + } + + @override + void clipRect(ui.Rect rect, + {ui.ClipOp clipOp = ui.ClipOp.intersect, bool doAntiAlias = true}) { + assert(rectIsValid(rect)); + assert(clipOp != null); + assert(doAntiAlias != null); + _clipRect(rect, clipOp, doAntiAlias); + } + + void _clipRect(ui.Rect rect, ui.ClipOp clipOp, bool doAntiAlias) { + _canvas.clipRect(rect, clipOp, doAntiAlias); + } + + @override + void clipRRect(ui.RRect rrect, {bool doAntiAlias = true}) { + assert(rrectIsValid(rrect)); + assert(doAntiAlias != null); + _clipRRect(rrect, doAntiAlias); + } + + void _clipRRect(ui.RRect rrect, bool doAntiAlias) { + _canvas.clipRRect(rrect, doAntiAlias); + } + + @override + void clipPath(ui.Path path, {bool doAntiAlias = true}) { + assert(path != null); // path is checked on the engine side + assert(doAntiAlias != null); + _clipPath(path, doAntiAlias); + } + + void _clipPath(ui.Path path, bool doAntiAlias) { + _canvas.clipPath(path, doAntiAlias); + } + + @override + void drawColor(ui.Color color, ui.BlendMode blendMode) { + assert(color != null); + assert(blendMode != null); + _drawColor(color, blendMode); + } + + void _drawColor(ui.Color color, ui.BlendMode blendMode) { + _canvas.drawColor(color, blendMode); + } + + @override + void drawLine(ui.Offset p1, ui.Offset p2, ui.Paint paint) { + assert(_offsetIsValid(p1)); + assert(_offsetIsValid(p2)); + assert(paint != null); + _drawLine(p1, p2, paint); + } + + void _drawLine(ui.Offset p1, ui.Offset p2, paint) { + _canvas.drawLine(p1, p2, paint); + } + + @override + void drawPaint(ui.Paint paint) { + assert(paint != null); + _drawPaint(paint); + } + + void _drawPaint(ui.Paint paint) { + _canvas.drawPaint(paint); + } + + @override + void drawRect(ui.Rect rect, ui.Paint paint) { + assert(rectIsValid(rect)); + assert(paint != null); + _drawRect(rect, paint); + } + + void _drawRect(ui.Rect rect, ui.Paint paint) { + _canvas.drawRect(rect, paint); + } + + @override + void drawRRect(ui.RRect rrect, ui.Paint paint) { + assert(rrectIsValid(rrect)); + assert(paint != null); + _drawRRect(rrect, paint); + } + + void _drawRRect(ui.RRect rrect, ui.Paint paint) { + _canvas.drawRRect(rrect, paint); + } + + @override + void drawDRRect(ui.RRect outer, ui.RRect inner, ui.Paint paint) { + assert(rrectIsValid(outer)); + assert(rrectIsValid(inner)); + assert(paint != null); + _drawDRRect(outer, inner, paint); + } + + void _drawDRRect(ui.RRect outer, ui.RRect inner, ui.Paint paint) { + _canvas.drawDRRect(outer, inner, paint); + } + + @override + void drawOval(ui.Rect rect, ui.Paint paint) { + assert(rectIsValid(rect)); + assert(paint != null); + _drawOval(rect, paint); + } + + void _drawOval(ui.Rect rect, ui.Paint paint) { + _canvas.drawOval(rect, paint); + } + + @override + void drawCircle(ui.Offset c, double radius, ui.Paint paint) { + assert(_offsetIsValid(c)); + assert(paint != null); + _drawCircle(c, radius, paint); + } + + void _drawCircle(ui.Offset c, double radius, ui.Paint paint) { + _canvas.drawCircle(c, radius, paint); + } + + @override + void drawArc(ui.Rect rect, double startAngle, double sweepAngle, + bool useCenter, ui.Paint paint) { + assert(rectIsValid(rect)); + assert(paint != null); + _drawArc(rect, startAngle, sweepAngle, useCenter, paint); + } + + void _drawArc(ui.Rect rect, double startAngle, double sweepAngle, + bool useCenter, ui.Paint paint) { + _canvas.drawArc(rect, startAngle, sweepAngle, useCenter, paint); + } + + @override + void drawPath(ui.Path path, ui.Paint paint) { + assert(path != null); // path is checked on the engine side + assert(paint != null); + _drawPath(path, paint); + } + + void _drawPath(ui.Path path, ui.Paint paint) { + _canvas.drawPath(path, paint); + } + + @override + void drawImage(ui.Image image, ui.Offset p, ui.Paint paint) { + assert(image != null); // image is checked on the engine side + assert(_offsetIsValid(p)); + assert(paint != null); + _drawImage(image, p, paint); + } + + void _drawImage(ui.Image image, ui.Offset p, ui.Paint paint) { + _canvas.drawImage(image, p, paint); + } + + @override + void drawImageRect(ui.Image image, ui.Rect src, ui.Rect dst, ui.Paint paint) { + assert(image != null); // image is checked on the engine side + assert(rectIsValid(src)); + assert(rectIsValid(dst)); + assert(paint != null); + _drawImageRect(image, src, dst, paint); + } + + void _drawImageRect( + ui.Image image, ui.Rect src, ui.Rect dst, ui.Paint paint) { + _canvas.drawImageRect(image, src, dst, paint); + } + + @override + void drawImageNine( + ui.Image image, ui.Rect center, ui.Rect dst, ui.Paint paint) { + assert(image != null); // image is checked on the engine side + assert(rectIsValid(center)); + assert(rectIsValid(dst)); + assert(paint != null); + _drawImageNine(image, center, dst, paint); + } + + void _drawImageNine( + ui.Image image, ui.Rect center, ui.Rect dst, ui.Paint paint) { + _canvas.drawImageNine(image, center, dst, paint); + } + + @override + void drawPicture(ui.Picture picture) { + assert(picture != null); // picture is checked on the engine side + _drawPicture(picture); + } + + void _drawPicture(ui.Picture picture) { + _canvas.drawPicture(picture); + } + + @override + void drawParagraph(ui.Paragraph paragraph, ui.Offset offset) { + assert(paragraph != null); + assert(_offsetIsValid(offset)); + _drawParagraph(paragraph, offset); + } + + void _drawParagraph(ui.Paragraph paragraph, ui.Offset offset) { + _canvas.drawParagraph(paragraph, offset); + } + + @override + void drawPoints( + ui.PointMode pointMode, List points, ui.Paint paint) { + assert(pointMode != null); + assert(points != null); + assert(paint != null); + _drawPoints(paint, pointMode, encodePointList(points)); + } + + @override + void drawRawPoints( + ui.PointMode pointMode, Float32List points, ui.Paint paint) { + assert(pointMode != null); + assert(points != null); + assert(paint != null); + if (points.length % 2 != 0) { + throw ArgumentError('"points" must have an even number of values.'); + } + _drawPoints(paint, pointMode, points); + } + + void _drawPoints(ui.Paint paint, ui.PointMode pointMode, Float32List points) { + _canvas.drawPoints(paint, pointMode, points); + } + + @override + void drawVertices( + ui.Vertices vertices, ui.BlendMode blendMode, ui.Paint paint) { + assert(vertices != null); // vertices is checked on the engine side + assert(paint != null); + assert(blendMode != null); + _drawVertices(vertices, blendMode, paint); + } + + void _drawVertices( + ui.Vertices vertices, ui.BlendMode blendMode, ui.Paint paint) { + _canvas.drawVertices(vertices, blendMode, paint); + } + + @override + void drawAtlas( + ui.Image atlas, + List transforms, + List rects, + List colors, + ui.BlendMode blendMode, + ui.Rect cullRect, + ui.Paint paint) { + assert(atlas != null); // atlas is checked on the engine side + assert(transforms != null); + assert(rects != null); + assert(colors != null); + assert(blendMode != null); + assert(paint != null); + + final int rectCount = rects.length; + if (transforms.length != rectCount) { + throw ArgumentError('"transforms" and "rects" lengths must match.'); + } + if (colors.isNotEmpty && colors.length != rectCount) { + throw ArgumentError( + 'If non-null, "colors" length must match that of "transforms" and "rects".'); + } + + final Float32List rstTransformBuffer = Float32List(rectCount * 4); + final Float32List rectBuffer = Float32List(rectCount * 4); + + for (int i = 0; i < rectCount; ++i) { + final int index0 = i * 4; + final int index1 = index0 + 1; + final int index2 = index0 + 2; + final int index3 = index0 + 3; + final ui.RSTransform rstTransform = transforms[i]; + final ui.Rect rect = rects[i]; + assert(rectIsValid(rect)); + rstTransformBuffer[index0] = rstTransform.scos; + rstTransformBuffer[index1] = rstTransform.ssin; + rstTransformBuffer[index2] = rstTransform.tx; + rstTransformBuffer[index3] = rstTransform.ty; + rectBuffer[index0] = rect.left; + rectBuffer[index1] = rect.top; + rectBuffer[index2] = rect.right; + rectBuffer[index3] = rect.bottom; + } + + final Int32List colorBuffer = + colors.isEmpty ? null : _encodeColorList(colors); + + _drawAtlas( + paint, atlas, rstTransformBuffer, rectBuffer, colorBuffer, blendMode); + } + + @override + void drawRawAtlas( + ui.Image atlas, + Float32List rstTransforms, + Float32List rects, + Int32List colors, + ui.BlendMode blendMode, + ui.Rect cullRect, + ui.Paint paint) { + assert(atlas != null); // atlas is checked on the engine side + assert(rstTransforms != null); + assert(rects != null); + assert(colors != null); + assert(blendMode != null); + assert(paint != null); + + final int rectCount = rects.length; + if (rstTransforms.length != rectCount) + throw ArgumentError('"rstTransforms" and "rects" lengths must match.'); + if (rectCount % 4 != 0) + throw ArgumentError( + '"rstTransforms" and "rects" lengths must be a multiple of four.'); + if (colors != null && colors.length * 4 != rectCount) + throw ArgumentError( + 'If non-null, "colors" length must be one fourth the length of "rstTransforms" and "rects".'); + + _drawAtlas(paint, atlas, rstTransforms, rects, colors, blendMode); + } + + // TODO(hterkelsen): Pass a cull_rect once CanvasKit supports that. + void _drawAtlas( + ui.Paint paint, + ui.Image atlas, + Float32List rstTransforms, + Float32List rects, + Int32List colors, + ui.BlendMode blendMode, + ) { + _canvas.drawAtlasRaw(paint, atlas, rstTransforms, rects, colors, blendMode); + } + + @override + void drawShadow(ui.Path path, ui.Color color, double elevation, + bool transparentOccluder) { + assert(path != null); // path is checked on the engine side + assert(color != null); + assert(transparentOccluder != null); + _drawShadow(path, color, elevation, transparentOccluder); + } + + void _drawShadow(ui.Path path, ui.Color color, double elevation, + bool transparentOccluder) { + _canvas.drawShadow(path, color, elevation, transparentOccluder); + } +} diff --git a/lib/web_ui/lib/src/engine/compositor/embedded_views.dart b/lib/web_ui/lib/src/engine/compositor/embedded_views.dart new file mode 100644 index 0000000000000..c45382d2c8dd0 --- /dev/null +++ b/lib/web_ui/lib/src/engine/compositor/embedded_views.dart @@ -0,0 +1,546 @@ +// 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. + +part of engine; + +/// This composites HTML views into the [ui.Scene]. +class HtmlViewEmbedder { + /// A picture recorder associated with a view id. + /// + /// When we composite in the platform view, we need to create a new canvas + /// for further paint commands to paint to, since the composited view will + /// be on top of the current canvas, and we want further paint commands to + /// be on top of the platform view. + final Map _pictureRecorders = + {}; + + /// The most recent composition parameters for a given view id. + /// + /// If we receive a request to composite a view, but the composition + /// parameters haven't changed, we can avoid having to recompute the + /// element stack that correctly composites the view into the scene. + final Map _currentCompositionParams = + {}; + + /// The HTML element associated with the given view id. + final Map _views = {}; + + /// The root view in the stack of mutator elements for the view id. + final Map _rootViews = {}; + + /// The overlay for the view id. + final Map _overlays = {}; + + /// The views that need to be recomposited into the scene on the next frame. + final Set _viewsToRecomposite = {}; + + /// The views that need to be disposed of on the next frame. + final Set _viewsToDispose = {}; + + /// The list of view ids that should be composited, in order. + List _compositionOrder = []; + + /// The most recent composition order. + List _activeCompositionOrder = []; + + /// The number of clipping elements used last time the view was composited. + Map _clipCount = {}; + + /// The size of the frame, in physical pixels. + ui.Size _frameSize; + + void set frameSize(ui.Size size) { + if (_frameSize == size) { + return; + } + _activeCompositionOrder.clear(); + _frameSize = size; + } + + void handlePlatformViewCall( + ByteData data, + ui.PlatformMessageResponseCallback callback, + ) { + const MethodCodec codec = StandardMethodCodec(); + final MethodCall decoded = codec.decodeMethodCall(data); + + switch (decoded.method) { + case 'create': + _create(decoded, callback); + return; + case 'dispose': + _dispose(decoded, callback); + return; + } + callback(null); + } + + void _create( + MethodCall methodCall, ui.PlatformMessageResponseCallback callback) { + final Map args = methodCall.arguments; + final int viewId = args['id']; + final String viewType = args['viewType']; + const MethodCodec codec = StandardMethodCodec(); + + if (_views[viewId] != null) { + callback(codec.encodeErrorEnvelope( + code: 'recreating_view', + message: 'trying to create an already created view', + details: 'view id: $viewId', + )); + return; + } + + final PlatformViewFactory factory = + platformViewRegistry.registeredFactories[viewType]; + if (factory == null) { + callback(codec.encodeErrorEnvelope( + code: 'unregistered_view_type', + message: 'trying to create a view with an unregistered type', + details: 'unregistered view type: $viewType', + )); + return; + } + + // TODO(het): Support creation parameters. + html.Element embeddedView = factory(viewId); + _views[viewId] = embeddedView; + + _rootViews[viewId] = embeddedView; + + callback(codec.encodeSuccessEnvelope(null)); + } + + void _dispose( + MethodCall methodCall, ui.PlatformMessageResponseCallback callback) { + int viewId = methodCall.arguments; + const MethodCodec codec = StandardMethodCodec(); + if (!_views.containsKey(viewId)) { + callback(codec.encodeErrorEnvelope( + code: 'unknown_view', + message: 'trying to dispose an unknown view', + details: 'view id: $viewId', + )); + } + _viewsToDispose.add(viewId); + callback(codec.encodeSuccessEnvelope(null)); + } + + List getCurrentCanvases() { + final List canvases = []; + for (int i = 0; i < _compositionOrder.length; i++) { + final int viewId = _compositionOrder[i]; + canvases.add(_pictureRecorders[viewId].recordingCanvas); + } + return canvases; + } + + void prerollCompositeEmbeddedView(int viewId, EmbeddedViewParams params) { + final pictureRecorder = SkPictureRecorder(); + pictureRecorder.beginRecording(ui.Offset.zero & _frameSize); + pictureRecorder.recordingCanvas.clear(ui.Color(0x00000000)); + _pictureRecorders[viewId] = pictureRecorder; + _compositionOrder.add(viewId); + + // Do nothing if the params didn't change. + if (_currentCompositionParams[viewId] == params) { + return; + } + _currentCompositionParams[viewId] = params; + _viewsToRecomposite.add(viewId); + } + + SkCanvas compositeEmbeddedView(int viewId) { + // Do nothing if this view doesn't need to be composited. + if (!_viewsToRecomposite.contains(viewId)) { + return _pictureRecorders[viewId].recordingCanvas; + } + _compositeWithParams(viewId, _currentCompositionParams[viewId]); + _viewsToRecomposite.remove(viewId); + return _pictureRecorders[viewId].recordingCanvas; + } + + void _compositeWithParams(int viewId, EmbeddedViewParams params) { + final html.Element platformView = _views[viewId]; + platformView.style.width = '${params.size.width}px'; + platformView.style.height = '${params.size.height}px'; + platformView.style.position = 'absolute'; + + final int currentClippingCount = _countClips(params.mutators); + final int previousClippingCount = _clipCount[viewId]; + if (currentClippingCount != previousClippingCount) { + _clipCount[viewId] = currentClippingCount; + html.Element oldPlatformViewRoot = _rootViews[viewId]; + html.Element newPlatformViewRoot = _reconstructClipViewsChain( + currentClippingCount, + platformView, + oldPlatformViewRoot, + ); + _rootViews[viewId] = newPlatformViewRoot; + } + _applyMutators(params.mutators, platformView); + } + + int _countClips(MutatorsStack mutators) { + int clipCount = 0; + for (final Mutator mutator in mutators) { + if (mutator.isClipType) { + clipCount++; + } + } + return clipCount; + } + + html.Element _reconstructClipViewsChain( + int numClips, + html.Element platformView, + html.Element headClipView, + ) { + int indexInFlutterView = -1; + if (headClipView.parent != null) { + indexInFlutterView = skiaSceneHost.children.indexOf(headClipView); + headClipView.remove(); + } + html.Element head = platformView; + int clipIndex = 0; + // Re-use as much existing clip views as needed. + while (head != headClipView && clipIndex < numClips) { + head = head.parent; + clipIndex++; + } + // If there weren't enough existing clip views, add more. + while (clipIndex < numClips) { + html.Element clippingView = html.Element.tag('flt-clip'); + clippingView.append(head); + head = clippingView; + clipIndex++; + } + head.remove(); + + // If the chain was previously attached, attach it to the same position. + if (indexInFlutterView > -1) { + skiaSceneHost.children.insert(indexInFlutterView, head); + } + return head; + } + + void _applyMutators(MutatorsStack mutators, html.Element embeddedView) { + html.Element head = embeddedView; + Matrix4 headTransform = Matrix4.identity(); + double embeddedOpacity = 1.0; + _resetAnchor(head); + + for (final Mutator mutator in mutators) { + switch (mutator.type) { + case MutatorType.transform: + headTransform.multiply(mutator.matrix); + head.style.transform = + float64ListToCssTransform(headTransform.storage); + break; + case MutatorType.clipRect: + case MutatorType.clipRRect: + case MutatorType.clipPath: + html.Element clipView = head.parent; + clipView.style.clip = ''; + clipView.style.clipPath = ''; + headTransform = Matrix4.identity(); + clipView.style.transform = ''; + if (mutator.rect != null) { + final ui.Rect rect = mutator.rect; + clipView.style.clip = 'rect(${rect.top}px, ${rect.right}px, ' + '${rect.bottom}px, ${rect.left}px)'; + } else if (mutator.rrect != null) { + final SkPath path = SkPath(); + path.addRRect(mutator.rrect); + _ensureSvgPathDefs(); + html.Element pathDefs = _svgPathDefs.querySelector('#sk_path_defs'); + _clipPathCount += 1; + html.Element newClipPath = + html.Element.html('' + '' + ''); + pathDefs.append(newClipPath); + clipView.style.clipPath = 'url(#svgClip$_clipPathCount)'; + } else if (mutator.path != null) { + final SkPath path = mutator.path; + _ensureSvgPathDefs(); + html.Element pathDefs = _svgPathDefs.querySelector('#sk_path_defs'); + _clipPathCount += 1; + html.Element newClipPath = + html.Element.html('' + '' + ''); + pathDefs.append(newClipPath); + clipView.style.clipPath = 'url(#svgClip$_clipPathCount)'; + } + _resetAnchor(clipView); + head = clipView; + break; + case MutatorType.opacity: + embeddedOpacity *= mutator.alphaFloat; + break; + } + } + + embeddedView.style.opacity = embeddedOpacity.toString(); + + // Reverse scale based on screen scale. + // + // HTML elements use logical (CSS) pixels, but we have been using physical + // pixels, so scale down the head element to match the logical resolution. + final double scale = html.window.devicePixelRatio; + final double inverseScale = 1 / scale; + final Matrix4 scaleMatrix = + Matrix4.diagonal3Values(inverseScale, inverseScale, 1); + headTransform.multiply(scaleMatrix); + head.style.transform = float64ListToCssTransform(headTransform.storage); + } + + /// Sets the transform origin to the top-left corner of the element. + /// + /// By default, the transform origin is the center of the element, but + /// Flutter assumes the transform origin is the top-left point. + void _resetAnchor(html.Element element) { + element.style.transformOrigin = '0 0 0'; + element.style.position = 'absolute'; + } + + int _clipPathCount = 0; + + html.Element _svgPathDefs; + + /// Ensures we add a container of SVG path defs to the DOM so they can + /// be referred to in clip-path: url(#blah). + void _ensureSvgPathDefs() { + if (_svgPathDefs != null) return; + _svgPathDefs = html.Element.html( + '', + treeSanitizer: _NullTreeSanitizer(), + ); + skiaSceneHost.append(_svgPathDefs); + } + + void submitFrame() { + disposeViews(); + + for (int i = 0; i < _compositionOrder.length; i++) { + int viewId = _compositionOrder[i]; + ensureOverlayInitialized(viewId); + final SurfaceFrame frame = + _overlays[viewId].surface.acquireFrame(_frameSize); + final SkCanvas canvas = frame.skiaCanvas; + canvas.drawPicture(_pictureRecorders[viewId].endRecording()); + frame.submit(); + } + _pictureRecorders.clear(); + if (_listEquals(_compositionOrder, _activeCompositionOrder)) { + _compositionOrder.clear(); + return; + } + _activeCompositionOrder.clear(); + + for (int i = 0; i < _compositionOrder.length; i++) { + int viewId = _compositionOrder[i]; + html.Element platformViewRoot = _rootViews[viewId]; + html.Element overlay = _overlays[viewId].surface.htmlElement; + platformViewRoot.remove(); + skiaSceneHost.append(platformViewRoot); + overlay.remove(); + skiaSceneHost.append(overlay); + _activeCompositionOrder.add(viewId); + } + _compositionOrder.clear(); + } + + void disposeViews() { + if (_viewsToDispose.isEmpty) { + return; + } + + for (int viewId in _viewsToDispose) { + final html.Element rootView = _rootViews[viewId]; + rootView.remove(); + _views.remove(viewId); + _rootViews.remove(viewId); + if (_overlays[viewId] != null) { + final Overlay overlay = _overlays[viewId]; + overlay.surface.htmlElement?.remove(); + } + _overlays.remove(viewId); + _currentCompositionParams.remove(viewId); + _clipCount.remove(viewId); + _viewsToRecomposite.remove(viewId); + } + _viewsToDispose.clear(); + } + + void ensureOverlayInitialized(int viewId) { + Overlay overlay = _overlays[viewId]; + if (overlay != null) { + return; + } + Surface surface = Surface(); + SkSurface skSurface = surface.acquireRenderSurface(_frameSize); + _overlays[viewId] = Overlay(surface, skSurface); + } +} + +/// The parameters passed to the view embedder. +class EmbeddedViewParams { + EmbeddedViewParams(this.offset, this.size, MutatorsStack mutators) + : mutators = MutatorsStack._copy(mutators); + + final ui.Offset offset; + final ui.Size size; + final MutatorsStack mutators; + + bool operator ==(dynamic other) { + if (identical(this, other)) return true; + if (other is! EmbeddedViewParams) return false; + + EmbeddedViewParams typedOther = other; + return offset == typedOther.offset && + size == typedOther.size && + mutators == typedOther.mutators; + } + + int get hashCode => ui.hashValues(offset, size, mutators); +} + +enum MutatorType { + clipRect, + clipRRect, + clipPath, + transform, + opacity, +} + +/// Stores mutation information like clipping or transform. +class Mutator { + const Mutator._( + this.type, + this.rect, + this.rrect, + this.path, + this.matrix, + this.alpha, + ); + + final MutatorType type; + final ui.Rect rect; + final ui.RRect rrect; + final ui.Path path; + final Matrix4 matrix; + final int alpha; + + const Mutator.clipRect(ui.Rect rect) + : this._(MutatorType.clipRect, rect, null, null, null, null); + const Mutator.clipRRect(ui.RRect rrect) + : this._(MutatorType.clipRRect, null, rrect, null, null, null); + const Mutator.clipPath(ui.Path path) + : this._(MutatorType.clipPath, null, null, path, null, null); + const Mutator.transform(Matrix4 matrix) + : this._(MutatorType.transform, null, null, null, matrix, null); + const Mutator.opacity(int alpha) + : this._(MutatorType.opacity, null, null, null, null, alpha); + + bool get isClipType => + type == MutatorType.clipRect || + type == MutatorType.clipRRect || + type == MutatorType.clipPath; + + double get alphaFloat => alpha / 255.0; + + bool operator ==(dynamic other) { + if (identical(this, other)) return true; + if (other is! Mutator) return false; + + final Mutator typedOther = other; + if (type != typedOther.type) { + return false; + } + + switch (type) { + case MutatorType.clipRect: + return rect == typedOther.rect; + case MutatorType.clipRRect: + return rrect == typedOther.rrect; + case MutatorType.clipPath: + return path == typedOther.path; + case MutatorType.transform: + return matrix == typedOther.matrix; + case MutatorType.opacity: + return alpha == typedOther.alpha; + default: + return false; + } + } + + int get hashCode => ui.hashValues(type, rect, rrect, path, matrix, alpha); +} + +/// A stack of mutators that can be applied to an embedded view. +class MutatorsStack extends Iterable { + MutatorsStack() : _mutators = []; + + MutatorsStack._copy(MutatorsStack original) + : _mutators = List.from(original._mutators); + + final List _mutators; + + void pushClipRect(ui.Rect rect) { + _mutators.add(Mutator.clipRect(rect)); + } + + void pushClipRRect(ui.RRect rrect) { + _mutators.add(Mutator.clipRRect(rrect)); + } + + void pushClipPath(ui.Path path) { + _mutators.add(Mutator.clipPath(path)); + } + + void pushTransform(Matrix4 matrix) { + _mutators.add(Mutator.transform(matrix)); + } + + void pushOpacity(int alpha) { + _mutators.add(Mutator.opacity(alpha)); + } + + void pop() { + _mutators.removeLast(); + } + + bool operator ==(dynamic other) { + if (identical(other, this)) return true; + if (other is! MutatorsStack) return false; + + final MutatorsStack typedOther = other; + if (_mutators.length != typedOther._mutators.length) { + return false; + } + + for (int i = 0; i < _mutators.length; i++) { + if (_mutators[i] != typedOther._mutators[i]) { + return false; + } + } + + return true; + } + + int get hashCode => ui.hashList(_mutators); + + @override + Iterator get iterator => _mutators.reversed.iterator; +} + +/// Represents a surface overlaying a platform view. +class Overlay { + final Surface surface; + final SkSurface skSurface; + + Overlay(this.surface, this.skSurface); +} diff --git a/lib/web_ui/lib/src/engine/compositor/engine_delegate.dart b/lib/web_ui/lib/src/engine/compositor/engine_delegate.dart index bf28ebbfb91eb..0a854696ca9c7 100644 --- a/lib/web_ui/lib/src/engine/compositor/engine_delegate.dart +++ b/lib/web_ui/lib/src/engine/compositor/engine_delegate.dart @@ -58,6 +58,7 @@ class Engine extends RuntimeDelegate { } layerTree.frameSize = frameSize; + layerTree.devicePixelRatio = _viewportMetrics.devicePixelRatio; _animator.render(layerTree); } diff --git a/lib/web_ui/lib/src/engine/compositor/fonts.dart b/lib/web_ui/lib/src/engine/compositor/fonts.dart index 45707f61de2ed..b079f4c86ab66 100644 --- a/lib/web_ui/lib/src/engine/compositor/fonts.dart +++ b/lib/web_ui/lib/src/engine/compositor/fonts.dart @@ -4,14 +4,20 @@ part of engine; +const String _robotoUrl = + 'https://fonts.gstatic.com/s/roboto/v20/KFOmCnqEu92Fr1Me5WZLCzYlKw.ttf'; + class SkiaFontCollection { - final Map, js.JsObject>> - _registeredTypefaces = , js.JsObject>>{}; + final List> _loadingFontBuffers = >[]; - final List> _fontLoadingFutures = >[]; + final Set registeredFamilies = {}; Future ensureFontsLoaded() async { - await Future.wait(_fontLoadingFutures); + final List fontBuffers = + (await Future.wait(_loadingFontBuffers)) + .map((ByteBuffer buffer) => buffer.asUint8List()) + .toList(); + skFontMgr = canvasKit['SkFontMgr'].callMethod('FromData', fontBuffers); } Future registerFonts(AssetManager assetManager) async { @@ -45,66 +51,27 @@ class SkiaFontCollection { final String family = fontFamily['family']; final List fontAssets = fontFamily['fonts']; + registeredFamilies.add(family); + for (dynamic fontAssetItem in fontAssets) { final Map fontAsset = fontAssetItem; final String asset = fontAsset['asset']; - final Map descriptors = {}; - for (String descriptor in fontAsset.keys) { - if (descriptor != 'asset') { - descriptors[descriptor] = '${fontAsset[descriptor]}'; - } - } - _fontLoadingFutures.add(_registerFont( - family, assetManager.getAssetUrl(asset), descriptors)); + _loadingFontBuffers.add(html.window + .fetch(assetManager.getAssetUrl(asset)) + .then((dynamic fetchResult) => fetchResult.arrayBuffer())); } } - } - Future _registerFont( - String family, String url, Map descriptors) async { - final dynamic fetchResult = await html.window.fetch(url); - final ByteBuffer resultBuffer = await fetchResult.arrayBuffer(); - final js.JsObject skTypeFace = skFontMgr.callMethod( - 'MakeTypefaceFromData', [resultBuffer.asUint8List()]); - _registeredTypefaces.putIfAbsent( - family, () => , js.JsObject>{}); - _registeredTypefaces[family][descriptors] = skTypeFace; - } - - js.JsObject getFont(String family, double size) { - if (_registeredTypefaces[family] == null) { - if (assertionsEnabled) { - html.window.console.warn('Using unregistered font: $family'); - } - return js.JsObject(canvasKit['SkFont'], [null, size]); + /// We need a default fallback font for CanvasKit, in order to + /// avoid crashing while laying out text with an unregistered font. We chose + /// Roboto to match Android. + if (!registeredFamilies.contains('Roboto')) { + // Download Roboto and add it to the font buffers. + _loadingFontBuffers.add(html.window + .fetch(_robotoUrl) + .then((dynamic fetchResult) => fetchResult.arrayBuffer())); } - - // We don't attempt to find a Typeface matching the text style. Instead, we - // try to find the "default" typeface. The default typeface either has no - // descriptors, or only has a descriptor of font-weight 400 (the default). - final Map, js.JsObject> typefaces = - _registeredTypefaces[family]; - js.JsObject skTypeface; - - for (MapEntry, js.JsObject> entry - in typefaces.entries) { - final Map descriptors = entry.key; - if (descriptors.isEmpty || - (descriptors.length == 1 && descriptors['weight'] == '400')) { - skTypeface = entry.value; - break; - } - } - - // If we couldn't find a suitable default, just use any typeface in the - // family. - if (skTypeface == null) { - skTypeface = typefaces.values.first; - } - - return js.JsObject(canvasKit['SkFont'], [skTypeface, size]); } - final js.JsObject skFontMgr = - js.JsObject(canvasKit['SkFontMgr']['RefDefault']); + js.JsObject skFontMgr; } diff --git a/lib/web_ui/lib/src/engine/compositor/image_filter.dart b/lib/web_ui/lib/src/engine/compositor/image_filter.dart new file mode 100644 index 0000000000000..0cd8840ca057e --- /dev/null +++ b/lib/web_ui/lib/src/engine/compositor/image_filter.dart @@ -0,0 +1,46 @@ +// 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. + +part of engine; + +/// The CanvasKit implementation of [ui.ImageFilter]. +/// +/// Currently only supports `blur`. +class SkImageFilter implements ui.ImageFilter { + js.JsObject skImageFilter; + + SkImageFilter.blur({double sigmaX = 0.0, double sigmaY = 0.0}) + : _sigmaX = sigmaX, + _sigmaY = sigmaY { + skImageFilter = canvasKit['SkImageFilter'].callMethod( + 'MakeBlur', + [ + sigmaX, + sigmaY, + canvasKit['TileMode']['Clamp'], + null, + ], + ); + } + + final double _sigmaX; + final double _sigmaY; + + @override + bool operator ==(dynamic other) { + if (other is! SkImageFilter) { + return false; + } + final SkImageFilter typedOther = other; + return _sigmaX == typedOther._sigmaX && _sigmaY == typedOther._sigmaY; + } + + @override + int get hashCode => ui.hashValues(_sigmaX, _sigmaY); + + @override + String toString() { + return 'ImageFilter.blur($_sigmaX, $_sigmaY)'; + } +} diff --git a/lib/web_ui/lib/src/engine/compositor/initialization.dart b/lib/web_ui/lib/src/engine/compositor/initialization.dart index 5ac06638edd8b..af69adccea2ee 100644 --- a/lib/web_ui/lib/src/engine/compositor/initialization.dart +++ b/lib/web_ui/lib/src/engine/compositor/initialization.dart @@ -9,7 +9,7 @@ const bool experimentalUseSkia = bool.fromEnvironment('FLUTTER_WEB_USE_SKIA', defaultValue: false); /// The URL to use when downloading the CanvasKit script and associated wasm. -const String canvasKitBaseUrl = 'https://unpkg.com/canvaskit-wasm@0.7.0/bin/'; +const String canvasKitBaseUrl = 'https://unpkg.com/canvaskit-wasm@0.10.0/bin/'; /// Initialize the Skia backend. /// @@ -32,6 +32,10 @@ Future initializeSkia() { }, ]); }); + + /// Add a Skia scene host. + skiaSceneHost = html.Element.tag('flt-scene'); + domRenderer.renderScene(skiaSceneHost); return canvasKitCompleter.future; } @@ -42,3 +46,6 @@ js.JsObject canvasKit; /// The Skia font collection. SkiaFontCollection skiaFontCollection; + +/// The scene host, where the root canvas and overlay canvases are added to. +html.Element skiaSceneHost; diff --git a/lib/web_ui/lib/src/engine/compositor/layer.dart b/lib/web_ui/lib/src/engine/compositor/layer.dart index 9c03a8b5c58dc..2c0b4f0ddfc6d 100644 --- a/lib/web_ui/lib/src/engine/compositor/layer.dart +++ b/lib/web_ui/lib/src/engine/compositor/layer.dart @@ -34,18 +34,36 @@ class PrerollContext { /// A raster cache. Used to register candidates for caching. final RasterCache rasterCache; - PrerollContext(this.rasterCache); + /// A compositor for embedded HTML views. + final HtmlViewEmbedder viewEmbedder; + + final MutatorsStack mutatorsStack = MutatorsStack(); + + PrerollContext(this.rasterCache, this.viewEmbedder); } /// A context shared by all layers during the paint pass. class PaintContext { - /// The canvas to paint to. - final SkCanvas canvas; + /// A multi-canvas that applies clips, transforms, and opacity + /// operations to all canvases (root canvas and overlay canvases for the + /// platform views). + SkNWayCanvas internalNodesCanvas; + + /// The canvas for leaf nodes to paint to. + SkCanvas leafNodesCanvas; /// A raster cache potentially containing pre-rendered pictures. final RasterCache rasterCache; - PaintContext(this.canvas, this.rasterCache); + /// A compositor for embedded HTML views. + final HtmlViewEmbedder viewEmbedder; + + PaintContext( + this.internalNodesCanvas, + this.leafNodesCanvas, + this.rasterCache, + this.viewEmbedder, + ); } /// A layer that contains child layers. @@ -93,30 +111,54 @@ abstract class ContainerLayer extends Layer { } } +class BackdropFilterLayer extends ContainerLayer { + final ui.ImageFilter _filter; + + BackdropFilterLayer(this._filter); + + @override + void paint(PaintContext context) { + context.internalNodesCanvas.saveLayerWithFilter(paintBounds, _filter); + paintChildren(context); + context.internalNodesCanvas.restore(); + } +} + /// A layer that clips its child layers by a given [Path]. class ClipPathLayer extends ContainerLayer { /// The path used to clip child layers. final ui.Path _clipPath; + final ui.Clip _clipBehavior; - ClipPathLayer(this._clipPath); + ClipPathLayer(this._clipPath, this._clipBehavior) + : assert(_clipBehavior != ui.Clip.none); @override - void preroll(PrerollContext prerollContext, Matrix4 matrix) { - final ui.Rect childPaintBounds = prerollChildren(prerollContext, matrix); + void preroll(PrerollContext context, Matrix4 matrix) { + context.mutatorsStack.pushClipPath(_clipPath); + final ui.Rect childPaintBounds = prerollChildren(context, matrix); final ui.Rect clipBounds = _clipPath.getBounds(); if (childPaintBounds.overlaps(clipBounds)) { paintBounds = childPaintBounds.intersect(clipBounds); } + context.mutatorsStack.pop(); } @override void paint(PaintContext paintContext) { assert(needsPainting); - paintContext.canvas.save(); - paintContext.canvas.clipPath(_clipPath); + paintContext.internalNodesCanvas.save(); + paintContext.internalNodesCanvas.clipPath(_clipPath, _clipBehavior != ui.Clip.hardEdge); + + if (_clipBehavior == ui.Clip.antiAliasWithSaveLayer) { + paintContext.internalNodesCanvas.saveLayer(paintBounds, null); + } paintChildren(paintContext); - paintContext.canvas.restore(); + if (_clipBehavior == ui.Clip.antiAliasWithSaveLayer) { + paintContext.internalNodesCanvas.restore(); + } + paintContext.internalNodesCanvas.restore(); } } @@ -124,25 +166,39 @@ class ClipPathLayer extends ContainerLayer { class ClipRectLayer extends ContainerLayer { /// The rectangle used to clip child layers. final ui.Rect _clipRect; + final ui.Clip _clipBehavior; - ClipRectLayer(this._clipRect); + ClipRectLayer(this._clipRect, this._clipBehavior) + : assert(_clipBehavior != ui.Clip.none); @override - void preroll(PrerollContext prerollContext, Matrix4 matrix) { - final ui.Rect childPaintBounds = prerollChildren(prerollContext, matrix); + void preroll(PrerollContext context, Matrix4 matrix) { + context.mutatorsStack.pushClipRect(_clipRect); + final ui.Rect childPaintBounds = prerollChildren(context, matrix); if (childPaintBounds.overlaps(_clipRect)) { paintBounds = childPaintBounds.intersect(_clipRect); } + context.mutatorsStack.pop(); } @override void paint(PaintContext paintContext) { assert(needsPainting); - paintContext.canvas.save(); - paintContext.canvas.clipRect(_clipRect); + paintContext.internalNodesCanvas.save(); + paintContext.internalNodesCanvas.clipRect( + _clipRect, + ui.ClipOp.intersect, + _clipBehavior != ui.Clip.hardEdge, + ); + if (_clipBehavior == ui.Clip.antiAliasWithSaveLayer) { + paintContext.internalNodesCanvas.saveLayer(_clipRect, null); + } paintChildren(paintContext); - paintContext.canvas.restore(); + if (_clipBehavior == ui.Clip.antiAliasWithSaveLayer) { + paintContext.internalNodesCanvas.restore(); + } + paintContext.internalNodesCanvas.restore(); } } @@ -150,25 +206,36 @@ class ClipRectLayer extends ContainerLayer { class ClipRRectLayer extends ContainerLayer { /// The rounded rectangle used to clip child layers. final ui.RRect _clipRRect; + final ui.Clip _clipBehavior; - ClipRRectLayer(this._clipRRect); + ClipRRectLayer(this._clipRRect, this._clipBehavior) + : assert(_clipBehavior != ui.Clip.none); @override - void preroll(PrerollContext prerollContext, Matrix4 matrix) { - final ui.Rect childPaintBounds = prerollChildren(prerollContext, matrix); + void preroll(PrerollContext context, Matrix4 matrix) { + context.mutatorsStack.pushClipRRect(_clipRRect); + final ui.Rect childPaintBounds = prerollChildren(context, matrix); if (childPaintBounds.overlaps(_clipRRect.outerRect)) { paintBounds = childPaintBounds.intersect(_clipRRect.outerRect); } + context.mutatorsStack.pop(); } @override void paint(PaintContext paintContext) { assert(needsPainting); - paintContext.canvas.save(); - paintContext.canvas.clipRRect(_clipRRect); + paintContext.internalNodesCanvas.save(); + paintContext.internalNodesCanvas + .clipRRect(_clipRRect, _clipBehavior != ui.Clip.hardEdge); + if (_clipBehavior == ui.Clip.antiAliasWithSaveLayer) { + paintContext.internalNodesCanvas.saveLayer(paintBounds, null); + } paintChildren(paintContext); - paintContext.canvas.restore(); + if (_clipBehavior == ui.Clip.antiAliasWithSaveLayer) { + paintContext.internalNodesCanvas.restore(); + } + paintContext.internalNodesCanvas.restore(); } } @@ -180,12 +247,16 @@ class OpacityLayer extends ContainerLayer implements ui.OpacityEngineLayer { OpacityLayer(this._alpha, this._offset); @override - void preroll(PrerollContext prerollContext, Matrix4 matrix) { + void preroll(PrerollContext context, Matrix4 matrix) { final Matrix4 childMatrix = Matrix4.copy(matrix); childMatrix.translate(_offset.dx, _offset.dy); - final ui.Rect childPaintBounds = - prerollChildren(prerollContext, childMatrix); - paintBounds = childPaintBounds.translate(_offset.dx, _offset.dy); + context.mutatorsStack + .pushTransform(Matrix4.translationValues(_offset.dx, _offset.dy, 0.0)); + context.mutatorsStack.pushOpacity(_alpha); + super.preroll(context, childMatrix); + context.mutatorsStack.pop(); + context.mutatorsStack.pop(); + paintBounds = paintBounds.translate(_offset.dx, _offset.dy); } @override @@ -195,16 +266,16 @@ class OpacityLayer extends ContainerLayer implements ui.OpacityEngineLayer { final ui.Paint paint = ui.Paint(); paint.color = ui.Color.fromARGB(_alpha, 0, 0, 0); - paintContext.canvas.save(); - paintContext.canvas.translate(_offset.dx, _offset.dy); + paintContext.internalNodesCanvas.save(); + paintContext.internalNodesCanvas.translate(_offset.dx, _offset.dy); final ui.Rect saveLayerBounds = paintBounds.shift(-_offset); - paintContext.canvas.saveLayer(saveLayerBounds, paint); + paintContext.internalNodesCanvas.saveLayer(saveLayerBounds, paint); paintChildren(paintContext); // Restore twice: once for the translate and once for the saveLayer. - paintContext.canvas.restore(); - paintContext.canvas.restore(); + paintContext.internalNodesCanvas.restore(); + paintContext.internalNodesCanvas.restore(); } } @@ -217,11 +288,12 @@ class TransformLayer extends ContainerLayer TransformLayer(this._transform); @override - void preroll(PrerollContext prerollContext, Matrix4 matrix) { + void preroll(PrerollContext context, Matrix4 matrix) { final Matrix4 childMatrix = matrix * _transform; - final ui.Rect childPaintBounds = - prerollChildren(prerollContext, childMatrix); + context.mutatorsStack.pushTransform(_transform); + final ui.Rect childPaintBounds = prerollChildren(context, childMatrix); paintBounds = _transformRect(_transform, childPaintBounds); + context.mutatorsStack.pop(); } /// Applies the given matrix as a perspective transform to the given point. @@ -264,17 +336,34 @@ class TransformLayer extends ContainerLayer void paint(PaintContext paintContext) { assert(needsPainting); - paintContext.canvas.save(); - paintContext.canvas.transform(_transform.storage); + paintContext.internalNodesCanvas.save(); + paintContext.internalNodesCanvas.transform(_transform.storage); paintChildren(paintContext); - paintContext.canvas.restore(); + paintContext.internalNodesCanvas.restore(); + } +} + +/// A layer that applies an [ui.ImageFilter] to its children. +class ImageFilterLayer extends ContainerLayer implements ui.OpacityEngineLayer { + ImageFilterLayer(this._filter); + + final ui.ImageFilter _filter; + + @override + void paint(PaintContext paintContext) { + assert(needsPainting); + final ui.Paint paint = ui.Paint(); + paint.imageFilter = _filter; + paintContext.internalNodesCanvas.saveLayer(paintBounds, paint); + paintChildren(paintContext); + paintContext.internalNodesCanvas.restore(); } } /// A layer containing a [Picture]. class PictureLayer extends Layer { /// The picture to paint into the canvas. - final ui.Picture picture; + final SkPicture picture; /// The offset at which to paint the picture. final ui.Offset offset; @@ -297,11 +386,11 @@ class PictureLayer extends Layer { assert(picture != null); assert(needsPainting); - paintContext.canvas.save(); - paintContext.canvas.translate(offset.dx, offset.dy); + paintContext.leafNodesCanvas.save(); + paintContext.leafNodesCanvas.translate(offset.dx, offset.dy); - paintContext.canvas.drawPicture(picture); - paintContext.canvas.restore(); + paintContext.leafNodesCanvas.drawPicture(picture); + paintContext.leafNodesCanvas.restore(); } } @@ -328,8 +417,60 @@ class PhysicalShapeLayer extends ContainerLayer @override void preroll(PrerollContext prerollContext, Matrix4 matrix) { prerollChildren(prerollContext, matrix); - paintBounds = - ElevationShadow.computeShadowRect(_path.getBounds(), _elevation); + + paintBounds = _path.getBounds(); + if (_elevation == 0.0) { + // No need to extend the paint bounds if there is no shadow. + return; + } else { + // Add some margin to the paint bounds to leave space for the shadow. + // We fill this whole region and clip children to it so we don't need to + // join the child paint bounds. + // The offset is calculated as follows: + + // .--- (kLightRadius) + // -------/ (light) + // | / + // | / + // |/ + // |O + // /| (kLightHeight) + // / | + // / | + // / | + // / | + // ------------- (layer) + // /| | + // / | | (elevation) + // A / | |B + // ------------------------------------------------ (canvas) + // --- (extent of shadow) + // + // E = lt } t = (r + w/2)/h + // } => + // r + w/2 = ht } E = (l/h)(r + w/2) + // + // Where: E = extent of shadow + // l = elevation of layer + // r = radius of the light source + // w = width of the layer + // h = light height + // t = tangent of AOB, i.e., multiplier for elevation to extent + final double devicePixelRatio = ui.window.devicePixelRatio; + + final double radius = kLightRadius * devicePixelRatio; + // tangent for x + double tx = (radius + paintBounds.width * 0.5) / kLightHeight; + // tangent for y + double ty = (radius + paintBounds.height * 0.5) / kLightHeight; + + paintBounds = ui.Rect.fromLTRB( + paintBounds.left - tx, + paintBounds.top - ty, + paintBounds.right + tx, + paintBounds.bottom + ty, + ); + } } @override @@ -337,26 +478,26 @@ class PhysicalShapeLayer extends ContainerLayer assert(needsPainting); if (_elevation != 0) { - drawShadow(paintContext.canvas, _path, _shadowColor, _elevation, + drawShadow(paintContext.leafNodesCanvas, _path, _shadowColor, _elevation, _color.alpha != 0xff); } final ui.Paint paint = ui.Paint()..color = _color; if (_clipBehavior != ui.Clip.antiAliasWithSaveLayer) { - paintContext.canvas.drawPath(_path, paint); + paintContext.leafNodesCanvas.drawPath(_path, paint); } - final int saveCount = paintContext.canvas.save(); + final int saveCount = paintContext.internalNodesCanvas.save(); switch (_clipBehavior) { case ui.Clip.hardEdge: - paintContext.canvas.clipPath(_path, doAntiAlias: false); + paintContext.internalNodesCanvas.clipPath(_path, false); break; case ui.Clip.antiAlias: - paintContext.canvas.clipPath(_path, doAntiAlias: true); + paintContext.internalNodesCanvas.clipPath(_path, true); break; case ui.Clip.antiAliasWithSaveLayer: - paintContext.canvas.clipPath(_path, doAntiAlias: true); - paintContext.canvas.saveLayer(paintBounds, null); + paintContext.internalNodesCanvas.clipPath(_path, true); + paintContext.internalNodesCanvas.saveLayer(paintBounds, null); break; case ui.Clip.none: break; @@ -367,12 +508,12 @@ class PhysicalShapeLayer extends ContainerLayer // (https://github.com/flutter/flutter/issues/18057#issue-328003931) // using saveLayer, we have to call drawPaint instead of drawPath as // anti-aliased drawPath will always have such artifacts. - paintContext.canvas.drawPaint(paint); + paintContext.leafNodesCanvas.drawPaint(paint); } paintChildren(paintContext); - paintContext.canvas.restoreToCount(saveCount); + paintContext.internalNodesCanvas.restoreToCount(saveCount); } /// Draws a shadow on the given [canvas] for the given [path]. @@ -384,3 +525,32 @@ class PhysicalShapeLayer extends ContainerLayer canvas.drawShadow(path, color, elevation, transparentOccluder); } } + +/// A layer which renders a platform view (an HTML element in this case). +class PlatformViewLayer extends Layer { + PlatformViewLayer(this.viewId, this.offset, this.width, this.height); + + final int viewId; + final ui.Offset offset; + final double width; + final double height; + + @override + void preroll(PrerollContext context, Matrix4 matrix) { + paintBounds = ui.Rect.fromLTWH(offset.dx, offset.dy, width, height); + context.viewEmbedder.prerollCompositeEmbeddedView( + viewId, + EmbeddedViewParams( + offset, + ui.Size(width, height), + context.mutatorsStack, + ), + ); + } + + @override + void paint(PaintContext context) { + SkCanvas canvas = context.viewEmbedder.compositeEmbeddedView(viewId); + context.leafNodesCanvas = canvas; + } +} diff --git a/lib/web_ui/lib/src/engine/compositor/layer_scene_builder.dart b/lib/web_ui/lib/src/engine/compositor/layer_scene_builder.dart index 0bb05f096d3f2..d1b8de526c396 100644 --- a/lib/web_ui/lib/src/engine/compositor/layer_scene_builder.dart +++ b/lib/web_ui/lib/src/engine/compositor/layer_scene_builder.dart @@ -17,7 +17,6 @@ class LayerScene implements ui.Scene { @override Future toImage(int width, int height) => null; - @override html.Element get webOnlyRootElement => null; } @@ -26,12 +25,13 @@ class LayerSceneBuilder implements ui.SceneBuilder { ContainerLayer currentLayer; @override - void addChildScene( - {ui.Offset offset = ui.Offset.zero, - double width = 0.0, - double height = 0.0, - ui.SceneHost sceneHost, - bool hitTestable = true}) { + void addChildScene({ + ui.Offset offset = ui.Offset.zero, + double width = 0.0, + double height = 0.0, + ui.SceneHost sceneHost, + bool hitTestable = true, + }) { throw UnimplementedError(); } @@ -42,10 +42,13 @@ class LayerSceneBuilder implements ui.SceneBuilder { } @override - void addPicture(ui.Offset offset, ui.Picture picture, - {bool isComplexHint = false, bool willChangeHint = false}) { - currentLayer - .add(PictureLayer(picture, offset, isComplexHint, willChangeHint)); + void addPicture( + ui.Offset offset, + ui.Picture picture, { + bool isComplexHint = false, + bool willChangeHint = false, + }) { + currentLayer.add(PictureLayer(picture, offset, isComplexHint, willChangeHint)); } @override @@ -57,11 +60,13 @@ class LayerSceneBuilder implements ui.SceneBuilder { } @override - void addTexture(int textureId, - {ui.Offset offset = ui.Offset.zero, - double width = 0.0, - double height = 0.0, - bool freeze = false}) { + void addTexture( + int textureId, { + ui.Offset offset = ui.Offset.zero, + double width = 0.0, + double height = 0.0, + bool freeze = false, + }) { // TODO(b/128315641): implement addTexture. } @@ -73,7 +78,7 @@ class LayerSceneBuilder implements ui.SceneBuilder { double height = 0.0, Object webOnlyPaintedBy, }) { - // TODO(b/128317425): implement addPlatformView. + currentLayer.add(PlatformViewLayer(viewId, offset, width, height)); } @override @@ -90,42 +95,68 @@ class LayerSceneBuilder implements ui.SceneBuilder { } @override - ui.BackdropFilterEngineLayer pushBackdropFilter(ui.ImageFilter filter, - {ui.EngineLayer oldLayer}) { - throw UnimplementedError(); + ui.BackdropFilterEngineLayer pushBackdropFilter( + ui.ImageFilter filter, { + ui.EngineLayer oldLayer, + }) { + pushLayer(BackdropFilterLayer(filter)); + return null; } @override - ui.ClipPathEngineLayer pushClipPath(ui.Path path, - {ui.Clip clipBehavior = ui.Clip.antiAlias, ui.EngineLayer oldLayer}) { - pushLayer(ClipPathLayer(path)); + ui.ClipPathEngineLayer pushClipPath( + ui.Path path, { + ui.Clip clipBehavior = ui.Clip.antiAlias, + ui.EngineLayer oldLayer, + }) { + pushLayer(ClipPathLayer(path, clipBehavior)); return null; } @override - ui.ClipRRectEngineLayer pushClipRRect(ui.RRect rrect, - {ui.Clip clipBehavior, ui.EngineLayer oldLayer}) { - pushLayer(ClipRRectLayer(rrect)); + ui.ClipRRectEngineLayer pushClipRRect( + ui.RRect rrect, { + ui.Clip clipBehavior, + ui.EngineLayer oldLayer, + }) { + pushLayer(ClipRRectLayer(rrect, clipBehavior)); return null; } @override - ui.ClipRectEngineLayer pushClipRect(ui.Rect rect, - {ui.Clip clipBehavior = ui.Clip.antiAlias, ui.EngineLayer oldLayer}) { - pushLayer(ClipRectLayer(rect)); + ui.ClipRectEngineLayer pushClipRect( + ui.Rect rect, { + ui.Clip clipBehavior = ui.Clip.antiAlias, + ui.EngineLayer oldLayer, + }) { + pushLayer(ClipRectLayer(rect, clipBehavior)); return null; } @override - ui.ColorFilterEngineLayer pushColorFilter(ui.ColorFilter filter, - {ui.ColorFilterEngineLayer oldLayer}) { + ui.ColorFilterEngineLayer pushColorFilter( + ui.ColorFilter filter, { + ui.ColorFilterEngineLayer oldLayer, + }) { assert(filter != null); throw UnimplementedError(); } + ui.ImageFilterEngineLayer pushImageFilter( + ui.ImageFilter filter, { + ui.ImageFilterEngineLayer oldLayer, + }) { + assert(filter != null); + pushLayer(ImageFilterLayer(filter)); + return null; + } + @override - ui.OffsetEngineLayer pushOffset(double dx, double dy, - {ui.EngineLayer oldLayer}) { + ui.OffsetEngineLayer pushOffset( + double dx, + double dy, { + ui.EngineLayer oldLayer, + }) { final Matrix4 matrix = Matrix4.translationValues(dx, dy, 0.0); final TransformLayer layer = TransformLayer(matrix); pushLayer(layer); @@ -133,8 +164,11 @@ class LayerSceneBuilder implements ui.SceneBuilder { } @override - ui.OpacityEngineLayer pushOpacity(int alpha, - {ui.EngineLayer oldLayer, ui.Offset offset = ui.Offset.zero}) { + ui.OpacityEngineLayer pushOpacity( + int alpha, { + ui.EngineLayer oldLayer, + ui.Offset offset = ui.Offset.zero, + }) { final OpacityLayer layer = OpacityLayer(alpha, offset); pushLayer(layer); return layer; @@ -157,14 +191,19 @@ class LayerSceneBuilder implements ui.SceneBuilder { @override ui.ShaderMaskEngineLayer pushShaderMask( - ui.Shader shader, ui.Rect maskRect, ui.BlendMode blendMode, - {ui.EngineLayer oldLayer}) { + ui.Shader shader, + ui.Rect maskRect, + ui.BlendMode blendMode, { + ui.EngineLayer oldLayer, + }) { throw UnimplementedError(); } @override - ui.TransformEngineLayer pushTransform(Float64List matrix4, - {ui.EngineLayer oldLayer}) { + ui.TransformEngineLayer pushTransform( + Float64List matrix4, { + ui.EngineLayer oldLayer, + }) { final Matrix4 matrix = Matrix4.fromList(matrix4); pushLayer(TransformLayer(matrix)); return null; @@ -200,8 +239,15 @@ class LayerSceneBuilder implements ui.SceneBuilder { } @override - void setProperties(double width, double height, double insetTop, - double insetRight, double insetBottom, double insetLeft, bool focusable) { + void setProperties( + double width, + double height, + double insetTop, + double insetRight, + double insetBottom, + double insetLeft, + bool focusable, + ) { throw UnimplementedError(); } } diff --git a/lib/web_ui/lib/src/engine/compositor/layer_tree.dart b/lib/web_ui/lib/src/engine/compositor/layer_tree.dart index ee428f5947d0f..d595fa98cfc38 100644 --- a/lib/web_ui/lib/src/engine/compositor/layer_tree.dart +++ b/lib/web_ui/lib/src/engine/compositor/layer_tree.dart @@ -12,6 +12,9 @@ class LayerTree { /// The size (in physical pixels) of the frame to paint this layer tree into. ui.Size frameSize; + /// The devicePixelRatio of the frame to paint this layer tree into. + double devicePixelRatio; + /// Performs a preroll phase before painting the layer tree. /// /// In this phase, the paint boundary for each layer is computed and @@ -19,8 +22,10 @@ class LayerTree { /// to raster. If [ignoreRasterCache] is `true`, then there will be no /// attempt to register pictures to cache. void preroll(Frame frame, {bool ignoreRasterCache = false}) { - final PrerollContext context = - PrerollContext(ignoreRasterCache ? null : frame.rasterCache); + final PrerollContext context = PrerollContext( + ignoreRasterCache ? null : frame.rasterCache, + frame.viewEmbedder, + ); rootLayer.preroll(context, Matrix4.identity()); } @@ -29,8 +34,19 @@ class LayerTree { /// If [ignoreRasterCache] is `true`, then the raster cache will /// not be used. void paint(Frame frame, {bool ignoreRasterCache = false}) { + final SkNWayCanvas internalNodesCanvas = SkNWayCanvas(); + internalNodesCanvas.addCanvas(frame.canvas); + final List overlayCanvases = + frame.viewEmbedder.getCurrentCanvases(); + for (int i = 0; i < overlayCanvases.length; i++) { + internalNodesCanvas.addCanvas(overlayCanvases[i]); + } final PaintContext context = PaintContext( - frame.canvas, ignoreRasterCache ? null : frame.rasterCache); + internalNodesCanvas, + frame.canvas, + ignoreRasterCache ? null : frame.rasterCache, + frame.viewEmbedder, + ); if (rootLayer.needsPainting) { rootLayer.paint(context); } @@ -45,7 +61,10 @@ class Frame { /// A cache of pre-rastered pictures. final RasterCache rasterCache; - Frame(this.canvas, this.rasterCache); + /// The platform view embedder. + final HtmlViewEmbedder viewEmbedder; + + Frame(this.canvas, this.rasterCache, this.viewEmbedder); /// Rasterize the given layer tree into this frame. bool raster(LayerTree layerTree, {bool ignoreRasterCache = false}) { @@ -61,7 +80,7 @@ class CompositorContext { RasterCache rasterCache; /// Acquire a frame using this compositor's settings. - Frame acquireFrame(SkCanvas canvas) { - return Frame(canvas, rasterCache); + Frame acquireFrame(SkCanvas canvas, HtmlViewEmbedder viewEmbedder) { + return Frame(canvas, rasterCache, viewEmbedder); } } diff --git a/lib/web_ui/lib/src/engine/compositor/n_way_canvas.dart b/lib/web_ui/lib/src/engine/compositor/n_way_canvas.dart new file mode 100644 index 0000000000000..7e59da1a1bbda --- /dev/null +++ b/lib/web_ui/lib/src/engine/compositor/n_way_canvas.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. + +part of engine; + +/// A virtual canvas that applies operations to multiple canvases at once. +class SkNWayCanvas { + final List _canvases = []; + + void addCanvas(SkCanvas canvas) { + _canvases.add(canvas); + } + + /// Calls [save] on all canvases. + int save() { + int saveCount; + for (int i = 0; i < _canvases.length; i++) { + saveCount = _canvases[i].save(); + } + return saveCount; + } + + /// Calls [saveLayer] on all canvases. + void saveLayer(ui.Rect bounds, ui.Paint paint) { + for (int i = 0; i < _canvases.length; i++) { + _canvases[i].saveLayer(bounds, paint); + } + } + + /// Calls [saveLayerWithFilter] on all canvases. + void saveLayerWithFilter(ui.Rect bounds, ui.ImageFilter filter) { + for (int i = 0; i < _canvases.length; i++) { + _canvases[i].saveLayerWithFilter(bounds, filter); + } + } + + /// Calls [restore] on all canvases. + void restore() { + for (int i = 0; i < _canvases.length; i++) { + _canvases[i].restore(); + } + } + + /// Calls [restoreToCount] on all canvases. + void restoreToCount(int count) { + for (int i = 0; i < _canvases.length; i++) { + _canvases[i].restoreToCount(count); + } + } + + /// Calls [translate] on all canvases. + void translate(double dx, double dy) { + for (int i = 0; i < _canvases.length; i++) { + _canvases[i].translate(dx, dy); + } + } + + /// Calls [transform] on all canvases. + void transform(Float64List matrix) { + for (int i = 0; i < _canvases.length; i++) { + _canvases[i].transform(matrix); + } + } + + /// Calls [clipPath] on all canvases. + void clipPath(ui.Path path, bool doAntiAlias) { + for (int i = 0; i < _canvases.length; i++) { + _canvases[i].clipPath(path, doAntiAlias); + } + } + + /// Calls [clipRect] on all canvases. + void clipRect(ui.Rect rect, ui.ClipOp clipOp, bool doAntiAlias) { + for (int i = 0; i < _canvases.length; i++) { + _canvases[i].clipRect(rect, clipOp, doAntiAlias); + } + } + + /// Calls [clipRRect] on all canvases. + void clipRRect(ui.RRect rrect, bool doAntiAlias) { + for (int i = 0; i < _canvases.length; i++) { + _canvases[i].clipRRect(rrect, doAntiAlias); + } + } +} diff --git a/lib/web_ui/lib/src/engine/compositor/painting.dart b/lib/web_ui/lib/src/engine/compositor/painting.dart new file mode 100644 index 0000000000000..883ab2f9e6c62 --- /dev/null +++ b/lib/web_ui/lib/src/engine/compositor/painting.dart @@ -0,0 +1,245 @@ +// 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. + +part of engine; + +/// The implementation of [ui.Paint] used by the CanvasKit backend. +/// +/// This class is backed by a Skia object that must be explicitly +/// deleted to avoid a memory leak. This is done by extending [SkiaObject]. +class SkPaint extends SkiaObject implements ui.Paint { + SkPaint(); + + static const ui.Color _defaultPaintColor = ui.Color(0xFF000000); + static final js.JsObject _skPaintStyleStroke = canvasKit['PaintStyle']['Stroke']; + static final js.JsObject _skPaintStyleFill = canvasKit['PaintStyle']['Fill']; + + @override + ui.BlendMode get blendMode => _blendMode; + @override + set blendMode(ui.BlendMode value) { + _blendMode = value; + _syncBlendMode(skiaObject); + } + void _syncBlendMode(js.JsObject object) { + final js.JsObject skBlendMode = makeSkBlendMode(_blendMode); + object.callMethod('setBlendMode', [skBlendMode]); + } + ui.BlendMode _blendMode = ui.BlendMode.srcOver; + + @override + ui.PaintingStyle get style => _style; + @override + set style(ui.PaintingStyle value) { + _style = value; + _syncStyle(skiaObject); + } + void _syncStyle(js.JsObject object) { + js.JsObject skPaintStyle; + switch (_style) { + case ui.PaintingStyle.stroke: + skPaintStyle = _skPaintStyleStroke; + break; + case ui.PaintingStyle.fill: + skPaintStyle = _skPaintStyleFill; + break; + } + object.callMethod('setStyle', [skPaintStyle]); + } + ui.PaintingStyle _style = ui.PaintingStyle.fill; + + @override + double get strokeWidth => _strokeWidth; + @override + set strokeWidth(double value) { + _strokeWidth = value; + _syncStrokeWidth(skiaObject); + } + void _syncStrokeWidth(js.JsObject object) { + object.callMethod('setStrokeWidth', [strokeWidth]); + } + double _strokeWidth = 0.0; + + // TODO(yjbanov): implement + @override + ui.StrokeCap get strokeCap => _strokeCap; + @override + set strokeCap(ui.StrokeCap value) { + _strokeCap = value; + } + ui.StrokeCap _strokeCap = ui.StrokeCap.butt; + + // TODO(yjbanov): implement + @override + ui.StrokeJoin get strokeJoin => _strokeJoin; + @override + set strokeJoin(ui.StrokeJoin value) { + _strokeJoin = value; + } + ui.StrokeJoin _strokeJoin = ui.StrokeJoin.miter; + + @override + bool get isAntiAlias => _isAntiAlias; + @override + set isAntiAlias(bool value) { + _isAntiAlias = value; + _syncAntiAlias(skiaObject); + } + void _syncAntiAlias(js.JsObject object) { + object.callMethod('setAntiAlias', [_isAntiAlias]); + } + bool _isAntiAlias = true; + + @override + ui.Color get color => _color; + @override + set color(ui.Color value) { + _color = value; + _syncColor(skiaObject); + } + void _syncColor(js.JsObject object) { + int colorValue = _defaultPaintColor.value; + if (_color != null) { + colorValue = _color.value; + } + object.callMethod('setColor', [colorValue]); + } + ui.Color _color = _defaultPaintColor; + + // TODO(yjbanov): implement + @override + bool get invertColors => _invertColors; + @override + set invertColors(bool value) { + _invertColors = value; + } + bool _invertColors = false; + + @override + ui.Shader get shader => _shader; + @override + set shader(ui.Shader value) { + _shader = value; + _syncShader(skiaObject); + } + void _syncShader(js.JsObject object) { + js.JsObject skShader; + if (_shader != null) { + skShader = _shader.createSkiaShader(); + } + object.callMethod('setShader', [skShader]); + } + EngineGradient _shader; + + @override + ui.MaskFilter get maskFilter => _maskFilter; + @override + set maskFilter(ui.MaskFilter value) { + _maskFilter = value; + _syncMaskFilter(skiaObject); + } + void _syncMaskFilter(js.JsObject object) { + js.JsObject skMaskFilter; + if (_maskFilter != null) { + final ui.BlurStyle blurStyle = _maskFilter.webOnlyBlurStyle; + final double sigma = _maskFilter.webOnlySigma; + + js.JsObject skBlurStyle; + switch (blurStyle) { + case ui.BlurStyle.normal: + skBlurStyle = canvasKit['BlurStyle']['Normal']; + break; + case ui.BlurStyle.solid: + skBlurStyle = canvasKit['BlurStyle']['Solid']; + break; + case ui.BlurStyle.outer: + skBlurStyle = canvasKit['BlurStyle']['Outer']; + break; + case ui.BlurStyle.inner: + skBlurStyle = canvasKit['BlurStyle']['Inner']; + break; + } + + skMaskFilter = canvasKit + .callMethod('MakeBlurMaskFilter', [skBlurStyle, sigma, true]); + } + object.callMethod('setMaskFilter', [skMaskFilter]); + } + ui.MaskFilter _maskFilter; + + // TODO(yjbanov): implement + @override + ui.FilterQuality get filterQuality => _filterQuality; + @override + set filterQuality(ui.FilterQuality value) { + _filterQuality = value; + } + ui.FilterQuality _filterQuality = ui.FilterQuality.none; + + @override + ui.ColorFilter get colorFilter => _colorFilter; + @override + set colorFilter(ui.ColorFilter value) { + _colorFilter = value; + _syncColorFilter(skiaObject); + } + void _syncColorFilter(js.JsObject object) { + js.JsObject skColorFilterJs; + if (_colorFilter != null) { + SkColorFilter skFilter = _colorFilter._toSkColorFilter(); + skColorFilterJs = skFilter.skColorFilter; + } + object.callMethod('setColorFilter', [skColorFilterJs]); + } + EngineColorFilter _colorFilter; + + // TODO(yjbanov): implement + @override + double get strokeMiterLimit => _strokeMiterLimit; + @override + set strokeMiterLimit(double value) { + _strokeMiterLimit = value; + } + double _strokeMiterLimit = 0.0; + + @override + ui.ImageFilter get imageFilter => _imageFilter; + @override + set imageFilter(ui.ImageFilter value) { + _imageFilter = value; + _syncImageFilter(skiaObject); + } + void _syncImageFilter(js.JsObject object) { + js.JsObject imageFilterJs; + if (_imageFilter != null) { + imageFilterJs = _imageFilter.skImageFilter; + } + object.callMethod('setImageFilter', [imageFilterJs]); + } + SkImageFilter _imageFilter; + + @override + js.JsObject createDefault() { + final obj = js.JsObject(canvasKit['SkPaint']); + // Sync fields whose Skia defaults are different from Flutter's. + _syncAntiAlias(obj); + _syncColor(obj); + return obj; + } + + @override + js.JsObject resurrect() { + final obj = js.JsObject(canvasKit['SkPaint']); + _syncBlendMode(obj); + _syncStyle(obj); + _syncStrokeWidth(obj); + _syncAntiAlias(obj); + _syncColor(obj); + _syncShader(obj); + _syncMaskFilter(obj); + _syncColorFilter(obj); + _syncImageFilter(obj); + return obj; + } +} diff --git a/lib/web_ui/lib/src/engine/compositor/path.dart b/lib/web_ui/lib/src/engine/compositor/path.dart index 43932a0fdbcac..56d9883589823 100644 --- a/lib/web_ui/lib/src/engine/compositor/path.dart +++ b/lib/web_ui/lib/src/engine/compositor/path.dart @@ -15,6 +15,11 @@ class SkPath implements ui.Path { fillType = ui.PathFillType.nonZero; } + // TODO(yjbanov): implement: https://github.com/flutter/flutter/issues/46812 + SkPath.from(SkPath other) { + throw UnimplementedError('SkPath.from is not implemented in the CanvasKit backend'); + } + SkPath._fromSkPath(js.JsObject skPath) : _skPath = skPath; ui.PathFillType _fillType; @@ -41,10 +46,11 @@ class SkPath implements ui.Path { @override void addArc(ui.Rect oval, double startAngle, double sweepAngle) { + const double toDegrees = 180.0 / math.pi; _skPath.callMethod('addArc', [ makeSkRect(oval), - startAngle, - sweepAngle, + startAngle * toDegrees, + sweepAngle * toDegrees, ]); } @@ -82,20 +88,9 @@ class SkPath implements ui.Path { @override void addPolygon(List points, bool close) { - // TODO(het): Use `addPoly` once CanvasKit makes it available. assert(points != null); - if (points.isEmpty) { - return; - } - - moveTo(points.first.dx, points.first.dy); - for (int i = 1; i < points.length; i++) { - final ui.Offset point = points[i]; - lineTo(point.dx, point.dy); - } - if (close) { - this.close(); - } + final Float32List encodedPoints = encodePointList(points); + _skPath.callMethod('addPoly', [encodedPoints, close]); } @override @@ -123,11 +118,11 @@ class SkPath implements ui.Path { @override void arcTo( ui.Rect rect, double startAngle, double sweepAngle, bool forceMoveTo) { - const double radsToDegrees = 180.0 / math.pi; + const double toDegrees = 180.0 / math.pi; _skPath.callMethod('arcTo', [ makeSkRect(rect), - startAngle * radsToDegrees, - sweepAngle * radsToDegrees, + startAngle * toDegrees, + sweepAngle * toDegrees, forceMoveTo, ]); } @@ -156,7 +151,7 @@ class SkPath implements ui.Path { @override ui.PathMetrics computeMetrics({bool forceClosed = false}) { - throw 'computeMetrics'; + return SkPathMetrics(this, forceClosed); } @override @@ -205,8 +200,7 @@ class SkPath implements ui.Path { @override ui.Rect getBounds() { final js.JsObject bounds = _skPath.callMethod('getBounds'); - return ui.Rect.fromLTRB( - bounds['fLeft'], bounds['fTop'], bounds['fRight'], bounds['fBottom']); + return fromSkRect(bounds); } @override @@ -230,33 +224,41 @@ class SkPath implements ui.Path { double rotation = 0.0, bool largeArc = false, bool clockwise = true}) { - throw 'relativeArcToPoint'; + _skPath.callMethod('rArcTo', [ + radius.x, + radius.y, + rotation, + !largeArc, + !clockwise, + arcEndDelta.dx, + arcEndDelta.dy, + ]); } @override void relativeConicTo(double x1, double y1, double x2, double y2, double w) { - throw 'relativeConicTo'; + _skPath.callMethod('rConicTo', [x1, y1, x2, y2, w]); } @override void relativeCubicTo( double x1, double y1, double x2, double y2, double x3, double y3) { - throw 'relativeCubicTo'; + _skPath.callMethod('rCubicTo', [x1, y1, x2, y2, x3, y3]); } @override void relativeLineTo(double dx, double dy) { - throw 'relativeLineTo'; + _skPath.callMethod('rLineTo', [dx, dy]); } @override void relativeMoveTo(double dx, double dy) { - throw 'relativeMoveTo'; + _skPath.callMethod('rMoveTo', [dx, dy]); } @override void relativeQuadraticBezierTo(double x1, double y1, double x2, double y2) { - throw 'relativeQuadraticBezierTo'; + _skPath.callMethod('rQuadTo', [x1, y1, x2, y2]); } @override @@ -274,10 +276,40 @@ class SkPath implements ui.Path { return SkPath._fromSkPath(newPath); } - @override - List get subpaths { - throw UnimplementedError( - 'Path.subpaths is not supported in the CanvasKit backend.'); + static SkPath combine( + ui.PathOperation operation, + ui.Path uiPath1, + ui.Path uiPath2, + ) { + final SkPath path1 = uiPath1; + final SkPath path2 = uiPath2; + js.JsObject pathOp; + switch (operation) { + case ui.PathOperation.difference: + pathOp = canvasKit['PathOp']['Difference']; + break; + case ui.PathOperation.intersect: + pathOp = canvasKit['PathOp']['Intersect']; + break; + case ui.PathOperation.union: + pathOp = canvasKit['PathOp']['Union']; + break; + case ui.PathOperation.xor: + pathOp = canvasKit['PathOp']['XOR']; + break; + case ui.PathOperation.reverseDifference: + pathOp = canvasKit['PathOp']['ReverseDifference']; + break; + } + final js.JsObject newPath = canvasKit.callMethod( + 'MakePathFromOp', + [ + path1._skPath, + path2._skPath, + pathOp, + ], + ); + return SkPath._fromSkPath(newPath); } @override @@ -287,27 +319,7 @@ class SkPath implements ui.Path { return SkPath._fromSkPath(newPath); } - @override - Ellipse get webOnlyPathAsCircle { - throw new UnimplementedError( - 'webOnlyPathAsCircle is not used in the CanvasKit backend.'); - } - - @override - ui.Rect get webOnlyPathAsRect { - throw new UnimplementedError( - 'webOnlyPathAsRect is not used in the CanvasKit backend.'); - } - - @override - ui.RRect get webOnlyPathAsRoundedRect { - throw new UnimplementedError( - 'webOnlyPathAsRoundedRect is not used in the CanvasKit backend.'); - } - - @override - List webOnlySerializeToCssPaint() { - throw new UnimplementedError( - 'webOnlySerializeToCssPaint is not used in the CanvasKit backend.'); + String toSvgString() { + return _skPath.callMethod('toSVGString'); } } diff --git a/lib/web_ui/lib/src/engine/compositor/path_metrics.dart b/lib/web_ui/lib/src/engine/compositor/path_metrics.dart new file mode 100644 index 0000000000000..bd9faea2e0bac --- /dev/null +++ b/lib/web_ui/lib/src/engine/compositor/path_metrics.dart @@ -0,0 +1,125 @@ +// 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. + +part of engine; + +class SkPathMetrics extends IterableBase + implements ui.PathMetrics { + SkPathMetrics(SkPath path, bool forceClosed) + : _iterator = SkPathMetricIterator._(_SkPathMeasure(path, forceClosed)); + + final Iterator _iterator; + + @override + Iterator get iterator => _iterator; +} + +class SkPathMetricIterator implements Iterator { + SkPathMetricIterator._(this._pathMeasure) : assert(_pathMeasure != null); + + _SkPathMetric _pathMetric; + _SkPathMeasure _pathMeasure; + + @override + ui.PathMetric get current => _pathMetric; + + @override + bool moveNext() { + if (_pathMeasure._nextContour()) { + _pathMetric = _SkPathMetric._(_pathMeasure); + return true; + } + _pathMetric = null; + return false; + } +} + +class _SkPathMetric implements ui.PathMetric { + _SkPathMetric._(this._measure) + : assert(_measure != null), + length = _measure.length(_measure.currentContourIndex), + isClosed = _measure.isClosed(_measure.currentContourIndex), + contourIndex = _measure.currentContourIndex; + + @override + final double length; + + @override + final bool isClosed; + + @override + final int contourIndex; + + final _SkPathMeasure _measure; + + @override + ui.Tangent getTangentForOffset(double distance) { + return _measure.getTangentForOffset(contourIndex, distance); + } + + @override + ui.Path extractPath(double start, double end, {bool startWithMoveTo = true}) { + return _measure.extractPath(contourIndex, start, end, + startWithMoveTo: startWithMoveTo); + } + + @override + String toString() => 'PathMetric{length: $length, isClosed: $isClosed, ' + 'contourIndex: $contourIndex}'; +} + +class _SkPathMeasure { + _SkPathMeasure(SkPath path, bool forceClosed) { + currentContourIndex = -1; + pathMeasure = js.JsObject(canvasKit['SkPathMeasure'], [ + path._skPath, + forceClosed, + 1, + ]); + } + + js.JsObject pathMeasure; + + double length(int contourIndex) { + assert(contourIndex == currentContourIndex, + 'PathMetrics are invalid if it is not the current contour.'); + return pathMeasure.callMethod('getLength'); + } + + ui.Tangent getTangentForOffset(int contourIndex, double distance) { + assert(contourIndex == currentContourIndex, + 'PathMetrics are invalid if it is not the current contour.'); + final js.JsObject posTan = + pathMeasure.callMethod('getPosTan', [distance]); + return ui.Tangent( + ui.Offset(posTan[0], posTan[1]), + ui.Offset(posTan[2], posTan[3]), + ); + } + + ui.Path extractPath(int contourIndex, double start, double end, + {bool startWithMoveTo = true}) { + assert(contourIndex == currentContourIndex, + 'PathMetrics are invalid if it is not the current contour.'); + final js.JsObject skPath = pathMeasure + .callMethod('getSegment', [start, end, startWithMoveTo]); + return SkPath._fromSkPath(skPath); + } + + bool isClosed(int contourIndex) { + assert(contourIndex == currentContourIndex, + 'PathMetrics are invalid if it is not the current contour.'); + return pathMeasure.callMethod('isClosed'); + } + + bool _nextContour() { + final bool next = pathMeasure.callMethod('nextContour'); + if (next) { + currentContourIndex++; + } + return next; + } + + int currentContourIndex; +} diff --git a/lib/web_ui/lib/src/engine/compositor/picture.dart b/lib/web_ui/lib/src/engine/compositor/picture.dart index 0796c75ea2257..d9eac4583c97a 100644 --- a/lib/web_ui/lib/src/engine/compositor/picture.dart +++ b/lib/web_ui/lib/src/engine/compositor/picture.dart @@ -6,24 +6,18 @@ part of engine; class SkPicture implements ui.Picture { final js.JsObject skPicture; + final ui.Rect cullRect; SkPicture(this.skPicture, this.cullRect); @override int get approximateBytesUsed => 0; - @override - final ui.Rect cullRect; - @override void dispose() { // TODO: implement dispose } - @override - // TODO: implement recordingCanvas - RecordingCanvas get recordingCanvas => null; - @override Future toImage(int width, int height) { // TODO: implement toImage diff --git a/lib/web_ui/lib/src/engine/compositor/picture_recorder.dart b/lib/web_ui/lib/src/engine/compositor/picture_recorder.dart index 9211bbddb7280..68e8cbdf3fe3e 100644 --- a/lib/web_ui/lib/src/engine/compositor/picture_recorder.dart +++ b/lib/web_ui/lib/src/engine/compositor/picture_recorder.dart @@ -5,30 +5,32 @@ part of engine; class SkPictureRecorder implements ui.PictureRecorder { - @override - ui.Rect cullRect; - + ui.Rect _cullRect; js.JsObject _recorder; + SkCanvas _recordingCanvas; - @override - RecordingCanvas beginRecording(ui.Rect bounds) { - cullRect = bounds; + SkCanvas beginRecording(ui.Rect bounds) { + _cullRect = bounds; _recorder = js.JsObject(canvasKit['SkPictureRecorder']); final js.JsObject skRect = js.JsObject(canvasKit['LTRBRect'], [bounds.left, bounds.top, bounds.right, bounds.bottom]); final js.JsObject skCanvas = _recorder.callMethod('beginRecording', [skRect]); - return SkRecordingCanvas(skCanvas); + _recordingCanvas = SkCanvas(skCanvas); + return _recordingCanvas; } + SkCanvas get recordingCanvas => _recordingCanvas; + @override ui.Picture endRecording() { final js.JsObject skPicture = _recorder.callMethod('finishRecordingAsPicture'); _recorder.callMethod('delete'); - return SkPicture(skPicture, cullRect); + _recorder = null; + return SkPicture(skPicture, _cullRect); } @override - bool get isRecording => false; + bool get isRecording => _recorder != null; } diff --git a/lib/web_ui/lib/src/engine/compositor/rasterizer.dart b/lib/web_ui/lib/src/engine/compositor/rasterizer.dart index 5a6dfcdda0771..c5246e7d03644 100644 --- a/lib/web_ui/lib/src/engine/compositor/rasterizer.dart +++ b/lib/web_ui/lib/src/engine/compositor/rasterizer.dart @@ -8,19 +8,54 @@ part of engine; class Rasterizer { final Surface surface; final CompositorContext context = CompositorContext(); + final HtmlViewEmbedder viewEmbedder = HtmlViewEmbedder(); + final List _postFrameCallbacks = []; - Rasterizer(this.surface); + Rasterizer(this.surface) { + surface.viewEmbedder = viewEmbedder; + } /// Creates a new frame from this rasterizer's surface, draws the given /// [LayerTree] into it, and then submits the frame. void draw(LayerTree layerTree) { - final SurfaceFrame frame = surface.acquireFrame(ui.window.physicalSize); - final SkCanvas canvas = frame.canvas; - final Frame compositorFrame = context.acquireFrame(canvas); + try { + if (layerTree == null) { + return; + } + + final ui.Size physicalSize = ui.window.physicalSize; + final ui.Size frameSize = ui.Size( + physicalSize.width.truncate().toDouble(), + physicalSize.height.truncate().toDouble(), + ); + + if (frameSize.isEmpty) { + return; + } + layerTree.frameSize = frameSize; + + final SurfaceFrame frame = surface.acquireFrame(layerTree.frameSize); + surface.viewEmbedder.frameSize = layerTree.frameSize; + final SkCanvas canvas = frame.skiaCanvas; + final Frame compositorFrame = context.acquireFrame(canvas, surface.viewEmbedder); - canvas.clear(); + compositorFrame.raster(layerTree, ignoreRasterCache: true); + surface.addToScene(); + frame.submit(); + surface.viewEmbedder.submitFrame(); + } finally { + _runPostFrameCallbacks(); + } + } + + void addPostFrameCallback(ui.VoidCallback callback) { + _postFrameCallbacks.add(callback); + } - compositorFrame.raster(layerTree, ignoreRasterCache: true); - frame.submit(); + void _runPostFrameCallbacks() { + for (int i = 0; i < _postFrameCallbacks.length; i++) { + final ui.VoidCallback callback = _postFrameCallbacks[i]; + callback(); + } } } diff --git a/lib/web_ui/lib/src/engine/compositor/recording_canvas.dart b/lib/web_ui/lib/src/engine/compositor/recording_canvas.dart deleted file mode 100644 index e6ab693615f76..0000000000000 --- a/lib/web_ui/lib/src/engine/compositor/recording_canvas.dart +++ /dev/null @@ -1,296 +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. - -part of engine; - -class SkRecordingCanvas implements RecordingCanvas { - final js.JsObject skCanvas; - SkRecordingCanvas(this.skCanvas); - - @override - bool _didDraw = true; - - @override - bool _hasArbitraryPaint = true; - - @override - int get saveCount => skCanvas.callMethod('getSaveCount'); - - // This is required to implement RecordingCanvas. - @override - int _saveCount = -1; - - @override - // TODO: implement _commands - List get _commands => null; - - @override - // TODO: implement _paintBounds - _PaintBounds get _paintBounds => null; - - @override - void apply(EngineCanvas engineCanvas) { - throw UnimplementedError("The Skia backend doesn't support apply()"); - } - - @override - void clipPath(ui.Path path, {bool doAntiAlias = true}) { - final SkPath skPath = path; - final js.JsObject intersectClipOp = canvasKit['ClipOp']['Intersect']; - skCanvas.callMethod('clipPath', [ - skPath._skPath, - intersectClipOp, - doAntiAlias, - ]); - } - - @override - void clipRRect( - ui.RRect rrect, { - bool doAntiAlias = true, - }) { - // TODO(het): Use `clipRRect` when CanvasKit makes it available. - // CanvasKit doesn't expose `Canvas.clipRRect`, so we create a path, add the - // RRect to it, and call clipPath with it. - final SkPath rrectPath = SkPath(); - rrectPath.addRRect(rrect); - clipPath(rrectPath, doAntiAlias: doAntiAlias); - } - - @override - void clipRect( - ui.Rect rect, { - ui.ClipOp clipOp = ui.ClipOp.intersect, - bool doAntiAlias = true, - }) { - js.JsObject skClipOp; - switch (clipOp) { - case ui.ClipOp.difference: - skClipOp = canvasKit['ClipOp']['Difference']; - break; - case ui.ClipOp.intersect: - skClipOp = canvasKit['ClipOp']['Intersect']; - break; - } - - skCanvas.callMethod( - 'clipRect', [makeSkRect(rect), skClipOp, doAntiAlias]); - } - - @override - ui.Rect computePaintBounds() { - throw UnimplementedError( - "The Skia backend doesn't use computePaintBounds()"); - } - - @override - void debugDumpCommands() { - throw UnimplementedError( - "The Skia backend doesn't use debugDumpCommands()"); - } - - @override - void debugEnforceArbitraryPaint() { - throw UnimplementedError( - "The Skia backend doesn't use debugEnforceArbitraryPaint()"); - } - - @override - String debugPrintCommands() { - throw UnimplementedError( - "The Skia backend doesn't use debugPrintCommands()"); - } - - @override - bool get didDraw => true; - - @override - void drawCircle(ui.Offset c, double radius, ui.Paint paint) { - skCanvas.callMethod('drawCircle', [ - c.dx, - c.dy, - radius, - makeSkPaint(paint), - ]); - } - - @override - void drawColor(ui.Color color, ui.BlendMode blendMode) { - // TODO(het): Implement this once SkCanvas.drawColor becomes available. - throw 'drawColor'; - } - - @override - void drawDRRect(ui.RRect outer, ui.RRect inner, ui.Paint paint) { - skCanvas.callMethod('drawDRRect', [ - makeSkRRect(outer), - makeSkRRect(inner), - makeSkPaint(paint), - ]); - } - - @override - void drawImage(ui.Image image, ui.Offset offset, ui.Paint paint) { - final SkImage skImage = image; - skCanvas.callMethod('drawImage', [ - skImage.skImage, - offset.dx, - offset.dy, - makeSkPaint(paint), - ]); - } - - @override - void drawImageRect(ui.Image image, ui.Rect src, ui.Rect dst, ui.Paint paint) { - final SkImage skImage = image; - skCanvas.callMethod('drawImageRect', [ - skImage.skImage, - makeSkRect(src), - makeSkRect(dst), - makeSkPaint(paint), - false, - ]); - } - - @override - void drawLine(ui.Offset p1, ui.Offset p2, ui.Paint paint) { - skCanvas.callMethod('drawLine', [ - p1.dx, - p1.dy, - p2.dx, - p2.dy, - makeSkPaint(paint), - ]); - } - - @override - void drawOval(ui.Rect rect, ui.Paint paint) { - skCanvas.callMethod('drawOval', [ - makeSkRect(rect), - makeSkPaint(paint), - ]); - } - - @override - void drawPaint(ui.Paint paint) { - skCanvas.callMethod('drawPaint', [makeSkPaint(paint)]); - } - - @override - void drawParagraph(ui.Paragraph paragraph, ui.Offset offset) { - // TODO(het): This doesn't support most paragraph features. We are just - // creating a font from the family and size, and drawing it with - // ShapedText. - final EngineParagraph engineParagraph = paragraph; - final ParagraphGeometricStyle style = engineParagraph.geometricStyle; - final js.JsObject skFont = skiaFontCollection.getFont( - style.effectiveFontFamily, style.fontSize ?? 12.0); - final js.JsObject skShapedTextOpts = js.JsObject.jsify({ - 'font': skFont, - 'leftToRight': true, - 'text': engineParagraph.plainText, - 'width': engineParagraph.width + 1, - }); - final js.JsObject skShapedText = - js.JsObject(canvasKit['ShapedText'], [skShapedTextOpts]); - skCanvas.callMethod('drawText', [ - skShapedText, - offset.dx + engineParagraph._alignOffset, - offset.dy, - makeSkPaint(engineParagraph._paint) - ]); - } - - @override - void drawPath(ui.Path path, ui.Paint paint) { - final js.JsObject skPaint = makeSkPaint(paint); - final SkPath enginePath = path; - final js.JsObject skPath = enginePath._skPath; - skCanvas.callMethod('drawPath', [skPath, skPaint]); - } - - @override - void drawRRect(ui.RRect rrect, ui.Paint paint) { - skCanvas.callMethod('drawRRect', [ - makeSkRRect(rrect), - makeSkPaint(paint), - ]); - } - - @override - void drawRect(ui.Rect rect, ui.Paint paint) { - final js.JsObject skRect = makeSkRect(rect); - final js.JsObject skPaint = makeSkPaint(paint); - skCanvas.callMethod('drawRect', [skRect, skPaint]); - } - - @override - void drawShadow(ui.Path path, ui.Color color, double elevation, - bool transparentOccluder) { - drawSkShadow(skCanvas, path, color, elevation, transparentOccluder); - } - - @override - void drawVertices( - ui.Vertices vertices, ui.BlendMode blendMode, ui.Paint paint) { - SkVertices skVertices = vertices; - skCanvas.callMethod('drawVertices', [ - skVertices.skVertices, - makeSkBlendMode(blendMode), - makeSkPaint(paint) - ]); - } - - @override - bool get hasArbitraryPaint => true; - - @override - void restore() { - skCanvas.callMethod('restore'); - } - - @override - void rotate(double radians) { - skCanvas - .callMethod('rotate', [radians * 180.0 / math.pi, 0.0, 0.0]); - } - - @override - void save() { - skCanvas.callMethod('save'); - } - - @override - void saveLayer(ui.Rect bounds, ui.Paint paint) { - skCanvas.callMethod('saveLayer', [ - makeSkRect(bounds), - makeSkPaint(paint), - ]); - } - - @override - void saveLayerWithoutBounds(ui.Paint paint) { - skCanvas.callMethod('saveLayer', [null, makeSkPaint(paint)]); - } - - @override - void scale(double sx, double sy) { - skCanvas.callMethod('scale', [sx, sy]); - } - - @override - void skew(double sx, double sy) { - skCanvas.callMethod('skew', [sx, sy]); - } - - @override - void transform(Float64List matrix4) { - skCanvas.callMethod('concat', >[makeSkMatrix(matrix4)]); - } - - @override - void translate(double dx, double dy) { - skCanvas.callMethod('translate', [dx, dy]); - } -} diff --git a/lib/web_ui/lib/src/engine/compositor/surface.dart b/lib/web_ui/lib/src/engine/compositor/surface.dart index 9b294d0dee49c..03930fb996e45 100644 --- a/lib/web_ui/lib/src/engine/compositor/surface.dart +++ b/lib/web_ui/lib/src/engine/compositor/surface.dart @@ -4,64 +4,161 @@ part of engine; +typedef SubmitCallback = bool Function(SurfaceFrame, SkCanvas); + /// A frame which contains a canvas to be drawn into. class SurfaceFrame { - final void Function(SkCanvas) submitFn; - final SkCanvas canvas; - SurfaceFrame(this.submitFn, this.canvas); + final SkSurface skiaSurface; + final SubmitCallback submitCallback; + bool _submitted; + + SurfaceFrame(this.skiaSurface, this.submitCallback) + : _submitted = false, + assert(submitCallback != null); /// Submit this frame to be drawn. - void submit() { - submitFn(canvas); + bool submit() { + if (_submitted) { + return false; + } + return submitCallback(this, skiaCanvas); } + + SkCanvas get skiaCanvas => skiaSurface?.getCanvas(); } /// A surface which can be drawn into by the compositor. /// -/// The underlying representation is a [BitmapCanvas], which can be reused by -/// successive frames if they are the same size. Otherwise, a new canvas is +/// The underlying representation is a [SkSurface], which can be reused by +/// successive frames if they are the same size. Otherwise, a new [SkSurface] is /// created. class Surface { - final _CanvasCache canvasCache = _CanvasCache(); + SkSurface _surface; + html.Element htmlElement; - /// This function is called with the canvas once drawing on it has been - /// completed for a frame. - final void Function(SkCanvas) submitFunction; + bool _addedToScene = false; - Surface(this.submitFunction); + /// The default view embedder. Coordinates embedding platform views and + /// overlaying subsequent draw operations on top. + HtmlViewEmbedder viewEmbedder; /// Acquire a frame of the given [size] containing a drawable canvas. /// /// The given [size] is in physical pixels. SurfaceFrame acquireFrame(ui.Size size) { - final SkCanvas canvas = canvasCache.acquireCanvas(size); - return SurfaceFrame(submitFunction, canvas); + final SkSurface surface = acquireRenderSurface(size); + + if (surface == null) return null; + + SubmitCallback submitCallback = + (SurfaceFrame surfaceFrame, SkCanvas canvas) { + return _presentSurface(canvas); + }; + + return SurfaceFrame(surface, submitCallback); } - Matrix4 get rootTransformation => null; -} + SkSurface acquireRenderSurface(ui.Size size) { + if (!_createOrUpdateSurfaces(size)) { + return null; + } + return _surface; + } -class _CanvasCache { - SkCanvas _canvas; + void addToScene() { + if (!_addedToScene) { + skiaSceneHost.children.insert(0, htmlElement); + } + _addedToScene = true; + } - SkCanvas acquireCanvas(ui.Size size) { - assert(size != null); - if (size == _canvas?.size) { - return _canvas; + bool _createOrUpdateSurfaces(ui.Size size) { + if (_surface != null && + size == + ui.Size( + _surface.width().toDouble(), + _surface.height().toDouble(), + )) { + return true; } + + _surface?.dispose(); + _surface = null; + htmlElement?.remove(); + htmlElement = null; + _addedToScene = false; + + if (size.isEmpty) { + html.window.console.error('Cannot create surfaces of empty size.'); + return false; + } + _surface = _wrapHtmlCanvas(size); + + if (_surface == null) { + html.window.console.error('Could not create a surface.'); + return false; + } + + return true; + } + + SkSurface _wrapHtmlCanvas(ui.Size size) { final ui.Size logicalSize = size / ui.window.devicePixelRatio; - final html.CanvasElement htmlCanvas = - html.CanvasElement(width: size.width.ceil(), height: size.height.ceil()) - ..id = 'flt-sk-canvas'; + final html.CanvasElement htmlCanvas = html.CanvasElement( + width: size.width.ceil(), height: size.height.ceil()); htmlCanvas.style ..position = 'absolute' ..width = '${logicalSize.width.ceil()}px' ..height = '${logicalSize.height.ceil()}px'; - domRenderer.renderScene(htmlCanvas); - final js.JsObject surface = - canvasKit.callMethod('MakeCanvasSurface', ['flt-sk-canvas']); - final js.JsObject skCanvas = surface.callMethod('getCanvas'); - _canvas = SkCanvas(skCanvas, htmlCanvas, surface, size); - return _canvas; + final js.JsObject glContext = canvasKit + .callMethod('GetWebGLContext', [htmlCanvas]); + final js.JsObject grContext = + canvasKit.callMethod('MakeGrContext', [glContext]); + final js.JsObject skSurface = + canvasKit.callMethod('MakeOnScreenGLSurface', [ + grContext, + size.width, + size.height, + ]); + + htmlElement = htmlCanvas; + + if (skSurface == null) { + return null; + } else { + return SkSurface(skSurface, glContext); + } + } + + bool _presentSurface(SkCanvas canvas) { + if (canvas == null) { + return false; + } + + canvasKit.callMethod('setCurrentContext', [_surface.context]); + _surface.getCanvas().flush(); + return true; + } +} + +/// A Dart wrapper around Skia's SkSurface. +class SkSurface { + final js.JsObject _surface; + final js.JsObject _glContext; + + SkSurface(this._surface, this._glContext); + + SkCanvas getCanvas() { + final js.JsObject skCanvas = _surface.callMethod('getCanvas'); + return SkCanvas(skCanvas); + } + + js.JsObject get context => _glContext; + + int width() => _surface.callMethod('width'); + int height() => _surface.callMethod('height'); + + void dispose() { + _surface.callMethod('dispose'); } } diff --git a/lib/web_ui/lib/src/engine/compositor/text.dart b/lib/web_ui/lib/src/engine/compositor/text.dart new file mode 100644 index 0000000000000..06f6805b88020 --- /dev/null +++ b/lib/web_ui/lib/src/engine/compositor/text.dart @@ -0,0 +1,496 @@ +// 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. +part of engine; + +// TODO(hterkelsen): Get rid of this once we can register font families with +// custom names. +/// CanvasKit does not yet allow us to specify family names when registering +/// fonts. CanvasKit reads the font name from the font's bytes. So, we map +/// some common family names to how they are registered in the Gallery app. +const Map _fontFamilyOverrides = { + 'GoogleSans': 'Google Sans', + 'GoogleSansDisplay': 'Google Sans Display', + 'MaterialIcons': 'Material Icons', + 'LibreFranklin': 'Libre Franklin', + 'AbrilFatface': 'Abril Fatface', + 'packages/cupertino_icons/CupertinoIcons': 'CupertinoIcons', +}; + +class SkParagraphStyle implements ui.ParagraphStyle { + SkParagraphStyle({ + ui.TextAlign textAlign, + ui.TextDirection textDirection, + int maxLines, + String fontFamily, + double fontSize, + double height, + ui.FontWeight fontWeight, + ui.FontStyle fontStyle, + ui.StrutStyle strutStyle, + String ellipsis, + ui.Locale locale, + }) { + skParagraphStyle = toSkParagraphStyle( + textAlign, + textDirection, + maxLines, + fontFamily, + fontSize, + height, + fontWeight, + fontStyle, + ellipsis, + ); + assert(skParagraphStyle != null); + _textDirection = textDirection ?? ui.TextDirection.ltr; + _fontFamily = fontFamily; + } + + js.JsObject skParagraphStyle; + ui.TextDirection _textDirection; + String _fontFamily; + + static Map toSkTextStyle( + String fontFamily, + double fontSize, + ui.FontWeight fontWeight, + ui.FontStyle fontStyle, + ) { + final Map skTextStyle = {}; + if (fontWeight != null || fontStyle != null) { + skTextStyle['fontStyle'] = toSkFontStyle(fontWeight, fontStyle); + } + + if (fontSize != null) { + skTextStyle['fontSize'] = fontSize; + } + + if (fontFamily == null || + !skiaFontCollection.registeredFamilies.contains(fontFamily)) { + fontFamily = 'Roboto'; + } + if (_fontFamilyOverrides.containsKey(fontFamily)) { + fontFamily = _fontFamilyOverrides[fontFamily]; + } + skTextStyle['fontFamilies'] = [fontFamily]; + + return skTextStyle; + } + + static js.JsObject toSkParagraphStyle( + ui.TextAlign textAlign, + ui.TextDirection textDirection, + int maxLines, + String fontFamily, + double fontSize, + double height, + ui.FontWeight fontWeight, + ui.FontStyle fontStyle, + String ellipsis, + ) { + final Map skParagraphStyle = {}; + + if (textAlign != null) { + switch (textAlign) { + case ui.TextAlign.left: + skParagraphStyle['textAlign'] = canvasKit['TextAlign']['Left']; + break; + case ui.TextAlign.right: + skParagraphStyle['textAlign'] = canvasKit['TextAlign']['Right']; + break; + case ui.TextAlign.center: + skParagraphStyle['textAlign'] = canvasKit['TextAlign']['Center']; + break; + case ui.TextAlign.justify: + skParagraphStyle['textAlign'] = canvasKit['TextAlign']['Justify']; + break; + case ui.TextAlign.start: + skParagraphStyle['textAlign'] = canvasKit['TextAlign']['Start']; + break; + case ui.TextAlign.end: + skParagraphStyle['textAlign'] = canvasKit['TextAlign']['End']; + break; + } + } + + if (textDirection != null) { + switch (textDirection) { + case ui.TextDirection.ltr: + skParagraphStyle['textDirection'] = canvasKit['TextDirection']['LTR']; + break; + case ui.TextDirection.rtl: + skParagraphStyle['textDirection'] = canvasKit['TextDirection']['RTL']; + break; + } + } + + if (height != null) { + skParagraphStyle['heightMultiplier'] = height; + } + + if (maxLines != null) { + skParagraphStyle['maxLines'] = maxLines; + } + + if (ellipsis != null) { + skParagraphStyle['ellipsis'] = ellipsis; + } + + skParagraphStyle['textStyle'] = + toSkTextStyle(fontFamily, fontSize, fontWeight, fontStyle); + + return canvasKit.callMethod( + 'ParagraphStyle', [js.JsObject.jsify(skParagraphStyle)]); + } +} + +class SkTextStyle implements ui.TextStyle { + js.JsObject skTextStyle; + + SkTextStyle({ + ui.Color color, + ui.TextDecoration decoration, + ui.Color decorationColor, + ui.TextDecorationStyle decorationStyle, + double decorationThickness, + ui.FontWeight fontWeight, + ui.FontStyle fontStyle, + ui.TextBaseline textBaseline, + String fontFamily, + List fontFamilyFallback, + double fontSize, + double letterSpacing, + double wordSpacing, + double height, + ui.Locale locale, + SkPaint background, + SkPaint foreground, + List shadows, + List fontFeatures, + }) { + final Map style = {}; + + if (background != null) { + style['backgroundColor'] = background.skiaObject; + } + + if (color != null) { + style['color'] = color.value; + } + + if (decoration != null) { + int decorationValue = canvasKit['NoDecoration']; + if (decoration.contains(ui.TextDecoration.underline)) { + decorationValue |= canvasKit['UnderlineDecoration']; + } + if (decoration.contains(ui.TextDecoration.overline)) { + decorationValue |= canvasKit['OverlineDecoration']; + } + if (decoration.contains(ui.TextDecoration.lineThrough)) { + decorationValue |= canvasKit['LineThroughDecoration']; + } + style['decoration'] = decorationValue; + } + + if (decorationThickness != null) { + style['decorationThickness'] = decorationThickness; + } + + if (fontSize != null) { + style['fontSize'] = fontSize; + } + + if (fontFamily == null || + !skiaFontCollection.registeredFamilies.contains(fontFamily)) { + fontFamily = 'Roboto'; + } + + if (_fontFamilyOverrides.containsKey(fontFamily)) { + fontFamily = _fontFamilyOverrides[fontFamily]; + } + List fontFamilies = [fontFamily]; + if (fontFamilyFallback != null) { + fontFamilies.addAll(fontFamilies); + } + + style['fontFamilies'] = fontFamilies; + + if (fontWeight != null || fontStyle != null) { + style['fontStyle'] = toSkFontStyle(fontWeight, fontStyle); + } + + if (foreground != null) { + style['foreground'] = foreground.skiaObject; + } + + // TODO(hterkelsen): Add support for + // - decorationColor + // - decorationStyle + // - textBaseline + // - letterSpacing + // - wordSpacing + // - height + // - locale + // - shadows + // - fontFeatures + skTextStyle = canvasKit + .callMethod('TextStyle', [js.JsObject.jsify(style)]); + assert(skTextStyle != null); + } +} + +Map toSkFontStyle( + ui.FontWeight fontWeight, ui.FontStyle fontStyle) { + Map style = {}; + if (fontWeight != null) { + switch (fontWeight) { + case ui.FontWeight.w100: + style['weight'] = canvasKit['FontWeight']['Thin']; + break; + case ui.FontWeight.w200: + style['weight'] = canvasKit['FontWeight']['ExtraLight']; + break; + case ui.FontWeight.w300: + style['weight'] = canvasKit['FontWeight']['Light']; + break; + case ui.FontWeight.w400: + style['weight'] = canvasKit['FontWeight']['Normal']; + break; + case ui.FontWeight.w500: + style['weight'] = canvasKit['FontWeight']['Medium']; + break; + case ui.FontWeight.w600: + style['weight'] = canvasKit['FontWeight']['SemiBold']; + break; + case ui.FontWeight.w700: + style['weight'] = canvasKit['FontWeight']['Bold']; + break; + case ui.FontWeight.w800: + style['weight'] = canvasKit['FontWeight']['ExtraBold']; + break; + case ui.FontWeight.w900: + style['weight'] = canvasKit['FontWeight']['ExtraBlack']; + break; + } + } + + if (fontStyle != null) { + switch (fontStyle) { + case ui.FontStyle.normal: + style['slant'] = canvasKit['FontSlant']['Upright']; + break; + case ui.FontStyle.italic: + style['slant'] = canvasKit['FontSlant']['Italic']; + break; + } + } + return style; +} + +class SkParagraph implements ui.Paragraph { + SkParagraph(this.skParagraph, this._textDirection, this._fontFamily); + + final js.JsObject skParagraph; + final ui.TextDirection _textDirection; + final String _fontFamily; + + @override + double get alphabeticBaseline => + skParagraph.callMethod('getAlphabeticBaseline'); + + @override + bool get didExceedMaxLines => skParagraph.callMethod('didExceedMaxLines'); + + @override + double get height => skParagraph.callMethod('getHeight'); + + @override + double get ideographicBaseline => + skParagraph.callMethod('getIdeographicBaseline'); + + @override + double get longestLine => skParagraph.callMethod('getLongestLine'); + + @override + double get maxIntrinsicWidth => + skParagraph.callMethod('getMaxIntrinsicWidth'); + + @override + double get minIntrinsicWidth => + skParagraph.callMethod('getMinIntrinsicWidth'); + + @override + double get width => skParagraph.callMethod('getMaxWidth'); + + // TODO(hterkelsen): Implement placeholders once it's in CanvasKit + @override + List getBoxesForPlaceholders() { + return const []; + } + + @override + List getBoxesForRange( + int start, + int end, { + ui.BoxHeightStyle boxHeightStyle: ui.BoxHeightStyle.tight, + ui.BoxWidthStyle boxWidthStyle: ui.BoxWidthStyle.tight, + }) { + js.JsObject heightStyle; + switch (boxHeightStyle) { + case ui.BoxHeightStyle.tight: + heightStyle = canvasKit['RectHeightStyle']['Tight']; + break; + case ui.BoxHeightStyle.max: + heightStyle = canvasKit['RectHeightStyle']['Max']; + break; + default: + // TODO(hterkelsen): Support all height styles + html.window.console.warn( + 'We do not support $boxHeightStyle. Defaulting to BoxHeightStyle.tight'); + heightStyle = canvasKit['RectHeightStyle']['Tight']; + break; + } + + js.JsObject widthStyle; + switch (boxWidthStyle) { + case ui.BoxWidthStyle.tight: + widthStyle = canvasKit['RectWidthStyle']['Tight']; + break; + case ui.BoxWidthStyle.max: + widthStyle = canvasKit['RectWidthStyle']['Max']; + break; + } + + List skRects = + skParagraph.callMethod('getRectsForRange', [ + start, + end, + heightStyle, + widthStyle, + ]); + + List result = List(skRects.length); + + for (int i = 0; i < skRects.length; i++) { + final js.JsObject rect = skRects[i]; + result[i] = ui.TextBox.fromLTRBD( + rect['fLeft'], + rect['fTop'], + rect['fRight'], + rect['fBottom'], + _textDirection, + ); + } + + return result; + } + + @override + ui.TextPosition getPositionForOffset(ui.Offset offset) { + js.JsObject positionWithAffinity = + skParagraph.callMethod('getGlyphPositionAtCoordinate', [ + offset.dx, + offset.dy, + ]); + return fromPositionWithAffinity(positionWithAffinity); + } + + @override + ui.TextRange getWordBoundary(ui.TextPosition position) { + js.JsObject skRange = + skParagraph.callMethod('getWordBoundary', [position.offset]); + return ui.TextRange(start: skRange['start'], end: skRange['end']); + } + + @override + void layout(ui.ParagraphConstraints constraints) { + assert(constraints.width != null); + // TODO(het): CanvasKit throws an exception when laid out with + // a font that wasn't registered. + try { + skParagraph.callMethod('layout', [constraints.width]); + } catch (e) { + html.window.console.warn('CanvasKit threw an exception while laying ' + 'out the paragraph. The font was "$_fontFamily". Exception:\n$e'); + rethrow; + } + } + + @override + ui.TextRange getLineBoundary(ui.TextPosition position) { + // TODO(hterkelsen): Implement this when it's added to CanvasKit + throw UnimplementedError('getLineBoundary'); + } + + @override + List computeLineMetrics() { + // TODO(hterkelsen): Implement this when it's added to CanvasKit + throw UnimplementedError('computeLineMetrics'); + } +} + +class SkParagraphBuilder implements ui.ParagraphBuilder { + js.JsObject _paragraphBuilder; + ui.TextDirection _textDirection; + String _fontFamily; + + SkParagraphBuilder(ui.ParagraphStyle style) { + SkParagraphStyle skStyle = style; + _textDirection = skStyle._textDirection; + _fontFamily = skStyle._fontFamily; + _paragraphBuilder = canvasKit['ParagraphBuilder'].callMethod( + 'Make', + [ + skStyle.skParagraphStyle, + skiaFontCollection.skFontMgr, + ], + ); + } + + // TODO(hterkelsen): Implement placeholders. + @override + void addPlaceholder( + double width, + double height, + ui.PlaceholderAlignment alignment, { + double scale, + double baselineOffset, + ui.TextBaseline baseline, + }) { + throw UnimplementedError('addPlaceholder'); + } + + @override + void addText(String text) { + _paragraphBuilder.callMethod('addText', [text]); + } + + @override + ui.Paragraph build() { + final SkParagraph paragraph = SkParagraph( + _paragraphBuilder.callMethod('build'), _textDirection, _fontFamily); + _paragraphBuilder.callMethod('delete'); + _paragraphBuilder = null; + return paragraph; + } + + @override + int get placeholderCount => throw UnimplementedError('placeholderCount'); + + // TODO(hterkelsen): Implement this once CanvasKit exposes placeholders. + @override + List get placeholderScales => const []; + + @override + void pop() { + _paragraphBuilder.callMethod('pop'); + } + + @override + void pushStyle(ui.TextStyle style) { + final SkTextStyle skStyle = style; + _paragraphBuilder + .callMethod('pushStyle', [skStyle.skTextStyle]); + } +} diff --git a/lib/web_ui/lib/src/engine/compositor/util.dart b/lib/web_ui/lib/src/engine/compositor/util.dart index 157e3b9ffdcb1..8a94bbfe80514 100644 --- a/lib/web_ui/lib/src/engine/compositor/util.dart +++ b/lib/web_ui/lib/src/engine/compositor/util.dart @@ -4,6 +4,97 @@ part of engine; +/// An object backed by a [js.JsObject] mapped onto a Skia C++ object in the +/// WebAssembly heap. +/// +/// These objects are automatically deleted when no longer used. +/// +/// Because there is no feedback from JavaScript's GC (no destructors or +/// finalizers), we pessimistically delete the underlying C++ object before +/// the Dart object is garbage-collected. The current algorithm deletes objects +/// at the end of every frame. This allows reusing the C++ objects within the +/// frame. In the future we may add smarter strategies that will allow us to +/// reuse C++ objects across frames. +/// +/// The lifecycle of a C++ object is as follows: +/// +/// - Create default: when instantiating a C++ object for a Dart object for the +/// first time, the C++ object is populated with default data (the defaults are +/// defined by Flutter; Skia defaults are corrected if necessary). The +/// default object is created by [createDefault]. +/// - Zero or more cycles of delete + resurrect: when a Dart object is reused +/// after its C++ object is deleted we create a new C++ object populated with +/// data from the current state of the Dart object. This is done using the +/// [resurrect] method. +/// - Final delete: if a Dart object is never reused, it is GC'd after its +/// underlying C++ object is deleted. This is implemented by [SkiaObjects]. +abstract class SkiaObject { + SkiaObject() { + _skiaObject = createDefault(); + SkiaObjects.manage(this); + } + + /// The JavaScript object that's mapped onto a Skia C++ object in the WebAssembly heap. + js.JsObject get skiaObject { + if (_skiaObject == null) { + _skiaObject = resurrect(); + SkiaObjects.manage(this); + } + return _skiaObject; + } + + /// Do not use this field outside this class. Use [skiaObject] instead. + js.JsObject _skiaObject; + + /// Instantiates a new Skia-backed JavaScript object containing default + /// values. + /// + /// The object is expected to represent Flutter's defaults. If Skia uses + /// different defaults from those used by Flutter, this method is expected + /// initialize the object to Flutter's defaults. + js.JsObject createDefault(); + + /// Creates a new Skia-backed JavaScript object containing data representing + /// the current state of the Dart object. + js.JsObject resurrect(); +} + +/// Singleton that manages the lifecycles of [SkiaObject] instances. +class SkiaObjects { + // TODO(yjbanov): some sort of LRU strategy would allow us to reuse objects + // beyond a single frame. + @visibleForTesting + static final List managedObjects = () { + window.rasterizer.addPostFrameCallback(postFrameCleanUp); + return []; + }(); + + /// Starts managing the lifecycle of [object]. + /// + /// The object's underlying WASM object is deleted by calling the + /// "delete" method when it goes out of scope. + /// + /// The current implementation deletes objects at the end of every frame. + static void manage(SkiaObject object) { + managedObjects.add(object); + } + + /// Deletes all C++ objects created this frame. + static void postFrameCleanUp() { + if (managedObjects.isEmpty) { + return; + } + + for (int i = 0; i < managedObjects.length; i++) { + final SkiaObject object = managedObjects[i]; + object._skiaObject.callMethod('delete'); + object._skiaObject = null; + } + + managedObjects.clear(); + } +} + js.JsObject makeSkRect(ui.Rect rect) { return js.JsObject(canvasKit['LTRBRect'], [rect.left, rect.top, rect.right, rect.bottom]); @@ -23,6 +114,31 @@ js.JsObject makeSkRRect(ui.RRect rrect) { }); } +ui.Rect fromSkRect(js.JsObject skRect) { + return ui.Rect.fromLTRB( + skRect['fLeft'], + skRect['fTop'], + skRect['fRight'], + skRect['fBottom'], + ); +} + +ui.TextPosition fromPositionWithAffinity(js.JsObject positionWithAffinity) { + if (positionWithAffinity['affinity'] == canvasKit['Affinity']['Upstream']) { + return ui.TextPosition( + offset: positionWithAffinity['pos'], + affinity: ui.TextAffinity.upstream, + ); + } else { + assert(positionWithAffinity['affinity'] == + canvasKit['Affinity']['Downstream']); + return ui.TextPosition( + offset: positionWithAffinity['pos'], + affinity: ui.TextAffinity.downstream, + ); + } +} + js.JsArray makeSkPoint(ui.Offset point) { final js.JsArray skPoint = js.JsArray(); skPoint.length = 2; @@ -31,6 +147,35 @@ js.JsArray makeSkPoint(ui.Offset point) { return skPoint; } +/// Creates a point list using a typed buffer created by CanvasKit.Malloc. +Float32List encodePointList(List points) { + assert(points != null); + final int pointCount = points.length; + final Float32List result = canvasKit.callMethod('Malloc', [js.context['Float32Array'], pointCount * 2]); + for (int i = 0; i < pointCount; ++i) { + final int xIndex = i * 2; + final int yIndex = xIndex + 1; + final ui.Offset point = points[i]; + assert(_offsetIsValid(point)); + result[xIndex] = point.dx; + result[yIndex] = point.dy; + } + return result; +} + +js.JsObject makeSkPointMode(ui.PointMode pointMode) { + switch (pointMode) { + case ui.PointMode.points: + return canvasKit['PointMode']['Points']; + case ui.PointMode.lines: + return canvasKit['PointMode']['Lines']; + case ui.PointMode.polygon: + return canvasKit['PointMode']['Polygon']; + default: + throw StateError('Unrecognized point mode $pointMode'); + } +} + js.JsObject makeSkBlendMode(ui.BlendMode blendMode) { switch (blendMode) { case ui.BlendMode.clear: @@ -96,75 +241,6 @@ js.JsObject makeSkBlendMode(ui.BlendMode blendMode) { } } -js.JsObject makeSkPaint(ui.Paint paint) { - final dynamic skPaint = js.JsObject(canvasKit['SkPaint']); - - if (paint.shader != null) { - final EngineGradient engineShader = paint.shader; - skPaint.callMethod( - 'setShader', [engineShader.createSkiaShader()]); - } - - if (paint.color != null) { - skPaint.callMethod('setColor', [paint.color.value]); - } - - js.JsObject skPaintStyle; - switch (paint.style) { - case ui.PaintingStyle.stroke: - skPaintStyle = canvasKit['PaintStyle']['Stroke']; - break; - case ui.PaintingStyle.fill: - skPaintStyle = canvasKit['PaintStyle']['Fill']; - break; - } - skPaint.callMethod('setStyle', [skPaintStyle]); - - js.JsObject skBlendMode = makeSkBlendMode(paint.blendMode); - if (skBlendMode != null) { - skPaint.callMethod('setBlendMode', [skBlendMode]); - } - - skPaint.callMethod('setAntiAlias', [paint.isAntiAlias]); - - if (paint.strokeWidth != 0.0) { - skPaint.callMethod('setStrokeWidth', [paint.strokeWidth]); - } - - if (paint.maskFilter != null) { - final ui.BlurStyle blurStyle = paint.maskFilter.webOnlyBlurStyle; - final double sigma = paint.maskFilter.webOnlySigma; - - js.JsObject skBlurStyle; - switch (blurStyle) { - case ui.BlurStyle.normal: - skBlurStyle = canvasKit['BlurStyle']['Normal']; - break; - case ui.BlurStyle.solid: - skBlurStyle = canvasKit['BlurStyle']['Solid']; - break; - case ui.BlurStyle.outer: - skBlurStyle = canvasKit['BlurStyle']['Outer']; - break; - case ui.BlurStyle.inner: - skBlurStyle = canvasKit['BlurStyle']['Inner']; - break; - } - - final js.JsObject skMaskFilter = canvasKit - .callMethod('MakeBlurMaskFilter', [skBlurStyle, sigma, true]); - skPaint.callMethod('setMaskFilter', [skMaskFilter]); - } - - if (paint.colorFilter != null) { - EngineColorFilter engineFilter = paint.colorFilter; - SkColorFilter skFilter = engineFilter._toSkColorFilter(); - skPaint.callMethod('setColorFilter', [skFilter.skColorFilter]); - } - - return skPaint; -} - // Mappings from SkMatrix-index to input-index. const List _skMatrixIndexToMatrix4Index = [ 0, 4, 12, // Row 1 @@ -187,12 +263,39 @@ js.JsArray makeSkMatrix(Float64List matrix4) { return skMatrix; } +/// Color stops used when the framework specifies `null`. +final js.JsArray _kDefaultColorStops = () { + final js.JsArray jsColorStops = js.JsArray(); + jsColorStops.length = 2; + jsColorStops[0] = 0; + jsColorStops[1] = 1; + return jsColorStops; +}(); + +/// Converts a list of color stops into a Skia-compatible JS array or color stops. +/// +/// In Flutter `null` means two color stops `[0, 1]` that in Skia must be specified explicitly. +js.JsArray makeSkiaColorStops(List colorStops) { + if (colorStops == null) { + return _kDefaultColorStops; + } + + final js.JsArray jsColorStops = js.JsArray.from(colorStops); + jsColorStops.length = colorStops.length; + return jsColorStops; +} + +// These must be kept in sync with `flow/layers/physical_shape_layer.cc`. +const double kLightHeight = 600.0; +const double kLightRadius = 800.0; + void drawSkShadow( js.JsObject skCanvas, SkPath path, ui.Color color, double elevation, bool transparentOccluder, + double devicePixelRatio, ) { const double ambientAlpha = 0.039; const double spotAlpha = 0.25; @@ -216,9 +319,10 @@ void drawSkShadow( skCanvas.callMethod('drawShadow', [ path._skPath, - js.JsArray.from([0, 0, elevation]), - js.JsArray.from([shadowX, shadowY, 600]), - 800, + js.JsArray.from([0, 0, devicePixelRatio * elevation]), + js.JsArray.from( + [shadowX, shadowY, devicePixelRatio * kLightHeight]), + devicePixelRatio * kLightRadius, tonalColors['ambient'], tonalColors['spot'], flags, diff --git a/lib/web_ui/lib/src/engine/compositor/vertices.dart b/lib/web_ui/lib/src/engine/compositor/vertices.dart index 7dfc609c9bef6..a4481ea206893 100644 --- a/lib/web_ui/lib/src/engine/compositor/vertices.dart +++ b/lib/web_ui/lib/src/engine/compositor/vertices.dart @@ -11,26 +11,13 @@ Int32List _encodeColorList(List colors) { return result; } -Float32List _encodePointList(List points) { - assert(points != null); - final int pointCount = points.length; - final Float32List result = Float32List(pointCount * 2); - for (int i = 0; i < pointCount; ++i) { - final int xIndex = i * 2; - final int yIndex = xIndex + 1; - final ui.Offset point = points[i]; - assert(_offsetIsValid(point)); - result[xIndex] = point.dx; - result[yIndex] = point.dy; - } - return result; -} - class SkVertices implements ui.Vertices { js.JsObject skVertices; final Int32List _colors; final Float32List _positions; final ui.VertexMode _mode; + final Float32List _textureCoordinates; + final Uint16List _indices; SkVertices( ui.VertexMode mode, @@ -40,9 +27,13 @@ class SkVertices implements ui.Vertices { List indices, }) : assert(mode != null), assert(positions != null), - _colors = Int32List.fromList(colors.map((ui.Color c) => c.value).toList()), - _positions = _offsetListToInt32List(positions), - _mode = mode { + _colors = + Int32List.fromList(colors.map((ui.Color c) => c.value).toList()), + _positions = encodePointList(positions), + _mode = mode, + _textureCoordinates = (textureCoordinates != null) + ? encodePointList(textureCoordinates) : null, + _indices = indices != null ? Uint16List.fromList(indices) : null { if (textureCoordinates != null && textureCoordinates.length != positions.length) throw ArgumentError( @@ -54,17 +45,11 @@ class SkVertices implements ui.Vertices { throw ArgumentError( '"indices" values must be valid indices in the positions list.'); - final Float32List encodedPositions = _encodePointList(positions); - final Float32List encodedTextureCoordinates = (textureCoordinates != null) - ? _encodePointList(textureCoordinates) - : null; + final Float32List encodedPositions = encodePointList(positions); final Int32List encodedColors = colors != null ? _encodeColorList(colors) : null; - final Uint16List encodedIndices = - indices != null ? Uint16List.fromList(indices) : null; - - if (!_init(mode, encodedPositions, encodedTextureCoordinates, encodedColors, - encodedIndices)) + if (!_init(mode, encodedPositions, _textureCoordinates, encodedColors, + _indices)) throw ArgumentError('Invalid configuration for vertices.'); } @@ -78,7 +63,9 @@ class SkVertices implements ui.Vertices { assert(positions != null), _colors = colors, _positions = positions, - _mode = mode { + _mode = mode, + _textureCoordinates = textureCoordinates, + _indices = indices { if (textureCoordinates != null && textureCoordinates.length != positions.length) throw ArgumentError( @@ -140,19 +127,6 @@ class SkVertices implements ui.Vertices { return encodedPoints; } - static Float32List _offsetListToInt32List(List offsetList) { - if (offsetList == null) { - return null; - } - final int length = offsetList.length; - final floatList = Float32List(length * 2); - for (int i = 0, destIndex = 0; i < length; i++, destIndex += 2) { - floatList[destIndex] = offsetList[i].dx; - floatList[destIndex + 1] = offsetList[i].dx; - } - return floatList; - } - @override Int32List get colors => _colors; @@ -161,4 +135,8 @@ class SkVertices implements ui.Vertices { @override ui.VertexMode get mode => _mode; + + Float32List get textureCoordinates => _textureCoordinates; + + Uint16List get indices => _indices; } diff --git a/lib/web_ui/lib/src/engine/dom_canvas.dart b/lib/web_ui/lib/src/engine/dom_canvas.dart index 5d207f5a28b39..7a8f377662c95 100644 --- a/lib/web_ui/lib/src/engine/dom_canvas.dart +++ b/lib/web_ui/lib/src/engine/dom_canvas.dart @@ -56,17 +56,17 @@ class DomCanvas extends EngineCanvas with SaveElementStackTracking { } @override - void drawLine(ui.Offset p1, ui.Offset p2, ui.PaintData paint) { + void drawLine(ui.Offset p1, ui.Offset p2, SurfacePaintData paint) { throw UnimplementedError(); } @override - void drawPaint(ui.PaintData paint) { + void drawPaint(SurfacePaintData paint) { throw UnimplementedError(); } @override - void drawRect(ui.Rect rect, ui.PaintData paint) { + void drawRect(ui.Rect rect, SurfacePaintData paint) { assert(paint.shader == null); final html.Element rectangle = html.Element.tag('draw-rect'); assert(() { @@ -126,27 +126,27 @@ class DomCanvas extends EngineCanvas with SaveElementStackTracking { } @override - void drawRRect(ui.RRect rrect, ui.PaintData paint) { + void drawRRect(ui.RRect rrect, SurfacePaintData paint) { throw UnimplementedError(); } @override - void drawDRRect(ui.RRect outer, ui.RRect inner, ui.PaintData paint) { + void drawDRRect(ui.RRect outer, ui.RRect inner, SurfacePaintData paint) { throw UnimplementedError(); } @override - void drawOval(ui.Rect rect, ui.PaintData paint) { + void drawOval(ui.Rect rect, SurfacePaintData paint) { throw UnimplementedError(); } @override - void drawCircle(ui.Offset c, double radius, ui.PaintData paint) { + void drawCircle(ui.Offset c, double radius, SurfacePaintData paint) { throw UnimplementedError(); } @override - void drawPath(ui.Path path, ui.PaintData paint) { + void drawPath(ui.Path path, SurfacePaintData paint) { throw UnimplementedError(); } @@ -157,13 +157,13 @@ class DomCanvas extends EngineCanvas with SaveElementStackTracking { } @override - void drawImage(ui.Image image, ui.Offset p, ui.PaintData paint) { + void drawImage(ui.Image image, ui.Offset p, SurfacePaintData paint) { throw UnimplementedError(); } @override void drawImageRect( - ui.Image image, ui.Rect src, ui.Rect dst, ui.PaintData paint) { + ui.Image image, ui.Rect src, ui.Rect dst, SurfacePaintData paint) { throw UnimplementedError(); } @@ -175,8 +175,13 @@ class DomCanvas extends EngineCanvas with SaveElementStackTracking { } @override - void drawVertices(ui.Vertices vertices, ui.BlendMode blendMode, - ui.PaintData paint) { + void drawVertices( + ui.Vertices vertices, ui.BlendMode blendMode, SurfacePaintData paint) { throw UnimplementedError(); } + + @override + void endOfPaint() { + // No reuse of elements yet to handle here. Noop. + } } diff --git a/lib/web_ui/lib/src/engine/dom_renderer.dart b/lib/web_ui/lib/src/engine/dom_renderer.dart index 9c91b5c8098a0..a3b8578f685df 100644 --- a/lib/web_ui/lib/src/engine/dom_renderer.dart +++ b/lib/web_ui/lib/src/engine/dom_renderer.dart @@ -62,6 +62,16 @@ class DomRenderer { static const String _staleHotRestartStore = '__flutter_state'; List _staleHotRestartState; + /// Used to decide if the browser tab still has the focus. + /// + /// This information is useful for deciding on the blur behavior. + /// See [DefaultTextEditingStrategy]. + /// + /// This getter calls the `hasFocus` method of the `Document` interface. + /// See for more details: + /// https://developer.mozilla.org/en-US/docs/Web/API/Document/hasFocus + bool get windowHasFocus => js_util.callMethod(html.document, 'hasFocus', []); + void _setupHotRestart() { // This persists across hot restarts to clear stale DOM. _staleHotRestartState = @@ -220,7 +230,8 @@ class DomRenderer { _styleElement = html.StyleElement(); html.document.head.append(_styleElement); final html.CssStyleSheet sheet = _styleElement.sheet; - + final bool isWebKit = browserEngine == BrowserEngine.webkit; + final bool isFirefox = browserEngine == BrowserEngine.firefox; // TODO(butterfly): use more efficient CSS selectors; descendant selectors // are slow. More info: // @@ -228,10 +239,19 @@ class DomRenderer { // This undoes browser's default layout attributes for paragraphs. We // compute paragraph layout ourselves. - sheet.insertRule(''' -flt-ruler-host p, flt-scene p { - margin: 0; -}''', sheet.cssRules.length); + if (isFirefox) { + // For firefox set line-height, otherwise textx at same font-size will + // measure differently in ruler. + sheet.insertRule( + 'flt-ruler-host p, flt-scene p ' + '{ margin: 0; line-height: 100%;}', + sheet.cssRules.length); + } else { + sheet.insertRule( + 'flt-ruler-host p, flt-scene p ' + '{ margin: 0; }', + sheet.cssRules.length); + } // This undoes browser's default painting and layout attributes of range // input, which is used in semantics. @@ -248,7 +268,7 @@ flt-semantics input[type=range] { left: 0; }''', sheet.cssRules.length); - if (browserEngine == BrowserEngine.webkit) { + if (isWebKit) { sheet.insertRule( 'flt-semantics input[type=range]::-webkit-slider-thumb {' ' -webkit-appearance: none;' @@ -256,7 +276,7 @@ flt-semantics input[type=range] { sheet.cssRules.length); } - if (browserEngine == BrowserEngine.firefox) { + if (isFirefox) { sheet.insertRule( 'input::-moz-selection {' ' background-color: transparent;' @@ -292,7 +312,7 @@ flt-semantics [contentEditable="true"] { // By default on iOS, Safari would highlight the element that's being tapped // on using gray background. This CSS rule disables that. - if (browserEngine == BrowserEngine.webkit) { + if (isWebKit) { sheet.insertRule(''' flt-glass-pane * { -webkit-tap-highlight-color: transparent; @@ -379,8 +399,19 @@ flt-glass-pane * { _glassPaneElement.append(_sceneHostElement); - EngineSemanticsOwner.instance.autoEnableOnTap(this); - PointerBinding(this); + final html.Element _accesibilityPlaceholder = EngineSemanticsOwner + .instance.semanticsHelper + .prepareAccesibilityPlaceholder(); + + // Insert the semantics placeholder after the scene host. For all widgets + // in the scene, except for platform widgets, the scene host will pass the + // pointer events through to the semantics tree. However, for platform + // views, the pointer events will not pass through, and will be handled + // by the platform view. + glassPaneElement + .insertBefore(_accesibilityPlaceholder, _sceneHostElement); + + PointerBinding.initInstance(_glassPaneElement); // Hide the DOM nodes used to render the scene from accessibility, because // the accessibility tree is built from the SemanticsNode tree as a parallel @@ -393,8 +424,7 @@ flt-glass-pane * { // is 1.0. window.debugOverrideDevicePixelRatio(1.0); - if (html.window.visualViewport == null && - browserEngine == BrowserEngine.webkit) { + if (html.window.visualViewport == null && isWebKit) { // Safari sometimes gives us bogus innerWidth/innerHeight values when the // page loads. When it changes the values to correct ones it does not // notify of the change via `onResize`. As a workaround, we setup a diff --git a/lib/web_ui/lib/src/engine/engine_canvas.dart b/lib/web_ui/lib/src/engine/engine_canvas.dart index e7c37ca0a79a6..de2fff8f6201d 100644 --- a/lib/web_ui/lib/src/engine/engine_canvas.dart +++ b/lib/web_ui/lib/src/engine/engine_canvas.dart @@ -40,34 +40,38 @@ abstract class EngineCanvas { void drawColor(ui.Color color, ui.BlendMode blendMode); - void drawLine(ui.Offset p1, ui.Offset p2, ui.PaintData paint); + void drawLine(ui.Offset p1, ui.Offset p2, SurfacePaintData paint); - void drawPaint(ui.PaintData paint); + void drawPaint(SurfacePaintData paint); - void drawRect(ui.Rect rect, ui.PaintData paint); + void drawRect(ui.Rect rect, SurfacePaintData paint); - void drawRRect(ui.RRect rrect, ui.PaintData paint); + void drawRRect(ui.RRect rrect, SurfacePaintData paint); - void drawDRRect(ui.RRect outer, ui.RRect inner, ui.PaintData paint); + void drawDRRect(ui.RRect outer, ui.RRect inner, SurfacePaintData paint); - void drawOval(ui.Rect rect, ui.PaintData paint); + void drawOval(ui.Rect rect, SurfacePaintData paint); - void drawCircle(ui.Offset c, double radius, ui.PaintData paint); + void drawCircle(ui.Offset c, double radius, SurfacePaintData paint); - void drawPath(ui.Path path, ui.PaintData paint); + void drawPath(ui.Path path, SurfacePaintData paint); void drawShadow( ui.Path path, ui.Color color, double elevation, bool transparentOccluder); - void drawImage(ui.Image image, ui.Offset p, ui.PaintData paint); + void drawImage(ui.Image image, ui.Offset p, SurfacePaintData paint); void drawImageRect( - ui.Image image, ui.Rect src, ui.Rect dst, ui.PaintData paint); + ui.Image image, ui.Rect src, ui.Rect dst, SurfacePaintData paint); void drawParagraph(EngineParagraph paragraph, ui.Offset offset); - void drawVertices(ui.Vertices vertices, ui.BlendMode blendMode, - ui.PaintData paint); + void drawVertices( + ui.Vertices vertices, ui.BlendMode blendMode, SurfacePaintData paint); + + /// Extension of Canvas API to mark the end of a stream of painting commands + /// to enable re-use/dispose optimizations. + void endOfPaint(); } /// Adds an [offset] transformation to a [transform] matrix and returns the @@ -261,7 +265,7 @@ html.Element _drawParagraphElement( paragraphStyle ..transformOrigin = '0 0 0' ..transform = - matrix4ToCssTransform(transformWithOffset(transform, offset)); + matrix4ToCssTransform3d(transformWithOffset(transform, offset)); } final ParagraphGeometricStyle style = paragraph._geometricStyle; diff --git a/lib/web_ui/lib/src/engine/houdini_canvas.dart b/lib/web_ui/lib/src/engine/houdini_canvas.dart index 2eb83b9c76269..26e7e07da44dc 100644 --- a/lib/web_ui/lib/src/engine/houdini_canvas.dart +++ b/lib/web_ui/lib/src/engine/houdini_canvas.dart @@ -151,42 +151,42 @@ class HoudiniCanvas extends EngineCanvas with SaveElementStackTracking { } @override - void drawLine(ui.Offset p1, ui.Offset p2, ui.PaintData paint) { + void drawLine(ui.Offset p1, ui.Offset p2, SurfacePaintData paint) { // Drawn using CSS Paint. } @override - void drawPaint(ui.PaintData paint) { + void drawPaint(SurfacePaintData paint) { // Drawn using CSS Paint. } @override - void drawRect(ui.Rect rect, ui.PaintData paint) { + void drawRect(ui.Rect rect, SurfacePaintData paint) { // Drawn using CSS Paint. } @override - void drawRRect(ui.RRect rrect, ui.PaintData paint) { + void drawRRect(ui.RRect rrect, SurfacePaintData paint) { // Drawn using CSS Paint. } @override - void drawDRRect(ui.RRect outer, ui.RRect inner, ui.PaintData paint) { + void drawDRRect(ui.RRect outer, ui.RRect inner, SurfacePaintData paint) { // Drawn using CSS Paint. } @override - void drawOval(ui.Rect rect, ui.PaintData paint) { + void drawOval(ui.Rect rect, SurfacePaintData paint) { // Drawn using CSS Paint. } @override - void drawCircle(ui.Offset c, double radius, ui.PaintData paint) { + void drawCircle(ui.Offset c, double radius, SurfacePaintData paint) { // Drawn using CSS Paint. } @override - void drawPath(ui.Path path, ui.PaintData paint) { + void drawPath(ui.Path path, SurfacePaintData paint) { // Drawn using CSS Paint. } @@ -197,13 +197,13 @@ class HoudiniCanvas extends EngineCanvas with SaveElementStackTracking { } @override - void drawImage(ui.Image image, ui.Offset p, ui.PaintData paint) { + void drawImage(ui.Image image, ui.Offset p, SurfacePaintData paint) { // TODO(yjbanov): implement. } @override void drawImageRect( - ui.Image image, ui.Rect src, ui.Rect dst, ui.PaintData paint) { + ui.Image image, ui.Rect src, ui.Rect dst, SurfacePaintData paint) { // TODO(yjbanov): implement src rectangle final HtmlImage htmlImage = image; final html.Element imageBox = html.Element.tag('flt-img'); @@ -229,10 +229,13 @@ class HoudiniCanvas extends EngineCanvas with SaveElementStackTracking { } @override - void drawVertices(ui.Vertices vertices, ui.BlendMode blendMode, - ui.PaintData paint) { + void drawVertices( + ui.Vertices vertices, ui.BlendMode blendMode, SurfacePaintData paint) { // TODO(flutter_web): implement. } + + @override + void endOfPaint() {} } class _SaveElementStackEntry { diff --git a/lib/web_ui/lib/src/engine/html_image_codec.dart b/lib/web_ui/lib/src/engine/html_image_codec.dart index 23d422f467483..397b171b97b50 100644 --- a/lib/web_ui/lib/src/engine/html_image_codec.dart +++ b/lib/web_ui/lib/src/engine/html_image_codec.dart @@ -3,7 +3,6 @@ // found in the LICENSE file. part of engine; - final bool _supportsDecode = js_util.getProperty( js_util.getProperty( js_util.getProperty(html.window, 'Image'), 'prototype'), @@ -23,46 +22,54 @@ class HtmlCodec implements ui.Codec { @override Future getNextFrame() async { - StreamSubscription loadSubscription; - StreamSubscription errorSubscription; final Completer completer = Completer(); - final html.ImageElement imgElement = html.ImageElement(); - // If the browser doesn't support asynchronous decoding of an image, - // then use the `onload` event to decide when it's ready to paint to the - // DOM. Unfortunately, this will case the image to be decoded synchronously - // on the main thread, and may cause dropped framed. - if (!_supportsDecode) { - loadSubscription = imgElement.onLoad.listen((html.Event event) { - loadSubscription.cancel(); - errorSubscription.cancel(); + if (_supportsDecode) { + final html.ImageElement imgElement = html.ImageElement(); + imgElement.src = src; + js_util.setProperty(imgElement, 'decoding', 'async'); + imgElement.decode().then((dynamic _) { final HtmlImage image = HtmlImage( imgElement, imgElement.naturalWidth, imgElement.naturalHeight, ); completer.complete(SingleFrameInfo(image)); + }).catchError((e) { + // This code path is hit on Chrome 80.0.3987.16 when too many + // images are on the page (~1000). + // Fallback here is to load using onLoad instead. + _decodeUsingOnLoad(completer); }); + } else { + _decodeUsingOnLoad(completer); } + return completer.future; + } + + void _decodeUsingOnLoad(Completer completer) { + StreamSubscription loadSubscription; + StreamSubscription errorSubscription; + final html.ImageElement imgElement = html.ImageElement(); + // If the browser doesn't support asynchronous decoding of an image, + // then use the `onload` event to decide when it's ready to paint to the + // DOM. Unfortunately, this will cause the image to be decoded synchronously + // on the main thread, and may cause dropped framed. errorSubscription = imgElement.onError.listen((html.Event event) { loadSubscription?.cancel(); errorSubscription.cancel(); completer.completeError(event); }); + loadSubscription = imgElement.onLoad.listen((html.Event event) { + loadSubscription.cancel(); + errorSubscription.cancel(); + final HtmlImage image = HtmlImage( + imgElement, + imgElement.naturalWidth, + imgElement.naturalHeight, + ); + completer.complete(SingleFrameInfo(image)); + }); imgElement.src = src; - // If the browser supports asynchronous image decoding, use that instead - // of `onload`. - if (_supportsDecode) { - imgElement.decode().then((dynamic _) { - errorSubscription.cancel(); - final HtmlImage image = HtmlImage( - imgElement, - imgElement.naturalWidth, - imgElement.naturalHeight, - ); - completer.complete(SingleFrameInfo(image)); - }); - } - return completer.future; } @override @@ -92,7 +99,7 @@ class SingleFrameInfo implements ui.FrameInfo { class HtmlImage implements ui.Image { final html.ImageElement imgElement; - + bool _requiresClone = false; HtmlImage(this.imgElement, this.width, this.height); @override @@ -117,6 +124,18 @@ class HtmlImage implements ui.Image { }); } + // Returns absolutely positioned actual image element on first call and + // clones on subsequent calls. + html.ImageElement cloneImageElement() { + if (_requiresClone) { + return imgElement.clone(true); + } else { + _requiresClone = true; + imgElement.style..position = 'absolute'; + return imgElement; + } + } + /// Returns an error message on failure, null on success. String _toByteData(int format, Callback callback) => null; } diff --git a/lib/web_ui/lib/src/engine/keyboard.dart b/lib/web_ui/lib/src/engine/keyboard.dart index 86e04de57681f..4cf743fda86f1 100644 --- a/lib/web_ui/lib/src/engine/keyboard.dart +++ b/lib/web_ui/lib/src/engine/keyboard.dart @@ -4,6 +4,23 @@ part of engine; +/// Contains a whitelist of keys that must be sent to Flutter under all +/// circumstances. +/// +/// When keys are pressed in a text field, we generally don't want to send them +/// to Flutter. This list of keys is the exception to that rule. Keys in this +/// list will be sent to Flutter even if pressed in a text field. +/// +/// A good example is the "Tab" and "Shift" keys which are used by the framework +/// to move focus between text fields. +const List _alwaysSentKeys = [ + 'Alt', + 'Control', + 'Meta', + 'Shift', + 'Tab', +]; + /// Provides keyboard bindings, such as the `flutter/keyevent` channel. class Keyboard { /// Initializes the [Keyboard] singleton. @@ -50,6 +67,10 @@ class Keyboard { static const JSONMessageCodec _messageCodec = JSONMessageCodec(); void _handleHtmlEvent(html.KeyboardEvent event) { + if (_shouldIgnoreEvent(event)) { + return; + } + if (_shouldPreventDefault(event)) { event.preventDefault(); } @@ -66,6 +87,20 @@ class Keyboard { _messageCodec.encodeMessage(eventData), _noopCallback); } + /// Whether the [Keyboard] class should ignore the given [html.KeyboardEvent]. + /// + /// When this method returns true, it prevents the keyboard event from being + /// sent to Flutter. + bool _shouldIgnoreEvent(html.KeyboardEvent event) { + // Keys in the [_alwaysSentKeys] list should never be ignored. + if (_alwaysSentKeys.contains(event.key)) { + return false; + } + // Other keys should be ignored if triggered on a text field. + return event.target is html.Element && + HybridTextEditing.isEditingElement(event.target); + } + bool _shouldPreventDefault(html.KeyboardEvent event) { switch (event.key) { case 'Tab': @@ -82,9 +117,6 @@ const int _modifierShift = 0x01; const int _modifierAlt = 0x02; const int _modifierControl = 0x04; const int _modifierMeta = 0x08; -const int _modifierNumLock = 0x10; -const int _modifierCapsLock = 0x20; -const int _modifierScrollLock = 0x40; /// Creates a bitmask representing the meta state of the [event]. int _getMetaState(html.KeyboardEvent event) { @@ -101,15 +133,8 @@ int _getMetaState(html.KeyboardEvent event) { if (event.getModifierState('Meta')) { metaState |= _modifierMeta; } - if (event.getModifierState('NumLock')) { - metaState |= _modifierNumLock; - } - if (event.getModifierState('CapsLock')) { - metaState |= _modifierCapsLock; - } - if (event.getModifierState('ScrollLock')) { - metaState |= _modifierScrollLock; - } + // TODO: Re-enable lock key modifiers once there is support on Flutter + // Framework. https://github.com/flutter/flutter/issues/46718 return metaState; } diff --git a/lib/web_ui/lib/src/engine/path_to_svg.dart b/lib/web_ui/lib/src/engine/path_to_svg.dart index 10dc5455740cd..0e138044ef074 100644 --- a/lib/web_ui/lib/src/engine/path_to_svg.dart +++ b/lib/web_ui/lib/src/engine/path_to_svg.dart @@ -6,7 +6,7 @@ part of engine; /// Converts [path] to SVG path syntax to be used as "d" attribute in path /// element. -void pathToSvg(ui.Path path, StringBuffer sb, +void pathToSvg(SurfacePath path, StringBuffer sb, {double offsetX = 0, double offsetY = 0}) { for (Subpath subPath in path.subpaths) { for (PathCommand command in subPath.commands) { @@ -96,9 +96,9 @@ void pathToSvg(ui.Path path, StringBuffer sb, final double blRadiusY = rrect.blRadiusY.abs(); final double brRadiusY = rrect.brRadiusY.abs(); - sb.write('L ${left + trRadiusX} $top '); + sb.write('M ${left + trRadiusX} $top '); // Top side and top-right corner - sb.write('M ${right - trRadiusX} $top '); + sb.write('L ${right - trRadiusX} $top '); _writeEllipse(sb, right - trRadiusX, top + trRadiusY, trRadiusX, trRadiusY, 0, 1.5 * math.pi, 2.0 * math.pi, false); // Right side and bottom-right corner diff --git a/lib/web_ui/lib/src/engine/picture.dart b/lib/web_ui/lib/src/engine/picture.dart new file mode 100644 index 0000000000000..a9c41abea7ceb --- /dev/null +++ b/lib/web_ui/lib/src/engine/picture.dart @@ -0,0 +1,70 @@ +// 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. + +part of engine; + +/// An implementation of [ui.PictureRecorder] backed by a [RecordingCanvas]. +class EnginePictureRecorder implements ui.PictureRecorder { + EnginePictureRecorder(); + + RecordingCanvas _canvas; + ui.Rect cullRect; + bool _isRecording = false; + + RecordingCanvas beginRecording(ui.Rect bounds) { + assert(!_isRecording); + cullRect = bounds; + _isRecording = true; + _canvas = RecordingCanvas(cullRect); + return _canvas; + } + + @override + bool get isRecording => _isRecording; + + @override + ui.Picture endRecording() { + // Returning null is what the flutter engine does: + // lib/ui/painting/picture_recorder.cc + if (!_isRecording) { + return null; + } + _isRecording = false; + return EnginePicture(_canvas, cullRect); + } +} + +/// An implementation of [ui.Picture] which is backed by a [RecordingCanvas]. +class EnginePicture implements ui.Picture { + /// This class is created by the engine, and should not be instantiated + /// or extended directly. + /// + /// To create a [Picture], use a [PictureRecorder]. + EnginePicture(this.recordingCanvas, this.cullRect); + + @override + Future toImage(int width, int height) async { + final BitmapCanvas canvas = BitmapCanvas(ui.Rect.fromLTRB(0, 0, width.toDouble(), height.toDouble())); + recordingCanvas.apply(canvas); + final String imageDataUrl = canvas.toDataUrl(); + final html.ImageElement imageElement = html.ImageElement() + ..src = imageDataUrl + ..width = width + ..height = height; + return HtmlImage( + imageElement, + width, + height, + ); + } + + @override + void dispose() {} + + @override + int get approximateBytesUsed => 0; + + final RecordingCanvas recordingCanvas; + final ui.Rect cullRect; +} diff --git a/lib/web_ui/lib/src/engine/platform_views.dart b/lib/web_ui/lib/src/engine/platform_views.dart index 33980510f3867..1675072eebb7f 100644 --- a/lib/web_ui/lib/src/engine/platform_views.dart +++ b/lib/web_ui/lib/src/engine/platform_views.dart @@ -6,7 +6,7 @@ part of engine; /// A registry for factories that create platform views. class PlatformViewRegistry { - final Map _registeredFactories = + final Map registeredFactories = {}; final Map _createdViews = {}; @@ -16,10 +16,10 @@ class PlatformViewRegistry { /// Register [viewTypeId] as being creating by the given [factory]. bool registerViewFactory(String viewTypeId, PlatformViewFactory factory) { - if (_registeredFactories.containsKey(viewTypeId)) { + if (registeredFactories.containsKey(viewTypeId)) { return false; } - _registeredFactories[viewTypeId] = factory; + registeredFactories[viewTypeId] = factory; return true; } @@ -65,7 +65,7 @@ void _createPlatformView( const MethodCodec codec = StandardMethodCodec(); // TODO(het): Use 'direction', 'width', and 'height'. - if (!platformViewRegistry._registeredFactories.containsKey(viewType)) { + if (!platformViewRegistry.registeredFactories.containsKey(viewType)) { callback(codec.encodeErrorEnvelope( code: 'Unregistered factory', message: "No factory registered for viewtype '$viewType'", @@ -74,7 +74,7 @@ void _createPlatformView( } // TODO(het): Use creation parameters. final html.Element element = - platformViewRegistry._registeredFactories[viewType](id); + platformViewRegistry.registeredFactories[viewType](id); platformViewRegistry._createdViews[id] = element; callback(codec.encodeSuccessEnvelope(null)); diff --git a/lib/web_ui/lib/src/engine/pointer_binding.dart b/lib/web_ui/lib/src/engine/pointer_binding.dart index e70b9ad1f92b5..30e55f90ec8b0 100644 --- a/lib/web_ui/lib/src/engine/pointer_binding.dart +++ b/lib/web_ui/lib/src/engine/pointer_binding.dart @@ -8,38 +8,78 @@ part of engine; const bool _debugLogPointerEvents = false; /// The signature of a callback that handles pointer events. -typedef PointerDataCallback = void Function(List); +typedef _PointerDataCallback = void Function(Iterable); + +// The mask for the bitfield of event buttons. Buttons not contained in this +// mask are cut off. +// +// In Flutter we used `kMaxUnsignedSMI`, but since that value is not available +// here, we use an already very large number (30 bits). +const int _kButtonsMask = 0x3FFFFFFF; + +// Intentionally set to -1 so it doesn't conflict with other device IDs. +const int _mouseDeviceId = -1; + +const int _kPrimaryMouseButton = 0x1; +const int _kSecondaryMouseButton = 0x2; +const int _kMiddleMouseButton =0x4; + +int _nthButton(int n) => 0x1 << n; + +/// Convert the `button` property of PointerEvent or MouseEvent to a bit mask of +/// its `buttons` property. +/// +/// The `button` property is a integer describing the button changed in an event, +/// which is sequentially 0 for LMB, 1 for MMB, 2 for RMB, 3 for backward and +/// 4 for forward, etc. +/// +/// The `buttons` property is a bitfield describing the buttons pressed after an +/// event, which is 0x1 for LMB, 0x4 for MMB, 0x2 for RMB, 0x8 for backward +/// and 0x10 for forward, etc. +@visibleForTesting +int convertButtonToButtons(int button) { + assert(button >= 0, 'Unexpected negative button $button.'); + switch(button) { + case 0: + return _kPrimaryMouseButton; + case 1: + return _kMiddleMouseButton; + case 2: + return _kSecondaryMouseButton; + default: + return _nthButton(button); + } +} class PointerBinding { /// The singleton instance of this object. static PointerBinding get instance => _instance; static PointerBinding _instance; - // Set of pointerIds that are added before routing hover and mouse wheel - // events. - // - // The device needs to send a one time PointerChange.add before hover and - // wheel events. - Set _activePointerIds = {}; - - PointerBinding(this.domRenderer) { + static void initInstance(html.Element glassPaneElement) { if (_instance == null) { - _instance = this; - _detector = const PointerSupportDetector(); - _adapter = _createAdapter(); + _instance = PointerBinding._(glassPaneElement); + assert(() { + registerHotRestartListener(() { + _instance._adapter?.clearListeners(); + _instance._pointerDataConverter?.clearPointerState(); + }); + return true; + }()); } - assert(() { - registerHotRestartListener(() { - _adapter?.clearListeners(); - _activePointerIds.clear(); - }); - return true; - }()); } - final DomRenderer domRenderer; + PointerBinding._(this.glassPaneElement) { + _pointerDataConverter = PointerDataConverter(); + _detector = const PointerSupportDetector(); + _adapter = _createAdapter(); + } + + final html.Element glassPaneElement; + PointerSupportDetector _detector; - BaseAdapter _adapter; + _BaseAdapter _adapter; + PointerDataConverter _pointerDataConverter; /// Should be used in tests to define custom detection of pointer support. /// @@ -62,28 +102,28 @@ class PointerBinding { newDetector ??= const PointerSupportDetector(); // When changing the detector, we need to swap the adapter. if (newDetector != _detector) { - _activePointerIds.clear(); _detector = newDetector; _adapter?.clearListeners(); _adapter = _createAdapter(); + _pointerDataConverter?.clearPointerState(); } } - BaseAdapter _createAdapter() { + _BaseAdapter _createAdapter() { if (_detector.hasPointerEvents) { - return PointerAdapter(_onPointerData, domRenderer); + return _PointerAdapter(_onPointerData, glassPaneElement, _pointerDataConverter); } if (_detector.hasTouchEvents) { - return TouchAdapter(_onPointerData, domRenderer); + return _TouchAdapter(_onPointerData, glassPaneElement, _pointerDataConverter); } if (_detector.hasMouseEvents) { - return MouseAdapter(_onPointerData, domRenderer); + return _MouseAdapter(_onPointerData, glassPaneElement, _pointerDataConverter); } return null; } - void _onPointerData(List data) { - final ui.PointerDataPacket packet = ui.PointerDataPacket(data: data); + void _onPointerData(Iterable data) { + final ui.PointerDataPacket packet = ui.PointerDataPacket(data: data.toList()); final ui.PointerDataPacketCallback callback = ui.window.onPointerDataPacket; if (callback != null) { callback(packet); @@ -103,65 +143,47 @@ class PointerSupportDetector { 'pointers:$hasPointerEvents, touch:$hasTouchEvents, mouse:$hasMouseEvents'; } -class _PressedButton { - const _PressedButton(this.deviceId, this.button); - - // The id of the device pressing the button. - final int deviceId; - - // The id of the button being pressed. - final int button; - - bool operator ==(other) { - if (other is! _PressedButton) return false; - final _PressedButton otherButton = other; - return deviceId == otherButton.deviceId && button == otherButton.button; - } - - int get hashCode => ((13801 + deviceId) * 37) + button; -} - /// Common functionality that's shared among adapters. -abstract class BaseAdapter { - static final Map _listeners = - {}; - - final DomRenderer domRenderer; - PointerDataCallback _callback; - - // A set of the buttons that are currently being pressed. - Set<_PressedButton> _pressedButtons = Set<_PressedButton>(); - - bool _isButtonDown(int device, int button) { - return _pressedButtons.contains(_PressedButton(device, button)); +abstract class _BaseAdapter { + _BaseAdapter(this._callback, this.glassPaneElement, this._pointerDataConverter) { + setup(); } - void _updateButtonDownState(int device, int button, bool value) { - if (value) { - _pressedButtons.add(_PressedButton(device, button)); - } else { - _pressedButtons.remove(_PressedButton(device, button)); - } - } - - BaseAdapter(this._callback, this.domRenderer) { - _setup(); - } + /// Listeners that are registered through dart to js api. + static final Map _listeners = + {}; + /// Listeners that are registered through native javascript api. + static final Map _nativeListeners = + {}; + final html.Element glassPaneElement; + _PointerDataCallback _callback; + PointerDataConverter _pointerDataConverter; /// Each subclass is expected to override this method to attach its own event /// listeners and convert events into pointer events. - void _setup(); + void setup(); /// Remove all active event listeners. void clearListeners() { - final html.Element glassPane = domRenderer.glassPaneElement; _listeners.forEach((String eventName, html.EventListener listener) { - glassPane.removeEventListener(eventName, listener, true); + glassPaneElement.removeEventListener(eventName, listener, true); + }); + // For native listener, we will need to remove it through native javascript + // api. + _nativeListeners.forEach((String eventName, html.EventListener listener) { + js_util.callMethod( + glassPaneElement, + 'removeEventListener', [ + 'wheel', + listener, + ] + ); }); _listeners.clear(); + _nativeListeners.clear(); } - void _addEventListener(String eventName, html.EventListener handler) { + void addEventListener(String eventName, html.EventListener handler) { final html.EventListener loggedHandler = (html.Event event) { if (_debugLogPointerEvents) { print(event.type); @@ -174,103 +196,288 @@ abstract class BaseAdapter { } }; _listeners[eventName] = loggedHandler; - domRenderer.glassPaneElement + glassPaneElement .addEventListener(eventName, loggedHandler, true); } + + /// Converts a floating number timestamp (in milliseconds) to a [Duration] by + /// splitting it into two integer components: milliseconds + microseconds. + static Duration _eventTimeStampToDuration(num milliseconds) { + final int ms = milliseconds.toInt(); + final int micro = + ((milliseconds - ms) * Duration.microsecondsPerMillisecond).toInt(); + return Duration(milliseconds: ms, microseconds: micro); + } } -const int _kPrimaryMouseButton = 0x1; -const int _kSecondaryMouseButton = 0x2; +mixin _WheelEventListenerMixin on _BaseAdapter { + List _convertWheelEventToPointerData( + html.WheelEvent event + ) { + const int domDeltaPixel = 0x00; + const int domDeltaLine = 0x01; + const int domDeltaPage = 0x02; + + // Flutter only supports pixel scroll delta. Convert deltaMode values + // to pixels. + double deltaX = event.deltaX; + double deltaY = event.deltaY; + switch (event.deltaMode) { + case domDeltaLine: + deltaX *= 32.0; + deltaY *= 32.0; + break; + case domDeltaPage: + deltaX *= ui.window.physicalSize.width; + deltaY *= ui.window.physicalSize.height; + break; + case domDeltaPixel: + default: + break; + } + final List data = []; + _pointerDataConverter.convert( + data, + change: ui.PointerChange.hover, + timeStamp: _BaseAdapter._eventTimeStampToDuration(event.timeStamp), + kind: ui.PointerDeviceKind.mouse, + signalKind: ui.PointerSignalKind.scroll, + device: _mouseDeviceId, + physicalX: event.client.x * ui.window.devicePixelRatio, + physicalY: event.client.y * ui.window.devicePixelRatio, + buttons: event.buttons, + pressure: 1.0, + pressureMin: 0.0, + pressureMax: 1.0, + scrollDeltaX: deltaX, + scrollDeltaY: deltaY, + ); + return data; + } + + void _addWheelEventListener(html.EventListener handler) { + final dynamic eventOptions = js_util.newObject(); + final html.EventListener jsHandler = js.allowInterop((html.Event event) => handler(event)); + _BaseAdapter._nativeListeners['wheel'] = jsHandler; + js_util.setProperty(eventOptions, 'passive', false); + js_util.callMethod( + glassPaneElement, + 'addEventListener', [ + 'wheel', + jsHandler, + eventOptions + ] + ); + } +} -int _pointerButtonFromHtmlEvent(html.Event event) { - if (event is html.PointerEvent) { - final html.PointerEvent pointerEvent = event; - return pointerEvent.button == 2 - ? _kSecondaryMouseButton - : _kPrimaryMouseButton; - } else if (event is html.MouseEvent) { - final html.MouseEvent mouseEvent = event; - return mouseEvent.button == 2 - ? _kSecondaryMouseButton - : _kPrimaryMouseButton; - } - return _kPrimaryMouseButton; +@immutable +class _SanitizedDetails { + const _SanitizedDetails({ + @required this.buttons, + @required this.change, + }); + + final ui.PointerChange change; + final int buttons; + + @override + String toString() => '$runtimeType(change: $change, buttons: $buttons)'; } -int _deviceFromHtmlEvent(event) { - if (event is html.PointerEvent) { - final html.PointerEvent pointerEvent = event; - return pointerEvent.pointerId; +class _ButtonSanitizer { + int _pressedButtons = 0; + + /// Transform [html.PointerEvent.buttons] to Flutter's PointerEvent buttons. + int _htmlButtonsToFlutterButtons(int buttons) { + // Flutter's button definition conveniently matches that of JavaScript + // from primary button (0x1) to forward button (0x10), which allows us to + // avoid transforming it bit by bit. + return buttons & _kButtonsMask; + } + + /// Given [html.PointerEvent.button] and [html.PointerEvent.buttons], tries to + /// infer the correct value for Flutter buttons. + int _inferDownFlutterButtons(int button, int buttons) { + if (buttons == 0 && button > -1) { + // In some cases, the browser sends `buttons:0` in a down event. In such + // case, we try to infer the value from `button`. + buttons = convertButtonToButtons(button); + } + return _htmlButtonsToFlutterButtons(buttons); + } + + List<_SanitizedDetails> sanitizeDownEvent({ + @required int button, + @required int buttons, + }) { + // If the pointer is already down, we just send a move event with the new + // `buttons` value. + if (_pressedButtons != 0) { + return sanitizeMoveEvent(buttons: buttons); + } + + _pressedButtons = _inferDownFlutterButtons(button, buttons); + return <_SanitizedDetails>[ + _SanitizedDetails( + change: ui.PointerChange.down, + buttons: _pressedButtons, + ) + ]; + } + + List<_SanitizedDetails> sanitizeMoveEvent({@required int buttons}) { + final int newPressedButtons = _htmlButtonsToFlutterButtons(buttons); + // This could happen when the context menu is active and the user clicks + // RMB somewhere else. The browser sends a down event with `buttons:0`. + // + // In this case, we keep the old `buttons` value so we don't confuse the + // framework. + if (_pressedButtons != 0 && newPressedButtons == 0) { + return <_SanitizedDetails>[ + _SanitizedDetails( + change: ui.PointerChange.move, + buttons: _pressedButtons, + ) + ]; + } + + // This could happen when the user clicks RMB then moves the mouse quickly. + // The brower sends a move event with `buttons:2` even though there's no + // buttons down yet. + if (_pressedButtons == 0 && newPressedButtons != 0) { + return <_SanitizedDetails>[ + _SanitizedDetails( + change: ui.PointerChange.hover, + buttons: _pressedButtons, + ) + ]; + } + + _pressedButtons = newPressedButtons; + return <_SanitizedDetails>[ + _SanitizedDetails( + change: _pressedButtons == 0 + ? ui.PointerChange.hover + : ui.PointerChange.move, + buttons: _pressedButtons, + ) + ]; + } + + List<_SanitizedDetails> sanitizeUpEvent() { + // The pointer could have been released by a `pointerout` event, in which + // case `pointerup` should have no effect. + if (_pressedButtons == 0) { + return <_SanitizedDetails>[]; + } + _pressedButtons = 0; + return <_SanitizedDetails>[_SanitizedDetails( + change: ui.PointerChange.up, + buttons: _pressedButtons, + )]; + } + + List<_SanitizedDetails> sanitizeCancelEvent() { + _pressedButtons = 0; + return <_SanitizedDetails>[_SanitizedDetails( + change: ui.PointerChange.cancel, + buttons: _pressedButtons, + )]; } - return _mouseDeviceId; } +typedef _PointerEventListener = dynamic Function(html.PointerEvent event); + /// Adapter class to be used with browsers that support native pointer events. -class PointerAdapter extends BaseAdapter { - PointerAdapter(PointerDataCallback callback, DomRenderer domRenderer) - : super(callback, domRenderer); +/// +/// For the difference between MouseEvent and PointerEvent, see _MouseAdapter. +class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { + _PointerAdapter( + _PointerDataCallback callback, + html.Element glassPaneElement, + PointerDataConverter _pointerDataConverter + ) : super(callback, glassPaneElement, _pointerDataConverter); + + final Map _sanitizers = {}; + + @visibleForTesting + Iterable debugTrackedDevices() => _sanitizers.keys; + + _ButtonSanitizer _ensureSanitizer(int device) { + return _sanitizers.putIfAbsent(device, () => _ButtonSanitizer()); + } + + _ButtonSanitizer _getSanitizer(int device) { + final _ButtonSanitizer sanitizer = _sanitizers[device]; + assert(sanitizer != null); + return sanitizer; + } + + void _removePointerIfUnhoverable(List<_SanitizedDetails> details, html.PointerEvent event) { + if (event.pointerType == 'touch') { + _sanitizers.remove(event.pointerId); + details.add(_SanitizedDetails( + buttons: 0, + change: ui.PointerChange.remove, + )); + } + } + + void _addPointerEventListener(String eventName, _PointerEventListener handler) { + addEventListener(eventName, (html.Event event) { + final html.PointerEvent pointerEvent = event; + return handler(pointerEvent); + }); + } @override - void _setup() { - _addEventListener('pointerdown', (html.Event event) { - final int pointerButton = _pointerButtonFromHtmlEvent(event); - final int device = _deviceFromHtmlEvent(event); - // The pointerdown event will cause an 'add' event on the framework side. - PointerBinding._instance._activePointerIds.add(device); - if (_isButtonDown(device, pointerButton)) { - // TODO(flutter_web): Remove this temporary fix for right click - // on web platform once context guesture is implemented. - _callback(_convertEventToPointerData(ui.PointerChange.up, event)); - } - _updateButtonDownState(device, pointerButton, true); - _callback(_convertEventToPointerData(ui.PointerChange.down, event)); + void setup() { + _addPointerEventListener('pointerdown', (html.PointerEvent event) { + final int device = event.pointerId; + final List pointerData = []; + final List<_SanitizedDetails> detailsList = + _ensureSanitizer(device).sanitizeDownEvent( + button: event.button, + buttons: event.buttons, + ); + _convertEventsToPointerData(data: pointerData, event: event, detailsList: detailsList); + _callback(pointerData); }); - _addEventListener('pointermove', (html.Event event) { - // TODO(flutter_web): During a drag operation pointermove will set - // button to -1 as opposed to mouse move which sets it to 2. - // This check is currently defaulting to primary button for now. - // Change this when context gesture is implemented in flutter framework. - final html.PointerEvent pointerEvent = event; - final int pointerButton = _pointerButtonFromHtmlEvent(pointerEvent); - final int device = _deviceFromHtmlEvent(event); - final List data = _convertEventToPointerData( - _isButtonDown(device, pointerButton) - ? ui.PointerChange.move - : ui.PointerChange.hover, - pointerEvent); - _ensureMouseDeviceAdded( - data, - pointerEvent.client.x, - pointerEvent.client.y, - pointerEvent.buttons, - pointerEvent.timeStamp, - pointerEvent.pointerId); - _callback(data); + _addPointerEventListener('pointermove', (html.PointerEvent event) { + final int device = event.pointerId; + final _ButtonSanitizer sanitizer = _ensureSanitizer(device); + final List pointerData = []; + final Iterable<_SanitizedDetails> detailsList = _expandEvents(event).expand( + (html.PointerEvent expandedEvent) => sanitizer.sanitizeMoveEvent(buttons: expandedEvent.buttons), + ); + _convertEventsToPointerData(data: pointerData, event: event, detailsList: detailsList); + _callback(pointerData); }); - _addEventListener('pointerup', (html.Event event) { - // The pointer could have been released by a `pointerout` event, in which - // case `pointerup` should have no effect. - final int pointerButton = _pointerButtonFromHtmlEvent(event); - final int device = _deviceFromHtmlEvent(event); - if (!_isButtonDown(device, pointerButton)) { - return; - } - _updateButtonDownState(device, pointerButton, false); - _callback(_convertEventToPointerData(ui.PointerChange.up, event)); + _addPointerEventListener('pointerup', (html.PointerEvent event) { + final int device = event.pointerId; + final List pointerData = []; + final List<_SanitizedDetails> detailsList = _getSanitizer(device).sanitizeUpEvent(); + _removePointerIfUnhoverable(detailsList, event); + _convertEventsToPointerData(data: pointerData, event: event, detailsList: detailsList); + _callback(pointerData); }); // A browser fires cancel event if it concludes the pointer will no longer // be able to generate events (example: device is deactivated) - _addEventListener('pointercancel', (html.Event event) { - final int pointerButton = _pointerButtonFromHtmlEvent(event); - final int device = _deviceFromHtmlEvent(event); - _updateButtonDownState(pointerButton, device, false); - _callback(_convertEventToPointerData(ui.PointerChange.cancel, event)); + _addPointerEventListener('pointercancel', (html.PointerEvent event) { + final int device = event.pointerId; + final List pointerData = []; + final List<_SanitizedDetails> detailsList = _getSanitizer(device).sanitizeCancelEvent(); + _removePointerIfUnhoverable(detailsList, event); + _convertEventsToPointerData(data: pointerData, event: event, detailsList: detailsList); + _callback(pointerData); }); - _addWheelEventListener((html.WheelEvent event) { + _addWheelEventListener((html.Event event) { + assert(event is html.WheelEvent); if (_debugLogPointerEvents) { print(event.type); } @@ -281,29 +488,40 @@ class PointerAdapter extends BaseAdapter { }); } - List _convertEventToPointerData( - ui.PointerChange change, - html.PointerEvent evt, - ) { - final List allEvents = _expandEvents(evt); - final List data = []; - for (int i = 0; i < allEvents.length; i++) { - final html.PointerEvent event = allEvents[i]; - data.add(ui.PointerData( - change: change, - timeStamp: _eventTimeStampToDuration(event.timeStamp), - kind: _pointerTypeToDeviceKind(event.pointerType), - device: event.pointerId, + // For each event that is de-coalesced from `event` and described in + // `detailsList`, convert it to pointer data and store in `data`. + void _convertEventsToPointerData({ + @required List data, + @required html.PointerEvent event, + @required Iterable<_SanitizedDetails> detailsList, + }) { + assert(data != null); + assert(event != null); + assert(detailsList != null); + final ui.PointerDeviceKind kind = _pointerTypeToDeviceKind(event.pointerType); + // We force `device: _mouseDeviceId` on mouse pointers because Wheel events + // might come before any PointerEvents, and since wheel events don't contain + // pointerId we always assign `device: _mouseDeviceId` to them. + final int device = kind == ui.PointerDeviceKind.mouse ? _mouseDeviceId : event.pointerId; + final double tilt = _computeHighestTilt(event); + final Duration timeStamp = _BaseAdapter._eventTimeStampToDuration(event.timeStamp); + for (_SanitizedDetails details in detailsList) { + _pointerDataConverter.convert( + data, + change: details.change, + timeStamp: timeStamp, + kind: kind, + signalKind: ui.PointerSignalKind.none, + device: device, physicalX: event.client.x * ui.window.devicePixelRatio, physicalY: event.client.y * ui.window.devicePixelRatio, - buttons: event.buttons, + buttons: details.buttons, pressure: event.pressure, pressureMin: 0.0, pressureMax: 1.0, - tilt: _computeHighestTilt(event), - )); + tilt: tilt, + ); } - return data; } List _expandEvents(html.PointerEvent event) { @@ -341,236 +559,251 @@ class PointerAdapter extends BaseAdapter { math.pi; } +typedef _TouchEventListener = dynamic Function(html.TouchEvent event); + /// Adapter to be used with browsers that support touch events. -class TouchAdapter extends BaseAdapter { - TouchAdapter(PointerDataCallback callback, DomRenderer domRenderer) - : super(callback, domRenderer); +class _TouchAdapter extends _BaseAdapter { + _TouchAdapter( + _PointerDataCallback callback, + html.Element glassPaneElement, + PointerDataConverter _pointerDataConverter + ) : super(callback, glassPaneElement, _pointerDataConverter); + + final Set _pressedTouches = {}; + bool _isTouchPressed(int identifier) => _pressedTouches.contains(identifier); + void _pressTouch(int identifier) { _pressedTouches.add(identifier); } + void _unpressTouch(int identifier) { _pressedTouches.remove(identifier); } + + void _addTouchEventListener(String eventName, _TouchEventListener handler) { + addEventListener(eventName, (html.Event event) { + final html.TouchEvent touchEvent = event; + return handler(touchEvent); + }); + } @override - void _setup() { - _addEventListener('touchstart', (html.Event event) { - _updateButtonDownState( - _deviceFromHtmlEvent(event), _kPrimaryMouseButton, true); - _callback(_convertEventToPointerData(ui.PointerChange.down, event)); + void setup() { + _addTouchEventListener('touchstart', (html.TouchEvent event) { + final Duration timeStamp = _BaseAdapter._eventTimeStampToDuration(event.timeStamp); + final List pointerData = []; + for (html.Touch touch in event.changedTouches) { + final nowPressed = _isTouchPressed(touch.identifier); + if (!nowPressed) { + _pressTouch(touch.identifier); + _convertEventToPointerData( + data: pointerData, + change: ui.PointerChange.down, + touch: touch, + pressed: true, + timeStamp: timeStamp, + ); + } + } + _callback(pointerData); }); - _addEventListener('touchmove', (html.Event event) { + _addTouchEventListener('touchmove', (html.TouchEvent event) { event.preventDefault(); // Prevents standard overscroll on iOS/Webkit. - if (!_isButtonDown(_deviceFromHtmlEvent(event), _kPrimaryMouseButton)) { - return; + final Duration timeStamp = _BaseAdapter._eventTimeStampToDuration(event.timeStamp); + final List pointerData = []; + for (html.Touch touch in event.changedTouches) { + final nowPressed = _isTouchPressed(touch.identifier); + if (nowPressed) { + _convertEventToPointerData( + data: pointerData, + change: ui.PointerChange.move, + touch: touch, + pressed: true, + timeStamp: timeStamp, + ); + } } - _callback(_convertEventToPointerData(ui.PointerChange.move, event)); + _callback(pointerData); }); - _addEventListener('touchend', (html.Event event) { + _addTouchEventListener('touchend', (html.TouchEvent event) { // On Safari Mobile, the keyboard does not show unless this line is // added. event.preventDefault(); - _updateButtonDownState( - _deviceFromHtmlEvent(event), _kPrimaryMouseButton, false); - _callback(_convertEventToPointerData(ui.PointerChange.up, event)); + final Duration timeStamp = _BaseAdapter._eventTimeStampToDuration(event.timeStamp); + final List pointerData = []; + for (html.Touch touch in event.changedTouches) { + final nowPressed = _isTouchPressed(touch.identifier); + if (nowPressed) { + _unpressTouch(touch.identifier); + _convertEventToPointerData( + data: pointerData, + change: ui.PointerChange.up, + touch: touch, + pressed: false, + timeStamp: timeStamp, + ); + _convertEventToPointerData( + data: pointerData, + change: ui.PointerChange.remove, + touch: touch, + pressed: false, + timeStamp: timeStamp, + ); + } + } + _callback(pointerData); }); - _addEventListener('touchcancel', (html.Event event) { - _callback(_convertEventToPointerData(ui.PointerChange.cancel, event)); + _addTouchEventListener('touchcancel', (html.TouchEvent event) { + final Duration timeStamp = _BaseAdapter._eventTimeStampToDuration(event.timeStamp); + final List pointerData = []; + for (html.Touch touch in event.changedTouches) { + final nowPressed = _isTouchPressed(touch.identifier); + if (nowPressed) { + _unpressTouch(touch.identifier); + _convertEventToPointerData( + data: pointerData, + change: ui.PointerChange.cancel, + touch: touch, + pressed: false, + timeStamp: timeStamp, + ); + _convertEventToPointerData( + data: pointerData, + change: ui.PointerChange.remove, + touch: touch, + pressed: false, + timeStamp: timeStamp, + ); + } + } + _callback(pointerData); }); } - List _convertEventToPointerData( - ui.PointerChange change, - html.TouchEvent event, - ) { - final html.TouchList touches = event.changedTouches; - final List data = List(touches.length); - final int len = touches.length; - for (int i = 0; i < len; i++) { - final html.Touch touch = touches[i]; - data[i] = ui.PointerData( - change: change, - timeStamp: _eventTimeStampToDuration(event.timeStamp), - kind: ui.PointerDeviceKind.touch, - signalKind: ui.PointerSignalKind.none, - device: touch.identifier, - physicalX: touch.client.x * ui.window.devicePixelRatio, - physicalY: touch.client.y * ui.window.devicePixelRatio, - pressure: 1.0, - pressureMin: 0.0, - pressureMax: 1.0, - ); - } - - return data; + void _convertEventToPointerData({ + @required List data, + @required ui.PointerChange change, + @required html.Touch touch, + @required bool pressed, + @required Duration timeStamp, + }) { + _pointerDataConverter.convert( + data, + change: change, + timeStamp: timeStamp, + kind: ui.PointerDeviceKind.touch, + signalKind: ui.PointerSignalKind.none, + device: touch.identifier, + physicalX: touch.client.x * ui.window.devicePixelRatio, + physicalY: touch.client.y * ui.window.devicePixelRatio, + buttons: pressed ? _kPrimaryMouseButton : 0, + pressure: 1.0, + pressureMin: 0.0, + pressureMax: 1.0, + ); } } -/// Intentionally set to -1 so it doesn't conflict with other device IDs. -const int _mouseDeviceId = -1; +typedef _MouseEventListener = dynamic Function(html.MouseEvent event); /// Adapter to be used with browsers that support mouse events. -class MouseAdapter extends BaseAdapter { - MouseAdapter(PointerDataCallback callback, DomRenderer domRenderer) - : super(callback, domRenderer); +/// +/// The difference between MouseEvent and PointerEvent can be illustrated using +/// a scenario of changing buttons during a drag sequence: LMB down, RMB down, +/// move, LMB up, RMB up, hover. +/// +/// LMB down RMB down move LMB up RMB up hover +/// PntEvt type | pointerdown pointermove pointermove pointermove pointerup pointermove +/// button | 0 2 -1 0 2 -1 +/// buttons | 0x1 0x3 0x3 0x2 0x0 0x0 +/// MosEvt type | mousedown mousedown mousemove mouseup mouseup mousemove +/// button | 0 2 0 0 2 0 +/// buttons | 0x1 0x3 0x3 0x2 0x0 0x0 +/// +/// The major differences are: +/// +/// * The type of events for changing buttons during a drag sequence. +/// * The `button` for dragging or hovering. +class _MouseAdapter extends _BaseAdapter with _WheelEventListenerMixin { + _MouseAdapter( + _PointerDataCallback callback, + html.Element glassPaneElement, + PointerDataConverter _pointerDataConverter + ) : super(callback, glassPaneElement, _pointerDataConverter); + + final _ButtonSanitizer _sanitizer = _ButtonSanitizer(); + + void _addMouseEventListener(String eventName, _MouseEventListener handler) { + addEventListener(eventName, (html.Event event) { + final html.MouseEvent mouseEvent = event; + return handler(mouseEvent); + }); + } @override - void _setup() { - _addEventListener('mousedown', (html.Event event) { - final int pointerButton = _pointerButtonFromHtmlEvent(event); - final int device = _deviceFromHtmlEvent(event); - if (_isButtonDown(device, pointerButton)) { - // TODO(flutter_web): Remove this temporary fix for right click - // on web platform once context guesture is implemented. - _callback(_convertEventToPointerData(ui.PointerChange.up, event)); - } - _updateButtonDownState(device, pointerButton, true); - _callback(_convertEventToPointerData(ui.PointerChange.down, event)); + void setup() { + _addMouseEventListener('mousedown', (html.MouseEvent event) { + final List pointerData = []; + final List<_SanitizedDetails> sanitizedDetails = + _sanitizer.sanitizeDownEvent( + button: event.button, + buttons: event.buttons, + ); + _convertEventsToPointerData(data: pointerData, event: event, detailsList: sanitizedDetails); + _callback(pointerData); }); - _addEventListener('mousemove', (html.Event event) { - final int pointerButton = _pointerButtonFromHtmlEvent(event); - final int device = _deviceFromHtmlEvent(event); - final List data = _convertEventToPointerData( - _isButtonDown(device, pointerButton) - ? ui.PointerChange.move - : ui.PointerChange.hover, - event); - _callback(data); + _addMouseEventListener('mousemove', (html.MouseEvent event) { + final List pointerData = []; + final List<_SanitizedDetails> sanitizedDetails = _sanitizer.sanitizeMoveEvent(buttons: event.buttons); + _convertEventsToPointerData(data: pointerData, event: event, detailsList: sanitizedDetails); + _callback(pointerData); }); - _addEventListener('mouseup', (html.Event event) { - final int device = _deviceFromHtmlEvent(event); - _updateButtonDownState(device, _pointerButtonFromHtmlEvent(event), false); - _callback(_convertEventToPointerData(ui.PointerChange.up, event)); + _addMouseEventListener('mouseup', (html.MouseEvent event) { + final List pointerData = []; + final bool isEndOfDrag = event.buttons == 0; + final List<_SanitizedDetails> sanitizedDetails = isEndOfDrag ? + _sanitizer.sanitizeUpEvent() : + _sanitizer.sanitizeMoveEvent(buttons: event.buttons); + _convertEventsToPointerData(data: pointerData, event: event, detailsList: sanitizedDetails); + _callback(pointerData); }); - _addWheelEventListener((html.WheelEvent event) { + _addWheelEventListener((html.Event event) { + assert(event is html.WheelEvent); if (_debugLogPointerEvents) { print(event.type); } _callback(_convertWheelEventToPointerData(event)); + // Prevent default so mouse wheel event doesn't get converted to + // a scroll event that semantic nodes would process. event.preventDefault(); }); } - List _convertEventToPointerData( - ui.PointerChange change, - html.MouseEvent event, - ) { - final List data = []; - // The mousedown event will cause an 'add' event on the framework side. - if (event.type == 'mousedown') { - PointerBinding._instance._activePointerIds.add(_mouseDeviceId); - } - if (event.type == 'mousemove') { - _ensureMouseDeviceAdded(data, event.client.x, event.client.y, - event.buttons, event.timeStamp, _mouseDeviceId); - } - data.add(ui.PointerData( - change: change, - timeStamp: _eventTimeStampToDuration(event.timeStamp), - kind: ui.PointerDeviceKind.mouse, - signalKind: ui.PointerSignalKind.none, - device: _mouseDeviceId, - physicalX: event.client.x * ui.window.devicePixelRatio, - physicalY: event.client.y * ui.window.devicePixelRatio, - buttons: event.buttons, - pressure: 1.0, - pressureMin: 0.0, - pressureMax: 1.0, - )); - return data; - } -} - -/// Convert a floating number timestamp (in milliseconds) to a [Duration] by -/// splitting it into two integer components: milliseconds + microseconds. -Duration _eventTimeStampToDuration(num milliseconds) { - final int ms = milliseconds.toInt(); - final int micro = - ((milliseconds - ms) * Duration.microsecondsPerMillisecond).toInt(); - return Duration(milliseconds: ms, microseconds: micro); -} - -void _ensureMouseDeviceAdded(List data, double clientX, - double clientY, int buttons, double timeStamp, int deviceId) { - if (PointerBinding.instance._activePointerIds.contains(deviceId)) { - return; - } - PointerBinding.instance._activePointerIds.add(deviceId); - // Only send [PointerChange.add] the first time. - data.insert( - 0, - ui.PointerData( - change: ui.PointerChange.add, - timeStamp: _eventTimeStampToDuration(timeStamp), + // For each event that is de-coalesced from `event` and described in + // `detailsList`, convert it to pointer data and store in `data`. + void _convertEventsToPointerData({ + @required List data, + @required html.MouseEvent event, + @required Iterable<_SanitizedDetails> detailsList, + }) { + assert(data != null); + assert(event != null); + assert(detailsList != null); + for (_SanitizedDetails details in detailsList) { + _pointerDataConverter.convert( + data, + change: details.change, + timeStamp: _BaseAdapter._eventTimeStampToDuration(event.timeStamp), kind: ui.PointerDeviceKind.mouse, - // In order for Flutter to actually add this pointer, we need to set the - // signal to none. signalKind: ui.PointerSignalKind.none, - device: deviceId, - physicalX: clientX * ui.window.devicePixelRatio, - physicalY: clientY * ui.window.devicePixelRatio, - buttons: buttons, + device: _mouseDeviceId, + physicalX: event.client.x * ui.window.devicePixelRatio, + physicalY: event.client.y * ui.window.devicePixelRatio, + buttons: details.buttons, pressure: 1.0, pressureMin: 0.0, pressureMax: 1.0, - scrollDeltaX: 0, - scrollDeltaY: 0, - )); -} - -List _convertWheelEventToPointerData( - html.WheelEvent event, -) { - const int domDeltaPixel = 0x00; - const int domDeltaLine = 0x01; - const int domDeltaPage = 0x02; - - // Flutter only supports pixel scroll delta. Convert deltaMode values - // to pixels. - double deltaX = event.deltaX; - double deltaY = event.deltaY; - switch (event.deltaMode) { - case domDeltaLine: - deltaX *= 32.0; - deltaY *= 32.0; - break; - case domDeltaPage: - deltaX *= ui.window.physicalSize.width; - deltaY *= ui.window.physicalSize.height; - break; - case domDeltaPixel: - default: - break; - } - - final List data = []; - _ensureMouseDeviceAdded(data, event.client.x, event.client.y, event.buttons, - event.timeStamp, _mouseDeviceId); - data.add(ui.PointerData( - change: ui.PointerChange.hover, - timeStamp: _eventTimeStampToDuration(event.timeStamp), - kind: ui.PointerDeviceKind.mouse, - signalKind: ui.PointerSignalKind.scroll, - device: _mouseDeviceId, - physicalX: event.client.x * ui.window.devicePixelRatio, - physicalY: event.client.y * ui.window.devicePixelRatio, - buttons: event.buttons, - pressure: 1.0, - pressureMin: 0.0, - pressureMax: 1.0, - scrollDeltaX: deltaX, - scrollDeltaY: deltaY, - )); - return data; -} - -void _addWheelEventListener(void listener(html.WheelEvent e)) { - final dynamic eventOptions = js_util.newObject(); - js_util.setProperty(eventOptions, 'passive', false); - js_util.callMethod(PointerBinding.instance.domRenderer.glassPaneElement, - 'addEventListener', [ - 'wheel', - js.allowInterop((html.WheelEvent event) => listener(event)), - eventOptions - ]); + ); + } + } } diff --git a/lib/web_ui/lib/src/engine/pointer_converter.dart b/lib/web_ui/lib/src/engine/pointer_converter.dart new file mode 100644 index 0000000000000..584dc990fd9ac --- /dev/null +++ b/lib/web_ui/lib/src/engine/pointer_converter.dart @@ -0,0 +1,709 @@ +// 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. + +part of engine; + +class _PointerState { + _PointerState(this.x, this.y); + + /// The identifier used in framework hit test. + int get pointer => _pointer; + int _pointer; + static int _pointerCount = 0; + void startNewPointer() { + _pointerCount += 1; + _pointer = _pointerCount; + } + + bool down = false; + + double x; + double y; +} + +/// Converter to convert web pointer data into a form that framework can +/// understand. +/// +/// This converter calculates pointer location delta and pointer identifier for +/// each pointer. Both are required by framework to correctly trigger gesture +/// activity. It also attempts to sanitize pointer data input sequence by always +/// synthesizing an add pointer data prior to hover or down if it the pointer is +/// not previously added. +/// +/// For example: +/// before: +/// hover -> down -> move -> up +/// after: +/// add(synthesize) -> hover -> down -> move -> up +/// +/// before: +/// down -> move -> up +/// after: +/// add(synthesize) -> down -> move -> up +class PointerDataConverter { + PointerDataConverter(); + + // Map from browser pointer identifiers to PointerEvent pointer identifiers. + final Map _pointers = {}; + + /// Clears the existing pointer states. + /// + /// This method is invoked during hot reload to make sure we have a clean + /// converter after hot reload. + void clearPointerState() { + _pointers.clear(); + _PointerState._pointerCount = 0; + } + + _PointerState _ensureStateForPointer(int device, double x, double y) { + return _pointers.putIfAbsent( + device, + () => _PointerState(x, y), + ); + } + + ui.PointerData _generateCompletePointerData({ + Duration timeStamp, + ui.PointerChange change, + ui.PointerDeviceKind kind, + ui.PointerSignalKind signalKind, + int device, + double physicalX, + double physicalY, + int buttons, + bool obscured, + double pressure, + double pressureMin, + double pressureMax, + double distance, + double distanceMax, + double size, + double radiusMajor, + double radiusMinor, + double radiusMin, + double radiusMax, + double orientation, + double tilt, + int platformData, + double scrollDeltaX, + double scrollDeltaY, + }) { + assert(_pointers.containsKey(device)); + final _PointerState state = _pointers[device]; + final double deltaX = physicalX - state.x; + final double deltaY = physicalY - state.y; + state.x = physicalX; + state.y = physicalY; + return ui.PointerData( + timeStamp: timeStamp, + change: change, + kind: kind, + signalKind: signalKind, + device: device, + pointerIdentifier: state.pointer ?? 0, + physicalX: physicalX, + physicalY: physicalY, + physicalDeltaX: deltaX, + physicalDeltaY: deltaY, + buttons: buttons, + obscured: obscured, + pressure: pressure, + pressureMin: pressureMin, + pressureMax: pressureMax, + distance: distance, + distanceMax: distanceMax, + size: size, + radiusMajor: radiusMajor, + radiusMinor: radiusMinor, + radiusMin: radiusMin, + radiusMax: radiusMax, + orientation: orientation, + tilt: tilt, + platformData: platformData, + scrollDeltaX: scrollDeltaX, + scrollDeltaY: scrollDeltaY, + ); + } + + bool _locationHasChanged(int device, double physicalX, double physicalY) { + assert(_pointers.containsKey(device)); + final _PointerState state = _pointers[device]; + return state.x != physicalX || state.y != physicalY; + } + + ui.PointerData _synthesizePointerData({ + Duration timeStamp, + ui.PointerChange change, + ui.PointerDeviceKind kind, + int device, + double physicalX, + double physicalY, + int buttons, + bool obscured, + double pressure, + double pressureMin, + double pressureMax, + double distance, + double distanceMax, + double size, + double radiusMajor, + double radiusMinor, + double radiusMin, + double radiusMax, + double orientation, + double tilt, + int platformData, + double scrollDeltaX, + double scrollDeltaY, + }) { + assert(_pointers.containsKey(device)); + final _PointerState state = _pointers[device]; + final double deltaX = physicalX - state.x; + final double deltaY = physicalY - state.y; + state.x = physicalX; + state.y = physicalY; + return ui.PointerData( + timeStamp: timeStamp, + change: change, + kind: kind, + // All the pointer data except scroll should not have a signal kind, and + // there is no use case for synthetic scroll event. We should be + // safe to default it to ui.PointerSignalKind.none. + signalKind: ui.PointerSignalKind.none, + device: device, + pointerIdentifier: state.pointer ?? 0, + physicalX: physicalX, + physicalY: physicalY, + physicalDeltaX: deltaX, + physicalDeltaY: deltaY, + buttons: buttons, + obscured: obscured, + synthesized: true, + pressure: pressure, + pressureMin: pressureMin, + pressureMax: pressureMax, + distance: distance, + distanceMax: distanceMax, + size: size, + radiusMajor: radiusMajor, + radiusMinor: radiusMinor, + radiusMin: radiusMin, + radiusMax: radiusMax, + orientation: orientation, + tilt: tilt, + platformData: platformData, + scrollDeltaX: scrollDeltaX, + scrollDeltaY: scrollDeltaY, + ); + } + + /// Converts the given html pointer event metrics into a sequence of framework-compatible + /// pointer data and stores it into [result] + void convert( + List result, { + Duration timeStamp = Duration.zero, + ui.PointerChange change = ui.PointerChange.cancel, + ui.PointerDeviceKind kind = ui.PointerDeviceKind.touch, + ui.PointerSignalKind signalKind, + int device = 0, + double physicalX = 0.0, + double physicalY = 0.0, + int buttons = 0, + bool obscured = false, + double pressure = 0.0, + double pressureMin = 0.0, + double pressureMax = 0.0, + double distance = 0.0, + double distanceMax = 0.0, + double size = 0.0, + double radiusMajor = 0.0, + double radiusMinor = 0.0, + double radiusMin = 0.0, + double radiusMax = 0.0, + double orientation = 0.0, + double tilt = 0.0, + int platformData = 0, + double scrollDeltaX = 0.0, + double scrollDeltaY = 0.0, + }) { + assert(change != null); + if (signalKind == null || + signalKind == ui.PointerSignalKind.none) { + switch (change) { + case ui.PointerChange.add: + assert(!_pointers.containsKey(device)); + _ensureStateForPointer(device, physicalX, physicalY); + assert(!_locationHasChanged(device, physicalX, physicalY)); + result.add( + _generateCompletePointerData( + timeStamp: timeStamp, + change: change, + kind: kind, + signalKind: signalKind, + device: device, + physicalX: physicalX, + physicalY: physicalY, + buttons: buttons, + obscured: obscured, + pressure: pressure, + pressureMin: pressureMin, + pressureMax: pressureMax, + distance: distance, + distanceMax: distanceMax, + size: size, + radiusMajor: radiusMajor, + radiusMinor: radiusMinor, + radiusMin: radiusMin, + radiusMax: radiusMax, + orientation: orientation, + tilt: tilt, + platformData: platformData, + scrollDeltaX: scrollDeltaX, + scrollDeltaY: scrollDeltaY, + ) + ); + break; + case ui.PointerChange.hover: + final bool alreadyAdded = _pointers.containsKey(device); + final _PointerState state = _ensureStateForPointer( + device, physicalX, physicalY); + assert(!state.down); + if (!alreadyAdded) { + // Synthesizes an add pointer data. + result.add( + _synthesizePointerData( + timeStamp: timeStamp, + change: ui.PointerChange.add, + kind: kind, + device: device, + physicalX: physicalX, + physicalY: physicalY, + buttons: buttons, + obscured: obscured, + pressure: pressure, + pressureMin: pressureMin, + pressureMax: pressureMax, + distance: distance, + distanceMax: distanceMax, + size: size, + radiusMajor: radiusMajor, + radiusMinor: radiusMinor, + radiusMin: radiusMin, + radiusMax: radiusMax, + orientation: orientation, + tilt: tilt, + platformData: platformData, + scrollDeltaX: scrollDeltaX, + scrollDeltaY: scrollDeltaY, + ) + ); + } + result.add( + _generateCompletePointerData( + timeStamp: timeStamp, + change: change, + kind: kind, + signalKind: signalKind, + device: device, + physicalX: physicalX, + physicalY: physicalY, + buttons: buttons, + obscured: obscured, + pressure: pressure, + pressureMin: pressureMin, + pressureMax: pressureMax, + distance: distance, + distanceMax: distanceMax, + size: size, + radiusMajor: radiusMajor, + radiusMinor: radiusMinor, + radiusMin: radiusMin, + radiusMax: radiusMax, + orientation: orientation, + tilt: tilt, + platformData: platformData, + scrollDeltaX: scrollDeltaX, + scrollDeltaY: scrollDeltaY, + ) + ); + break; + case ui.PointerChange.down: + final bool alreadyAdded = _pointers.containsKey(device); + final _PointerState state = _ensureStateForPointer( + device, physicalX, physicalY); + assert(!state.down); + state.startNewPointer(); + if (!alreadyAdded) { + // Synthesizes an add pointer data. + result.add( + _synthesizePointerData( + timeStamp: timeStamp, + change: ui.PointerChange.add, + kind: kind, + device: device, + physicalX: physicalX, + physicalY: physicalY, + buttons: buttons, + obscured: obscured, + pressure: pressure, + pressureMin: pressureMin, + pressureMax: pressureMax, + distance: distance, + distanceMax: distanceMax, + size: size, + radiusMajor: radiusMajor, + radiusMinor: radiusMinor, + radiusMin: radiusMin, + radiusMax: radiusMax, + orientation: orientation, + tilt: tilt, + platformData: platformData, + scrollDeltaX: scrollDeltaX, + scrollDeltaY: scrollDeltaY, + ) + ); + } + if (_locationHasChanged(device, physicalX, physicalY)) { + assert(alreadyAdded); + // Synthesize a hover of the pointer to the down location before + // sending the down event, if necessary. + result.add( + _synthesizePointerData( + timeStamp: timeStamp, + change: ui.PointerChange.hover, + kind: kind, + device: device, + physicalX: physicalX, + physicalY: physicalY, + buttons: 0, + obscured: obscured, + pressure: 0.0, + pressureMin: pressureMin, + pressureMax: pressureMax, + distance: distance, + distanceMax: distanceMax, + size: size, + radiusMajor: radiusMajor, + radiusMinor: radiusMinor, + radiusMin: radiusMin, + radiusMax: radiusMax, + orientation: orientation, + tilt: tilt, + platformData: platformData, + scrollDeltaX: scrollDeltaX, + scrollDeltaY: scrollDeltaY, + ) + ); + } + state.down = true; + result.add( + _generateCompletePointerData( + timeStamp: timeStamp, + change: change, + kind: kind, + signalKind: signalKind, + device: device, + physicalX: physicalX, + physicalY: physicalY, + buttons: buttons, + obscured: obscured, + pressure: pressure, + pressureMin: pressureMin, + pressureMax: pressureMax, + distance: distance, + distanceMax: distanceMax, + size: size, + radiusMajor: radiusMajor, + radiusMinor: radiusMinor, + radiusMin: radiusMin, + radiusMax: radiusMax, + orientation: orientation, + tilt: tilt, + platformData: platformData, + scrollDeltaX: scrollDeltaX, + scrollDeltaY: scrollDeltaY, + ) + ); + break; + case ui.PointerChange.move: + assert(_pointers.containsKey(device)); + final _PointerState state = _pointers[device]; + assert(state.down); + result.add( + _generateCompletePointerData( + timeStamp: timeStamp, + change: change, + kind: kind, + signalKind: signalKind, + device: device, + physicalX: physicalX, + physicalY: physicalY, + buttons: buttons, + obscured: obscured, + pressure: pressure, + pressureMin: pressureMin, + pressureMax: pressureMax, + distance: distance, + distanceMax: distanceMax, + size: size, + radiusMajor: radiusMajor, + radiusMinor: radiusMinor, + radiusMin: radiusMin, + radiusMax: radiusMax, + orientation: orientation, + tilt: tilt, + platformData: platformData, + scrollDeltaX: scrollDeltaX, + scrollDeltaY: scrollDeltaY, + ) + ); + break; + case ui.PointerChange.up: + case ui.PointerChange.cancel: + assert(_pointers.containsKey(device)); + final _PointerState state = _pointers[device]; + assert(state.down); + // Cancel events can have different coordinates due to various + // reasons (window lost focus which is accompanied by window + // movement, or PointerEvent simply always gives 0). Instead of + // caring about the coordinates, we want to cancel the pointers as + // soon as possible. + if (change == ui.PointerChange.cancel) { + physicalX = state.x; + physicalY = state.y; + } + if (_locationHasChanged(device, physicalX, physicalY)) { + // Synthesize a move of the pointer to the up location before + // sending the up event, if necessary. + result.add( + _synthesizePointerData( + timeStamp: timeStamp, + change: ui.PointerChange.move, + kind: kind, + device: device, + physicalX: physicalX, + physicalY: physicalY, + buttons: buttons, + obscured: obscured, + pressure: pressure, + pressureMin: pressureMin, + pressureMax: pressureMax, + distance: distance, + distanceMax: distanceMax, + size: size, + radiusMajor: radiusMajor, + radiusMinor: radiusMinor, + radiusMin: radiusMin, + radiusMax: radiusMax, + orientation: orientation, + tilt: tilt, + platformData: platformData, + scrollDeltaX: scrollDeltaX, + scrollDeltaY: scrollDeltaY, + ) + ); + } + state.down = false; + result.add( + _generateCompletePointerData( + timeStamp: timeStamp, + change: change, + kind: kind, + signalKind: signalKind, + device: device, + physicalX: physicalX, + physicalY: physicalY, + buttons: buttons, + obscured: obscured, + pressure: pressure, + pressureMin: pressureMin, + pressureMax: pressureMax, + distance: distance, + distanceMax: distanceMax, + size: size, + radiusMajor: radiusMajor, + radiusMinor: radiusMinor, + radiusMin: radiusMin, + radiusMax: radiusMax, + orientation: orientation, + tilt: tilt, + platformData: platformData, + scrollDeltaX: scrollDeltaX, + scrollDeltaY: scrollDeltaY, + ) + ); + break; + case ui.PointerChange.remove: + assert(_pointers.containsKey(device)); + final _PointerState state = _pointers[device]; + assert(!state.down); + result.add( + _generateCompletePointerData( + timeStamp: timeStamp, + change: change, + kind: kind, + signalKind: signalKind, + device: device, + physicalX: state.x, + physicalY: state.y, + buttons: buttons, + obscured: obscured, + pressure: pressure, + pressureMin: pressureMin, + pressureMax: pressureMax, + distance: distance, + distanceMax: distanceMax, + size: size, + radiusMajor: radiusMajor, + radiusMinor: radiusMinor, + radiusMin: radiusMin, + radiusMax: radiusMax, + orientation: orientation, + tilt: tilt, + platformData: platformData, + scrollDeltaX: scrollDeltaX, + scrollDeltaY: scrollDeltaY, + ) + ); + _pointers.remove(device); + break; + } + } else { + switch (signalKind) { + case ui.PointerSignalKind.scroll: + final bool alreadyAdded = _pointers.containsKey(device); + final _PointerState state = _ensureStateForPointer( + device, physicalX, physicalY); + if (!alreadyAdded) { + // Synthesizes an add pointer data. + result.add( + _synthesizePointerData( + timeStamp: timeStamp, + change: ui.PointerChange.add, + kind: kind, + device: device, + physicalX: physicalX, + physicalY: physicalY, + buttons: buttons, + obscured: obscured, + pressure: pressure, + pressureMin: pressureMin, + pressureMax: pressureMax, + distance: distance, + distanceMax: distanceMax, + size: size, + radiusMajor: radiusMajor, + radiusMinor: radiusMinor, + radiusMin: radiusMin, + radiusMax: radiusMax, + orientation: orientation, + tilt: tilt, + platformData: platformData, + scrollDeltaX: scrollDeltaX, + scrollDeltaY: scrollDeltaY, + ) + ); + } + if (_locationHasChanged(device, physicalX, physicalY)) { + // Synthesize a hover/move of the pointer to the scroll location + // before sending the scroll event, if necessary, so that clients + // don't have to worry about native ordering of hover and scroll + // events. + if (state.down) { + result.add( + _synthesizePointerData( + timeStamp: timeStamp, + change: ui.PointerChange.move, + kind: kind, + device: device, + physicalX: physicalX, + physicalY: physicalY, + buttons: buttons, + obscured: obscured, + pressure: pressure, + pressureMin: pressureMin, + pressureMax: pressureMax, + distance: distance, + distanceMax: distanceMax, + size: size, + radiusMajor: radiusMajor, + radiusMinor: radiusMinor, + radiusMin: radiusMin, + radiusMax: radiusMax, + orientation: orientation, + tilt: tilt, + platformData: platformData, + scrollDeltaX: scrollDeltaX, + scrollDeltaY: scrollDeltaY, + ) + ); + } else { + result.add( + _synthesizePointerData( + timeStamp: timeStamp, + change: ui.PointerChange.hover, + kind: kind, + device: device, + physicalX: physicalX, + physicalY: physicalY, + buttons: buttons, + obscured: obscured, + pressure: pressure, + pressureMin: pressureMin, + pressureMax: pressureMax, + distance: distance, + distanceMax: distanceMax, + size: size, + radiusMajor: radiusMajor, + radiusMinor: radiusMinor, + radiusMin: radiusMin, + radiusMax: radiusMax, + orientation: orientation, + tilt: tilt, + platformData: platformData, + scrollDeltaX: scrollDeltaX, + scrollDeltaY: scrollDeltaY, + ) + ); + } + } + result.add( + _generateCompletePointerData( + timeStamp: timeStamp, + change: change, + kind: kind, + signalKind: signalKind, + device: device, + physicalX: physicalX, + physicalY: physicalY, + buttons: buttons, + obscured: obscured, + pressure: pressure, + pressureMin: pressureMin, + pressureMax: pressureMax, + distance: distance, + distanceMax: distanceMax, + size: size, + radiusMajor: radiusMajor, + radiusMinor: radiusMinor, + radiusMin: radiusMin, + radiusMax: radiusMax, + orientation: orientation, + tilt: tilt, + platformData: platformData, + scrollDeltaX: scrollDeltaX, + scrollDeltaY: scrollDeltaY, + ) + ); + break; + case ui.PointerSignalKind.none: + assert(false); // This branch should already have 'none' filtered out. + break; + case ui.PointerSignalKind.unknown: + // Ignore unknown signals. + break; + } + } + } +} diff --git a/lib/web_ui/lib/src/engine/render_vertices.dart b/lib/web_ui/lib/src/engine/render_vertices.dart index a64022a0c6632..d3648c4b6fcc7 100644 --- a/lib/web_ui/lib/src/engine/render_vertices.dart +++ b/lib/web_ui/lib/src/engine/render_vertices.dart @@ -23,7 +23,7 @@ abstract class _GlRenderer { Matrix4 transform, ui.Vertices vertices, ui.BlendMode blendMode, - ui.PaintData paint); + SurfacePaintData paint); void drawHairline(html.CanvasRenderingContext2D _ctx, Float32List positions); } @@ -86,7 +86,7 @@ class _WebGlRenderer implements _GlRenderer { Matrix4 transform, ui.Vertices vertices, ui.BlendMode blendMode, - ui.PaintData paint) { + SurfacePaintData paint) { // Compute bounds of vertices. final Float32List positions = vertices.positions; ui.Rect bounds = _computeVerticesBounds(positions, transform); diff --git a/lib/web_ui/lib/src/engine/rrect_renderer.dart b/lib/web_ui/lib/src/engine/rrect_renderer.dart index f463f2c5bad4f..d32ae205341c8 100644 --- a/lib/web_ui/lib/src/engine/rrect_renderer.dart +++ b/lib/web_ui/lib/src/engine/rrect_renderer.dart @@ -222,3 +222,27 @@ class _RRectToPathRenderer extends _RRectRenderer { antiClockwise ? startAngle - endAngle : endAngle - startAngle); } } + +typedef RRectRendererEllipseCallback = void Function(double centerX, double centerY, double radiusX, double radiusY, double rotation, double startAngle, double endAngle, bool antiClockwise); +typedef RRectRendererCallback = void Function(double x, double y); + +/// Converts RRect to path primitives with callbacks. +class RRectMetricsRenderer extends _RRectRenderer { + RRectMetricsRenderer({this.moveToCallback, this.lineToCallback, this.ellipseCallback}); + + final RRectRendererEllipseCallback ellipseCallback; + final RRectRendererCallback lineToCallback; + final RRectRendererCallback moveToCallback; + @override + void beginPath() {} + + @override + void ellipse(double centerX, double centerY, double radiusX, double radiusY, double rotation, double startAngle, double endAngle, bool antiClockwise) => ellipseCallback( + centerX, centerY, radiusX, radiusY, rotation, startAngle, endAngle, antiClockwise); + + @override + void lineTo(double x, double y) => lineToCallback(x, y); + + @override + void moveTo(double x, double y) => moveToCallback(x, y); +} diff --git a/lib/web_ui/lib/src/engine/semantics/semantics.dart b/lib/web_ui/lib/src/engine/semantics/semantics.dart index 6005d288ea8b3..f1f55f0c719e4 100644 --- a/lib/web_ui/lib/src/engine/semantics/semantics.dart +++ b/lib/web_ui/lib/src/engine/semantics/semantics.dart @@ -1198,195 +1198,7 @@ class EngineSemanticsOwner { _now = () => DateTime.now(); } - /// A temporary placeholder used to capture a request to activate semantics. - html.Element _semanticsPlaceholder; - - /// We do not immediately enable semantics when the user requests it, but - /// instead wait for a short period of time before doing it. This is because - /// the request comes as a tap on the [_semanticsPlaceholder]. The tap, - /// depending on the browser, comes as a burst of events. For example, Safari - /// sends "touchstart", "touchend", and "click". So during a short time period - /// we consume all events and prevent forwarding to the framework. Otherwise, - /// the events will be interpreted twice, once as a request to activate - /// semantics, and a second time by Flutter's gesture recognizers. - Timer _semanticsActivationTimer; - - /// The number of events we processed that could potentially activate - /// semantics. - int _semanticsActivationAttempts = 0; - - /// The maximum [_semanticsActivationAttempts] before we give up waiting for - /// the user to enable semantics. - /// - /// This number is arbitrary and can be adjusted if it doesn't work well. - static const int _kMaxSemanticsActivationAttempts = 20; - - /// Whether we are waiting for the user to enable semantics. - bool get _isWaitingToEnableSemantics => _semanticsPlaceholder != null; - - /// Instructs [_tryEnableSemantics] to remove [_semanticsPlaceholder]. - /// - /// On Chrome the placeholder is removed upon any next event. - /// - /// On Safari the placeholder is removed upon the next "touchend" event. This - /// is to prevent Safari from swallowing the event that happens on an element - /// that's being removed. Chrome doesn't have this issue. - bool _schedulePlaceholderRemoval = false; - - /// Attempts to activate semantics. - /// - /// Returns true if the `event` is not related to semantics activation and - /// should be forwarded to the framework. - bool _tryEnableSemantics(html.Event event) { - if (_schedulePlaceholderRemoval) { - final bool removeNow = - browserEngine != BrowserEngine.webkit || event.type == 'touchend'; - if (removeNow) { - _semanticsPlaceholder.remove(); - _semanticsPlaceholder = null; - _semanticsActivationTimer = null; - } - return true; - } - - if (semanticsEnabled) { - // Semantics already enabled, forward to framework as normal. - return true; - } - - _semanticsActivationAttempts += 1; - if (_semanticsActivationAttempts >= _kMaxSemanticsActivationAttempts) { - // We have received multiple user events, none of which resulted in - // semantics activation. This is a signal that the user is not interested - // in semantics, and so we will stop waiting for it. - _schedulePlaceholderRemoval = true; - return true; - } - - const List kInterestingEventTypes = [ - 'click', - 'touchstart', - 'touchend', - ]; - - if (!kInterestingEventTypes.contains(event.type)) { - // The event is not relevant, forward to framework as normal. - return true; - } - - if (_semanticsActivationTimer != null) { - // We are in a waiting period to activate a timer. While the timer is - // active we should consume events pertaining to semantics activation. - // Otherwise the event will also be interpreted by the framework and - // potentially result in activating a gesture in the app. - return false; - } - - // In Chrome the debouncing works well enough to detect accessibility - // request. - final bool blinkEnableConditionPassed = - browserEngine == BrowserEngine.blink && - _gestureMode == GestureMode.browserGestures; - - // In Safari debouncing doesn't work. Instead we look at where exactly - // (within 1 pixel) the event landed. If it landed exactly in the middle of - // the placeholder we interpret it as a signal to enable accessibility. This - // is because when VoiceOver generates a tap it lands it in the middle of - // the focused element. This method is a bit flawed in that a user's finger - // could theoretically land in the middle of the element too. However, the - // chance of that happening is very small. Even low-end phones typically - // have >2 million pixels (e.g. Moto G4). It is very unlikely that a user - // will land their finger exactly in the middle. In the worst case an - // unlucky user would accidentally enable accessibility and the app will be - // slightly slower than normal, but the app will continue functioning as - // normal. Our semantics tree is designed to not interfere with Flutter's - // gesture detection. - bool safariEnableConditionPassed = false; - if (browserEngine == BrowserEngine.webkit) { - html.Point activationPoint; - - switch (event.type) { - case 'click': - final html.MouseEvent click = event; - activationPoint = click.offset; - break; - case 'touchstart': - case 'touchend': - final html.TouchEvent touch = event; - activationPoint = touch.changedTouches.first.client; - break; - default: - // The event is not relevant, forward to framework as normal. - return true; - } - - assert(activationPoint != null); - - final html.Rectangle activatingElementRect = - domRenderer.glassPaneElement.getBoundingClientRect(); - final double midX = activatingElementRect.left + - (activatingElementRect.right - activatingElementRect.left) / 2; - final double midY = activatingElementRect.top + - (activatingElementRect.bottom - activatingElementRect.top) / 2; - final double deltaX = activationPoint.x - midX; - final double deltaY = activationPoint.y - midY; - final double deltaSquared = deltaX * deltaX + deltaY * deltaY; - if (deltaSquared < 1.0) { - safariEnableConditionPassed = true; - } - } - - if (blinkEnableConditionPassed || safariEnableConditionPassed) { - assert(_semanticsActivationTimer == null); - _semanticsActivationTimer = Timer(const Duration(milliseconds: 300), () { - semanticsEnabled = true; - _schedulePlaceholderRemoval = true; - }); - return false; - } - - // This was not a semantics activating event; forward as normal. - return true; - } - - /// The message in the label for the placeholder element used to enable - /// accessibility. - /// - /// This uses US English as the default message. Set this value prior to - /// calling `runApp` to translate to another language. - static String placeholderMessage = 'Enable accessibility'; - - /// Enables accessibility when the user taps on the glasspane via an - /// accessibility focus. - /// - /// This creates a placeholder inside the glasspane, which, when focused, - /// announces that accessibility can be enabled by tapping. - void autoEnableOnTap(DomRenderer domRenderer) { - _semanticsPlaceholder = html.Element.tag('flt-semantics-placeholder'); - - // Only listen to "click" because other kinds of events are reported via - // PointerBinding. - _semanticsPlaceholder.addEventListener('click', (html.Event event) { - _tryEnableSemantics(event); - }, true); - - _semanticsPlaceholder - ..setAttribute('role', 'button') - ..setAttribute('aria-label', placeholderMessage); - _semanticsPlaceholder.style - ..position = 'absolute' - ..left = '0' - ..top = '0' - ..right = '0' - ..bottom = '0'; - // Insert the semantics placeholder after the scene host. For all widgets - // in the scene, except for platform widgets, the scene host will pass the - // pointer events through to the semantics tree. However, for platform - // views, the pointer events will not pass through, and will be handled - // by the platform view. - domRenderer.glassPaneElement - .insertBefore(_semanticsPlaceholder, domRenderer.sceneHostElement); - } + final SemanticsHelper semanticsHelper = SemanticsHelper(); /// Whether the user has requested that [updateSemantics] be called when /// the semantic contents of window changes. @@ -1526,18 +1338,15 @@ class EngineSemanticsOwner { 'mousedown', 'mousemove', 'mouseup', + 'keyup', + 'keydown', ]; if (_pointerEventTypes.contains(event.type)) { _temporarilyDisableBrowserGestureMode(); } - if (!_isWaitingToEnableSemantics) { - // Forward to framework as normal. - return true; - } else { - return _tryEnableSemantics(event); - } + return semanticsHelper.shouldEnableSemantics(event); } /// Callbacks called when the [GestureMode] changes. diff --git a/lib/web_ui/lib/src/engine/semantics/semantics_helper.dart b/lib/web_ui/lib/src/engine/semantics/semantics_helper.dart new file mode 100644 index 0000000000000..d7ce9ae9c65df --- /dev/null +++ b/lib/web_ui/lib/src/engine/semantics/semantics_helper.dart @@ -0,0 +1,391 @@ +// 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. + +part of engine; + +/// The maximum [semanticsActivationAttempts] before we give up waiting for +/// the user to enable semantics. +/// +/// This number is arbitrary and can be adjusted if it doesn't work well. +const int kMaxSemanticsActivationAttempts = 20; + +/// After an event related to semantics activation has been received, we consume +/// the consecutive events on the engine. Do not send them to the framework. +/// For example when a 'mousedown' targeting a placeholder received following +/// 'mouseup' is also not sent to the framework. +/// Otherwise these events can cause unintended gestures on the framework side. +const Duration _periodToConsumeEvents = const Duration(milliseconds: 300); + +/// The message in the label for the placeholder element used to enable +/// accessibility. +/// +/// This uses US English as the default message. Set this value prior to +/// calling `runApp` to translate to another language. +String placeholderMessage = 'Enable accessibility'; + +/// A helper for [EngineSemanticsOwner]. +/// +/// [SemanticsHelper] prepares and placeholder to enable semantics. +/// +/// It decides if an event is purely semantics enabling related or a regular +/// event which should be forwarded to the framework. +/// +/// It does this by using a [SemanticsEnabler]. The [SemanticsEnabler] +/// implementation is choosen using form factor type. +/// +/// See [DesktopSemanticsEnabler], [MobileSemanticsEnabler]. +class SemanticsHelper { + SemanticsEnabler _semanticsEnabler = + isDesktop ? DesktopSemanticsEnabler() : MobileSemanticsEnabler(); + + @visibleForTesting + set semanticsEnabler(SemanticsEnabler semanticsEnabler) { + this._semanticsEnabler = semanticsEnabler; + } + + bool shouldEnableSemantics(html.Event event) { + return _semanticsEnabler.shouldEnableSemantics(event); + } + + html.Element prepareAccesibilityPlaceholder() { + return _semanticsEnabler.prepareAccesibilityPlaceholder(); + } +} + +@visibleForTesting +abstract class SemanticsEnabler { + /// Whether to enable semantics. + /// + /// Semantics should be enabled if the web engine is no longer waiting for + /// extra signals from the user events. See [isWaitingToEnableSemantics]. + /// + /// Or if the received [html.Event] is suitable/enough for enabling the + /// semantics. See [tryEnableSemantics]. + bool shouldEnableSemantics(html.Event event) { + if (!isWaitingToEnableSemantics) { + // Forward to framework as normal. + return true; + } else { + return tryEnableSemantics(event); + } + } + + /// Attempts to activate semantics. + /// + /// Returns true if the `event` is not related to semantics activation and + /// should be forwarded to the framework. + bool tryEnableSemantics(html.Event event); + + /// Creates the placeholder for accesibility. + /// + /// Puts it inside the glasspane. + /// + /// On focus the element announces that accessibility can be enabled by + /// tapping/clicking. (Announcement depends on the assistive technology) + html.Element prepareAccesibilityPlaceholder(); + + /// Whether platform is still consisering enabling semantics. + /// + /// At this stage a relevant set of events are always assessed to see if + /// they activate the semantics. + /// + /// If not they are sent to framework as normal events. + bool get isWaitingToEnableSemantics; +} + +@visibleForTesting +class DesktopSemanticsEnabler extends SemanticsEnabler { + /// We do not immediately enable semantics when the user requests it, but + /// instead wait for a short period of time before doing it. This is because + /// the request comes as an event targeted on the [_semanticsPlaceholder]. + /// This event, depending on the browser, comes as a burst of events. + /// For example, Safari on MacOS sends "pointerup", "pointerdown". So during a + /// short time period we consume all events and prevent forwarding to the + /// framework. Otherwise, the events will be interpreted twice, once as a + /// request to activate semantics, and a second time by Flutter's gesture + /// recognizers. + @visibleForTesting + Timer semanticsActivationTimer; + + /// A temporary placeholder used to capture a request to activate semantics. + html.Element _semanticsPlaceholder; + + /// The number of events we processed that could potentially activate + /// semantics. + int semanticsActivationAttempts = 0; + + /// Instructs [_tryEnableSemantics] to remove [_semanticsPlaceholder]. + /// + /// The placeholder is removed upon any next event. + bool _schedulePlaceholderRemoval = false; + + /// Whether we are waiting for the user to enable semantics. + @override + bool get isWaitingToEnableSemantics => _semanticsPlaceholder != null; + + @override + bool tryEnableSemantics(html.Event event) { + if (_schedulePlaceholderRemoval) { + _semanticsPlaceholder.remove(); + _semanticsPlaceholder = null; + semanticsActivationTimer = null; + return true; + } + + if (EngineSemanticsOwner.instance.semanticsEnabled) { + // Semantics already enabled, forward to framework as normal. + return true; + } + + // In touch screen laptops, the touch is received as a mouse click + const Set kInterestingEventTypes = { + 'click', + 'keyup', + 'keydown', + 'mouseup', + 'mousedown', + 'pointerdown', + 'pointerup', + }; + + if (!kInterestingEventTypes.contains(event.type)) { + // The event is not relevant, forward to framework as normal. + return true; + } + + semanticsActivationAttempts += 1; + if (semanticsActivationAttempts >= kMaxSemanticsActivationAttempts) { + // We have received multiple user events, none of which resulted in + // semantics activation. This is a signal that the user is not interested + // in semantics, and so we will stop waiting for it. + _schedulePlaceholderRemoval = true; + return true; + } + + if (semanticsActivationTimer != null) { + // We are in a waiting period to activate a timer. While the timer is + // active we should consume events pertaining to semantics activation. + // Otherwise the event will also be interpreted by the framework and + // potentially result in activating a gesture in the app. + return false; + } + + // Check for the event target. + final bool enableConditionPassed = (event.target == _semanticsPlaceholder); + + if (enableConditionPassed) { + assert(semanticsActivationTimer == null); + semanticsActivationTimer = Timer(_periodToConsumeEvents, () { + EngineSemanticsOwner.instance.semanticsEnabled = true; + _schedulePlaceholderRemoval = true; + }); + return false; + } + + // This was not a semantics activating event; forward as normal. + return true; + } + + @override + html.Element prepareAccesibilityPlaceholder() { + _semanticsPlaceholder = html.Element.tag('flt-semantics-placeholder'); + + // Only listen to "click" because other kinds of events are reported via + // PointerBinding. + _semanticsPlaceholder.addEventListener('click', (html.Event event) { + tryEnableSemantics(event); + }, true); + + // Adding roles to semantics placeholder. 'aria-live' will make sure that + // the content is announced to the assistive technology user as soon as the + // page receives focus. 'tab-index' makes sure the button is the first + // target of tab. 'aria-label' is used to define the placeholder message + // to the assistive technology user. + _semanticsPlaceholder + ..setAttribute('role', 'button') + ..setAttribute('aria-live', 'true') + ..setAttribute('tabindex', '0') + ..setAttribute('aria-label', placeholderMessage); + _semanticsPlaceholder.style + ..position = 'absolute' + ..left = '-1px' + ..top = '-1px' + ..width = '1px' + ..height = '1px'; + return _semanticsPlaceholder; + } +} + +@visibleForTesting +class MobileSemanticsEnabler extends SemanticsEnabler { + /// We do not immediately enable semantics when the user requests it, but + /// instead wait for a short period of time before doing it. This is because + /// the request comes as an event targeted on the [_semanticsPlaceholder]. + /// This event, depending on the browser, comes as a burst of events. + /// For example, Safari on IOS sends "touchstart", "touchend", and "click". + /// So during a short time period we consume all events and prevent forwarding + /// to the framework. Otherwise, the events will be interpreted twice, once as + /// a request to activate semantics, and a second time by Flutter's gesture + /// recognizers. + @visibleForTesting + Timer semanticsActivationTimer; + + /// A temporary placeholder used to capture a request to activate semantics. + html.Element _semanticsPlaceholder; + + /// The number of events we processed that could potentially activate + /// semantics. + int semanticsActivationAttempts = 0; + + /// Instructs [_tryEnableSemantics] to remove [_semanticsPlaceholder]. + /// + /// For Blink browser engine the placeholder is removed upon any next event. + /// + /// For Webkit browser engine the placeholder is removed upon the next + /// "touchend" event. This is to prevent Safari from swallowing the event + /// that happens on an element that's being removed. Blink doesn't have + /// this issue. + bool _schedulePlaceholderRemoval = false; + + /// Whether we are waiting for the user to enable semantics. + @override + bool get isWaitingToEnableSemantics => _semanticsPlaceholder != null; + + @override + bool tryEnableSemantics(html.Event event) { + if (_schedulePlaceholderRemoval) { + final bool removeNow = + (browserEngine != BrowserEngine.webkit || event.type == 'touchend'); + if (removeNow) { + _semanticsPlaceholder.remove(); + _semanticsPlaceholder = null; + semanticsActivationTimer = null; + } + return true; + } + + if (EngineSemanticsOwner.instance.semanticsEnabled) { + // Semantics already enabled, forward to framework as normal. + return true; + } + + semanticsActivationAttempts += 1; + if (semanticsActivationAttempts >= kMaxSemanticsActivationAttempts) { + // We have received multiple user events, none of which resulted in + // semantics activation. This is a signal that the user is not interested + // in semantics, and so we will stop waiting for it. + _schedulePlaceholderRemoval = true; + return true; + } + + const Set kInterestingEventTypes = { + 'click', + 'touchstart', + 'touchend', + }; + + if (!kInterestingEventTypes.contains(event.type)) { + // The event is not relevant, forward to framework as normal. + return true; + } + + if (semanticsActivationTimer != null) { + // We are in a waiting period to activate a timer. While the timer is + // active we should consume events pertaining to semantics activation. + // Otherwise the event will also be interpreted by the framework and + // potentially result in activating a gesture in the app. + return false; + } + + // In Chrome the debouncing works well enough to detect accessibility + // request. + final bool blinkEnableConditionPassed = + browserEngine == BrowserEngine.blink && + EngineSemanticsOwner.instance.gestureMode == + GestureMode.browserGestures; + + // In Safari debouncing doesn't work. Instead we look at where exactly + // (within 1 pixel) the event landed. If it landed exactly in the middle of + // the placeholder we interpret it as a signal to enable accessibility. This + // is because when VoiceOver generates a tap it lands it in the middle of + // the focused element. This method is a bit flawed in that a user's finger + // could theoretically land in the middle of the element too. However, the + // chance of that happening is very small. Even low-end phones typically + // have >2 million pixels (e.g. Moto G4). It is very unlikely that a user + // will land their finger exactly in the middle. In the worst case an + // unlucky user would accidentally enable accessibility and the app will be + // slightly slower than normal, but the app will continue functioning as + // normal. Our semantics tree is designed to not interfere with Flutter's + // gesture detection. + bool safariEnableConditionPassed = false; + if (browserEngine == BrowserEngine.webkit) { + html.Point activationPoint; + + switch (event.type) { + case 'click': + final html.MouseEvent click = event; + activationPoint = click.offset; + break; + case 'touchstart': + case 'touchend': + final html.TouchEvent touch = event; + activationPoint = touch.changedTouches.first.client; + break; + default: + // The event is not relevant, forward to framework as normal. + return true; + } + + assert(activationPoint != null); + + final html.Rectangle activatingElementRect = + domRenderer.glassPaneElement.getBoundingClientRect(); + final double midX = activatingElementRect.left + + (activatingElementRect.right - activatingElementRect.left) / 2; + final double midY = activatingElementRect.top + + (activatingElementRect.bottom - activatingElementRect.top) / 2; + final double deltaX = activationPoint.x - midX; + final double deltaY = activationPoint.y - midY; + final double deltaSquared = deltaX * deltaX + deltaY * deltaY; + if (deltaSquared < 1.0) { + safariEnableConditionPassed = true; + } + } + + if (blinkEnableConditionPassed || safariEnableConditionPassed) { + assert(semanticsActivationTimer == null); + semanticsActivationTimer = Timer(_periodToConsumeEvents, () { + EngineSemanticsOwner.instance.semanticsEnabled = true; + _schedulePlaceholderRemoval = true; + }); + return false; + } + + // This was not a semantics activating event; forward as normal. + return true; + } + + @override + html.Element prepareAccesibilityPlaceholder() { + _semanticsPlaceholder = html.Element.tag('flt-semantics-placeholder'); + + // Only listen to "click" because other kinds of events are reported via + // PointerBinding. + _semanticsPlaceholder.addEventListener('click', (html.Event event) { + tryEnableSemantics(event); + }, true); + + _semanticsPlaceholder + ..setAttribute('role', 'button') + ..setAttribute('aria-label', placeholderMessage); + _semanticsPlaceholder.style + ..position = 'absolute' + ..left = '0' + ..top = '0' + ..right = '0' + ..bottom = '0'; + + return _semanticsPlaceholder; + } +} diff --git a/lib/web_ui/lib/src/engine/semantics/text_field.dart b/lib/web_ui/lib/src/engine/semantics/text_field.dart index 107d9237c822b..3fd681a61dbea 100644 --- a/lib/web_ui/lib/src/engine/semantics/text_field.dart +++ b/lib/web_ui/lib/src/engine/semantics/text_field.dart @@ -4,6 +4,70 @@ part of engine; +/// Text editing used by accesibility mode. +/// +/// [SemanticsTextEditingStrategy] assumes the caller will own the creation, +/// insertion and disposal of the DOM element. Due to this +/// [initializeElementPlacement], [initializeTextEditing] and +/// [disable] strategies are handled differently. +/// +/// This class is still responsible for hooking up the DOM element with the +/// [HybridTextEditing] instance so that changes are communicated to Flutter. +class SemanticsTextEditingStrategy extends DefaultTextEditingStrategy { + /// Creates a [SemanticsTextEditingStrategy] that eagerly instantiates + /// [domElement] so the caller can insert it before calling + /// [SemanticsTextEditingStrategy.enable]. + SemanticsTextEditingStrategy( + HybridTextEditing owner, html.HtmlElement domElement) + : super(owner) { + // Make sure the DOM element is of a type that we support for text editing. + // TODO(yjbanov): move into initializer list when https://github.com/dart-lang/sdk/issues/37881 is fixed. + assert((domElement is html.InputElement) || + (domElement is html.TextAreaElement)); + super.domElement = domElement; + } + + @override + void disable() { + // We don't want to remove the DOM element because the caller is responsible + // for that. + // + // Remove focus from the editable element to cause the keyboard to hide. + // Otherwise, the keyboard stays on screen even when the user navigates to + // a different screen (e.g. by hitting the "back" button). + domElement.blur(); + } + + @override + void initializeElementPlacement() { + // Element placement is done by [TextField]. + } + + @override + void initializeTextEditing(InputConfiguration inputConfig, + {_OnChangeCallback onChange, _OnActionCallback onAction}) { + // In accesibilty mode, the user of this class is supposed to insert the + // [domElement] on their own. Let's make sure they did. + assert(domElement != null); + assert(html.document.body.contains(domElement)); + + isEnabled = true; + _inputConfiguration = inputConfig; + _onChange = onChange; + _onAction = onAction; + + domElement.focus(); + } + + @override + void setEditingState(EditingState editingState) { + super.setEditingState(editingState); + + // Refocus after setting editing state. + domElement.focus(); + } +} + /// Manages semantics objects that represent editable text fields. /// /// This role is implemented via a content-editable HTML element. This role does @@ -19,15 +83,15 @@ class TextField extends RoleManager { semanticsObject.hasFlag(ui.SemanticsFlag.isMultiline) ? html.TextAreaElement() : html.InputElement(); - persistentTextEditingElement = PersistentTextEditingElement( + textEditingElement = SemanticsTextEditingStrategy( textEditing, editableDomElement, ); _setupDomElement(); } - PersistentTextEditingElement persistentTextEditingElement; - html.Element get _textFieldElement => persistentTextEditingElement.domElement; + SemanticsTextEditingStrategy textEditingElement; + html.Element get _textFieldElement => textEditingElement.domElement; void _setupDomElement() { // On iOS, even though the semantic text field is transparent, the cursor @@ -60,7 +124,10 @@ class TextField extends RoleManager { switch (browserEngine) { case BrowserEngine.blink: + case BrowserEngine.edge: + case BrowserEngine.ie11: case BrowserEngine.firefox: + case BrowserEngine.ie11: case BrowserEngine.unknown: _initializeForBlink(); break; @@ -80,7 +147,7 @@ class TextField extends RoleManager { return; } - textEditing.useCustomEditableElement(persistentTextEditingElement); + textEditing.useCustomEditableElement(textEditingElement); ui.window .onSemanticsAction(semanticsObject.id, ui.SemanticsAction.tap, null); }); @@ -96,7 +163,7 @@ class TextField extends RoleManager { num lastTouchStartOffsetY; _textFieldElement.addEventListener('touchstart', (html.Event event) { - textEditing.useCustomEditableElement(persistentTextEditingElement); + textEditing.useCustomEditableElement(textEditingElement); final html.TouchEvent touchEvent = event; lastTouchStartOffsetX = touchEvent.changedTouches.last.client.x; lastTouchStartOffsetY = touchEvent.changedTouches.last.client.y; diff --git a/lib/web_ui/lib/src/engine/shader.dart b/lib/web_ui/lib/src/engine/shader.dart index fb591fa6f9900..f1a5856be51ea 100644 --- a/lib/web_ui/lib/src/engine/shader.dart +++ b/lib/web_ui/lib/src/engine/shader.dart @@ -142,21 +142,11 @@ class GradientLinear extends EngineGradient { jsColors[i] = colors[i].value; } - js.JsArray jsColorStops; - if (colorStops == null) { - jsColorStops = js.JsArray(); - jsColorStops.length = 2; - jsColorStops[0] = 0; - jsColorStops[1] = 1; - } else { - jsColorStops = js.JsArray.from(colorStops); - jsColorStops.length = colorStops.length; - } return canvasKit.callMethod('MakeLinearGradientShader', [ makeSkPoint(from), makeSkPoint(to), jsColors, - jsColorStops, + makeSkiaColorStops(colorStops), tileMode.index, ]); } @@ -178,13 +168,16 @@ class GradientRadial extends EngineGradient { @override Object createPaintStyle(html.CanvasRenderingContext2D ctx) { - if (matrix4 != null && !Matrix4.fromFloat64List(matrix4).isIdentity()) { - throw UnimplementedError( - 'matrix4 not supported in GradientRadial shader'); - } - if (tileMode != ui.TileMode.clamp) { - throw UnimplementedError( - 'TileMode not supported in GradientRadial shader'); + if (!experimentalUseSkia) { + // The DOM backend does not (yet) support all parameters. + if (matrix4 != null && !Matrix4.fromFloat64List(matrix4).isIdentity()) { + throw UnimplementedError( + 'matrix4 not supported in GradientRadial shader'); + } + if (tileMode != ui.TileMode.clamp) { + throw UnimplementedError( + 'TileMode not supported in GradientRadial shader'); + } } final html.CanvasGradient gradient = ctx.createRadialGradient( center.dx, center.dy, 0, center.dx, center.dy, radius); @@ -203,7 +196,23 @@ class GradientRadial extends EngineGradient { @override js.JsObject createSkiaShader() { - throw UnimplementedError(); + assert(experimentalUseSkia); + + final js.JsArray jsColors = js.JsArray(); + jsColors.length = colors.length; + for (int i = 0; i < colors.length; i++) { + jsColors[i] = colors[i].value; + } + + return canvasKit.callMethod('MakeRadialGradientShader', [ + makeSkPoint(center), + radius, + jsColors, + makeSkiaColorStops(colorStops), + tileMode.index, + matrix4 != null ? makeSkMatrix(matrix4) : null, + 0, + ]); } } @@ -231,3 +240,30 @@ class GradientConical extends EngineGradient { throw UnimplementedError(); } } + +/// Backend implementation of [ui.ImageFilter]. +/// +/// Currently only `blur` is supported. +class EngineImageFilter implements ui.ImageFilter { + EngineImageFilter.blur({this.sigmaX = 0.0, this.sigmaY = 0.0}); + + final double sigmaX; + final double sigmaY; + + @override + bool operator ==(dynamic other) { + if (other is! EngineImageFilter) { + return false; + } + final EngineImageFilter typedOther = other; + return sigmaX == typedOther.sigmaX && sigmaY == typedOther.sigmaY; + } + + @override + int get hashCode => ui.hashValues(sigmaX, sigmaY); + + @override + String toString() { + return 'ImageFilter.blur($sigmaX, $sigmaY)'; + } +} diff --git a/lib/web_ui/lib/src/engine/surface/backdrop_filter.dart b/lib/web_ui/lib/src/engine/surface/backdrop_filter.dart index 1b87e2563752f..2813b495a973e 100644 --- a/lib/web_ui/lib/src/engine/surface/backdrop_filter.dart +++ b/lib/web_ui/lib/src/engine/surface/backdrop_filter.dart @@ -10,7 +10,7 @@ class PersistedBackdropFilter extends PersistedContainerSurface PersistedBackdropFilter(PersistedBackdropFilter oldLayer, this.filter) : super(oldLayer); - final ui.ImageFilter filter; + final EngineImageFilter filter; /// The dedicated child container element that's separate from the /// [rootElement] is used to host child in front of [filterElement] that @@ -24,10 +24,6 @@ class PersistedBackdropFilter extends PersistedContainerSurface // Reference to transform last used to cache [_invertedTransform]. Matrix4 _previousTransform; - @override - Matrix4 get localTransformInverse => - _localTransformInverse ??= Matrix4.identity(); - @override void adoptElements(PersistedBackdropFilter oldSurface) { super.adoptElements(oldSurface); @@ -88,8 +84,7 @@ class PersistedBackdropFilter extends PersistedContainerSurface // CSS uses pixel radius for blur. Flutter & SVG use sigma parameters. For // Gaussian blur with standard deviation (normal distribution), // the blur will fall within 2 * sigma pixels. - domRenderer.setElementStyle(_filterElement, 'backdrop-filter', - 'blur(${math.max(filter.sigmaX, filter.sigmaY) * 2}px)'); + domRenderer.setElementStyle(_filterElement, 'backdrop-filter', _imageFilterToCss(filter)); } } diff --git a/lib/web_ui/lib/src/engine/surface/clip.dart b/lib/web_ui/lib/src/engine/surface/clip.dart index 66db0c1cb0331..8bafbc58c1d3f 100644 --- a/lib/web_ui/lib/src/engine/surface/clip.dart +++ b/lib/web_ui/lib/src/engine/surface/clip.dart @@ -71,10 +71,6 @@ class PersistedClipRect extends PersistedContainerSurface _projectedClip = null; } - @override - Matrix4 get localTransformInverse => - _localTransformInverse ??= Matrix4.identity(); - @override html.Element createElement() { return super.createElement()..setAttribute('clip-type', 'rect'); @@ -122,10 +118,6 @@ class PersistedClipRRect extends PersistedContainerSurface _projectedClip = null; } - @override - Matrix4 get localTransformInverse => - _localTransformInverse ??= Matrix4.identity(); - @override html.Element createElement() { return super.createElement()..setAttribute('clip-type', 'rrect'); @@ -167,7 +159,7 @@ class PersistedPhysicalShape extends PersistedContainerSurface shadowColor = ui.Color(shadowColor), super(oldLayer); - final ui.Path path; + final SurfacePath path; final double elevation; final ui.Color color; final ui.Color shadowColor; @@ -193,10 +185,6 @@ class PersistedPhysicalShape extends PersistedContainerSurface _projectedClip = null; } - @override - Matrix4 get localTransformInverse => - _localTransformInverse ??= Matrix4.identity(); - void _applyColor() { rootElement.style.backgroundColor = color.toCssString(); } @@ -350,10 +338,6 @@ class PersistedClipPath extends PersistedContainerSurface _localClipBounds ??= clipPath.getBounds(); } - @override - Matrix4 get localTransformInverse => - _localTransformInverse ??= Matrix4.identity(); - @override void apply() { if (clipPath == null) { diff --git a/lib/web_ui/lib/src/engine/surface/image_filter.dart b/lib/web_ui/lib/src/engine/surface/image_filter.dart new file mode 100644 index 0000000000000..105d1d868f7a4 --- /dev/null +++ b/lib/web_ui/lib/src/engine/surface/image_filter.dart @@ -0,0 +1,32 @@ +// 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. + +part of engine; + +/// A surface that applies an [imageFilter] to its children. +class PersistedImageFilter extends PersistedContainerSurface + implements ui.ImageFilterEngineLayer { + PersistedImageFilter(PersistedImageFilter oldLayer, this.filter) : super(oldLayer); + + final ui.ImageFilter filter; + + @override + html.Element createElement() { + return defaultCreateElement('flt-image-filter'); + } + + @override + void apply() { + rootElement.style.filter = _imageFilterToCss(filter); + } + + @override + void update(PersistedImageFilter oldSurface) { + super.update(oldSurface); + + if (oldSurface.filter != filter) { + apply(); + } + } +} diff --git a/lib/web_ui/lib/src/engine/surface/painting.dart b/lib/web_ui/lib/src/engine/surface/painting.dart new file mode 100644 index 0000000000000..0322cfac58c4f --- /dev/null +++ b/lib/web_ui/lib/src/engine/surface/painting.dart @@ -0,0 +1,1366 @@ +// 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. + +part of engine; + +/// Implementation of [ui.Paint] used by the HTML rendering backend. +class SurfacePaint implements ui.Paint { + SurfacePaintData _paintData = SurfacePaintData(); + + @override + ui.BlendMode get blendMode => _paintData.blendMode ?? ui.BlendMode.srcOver; + + @override + set blendMode(ui.BlendMode value) { + if (_frozen) { + _paintData = _paintData.clone(); + _frozen = false; + } + _paintData.blendMode = value; + } + + @override + ui.PaintingStyle get style => _paintData.style ?? ui.PaintingStyle.fill; + + @override + set style(ui.PaintingStyle value) { + if (_frozen) { + _paintData = _paintData.clone(); + _frozen = false; + } + _paintData.style = value; + } + + @override + double get strokeWidth => _paintData.strokeWidth ?? 0.0; + + @override + set strokeWidth(double value) { + if (_frozen) { + _paintData = _paintData.clone(); + _frozen = false; + } + _paintData.strokeWidth = value; + } + + @override + ui.StrokeCap get strokeCap => _paintData.strokeCap; + + @override + set strokeCap(ui.StrokeCap value) { + if (_frozen) { + _paintData = _paintData.clone(); + _frozen = false; + } + _paintData.strokeCap = value; + } + + @override + ui.StrokeJoin get strokeJoin => _paintData.strokeJoin; + + @override + set strokeJoin(ui.StrokeJoin value) { + if (_frozen) { + _paintData = _paintData.clone(); + _frozen = false; + } + _paintData.strokeJoin = value; + } + + @override + bool get isAntiAlias => _paintData.isAntiAlias; + + @override + set isAntiAlias(bool value) { + if (_frozen) { + _paintData = _paintData.clone(); + _frozen = false; + } + _paintData.isAntiAlias = value; + } + + @override + ui.Color get color => _paintData.color; + + @override + set color(ui.Color value) { + if (_frozen) { + _paintData = _paintData.clone(); + _frozen = false; + } + _paintData.color = value.runtimeType == ui.Color ? value : ui.Color(value.value); + } + + @override + bool get invertColors { + return false; + } + + @override + set invertColors(bool value) {} + + static const ui.Color _defaultPaintColor = ui.Color(0xFF000000); + + @override + ui.Shader get shader => _paintData.shader; + + @override + set shader(ui.Shader value) { + if (_frozen) { + _paintData = _paintData.clone(); + _frozen = false; + } + _paintData.shader = value; + } + + @override + ui.MaskFilter get maskFilter => _paintData.maskFilter; + + @override + set maskFilter(ui.MaskFilter value) { + if (_frozen) { + _paintData = _paintData.clone(); + _frozen = false; + } + _paintData.maskFilter = value; + } + + @override + ui.FilterQuality get filterQuality => _paintData.filterQuality; + + @override + set filterQuality(ui.FilterQuality value) { + if (_frozen) { + _paintData = _paintData.clone(); + _frozen = false; + } + _paintData.filterQuality = value; + } + + @override + ui.ColorFilter get colorFilter => _paintData.colorFilter; + + @override + set colorFilter(ui.ColorFilter value) { + if (_frozen) { + _paintData = _paintData.clone(); + _frozen = false; + } + _paintData.colorFilter = value; + } + + // TODO(flutter_web): see https://github.com/flutter/flutter/issues/33605 + @override + double get strokeMiterLimit { + return null; + } + + @override + set strokeMiterLimit(double value) { + assert(value != null); + } + + @override + ui.ImageFilter get imageFilter { + // TODO(flutter/flutter#35156): Implement ImageFilter. + return null; + } + + @override + set imageFilter(ui.ImageFilter value) { + // TODO(flutter/flutter#35156): Implement ImageFilter. + } + + // True if Paint instance has used in RecordingCanvas. + bool _frozen = false; + + // Marks this paint object as previously used. + SurfacePaintData get paintData { + // Flip bit so next time object gets mutated we create a clone of + // current paint data. + _frozen = true; + return _paintData; + } + + @override + String toString() { + final StringBuffer result = StringBuffer(); + String semicolon = ''; + result.write('Paint('); + if (style == ui.PaintingStyle.stroke) { + result.write('$style'); + if (strokeWidth != null && strokeWidth != 0.0) + result.write(' $strokeWidth'); + else + result.write(' hairline'); + if (strokeCap != null && strokeCap != ui.StrokeCap.butt) + result.write(' $strokeCap'); + semicolon = '; '; + } + if (isAntiAlias != true) { + result.write('${semicolon}antialias off'); + semicolon = '; '; + } + if (color != _defaultPaintColor) { + if (color != null) + result.write('$semicolon$color'); + else + result.write('${semicolon}no color'); + semicolon = '; '; + } + result.write(')'); + return result.toString(); + } +} + +/// Private Paint context data used for recording canvas commands allowing +/// Paint to be mutated post canvas draw operations. +class SurfacePaintData { + ui.BlendMode blendMode; + ui.PaintingStyle style; + double strokeWidth; + ui.StrokeCap strokeCap; + ui.StrokeJoin strokeJoin; + bool isAntiAlias = true; + ui.Color color; + ui.Shader shader; + ui.MaskFilter maskFilter; + ui.FilterQuality filterQuality; + ui.ColorFilter colorFilter; + + // Internal for recording canvas use. + SurfacePaintData clone() { + return SurfacePaintData() + ..blendMode = blendMode + ..filterQuality = filterQuality + ..maskFilter = maskFilter + ..shader = shader + ..isAntiAlias = isAntiAlias + ..color = color + ..colorFilter = colorFilter + ..strokeWidth = strokeWidth + ..style = style + ..strokeJoin = strokeJoin + ..strokeCap = strokeCap; + } +} + +/// A complex, one-dimensional subset of a plane. +/// +/// A path consists of a number of subpaths, and a _current point_. +/// +/// Subpaths consist of segments of various types, such as lines, +/// arcs, or beziers. Subpaths can be open or closed, and can +/// self-intersect. +/// +/// Closed subpaths enclose a (possibly discontiguous) region of the +/// plane based on the current [fillType]. +/// +/// The _current point_ is initially at the origin. After each +/// operation adding a segment to a subpath, the current point is +/// updated to the end of that segment. +/// +/// Paths can be drawn on canvases using [Canvas.drawPath], and can +/// used to create clip regions using [Canvas.clipPath]. +class SurfacePath implements ui.Path { + final List subpaths; + ui.PathFillType _fillType = ui.PathFillType.nonZero; + + Subpath get _currentSubpath => subpaths.isEmpty ? null : subpaths.last; + + List get _commands => _currentSubpath?.commands; + + /// The current x-coordinate for this path. + double get _currentX => _currentSubpath?.currentX ?? 0.0; + + /// The current y-coordinate for this path. + double get _currentY => _currentSubpath?.currentY ?? 0.0; + + /// Recorder used for hit testing paths. + static ui.RawRecordingCanvas _rawRecorder; + + SurfacePath() : subpaths = []; + + /// Creates a copy of another [Path]. + /// + /// This copy is fast and does not require additional memory unless either + /// the `source` path or the path returned by this constructor are modified. + SurfacePath.from(SurfacePath source) + : subpaths = List.from(source.subpaths); + + SurfacePath._clone(this.subpaths, this._fillType); + + /// Determines how the interior of this path is calculated. + /// + /// Defaults to the non-zero winding rule, [PathFillType.nonZero]. + @override + ui.PathFillType get fillType => _fillType; + @override + set fillType(ui.PathFillType value) { + _fillType = value; + } + + /// Opens a new subpath with starting point (x, y). + void _openNewSubpath(double x, double y) { + subpaths.add(Subpath(x, y)); + _setCurrentPoint(x, y); + } + + /// Sets the current point to (x, y). + void _setCurrentPoint(double x, double y) { + _currentSubpath.currentX = x; + _currentSubpath.currentY = y; + } + + /// Starts a new subpath at the given coordinate. + @override + void moveTo(double x, double y) { + _openNewSubpath(x, y); + _commands.add(MoveTo(x, y)); + } + + /// Starts a new subpath at the given offset from the current point. + @override + void relativeMoveTo(double dx, double dy) { + final double newX = _currentX + dx; + final double newY = _currentY + dy; + _openNewSubpath(newX, newY); + _commands.add(MoveTo(newX, newY)); + } + + /// Adds a straight line segment from the current point to the given + /// point. + @override + void lineTo(double x, double y) { + if (subpaths.isEmpty) { + moveTo(0.0, 0.0); + } + _commands.add(LineTo(x, y)); + _setCurrentPoint(x, y); + } + + /// Adds a straight line segment from the current point to the point + /// at the given offset from the current point. + @override + void relativeLineTo(double dx, double dy) { + final double newX = _currentX + dx; + final double newY = _currentY + dy; + if (subpaths.isEmpty) { + moveTo(0.0, 0.0); + } + _commands.add(LineTo(newX, newY)); + _setCurrentPoint(newX, newY); + } + + void _ensurePathStarted() { + if (subpaths.isEmpty) { + subpaths.add(Subpath(0.0, 0.0)); + } + } + + /// Adds a quadratic bezier segment that curves from the current + /// point to the given point (x2,y2), using the control point + /// (x1,y1). + @override + void quadraticBezierTo(double x1, double y1, double x2, double y2) { + _ensurePathStarted(); + _commands.add(QuadraticCurveTo(x1, y1, x2, y2)); + _setCurrentPoint(x2, y2); + } + + /// Adds a quadratic bezier segment that curves from the current + /// point to the point at the offset (x2,y2) from the current point, + /// using the control point at the offset (x1,y1) from the current + /// point. + @override + void relativeQuadraticBezierTo(double x1, double y1, double x2, double y2) { + _ensurePathStarted(); + _commands.add(QuadraticCurveTo( + x1 + _currentX, y1 + _currentY, x2 + _currentX, y2 + _currentY)); + _setCurrentPoint(x2 + _currentX, y2 + _currentY); + } + + /// Adds a cubic bezier segment that curves from the current point + /// to the given point (x3,y3), using the control points (x1,y1) and + /// (x2,y2). + @override + void cubicTo( + double x1, double y1, double x2, double y2, double x3, double y3) { + _ensurePathStarted(); + _commands.add(BezierCurveTo(x1, y1, x2, y2, x3, y3)); + _setCurrentPoint(x3, y3); + } + + /// Adds a cubic bezier segment that curves from the current point + /// to the point at the offset (x3,y3) from the current point, using + /// the control points at the offsets (x1,y1) and (x2,y2) from the + /// current point. + @override + void relativeCubicTo( + double x1, double y1, double x2, double y2, double x3, double y3) { + _ensurePathStarted(); + _commands.add(BezierCurveTo(x1 + _currentX, y1 + _currentY, + x2 + _currentX, y2 + _currentY, x3 + _currentX, y3 + _currentY)); + _setCurrentPoint(x3 + _currentX, y3 + _currentY); + } + + /// Adds a bezier segment that curves from the current point to the + /// given point (x2,y2), using the control points (x1,y1) and the + /// weight w. If the weight is greater than 1, then the curve is a + /// hyperbola; if the weight equals 1, it's a parabola; and if it is + /// less than 1, it is an ellipse. + @override + void conicTo(double x1, double y1, double x2, double y2, double w) { + final List quads = + Conic(_currentX, _currentY, x1, y1, x2, y2, w).toQuads(); + final int len = quads.length; + for (int i = 1; i < len; i += 2) { + quadraticBezierTo( + quads[i].dx, quads[i].dy, quads[i + 1].dx, quads[i + 1].dy); + } + } + + /// Adds a bezier segment that curves from the current point to the + /// point at the offset (x2,y2) from the current point, using the + /// control point at the offset (x1,y1) from the current point and + /// the weight w. If the weight is greater than 1, then the curve is + /// a hyperbola; if the weight equals 1, it's a parabola; and if it + /// is less than 1, it is an ellipse. + @override + void relativeConicTo(double x1, double y1, double x2, double y2, double w) { + conicTo(_currentX + x1, _currentY + y1, _currentX + x2, _currentY + y2, w); + } + + /// If the `forceMoveTo` argument is false, adds a straight line + /// segment and an arc segment. + /// + /// If the `forceMoveTo` argument is true, starts a new subpath + /// consisting of an arc segment. + /// + /// In either case, the arc segment consists of the arc that follows + /// the edge of the oval bounded by the given rectangle, from + /// startAngle radians around the oval up to startAngle + sweepAngle + /// radians around the oval, with zero radians being the point on + /// the right hand side of the oval that crosses the horizontal line + /// that intersects the center of the rectangle and with positive + /// angles going clockwise around the oval. + /// + /// The line segment added if `forceMoveTo` is false starts at the + /// current point and ends at the start of the arc. + @override + void arcTo( + ui.Rect rect, double startAngle, double sweepAngle, bool forceMoveTo) { + assert(rectIsValid(rect)); + final ui.Offset center = rect.center; + final double radiusX = rect.width / 2; + final double radiusY = rect.height / 2; + final double startX = radiusX * math.cos(startAngle) + center.dx; + final double startY = radiusY * math.sin(startAngle) + center.dy; + if (forceMoveTo) { + _openNewSubpath(startX, startY); + } else { + lineTo(startX, startY); + } + _commands.add(Ellipse(center.dx, center.dy, radiusX, radiusY, 0.0, + startAngle, startAngle + sweepAngle, sweepAngle.isNegative)); + + _setCurrentPoint(radiusX * math.cos(startAngle + sweepAngle) + center.dx, + radiusY * math.sin(startAngle + sweepAngle) + center.dy); + } + + /// Appends up to four conic curves weighted to describe an oval of `radius` + /// and rotated by `rotation`. + /// + /// The first curve begins from the last point in the path and the last ends + /// at `arcEnd`. The curves follow a path in a direction determined by + /// `clockwise` and `largeArc` in such a way that the sweep angle + /// is always less than 360 degrees. + /// + /// A simple line is appended if either either radii are zero or the last + /// point in the path is `arcEnd`. The radii are scaled to fit the last path + /// point if both are greater than zero but too small to describe an arc. + /// + /// See Conversion from endpoint to center parametrization described in + /// https://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter + /// as reference for implementation. + @override + void arcToPoint( + ui.Offset arcEnd, { + ui.Radius radius = ui.Radius.zero, + double rotation = 0.0, + bool largeArc = false, + bool clockwise = true, + }) { + assert(offsetIsValid(arcEnd)); + assert(radiusIsValid(radius)); + // _currentX, _currentY are the coordinates of start point on path, + // arcEnd is final point of arc. + // rx,ry are the radii of the eclipse (semi-major/semi-minor axis) + // largeArc is false if arc is spanning less than or equal to 180 degrees. + // clockwise is false if arc sweeps through decreasing angles or true + // if sweeping through increasing angles. + // rotation is the angle from the x-axis of the current coordinate + // system to the x-axis of the eclipse. + + double rx = radius.x.abs(); + double ry = radius.y.abs(); + + // If the current point and target point for the arc are identical, it + // should be treated as a zero length path. This ensures continuity in + // animations. + final bool isSamePoint = _currentX == arcEnd.dx && _currentY == arcEnd.dy; + + // If rx = 0 or ry = 0 then this arc is treated as a straight line segment + // (a "lineto") joining the endpoints. + // http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters + if (isSamePoint || rx.toInt() == 0 || ry.toInt() == 0) { + _commands.add(LineTo(arcEnd.dx, arcEnd.dy)); + _setCurrentPoint(arcEnd.dx, arcEnd.dy); + return; + } + + // As an intermediate point to finding center parametrization, place the + // origin on the midpoint between start/end points and rotate to align + // coordinate axis with axes of the ellipse. + final double midPointX = (_currentX - arcEnd.dx) / 2.0; + final double midPointY = (_currentY - arcEnd.dy) / 2.0; + + // Convert rotation or radians. + final double xAxisRotation = math.pi * rotation / 180.0; + + // Cache cos/sin value. + final double cosXAxisRotation = math.cos(xAxisRotation); + final double sinXAxisRotation = math.sin(xAxisRotation); + + // Calculate rotate midpoint as x/yPrime. + final double xPrime = + (cosXAxisRotation * midPointX) + (sinXAxisRotation * midPointY); + final double yPrime = + (-sinXAxisRotation * midPointX) + (cosXAxisRotation * midPointY); + + // Check if the radii are big enough to draw the arc, scale radii if not. + // http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii + double rxSquare = rx * rx; + double rySquare = ry * ry; + final double xPrimeSquare = xPrime * xPrime; + final double yPrimeSquare = yPrime * yPrime; + + double radiiScale = (xPrimeSquare / rxSquare) + (yPrimeSquare / rySquare); + if (radiiScale > 1) { + radiiScale = math.sqrt(radiiScale); + rx *= radiiScale; + ry *= radiiScale; + rxSquare = rx * rx; + rySquare = ry * ry; + } + + // Compute transformed center. eq. 5.2 + final double distanceSquare = + (rxSquare * yPrimeSquare) + rySquare * xPrimeSquare; + final double cNumerator = (rxSquare * rySquare) - distanceSquare; + double scaleFactor = math.sqrt(math.max(cNumerator / distanceSquare, 0.0)); + if (largeArc == clockwise) { + scaleFactor = -scaleFactor; + } + // Ready to compute transformed center. + final double cxPrime = scaleFactor * ((rx * yPrime) / ry); + final double cyPrime = scaleFactor * (-(ry * xPrime) / rx); + + // Rotate to find actual center. + final double cx = cosXAxisRotation * cxPrime - + sinXAxisRotation * cyPrime + + ((_currentX + arcEnd.dx) / 2.0); + final double cy = sinXAxisRotation * cxPrime + + cosXAxisRotation * cyPrime + + ((_currentY + arcEnd.dy) / 2.0); + + // Calculate start angle and sweep. + // Start vector is from midpoint of start/end points to transformed center. + final double startVectorX = (xPrime - cxPrime) / rx; + final double startVectorY = (yPrime - cyPrime) / ry; + + final double startAngle = math.atan2(startVectorY, startVectorX); + final double endVectorX = (-xPrime - cxPrime) / rx; + final double endVectorY = (-yPrime - cyPrime) / ry; + double sweepAngle = math.atan2(endVectorY, endVectorX) - startAngle; + + if (clockwise && sweepAngle < 0) { + sweepAngle += math.pi * 2.0; + } else if (!clockwise && sweepAngle > 0) { + sweepAngle -= math.pi * 2.0; + } + + _commands.add(Ellipse(cx, cy, rx, ry, xAxisRotation, startAngle, + startAngle + sweepAngle, sweepAngle.isNegative)); + + _setCurrentPoint(arcEnd.dx, arcEnd.dy); + } + + /// Appends up to four conic curves weighted to describe an oval of `radius` + /// and rotated by `rotation`. + /// + /// The last path point is described by (px, py). + /// + /// The first curve begins from the last point in the path and the last ends + /// at `arcEndDelta.dx + px` and `arcEndDelta.dy + py`. The curves follow a + /// path in a direction determined by `clockwise` and `largeArc` + /// in such a way that the sweep angle is always less than 360 degrees. + /// + /// A simple line is appended if either either radii are zero, or, both + /// `arcEndDelta.dx` and `arcEndDelta.dy` are zero. The radii are scaled to + /// fit the last path point if both are greater than zero but too small to + /// describe an arc. + @override + void relativeArcToPoint( + ui.Offset arcEndDelta, { + ui.Radius radius = ui.Radius.zero, + double rotation = 0.0, + bool largeArc = false, + bool clockwise = true, + }) { + assert(offsetIsValid(arcEndDelta)); + assert(radiusIsValid(radius)); + arcToPoint(ui.Offset(_currentX + arcEndDelta.dx, _currentY + arcEndDelta.dy), + radius: radius, + rotation: rotation, + largeArc: largeArc, + clockwise: clockwise); + } + + /// Adds a new subpath that consists of four lines that outline the + /// given rectangle. + @override + void addRect(ui.Rect rect) { + assert(rectIsValid(rect)); + _openNewSubpath(rect.left, rect.top); + _commands + .add(RectCommand(rect.left, rect.top, rect.width, rect.height)); + } + + /// Adds a new subpath that consists of a curve that forms the + /// ellipse that fills the given rectangle. + /// + /// To add a circle, pass an appropriate rectangle as `oval`. + /// [Rect.fromCircle] can be used to easily describe the circle's center + /// [Offset] and radius. + @override + void addOval(ui.Rect oval) { + assert(rectIsValid(oval)); + final ui.Offset center = oval.center; + final double radiusX = oval.width / 2; + final double radiusY = oval.height / 2; + + /// At startAngle = 0, the path will begin at center + cos(0) * radius. + _openNewSubpath(center.dx + radiusX, center.dy); + _commands.add(Ellipse( + center.dx, center.dy, radiusX, radiusY, 0.0, 0.0, 2 * math.pi, false)); + } + + /// Adds a new subpath with one arc segment that consists of the arc + /// that follows the edge of the oval bounded by the given + /// rectangle, from startAngle radians around the oval up to + /// startAngle + sweepAngle radians around the oval, with zero + /// radians being the point on the right hand side of the oval that + /// crosses the horizontal line that intersects the center of the + /// rectangle and with positive angles going clockwise around the + /// oval. + @override + void addArc(ui.Rect oval, double startAngle, double sweepAngle) { + assert(rectIsValid(oval)); + final ui.Offset center = oval.center; + final double radiusX = oval.width / 2; + final double radiusY = oval.height / 2; + _openNewSubpath(radiusX * math.cos(startAngle) + center.dx, + radiusY * math.sin(startAngle) + center.dy); + _commands.add(Ellipse(center.dx, center.dy, radiusX, radiusY, 0.0, + startAngle, startAngle + sweepAngle, sweepAngle.isNegative)); + + _setCurrentPoint(radiusX * math.cos(startAngle + sweepAngle) + center.dx, + radiusY * math.sin(startAngle + sweepAngle) + center.dy); + } + + /// Adds a new subpath with a sequence of line segments that connect the given + /// points. + /// + /// If `close` is true, a final line segment will be added that connects the + /// last point to the first point. + /// + /// The `points` argument is interpreted as offsets from the origin. + @override + void addPolygon(List points, bool close) { + assert(points != null); + if (points.isEmpty) { + return; + } + + moveTo(points.first.dx, points.first.dy); + for (int i = 1; i < points.length; i++) { + final ui.Offset point = points[i]; + lineTo(point.dx, point.dy); + } + if (close) { + this.close(); + } else { + _setCurrentPoint(points.last.dx, points.last.dy); + } + } + + /// Adds a new subpath that consists of the straight lines and + /// curves needed to form the rounded rectangle described by the + /// argument. + @override + void addRRect(ui.RRect rrect) { + assert(rrectIsValid(rrect)); + + // Set the current point to the top left corner of the rectangle (the + // point on the top of the rectangle farthest to the left that isn't in + // the rounded corner). + // TODO(het): Is this the current point in Flutter? + _openNewSubpath(rrect.tallMiddleRect.left, rrect.top); + _commands.add(RRectCommand(rrect)); + } + + /// Adds a new subpath that consists of the given `path` offset by the given + /// `offset`. + /// + /// If `matrix4` is specified, the path will be transformed by this matrix + /// after the matrix is translated by the given offset. The matrix is a 4x4 + /// matrix stored in column major order. + @override + void addPath(ui.Path path, ui.Offset offset, {Float64List matrix4}) { + assert(path != null); // path is checked on the engine side + assert(offsetIsValid(offset)); + if (matrix4 != null) { + assert(matrix4IsValid(matrix4)); + _addPathWithMatrix(path, offset.dx, offset.dy, matrix4); + } else { + _addPath(path, offset.dx, offset.dy); + } + } + + void _addPath(SurfacePath path, double dx, double dy) { + if (dx == 0.0 && dy == 0.0) { + subpaths.addAll(path.subpaths); + } else { + subpaths.addAll(path + .transform(Matrix4.translationValues(dx, dy, 0.0).storage) + .subpaths); + } + } + + void _addPathWithMatrix(SurfacePath path, double dx, double dy, Float64List matrix) { + final Matrix4 transform = Matrix4.fromFloat64List(matrix); + transform.translate(dx, dy); + subpaths.addAll(path.transform(transform.storage).subpaths); + } + + /// Adds the given path to this path by extending the current segment of this + /// path with the the first segment of the given path. + /// + /// If `matrix4` is specified, the path will be transformed by this matrix + /// after the matrix is translated by the given `offset`. The matrix is a 4x4 + /// matrix stored in column major order. + @override + void extendWithPath(ui.Path path, ui.Offset offset, {Float64List matrix4}) { + assert(path != null); // path is checked on the engine side + assert(offsetIsValid(offset)); + if (matrix4 != null) { + assert(matrix4IsValid(matrix4)); + _extendWithPathAndMatrix(path, offset.dx, offset.dy, matrix4); + } else { + _extendWithPath(path, offset.dx, offset.dy); + } + } + + void _extendWithPath(SurfacePath path, double dx, double dy) { + if (dx == 0.0 && dy == 0.0) { + assert(path.subpaths.length == 1); + _ensurePathStarted(); + _commands.addAll(path.subpaths.single.commands); + _setCurrentPoint( + path.subpaths.single.currentX, path.subpaths.single.currentY); + } else { + throw UnimplementedError('Cannot extend path with non-zero offset'); + } + } + + void _extendWithPathAndMatrix( + SurfacePath path, double dx, double dy, Float64List matrix) { + throw UnimplementedError('Cannot extend path with transform matrix'); + } + + /// Closes the last subpath, as if a straight line had been drawn + /// from the current point to the first point of the subpath. + @override + void close() { + _ensurePathStarted(); + _commands.add(const CloseCommand()); + _setCurrentPoint(_currentSubpath.startX, _currentSubpath.startY); + } + + /// Clears the [Path] object of all subpaths, returning it to the + /// same state it had when it was created. The _current point_ is + /// reset to the origin. + @override + void reset() { + subpaths.clear(); + } + + /// Tests to see if the given point is within the path. (That is, whether the + /// point would be in the visible portion of the path if the path was used + /// with [Canvas.clipPath].) + /// + /// The `point` argument is interpreted as an offset from the origin. + /// + /// Returns true if the point is in the path, and false otherwise. + /// + /// Note: Not very efficient, it creates a canvas, plays path and calls + /// Context2D isPointInPath. If performance becomes issue, retaining + /// RawRecordingCanvas can remove create/remove rootElement cost. + @override + bool contains(ui.Offset point) { + assert(offsetIsValid(point)); + final int subPathCount = subpaths.length; + if (subPathCount == 0) { + return false; + } + final double pointX = point.dx; + final double pointY = point.dy; + if (subPathCount == 1) { + // Optimize for rect/roundrect checks. + final Subpath subPath = subpaths[0]; + if (subPath.commands.length == 1) { + final PathCommand cmd = subPath.commands[0]; + if (cmd is RectCommand) { + if (pointY < cmd.y || pointY > (cmd.y + cmd.height)) { + return false; + } + if (pointX < cmd.x || pointX > (cmd.x + cmd.width)) { + return false; + } + return true; + } else if (cmd is RRectCommand) { + final ui.RRect rRect = cmd.rrect; + if (pointY < rRect.top || pointY > rRect.bottom) { + return false; + } + if (pointX < rRect.left || pointX > rRect.right) { + return false; + } + if (pointX < (rRect.left + rRect.tlRadiusX) && + pointY < (rRect.top + rRect.tlRadiusY)) { + // Top left corner + return _ellipseContains( + pointX, + pointY, + rRect.left + rRect.tlRadiusX, + rRect.top + rRect.tlRadiusY, + rRect.tlRadiusX, + rRect.tlRadiusY); + } else if (pointX >= (rRect.right - rRect.trRadiusX) && + pointY < (rRect.top + rRect.trRadiusY)) { + // Top right corner + return _ellipseContains( + pointX, + pointY, + rRect.right - rRect.trRadiusX, + rRect.top + rRect.trRadiusY, + rRect.trRadiusX, + rRect.trRadiusY); + } else if (pointX >= (rRect.right - rRect.brRadiusX) && + pointY >= (rRect.bottom - rRect.brRadiusY)) { + // Bottom right corner + return _ellipseContains( + pointX, + pointY, + rRect.right - rRect.brRadiusX, + rRect.bottom - rRect.brRadiusY, + rRect.trRadiusX, + rRect.trRadiusY); + } else if (pointX < (rRect.left + rRect.blRadiusX) && + pointY >= (rRect.bottom - rRect.blRadiusY)) { + // Bottom left corner + return _ellipseContains( + pointX, + pointY, + rRect.left + rRect.blRadiusX, + rRect.bottom - rRect.blRadiusY, + rRect.trRadiusX, + rRect.trRadiusY); + } + return true; + } + } + } + final ui.Size size = window.physicalSize / window.devicePixelRatio; + _rawRecorder ??= ui.RawRecordingCanvas(size); + // Account for the shift due to padding. + _rawRecorder.translate(-BitmapCanvas.kPaddingPixels.toDouble(), + -BitmapCanvas.kPaddingPixels.toDouble()); + _rawRecorder.drawPath( + this, (SurfacePaint()..color = const ui.Color(0xFF000000)).paintData); + final bool result = _rawRecorder._canvasPool.context.isPointInPath(pointX, pointY); + _rawRecorder.dispose(); + return result; + } + + /// Returns a copy of the path with all the segments of every + /// subpath translated by the given offset. + @override + SurfacePath shift(ui.Offset offset) { + assert(offsetIsValid(offset)); + final List shiftedSubPaths = []; + for (final Subpath subPath in subpaths) { + shiftedSubPaths.add(subPath.shift(offset)); + } + return SurfacePath._clone(shiftedSubPaths, fillType); + } + + /// Returns a copy of the path with all the segments of every + /// sub path transformed by the given matrix. + @override + SurfacePath transform(Float64List matrix4) { + assert(matrix4IsValid(matrix4)); + final SurfacePath transformedPath = SurfacePath(); + for (final Subpath subPath in subpaths) { + for (final PathCommand cmd in subPath.commands) { + cmd.transform(matrix4, transformedPath); + } + } + return transformedPath; + } + + /// Computes the bounding rectangle for this path. + /// + /// A path containing only axis-aligned points on the same straight line will + /// have no area, and therefore `Rect.isEmpty` will return true for such a + /// path. Consider checking `rect.width + rect.height > 0.0` instead, or + /// using the [computeMetrics] API to check the path length. + /// + /// For many more elaborate paths, the bounds may be inaccurate. For example, + /// when a path contains a circle, the points used to compute the bounds are + /// the circle's implied control points, which form a square around the + /// circle; if the circle has a transformation applied using [transform] then + /// that square is rotated, and the (axis-aligned, non-rotated) bounding box + /// therefore ends up grossly overestimating the actual area covered by the + /// circle. + // see https://skia.org/user/api/SkPath_Reference#SkPath_getBounds + @override + ui.Rect getBounds() { + // Sufficiently small number for curve eq. + const double epsilon = 0.000000001; + bool ltrbInitialized = false; + double left = 0.0, top = 0.0, right = 0.0, bottom = 0.0; + double curX = 0.0; + double curY = 0.0; + double minX = 0.0, maxX = 0.0, minY = 0.0, maxY = 0.0; + for (Subpath subpath in subpaths) { + for (PathCommand op in subpath.commands) { + bool skipBounds = false; + switch (op.type) { + case PathCommandTypes.moveTo: + final MoveTo cmd = op; + curX = minX = maxX = cmd.x; + curY = minY = maxY = cmd.y; + break; + case PathCommandTypes.lineTo: + final LineTo cmd = op; + curX = minX = maxX = cmd.x; + curY = minY = maxY = cmd.y; + break; + case PathCommandTypes.ellipse: + final Ellipse cmd = op; + // Rotate 4 corners of bounding box. + final double rx = cmd.radiusX; + final double ry = cmd.radiusY; + final double cosVal = math.cos(cmd.rotation); + final double sinVal = math.sin(cmd.rotation); + final double rxCos = rx * cosVal; + final double ryCos = ry * cosVal; + final double rxSin = rx * sinVal; + final double rySin = ry * sinVal; + + final double leftDeltaX = rxCos - rySin; + final double rightDeltaX = -rxCos - rySin; + final double topDeltaY = ryCos + rxSin; + final double bottomDeltaY = ryCos - rxSin; + + final double centerX = cmd.x; + final double centerY = cmd.y; + + double rotatedX = centerX + leftDeltaX; + double rotatedY = centerY + topDeltaY; + minX = maxX = rotatedX; + minY = maxY = rotatedY; + + rotatedX = centerX + rightDeltaX; + rotatedY = centerY + bottomDeltaY; + minX = math.min(minX, rotatedX); + maxX = math.max(maxX, rotatedX); + minY = math.min(minY, rotatedY); + maxY = math.max(maxY, rotatedY); + + rotatedX = centerX - leftDeltaX; + rotatedY = centerY - topDeltaY; + minX = math.min(minX, rotatedX); + maxX = math.max(maxX, rotatedX); + minY = math.min(minY, rotatedY); + maxY = math.max(maxY, rotatedY); + + rotatedX = centerX - rightDeltaX; + rotatedY = centerY - bottomDeltaY; + minX = math.min(minX, rotatedX); + maxX = math.max(maxX, rotatedX); + minY = math.min(minY, rotatedY); + maxY = math.max(maxY, rotatedY); + + curX = centerX + cmd.radiusX; + curY = centerY; + break; + case PathCommandTypes.quadraticCurveTo: + final QuadraticCurveTo cmd = op; + final double x1 = curX; + final double y1 = curY; + final double cpX = cmd.x1; + final double cpY = cmd.y1; + final double x2 = cmd.x2; + final double y2 = cmd.y2; + + minX = math.min(x1, x2); + minY = math.min(y1, y2); + maxX = math.max(x1, x2); + maxY = math.max(y1, y2); + + // Curve equation : (1-t)(1-t)P1 + 2t(1-t)CP + t*t*P2. + // At extrema's derivative = 0. + // Solve for + // -2x1+2tx1 + 2cpX + 4tcpX + 2tx2 = 0 + // -2x1 + 2cpX +2t(x1 + 2cpX + x2) = 0 + // t = (x1 - cpX) / (x1 - 2cpX + x2) + + double denom = x1 - (2 * cpX) + x2; + if (denom.abs() > epsilon) { + final num t1 = (x1 - cpX) / denom; + if ((t1 >= 0) && (t1 <= 1.0)) { + // Solve (x,y) for curve at t = tx to find extrema + final num tprime = 1.0 - t1; + final num extremaX = (tprime * tprime * x1) + + (2 * t1 * tprime * cpX) + + (t1 * t1 * x2); + final num extremaY = (tprime * tprime * y1) + + (2 * t1 * tprime * cpY) + + (t1 * t1 * y2); + // Expand bounds. + minX = math.min(minX, extremaX); + maxX = math.max(maxX, extremaX); + minY = math.min(minY, extremaY); + maxY = math.max(maxY, extremaY); + } + } + // Now calculate dy/dt = 0 + denom = y1 - (2 * cpY) + y2; + if (denom.abs() > epsilon) { + final num t2 = (y1 - cpY) / denom; + if ((t2 >= 0) && (t2 <= 1.0)) { + final num tprime2 = 1.0 - t2; + final num extrema2X = (tprime2 * tprime2 * x1) + + (2 * t2 * tprime2 * cpX) + + (t2 * t2 * x2); + final num extrema2Y = (tprime2 * tprime2 * y1) + + (2 * t2 * tprime2 * cpY) + + (t2 * t2 * y2); + // Expand bounds. + minX = math.min(minX, extrema2X); + maxX = math.max(maxX, extrema2X); + minY = math.min(minY, extrema2Y); + maxY = math.max(maxY, extrema2Y); + } + } + curX = x2; + curY = y2; + break; + case PathCommandTypes.bezierCurveTo: + final BezierCurveTo cmd = op; + final double startX = curX; + final double startY = curY; + final double cpX1 = cmd.x1; + final double cpY1 = cmd.y1; + final double cpX2 = cmd.x2; + final double cpY2 = cmd.y2; + final double endX = cmd.x3; + final double endY = cmd.y3; + // Bounding box is defined by all points on the curve where + // monotonicity changes. + minX = math.min(startX, endX); + minY = math.min(startY, endY); + maxX = math.max(startX, endX); + maxY = math.max(startY, endY); + + double extremaX; + double extremaY; + double a, b, c; + + // Check for simple case of strong ordering before calculating + // extrema + if (!(((startX < cpX1) && (cpX1 < cpX2) && (cpX2 < endX)) || + ((startX > cpX1) && (cpX1 > cpX2) && (cpX2 > endX)))) { + // The extrema point is dx/dt B(t) = 0 + // The derivative of B(t) for cubic bezier is a quadratic equation + // with multiple roots + // B'(t) = a*t*t + b*t + c*t + a = -startX + (3 * (cpX1 - cpX2)) + endX; + b = 2 * (startX - (2 * cpX1) + cpX2); + c = -startX + cpX1; + + // Now find roots for quadratic equation with known coefficients + // a,b,c + // The roots are (-b+-sqrt(b*b-4*a*c)) / 2a + num s = (b * b) - (4 * a * c); + // If s is negative, we have no real roots + if ((s >= 0.0) && (a.abs() > epsilon)) { + if (s == 0.0) { + // we have only 1 root + final num t = -b / (2 * a); + final num tprime = 1.0 - t; + if ((t >= 0.0) && (t <= 1.0)) { + extremaX = ((tprime * tprime * tprime) * startX) + + ((3 * tprime * tprime * t) * cpX1) + + ((3 * tprime * t * t) * cpX2) + + (t * t * t * endX); + minX = math.min(extremaX, minX); + maxX = math.max(extremaX, maxX); + } + } else { + // we have 2 roots + s = math.sqrt(s); + num t = (-b - s) / (2 * a); + num tprime = 1.0 - t; + if ((t >= 0.0) && (t <= 1.0)) { + extremaX = ((tprime * tprime * tprime) * startX) + + ((3 * tprime * tprime * t) * cpX1) + + ((3 * tprime * t * t) * cpX2) + + (t * t * t * endX); + minX = math.min(extremaX, minX); + maxX = math.max(extremaX, maxX); + } + // check 2nd root + t = (-b + s) / (2 * a); + tprime = 1.0 - t; + if ((t >= 0.0) && (t <= 1.0)) { + extremaX = ((tprime * tprime * tprime) * startX) + + ((3 * tprime * tprime * t) * cpX1) + + ((3 * tprime * t * t) * cpX2) + + (t * t * t * endX); + + minX = math.min(extremaX, minX); + maxX = math.max(extremaX, maxX); + } + } + } + } + + // Now calc extremes for dy/dt = 0 just like above + if (!(((startY < cpY1) && (cpY1 < cpY2) && (cpY2 < endY)) || + ((startY > cpY1) && (cpY1 > cpY2) && (cpY2 > endY)))) { + // The extrema point is dy/dt B(t) = 0 + // The derivative of B(t) for cubic bezier is a quadratic equation + // with multiple roots + // B'(t) = a*t*t + b*t + c*t + a = -startY + (3 * (cpY1 - cpY2)) + endY; + b = 2 * (startY - (2 * cpY1) + cpY2); + c = -startY + cpY1; + + // Now find roots for quadratic equation with known coefficients + // a,b,c + // The roots are (-b+-sqrt(b*b-4*a*c)) / 2a + num s = (b * b) - (4 * a * c); + // If s is negative, we have no real roots + if ((s >= 0.0) && (a.abs() > epsilon)) { + if (s == 0.0) { + // we have only 1 root + final num t = -b / (2 * a); + final num tprime = 1.0 - t; + if ((t >= 0.0) && (t <= 1.0)) { + extremaY = ((tprime * tprime * tprime) * startY) + + ((3 * tprime * tprime * t) * cpY1) + + ((3 * tprime * t * t) * cpY2) + + (t * t * t * endY); + minY = math.min(extremaY, minY); + maxY = math.max(extremaY, maxY); + } + } else { + // we have 2 roots + s = math.sqrt(s); + final num t = (-b - s) / (2 * a); + final num tprime = 1.0 - t; + if ((t >= 0.0) && (t <= 1.0)) { + extremaY = ((tprime * tprime * tprime) * startY) + + ((3 * tprime * tprime * t) * cpY1) + + ((3 * tprime * t * t) * cpY2) + + (t * t * t * endY); + minY = math.min(extremaY, minY); + maxY = math.max(extremaY, maxY); + } + // check 2nd root + final num t2 = (-b + s) / (2 * a); + final num tprime2 = 1.0 - t2; + if ((t2 >= 0.0) && (t2 <= 1.0)) { + extremaY = ((tprime2 * tprime2 * tprime2) * startY) + + ((3 * tprime2 * tprime2 * t2) * cpY1) + + ((3 * tprime2 * t2 * t2) * cpY2) + + (t2 * t2 * t2 * endY); + minY = math.min(extremaY, minY); + maxY = math.max(extremaY, maxY); + } + } + } + } + break; + case PathCommandTypes.rect: + final RectCommand cmd = op; + left = cmd.x; + double width = cmd.width; + if (cmd.width < 0) { + left -= width; + width = -width; + } + double top = cmd.y; + double height = cmd.height; + if (cmd.height < 0) { + top -= height; + height = -height; + } + curX = minX = left; + maxX = left + width; + curY = minY = top; + maxY = top + height; + break; + case PathCommandTypes.rRect: + final RRectCommand cmd = op; + final ui.RRect rRect = cmd.rrect; + curX = minX = rRect.left; + maxX = rRect.left + rRect.width; + curY = minY = rRect.top; + maxY = rRect.top + rRect.height; + break; + case PathCommandTypes.close: + default: + skipBounds = false; + break; + } + if (!skipBounds) { + if (!ltrbInitialized) { + left = minX; + right = maxX; + top = minY; + bottom = maxY; + ltrbInitialized = true; + } else { + left = math.min(left, minX); + right = math.max(right, maxX); + top = math.min(top, minY); + bottom = math.max(bottom, maxY); + } + } + } + } + return ltrbInitialized + ? ui.Rect.fromLTRB(left, top, right, bottom) + : ui.Rect.zero; + } + + /// Creates a [PathMetrics] object for this path. + /// + /// If `forceClosed` is set to true, the contours of the path will be measured + /// as if they had been closed, even if they were not explicitly closed. + @override + SurfacePathMetrics computeMetrics({bool forceClosed = false}) { + return SurfacePathMetrics._(this, forceClosed); + } + + /// Detects if path is rounded rectangle and returns rounded rectangle or + /// null. + /// + /// Used for web optimization of physical shape represented as + /// a persistent div. + ui.RRect get webOnlyPathAsRoundedRect { + if (subpaths.length != 1) { + return null; + } + final Subpath subPath = subpaths[0]; + if (subPath.commands.length != 1) { + return null; + } + final PathCommand command = subPath.commands[0]; + return (command is RRectCommand) ? command.rrect : null; + } + + /// Detects if path is simple rectangle and returns rectangle or null. + /// + /// Used for web optimization of physical shape represented as + /// a persistent div. + ui.Rect get webOnlyPathAsRect { + if (subpaths.length != 1) { + return null; + } + final Subpath subPath = subpaths[0]; + if (subPath.commands.length != 1) { + return null; + } + final PathCommand command = subPath.commands[0]; + return (command is RectCommand) + ? ui.Rect.fromLTWH(command.x, command.y, command.width, command.height) + : null; + } + + /// Detects if path is simple oval and returns [Ellipse] or null. + /// + /// Used for web optimization of physical shape represented as + /// a persistent div. + Ellipse get webOnlyPathAsCircle { + if (subpaths.length != 1) { + return null; + } + final Subpath subPath = subpaths[0]; + if (subPath.commands.length != 1) { + return null; + } + final PathCommand command = subPath.commands[0]; + if (command is Ellipse) { + final Ellipse ellipse = command; + if ((ellipse.endAngle - ellipse.startAngle) % (2 * math.pi) == 0.0) { + return ellipse; + } + } + return null; + } + + /// Serializes this path to a value that's sent to a CSS custom painter for + /// painting. + List webOnlySerializeToCssPaint() { + final List serializedSubpaths = []; + for (int i = 0; i < subpaths.length; i++) { + serializedSubpaths.add(subpaths[i].serializeToCssPaint()); + } + return serializedSubpaths; + } + + @override + String toString() { + if (assertionsEnabled) { + return 'Path(${subpaths.join(', ')})'; + } else { + return super.toString(); + } + } +} + +// Returns true if point is inside ellipse. +bool _ellipseContains(double px, double py, double centerX, double centerY, + double radiusX, double radiusY) { + final double dx = px - centerX; + final double dy = py - centerY; + return ((dx * dx) / (radiusX * radiusX)) + ((dy * dy) / (radiusY * radiusY)) < + 1.0; +} diff --git a/lib/web_ui/lib/src/engine/surface/path_metrics.dart b/lib/web_ui/lib/src/engine/surface/path_metrics.dart new file mode 100644 index 0000000000000..817f7243cfbbc --- /dev/null +++ b/lib/web_ui/lib/src/engine/surface/path_metrics.dart @@ -0,0 +1,533 @@ +// 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. + +part of engine; + +/// An iterable collection of [PathMetric] objects describing a [Path]. +/// +/// A [PathMetrics] object is created by using the [Path.computeMetrics] method, +/// and represents the path as it stood at the time of the call. Subsequent +/// modifications of the path do not affect the [PathMetrics] object. +/// +/// Each path metric corresponds to a segment, or contour, of a path. +/// +/// For example, a path consisting of a [Path.lineTo], a [Path.moveTo], and +/// another [Path.lineTo] will contain two contours and thus be represented by +/// two [PathMetric] objects. +/// +/// When iterating across a [PathMetrics]' contours, the [PathMetric] objects +/// are only valid until the next one is obtained. +class SurfacePathMetrics extends IterableBase implements ui.PathMetrics { + SurfacePathMetrics._(SurfacePath path, bool forceClosed) + : _iterator = SurfacePathMetricIterator._(SurfacePathMetric._(path, forceClosed)); + + final SurfacePathMetricIterator _iterator; + + @override + Iterator get iterator => _iterator; +} + +/// Tracks iteration from one segment of a path to the next for measurement. +class SurfacePathMetricIterator implements Iterator { + SurfacePathMetricIterator._(this._pathMetric); + + SurfacePathMetric _pathMetric; + bool _firstTime = true; + + @override + SurfacePathMetric get current => + _firstTime ? null : _pathMetric._segments.isEmpty ? null : _pathMetric; + + @override + bool moveNext() { + // PathMetric isn't a normal iterable - it's already initialized to its + // first Path. Should only call _moveNext when done with the first one. + if (_firstTime == true) { + _firstTime = false; + return _pathMetric._segments.isNotEmpty; + } else if (_pathMetric?._moveNext() == true) { + return true; + } + _pathMetric = null; + return false; + } +} + +// Maximum range value used in curve subdivision using Casteljau algorithm. +const int _kMaxTValue = 0x3FFFFFFF; +// Distance at which we stop subdividing cubic and quadratic curves. +const double _fTolerance = 0.5; + +/// Utilities for measuring a [Path] and extracting subpaths. +/// +/// Iterate over the object returned by [Path.computeMetrics] to obtain +/// [PathMetric] objects. +/// +/// Once created, metrics will only be valid while the iterator is at the given +/// contour. When the next contour's [PathMetric] is obtained, this object +/// becomes invalid. +/// +/// Implementation is based on +/// https://github.com/google/skia/blob/master/src/core/SkContourMeasure.cpp +/// to maintain consistency with native platforms. +class SurfacePathMetric implements ui.PathMetric { + final SurfacePath _path; + final bool _forceClosed; + + // If the contour ends with a call to [Path.close] (which may + // have been implied when using [Path.addRect]) + bool _isClosed; + // Iterator index into [Path.subPaths] + int _subPathIndex = 0; + List<_PathSegment> _segments; + double _contourLength; + + /// Create a new empty [Path] object. + SurfacePathMetric._(this._path, this._forceClosed) { + _buildSegments(); + } + + @override + int get contourIndex { + throw UnimplementedError('contourIndex is not implemented in the HTML backend'); + } + + /// Return the total length of the current contour. + @override + double get length => _contourLength; + + /// Computes the position of hte current contour at the given offset, and the + /// angle of the path at that point. + /// + /// For example, calling this method with a distance of 1.41 for a line from + /// 0.0,0.0 to 2.0,2.0 would give a point 1.0,1.0 and the angle 45 degrees + /// (but in radians). + /// + /// Returns null if the contour has zero [length]. + /// + /// The distance is clamped to the [length] of the current contour. + @override + ui.Tangent getTangentForOffset(double distance) { + final Float32List posTan = _getPosTan(distance); + // first entry == 0 indicates that Skia returned false + if (posTan[0] == 0.0) { + return null; + } else { + return ui.Tangent( + ui.Offset(posTan[1], posTan[2]), ui.Offset(posTan[3], posTan[4])); + } + } + + Float32List _getPosTan(double distance) => throw UnimplementedError(); + + /// Given a start and stop distance, return the intervening segment(s). + /// + /// `start` and `end` are pinned to legal values (0..[length]) + /// Returns null if the segment is 0 length or `start` > `stop`. + /// Begin the segment with a moveTo if `startWithMoveTo` is true. + @override + SurfacePath extractPath(double start, double end, {bool startWithMoveTo = true}) => + throw UnimplementedError(); + + /// Whether the contour is closed. + /// + /// Returns true if the contour ends with a call to [Path.close] (which may + /// have been implied when using [Path.addRect]) or if `forceClosed` was + /// specified as true in the call to [Path.computeMetrics]. Returns false + /// otherwise. + @override + bool get isClosed { + return _isClosed; + } + + // Move to the next contour in the path. + // + // A path can have a next contour if [Path.moveTo] was called after drawing + // began. Return true if one exists, or false. + // + // This is not exactly congruent with a regular [Iterator.moveNext]. + // Typically, [Iterator.moveNext] should be called before accessing the + // [Iterator.current]. In this case, the [PathMetric] is valid before + // calling `_moveNext` - `_moveNext` should be called after the first + // iteration is done instead of before. + bool _moveNext() { + if (_subPathIndex == (_path.subpaths.length - 1)) { + return false; + } + ++_subPathIndex; + _buildSegments(); + return true; + } + + void _buildSegments() { + _segments = <_PathSegment>[]; + _isClosed = false; + double distance = 0.0; + bool haveSeenMoveTo = false; + + if (_path.subpaths.isEmpty) { + _contourLength = 0; + return; + } + final Subpath subpath = _path.subpaths[_subPathIndex]; + final List commands = subpath.commands; + double currentX = 0.0, currentY = 0.0; + final Function lineToHandler = (double x, double y) { + final double dx = currentX - x; + final double dy = currentY - y; + final double prevDistance = distance; + distance += math.sqrt(dx * dx + dy * dy); + // As we accumulate distance, we have to check that the result of += + // actually made it larger, since a very small delta might be > 0, but + // still have no effect on distance (if distance >>> delta). + if (distance > prevDistance) { + _segments.add(_PathSegment(PathCommandTypes.lineTo, distance, + [currentX, currentY, x, y])); + } + currentX = x; + currentY = y; + }; + _EllipseSegmentResult ellipseResult; + for (PathCommand command in commands) { + switch (command.type) { + case PathCommandTypes.moveTo: + final MoveTo moveTo = command; + currentX = moveTo.x; + currentY = moveTo.y; + haveSeenMoveTo = true; + break; + case PathCommandTypes.lineTo: + assert(haveSeenMoveTo); + final LineTo lineTo = command; + lineToHandler(lineTo.x, lineTo.y); + break; + case PathCommandTypes.bezierCurveTo: + assert(haveSeenMoveTo); + final BezierCurveTo curve = command; + // Compute cubic curve distance. + distance = _computeCubicSegments( + currentX, + currentY, + curve.x1, + curve.y1, + curve.x2, + curve.y2, + curve.x3, + curve.y3, + distance, + 0, + _kMaxTValue, + _segments); + break; + case PathCommandTypes.quadraticCurveTo: + assert(haveSeenMoveTo); + final QuadraticCurveTo quadraticCurveTo = command; + // Compute quad curve distance. + distance = _computeQuadSegments( + currentX, + currentY, + quadraticCurveTo.x1, + quadraticCurveTo.y1, + quadraticCurveTo.x2, + quadraticCurveTo.y2, + distance, + 0, + _kMaxTValue); + break; + case PathCommandTypes.close: + break; + case PathCommandTypes.ellipse: + final Ellipse ellipse = command; + ellipseResult ??= _EllipseSegmentResult(); + _computeEllipseSegments( + currentX, + currentY, + distance, + ellipse.x, + ellipse.y, + ellipse.startAngle, + ellipse.endAngle, + ellipse.rotation, + ellipse.radiusX, + ellipse.radiusY, + ellipse.anticlockwise, + ellipseResult, + _segments); + distance = ellipseResult.distance; + currentX = ellipseResult.endPointX; + currentY = ellipseResult.endPointY; + _isClosed = true; + break; + case PathCommandTypes.rRect: + final RRectCommand rrectCommand = command; + final ui.RRect rrect = rrectCommand.rrect; + RRectMetricsRenderer(moveToCallback: (double x, double y) { + currentX = x; + currentY = y; + _isClosed = true; + haveSeenMoveTo = true; + }, lineToCallback: (double x, double y) { + lineToHandler(x, y); + }, ellipseCallback: (double centerX, + double centerY, + double radiusX, + double radiusY, + double rotation, + double startAngle, + double endAngle, + bool antiClockwise) { + ellipseResult ??= _EllipseSegmentResult(); + _computeEllipseSegments( + currentX, + currentY, + distance, + centerX, + centerY, + startAngle, + endAngle, + rotation, + radiusX, + radiusY, + antiClockwise, + ellipseResult, + _segments); + distance = ellipseResult.distance; + currentX = ellipseResult.endPointX; + currentY = ellipseResult.endPointY; + }).render(rrect); + _isClosed = true; + break; + case PathCommandTypes.rect: + final RectCommand rectCommand = command; + final double x = rectCommand.x; + final double y = rectCommand.y; + final double width = rectCommand.width; + final double height = rectCommand.height; + currentX = x; + currentY = y; + lineToHandler(x + width, y); + lineToHandler(x + width, y + height); + lineToHandler(x, y + height); + lineToHandler(x, y); + _isClosed = true; + break; + default: + throw UnimplementedError('Unknown path command $command'); + } + } + if (!_isClosed && _forceClosed && _segments.isNotEmpty) { + _PathSegment firstSegment = _segments.first; + lineToHandler(firstSegment.points[0], firstSegment.points[1]); + } + _contourLength = distance; + } + + static bool _tspanBigEnough(int tSpan) => (tSpan >> 10) != 0; + + static bool _cubicTooCurvy(double x0, double y0, double x1, double y1, + double x2, double y2, double x3, double y3) { + // Measure distance from start-end line at 1/3 and 2/3rds to control + // points. If distance is less than _fTolerance we should continue + // subdividing curve. Uses approx distance for speed. + // + // p1 = point 1/3rd between start,end points. + final double p1x = (x0 * 2 / 3) + (x3 / 3); + final double p1y = (y0 * 2 / 3) + (y3 / 3); + if ((p1x - x1).abs() > _fTolerance) { + return true; + } + if ((p1y - y1).abs() > _fTolerance) { + return true; + } + // p2 = point 2/3rd between start,end points. + final double p2x = (x0 / 3) + (x3 * 2 / 3); + final double p2y = (y0 / 3) + (y3 * 2 / 3); + if ((p2x - x2).abs() > _fTolerance) { + return true; + } + if ((p2y - y2).abs() > _fTolerance) { + return true; + } + return false; + } + + // Recursively subdivides cubic and adds segments. + static double _computeCubicSegments( + double x0, + double y0, + double x1, + double y1, + double x2, + double y2, + double x3, + double y3, + double distance, + int tMin, + int tMax, + List<_PathSegment> segments) { + if (_tspanBigEnough(tMax - tMin) && + _cubicTooCurvy(x0, y0, x1, y1, x2, y2, x3, y3)) { + // Chop cubic into two halves (De Cateljau's algorithm) + // See https://en.wikipedia.org/wiki/De_Casteljau%27s_algorithm + final double abX = (x0 + x1) / 2; + final double abY = (y0 + y1) / 2; + final double bcX = (x1 + x2) / 2; + final double bcY = (y1 + y2) / 2; + final double cdX = (x2 + x3) / 2; + final double cdY = (y2 + y3) / 2; + final double abcX = (abX + bcX) / 2; + final double abcY = (abY + bcY) / 2; + final double bcdX = (bcX + cdX) / 2; + final double bcdY = (bcY + cdY) / 2; + final double abcdX = (abcX + bcdX) / 2; + final double abcdY = (abcY + bcdY) / 2; + final int tHalf = (tMin + tMax) >> 1; + distance = _computeCubicSegments( + x0, y0, abX, abY, abcX, abcY, abcdX, abcdY, distance, tMin, tHalf, segments); + distance = _computeCubicSegments( + abcdX, abcdY, bcdX, bcdY, cdX, cdY, x3, y3, distance, tHalf, tMax, segments); + } else { + final double dx = x0 - x3; + final double dy = y0 - y3; + final double startToEndDistance = math.sqrt(dx * dx + dy * dy); + final double prevDistance = distance; + distance += startToEndDistance; + if (distance > prevDistance) { + segments.add(_PathSegment(PathCommandTypes.bezierCurveTo, + distance, [x0, y0, x1, y1, x2, y2, x3, y3])); + } + } + return distance; + } + + static bool _quadTooCurvy( + double x0, double y0, double x1, double y1, double x2, double y2) { + // (a/4 + b/2 + c/4) - (a/2 + c/2) = -a/4 + b/2 - c/4 + final double dx = (x1 / 2) - (x0 + x2) / 4; + if (dx.abs() > _fTolerance) { + return true; + } + final double dy = (y1 / 2) - (y0 + y2) / 4; + if (dy.abs() > _fTolerance) { + return true; + } + return false; + } + + double _computeQuadSegments(double x0, double y0, double x1, double y1, + double x2, double y2, double distance, int tMin, int tMax) { + if (_tspanBigEnough(tMax - tMin) && _quadTooCurvy(x0, y0, x1, y1, x2, y2)) { + final double p01x = (x0 + x1) / 2; + final double p01y = (y0 + y1) / 2; + final double p12x = (x1 + x2) / 2; + final double p12y = (y1 + y2) / 2; + final double p012x = (p01x + p12x) / 2; + final double p012y = (p01y + p12y) / 2; + final int tHalf = (tMin + tMax) >> 1; + distance = _computeQuadSegments( + x0, y0, p01x, p01y, p012x, p012y, distance, tMin, tHalf); + distance = _computeQuadSegments( + p012x, p012y, p12x, p12y, x2, y2, distance, tMin, tHalf); + } else { + final double dx = x0 - x2; + final double dy = y0 - y2; + final double startToEndDistance = math.sqrt(dx * dx + dy * dy); + final double prevDistance = distance; + distance += startToEndDistance; + if (distance > prevDistance) { + _segments.add(_PathSegment(PathCommandTypes.quadraticCurveTo, + distance, [x0, y0, x1, y1, x2, y2])); + } + } + return distance; + } + + // Create segments by converting arc to cubics. + // See http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter. + static void _computeEllipseSegments( + double startX, + double startY, + double distance, + double cx, + double cy, + double startAngle, + double endAngle, + double rotation, + double radiusX, + double radiusY, + bool anticlockwise, + _EllipseSegmentResult result, + List<_PathSegment> segments) { + final double endX = cx + (radiusX * math.cos(endAngle)); + final double endY = cy + (radiusY * math.sin(endAngle)); + result.endPointX = endX; + result.endPointY = endY; + // Check for http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters + // Treat as line segment from start to end if arc has zero radii. + // If start and end point are the same treat as zero length path. + if ((radiusX == 0 || radiusY == 0) || (startX == endX && startY == endY)) { + result.distance = distance; + return; + } + final double rxAbs = radiusX.abs(); + final double ryAbs = radiusY.abs(); + + final double theta1 = startAngle; + final double theta2 = endAngle; + final double thetaArc = theta2 - theta1; + + // Add 0.01f to make sure we have enough segments when thetaArc is close + // to pi/2. + final int numSegments = (thetaArc / ((math.pi / 2.0) + 0.01)).abs().ceil(); + double x0 = startX; + double y0 = startY; + for (int segmentIndex = 0; segmentIndex < numSegments; segmentIndex++) { + final double startTheta = + theta1 + (segmentIndex * thetaArc / numSegments); + final double endTheta = + theta1 + ((segmentIndex + 1) * thetaArc / numSegments); + final double t = (4.0 / 3.0) * math.tan((endTheta - startTheta) / 4); + if (!t.isFinite) { + result.distance = distance; + return; + } + final double sinStartTheta = math.sin(startTheta); + final double cosStartTheta = math.cos(startTheta); + final double sinEndTheta = math.sin(endTheta); + final double cosEndTheta = math.cos(endTheta); + + // Compute cubic segment start, control point and end (target). + final double p1x = rxAbs * (cosStartTheta - t * sinStartTheta) + cx; + final double p1y = ryAbs * (sinStartTheta + t * cosStartTheta) + cy; + final double targetPointX = rxAbs * cosEndTheta + cx; + final double targetPointY = ryAbs * sinEndTheta + cy; + final double p2x = targetPointX + rxAbs * (t * sinEndTheta); + final double p2y = targetPointY + ryAbs * (-t * cosEndTheta); + + distance = _computeCubicSegments(x0, y0, p1x, p1y, p2x, p2y, targetPointX, + targetPointY, distance, 0, _kMaxTValue, segments); + x0 = targetPointX; + y0 = targetPointY; + } + result.distance = distance; + } + + @override + String toString() => 'PathMetric'; +} + +class _EllipseSegmentResult { + double endPointX; + double endPointY; + double distance; + _EllipseSegmentResult(); +} + +class _PathSegment { + _PathSegment(this.segmentType, this.distance, this.points); + + final int segmentType; + final double distance; + final List points; +} diff --git a/lib/web_ui/lib/src/engine/surface/picture.dart b/lib/web_ui/lib/src/engine/surface/picture.dart index 7186d8deb4c36..336dd0ca74bc4 100644 --- a/lib/web_ui/lib/src/engine/surface/picture.dart +++ b/lib/web_ui/lib/src/engine/surface/picture.dart @@ -100,10 +100,6 @@ class PersistedHoudiniPicture extends PersistedPicture { return existingSurface.picture == picture ? 0.0 : 1.0; } - @override - Matrix4 get localTransformInverse => - _localTransformInverse ??= Matrix4.identity(); - static void _registerCssPainter() { _cssPainterRegistered = true; final dynamic css = js_util.getProperty(html.window, 'CSS'); @@ -173,10 +169,10 @@ class PersistedStandardPicture extends PersistedPicture { // The canvas needs to be resized before painting. return 1.0; } else { - final int newPixelCount = oldCanvas._widthToPhysical(_exactLocalCullRect.width) - * oldCanvas._heightToPhysical(_exactLocalCullRect.height); + final int newPixelCount = BitmapCanvas._widthToPhysical(_exactLocalCullRect.width) + * BitmapCanvas._heightToPhysical(_exactLocalCullRect.height); final int oldPixelCount = - oldCanvas.widthInBitmapPixels * oldCanvas.heightInBitmapPixels; + oldCanvas._widthInBitmapPixels * oldCanvas._heightInBitmapPixels; if (oldPixelCount == 0) { return 1.0; @@ -324,7 +320,7 @@ class PersistedStandardPicture extends PersistedPicture { _surfaceStatsFor(this) ..allocateBitmapCanvasCount += 1 ..allocatedBitmapSizeInPixels = - canvas.widthInBitmapPixels * canvas.heightInBitmapPixels; + canvas._widthInBitmapPixels * canvas._heightInBitmapPixels; } return canvas; } @@ -340,7 +336,7 @@ abstract class PersistedPicture extends PersistedLeafSurface { final double dx; final double dy; - final ui.Picture picture; + final EnginePicture picture; final ui.Rect localPaintBounds; final int hints; diff --git a/lib/web_ui/lib/src/engine/recording_canvas.dart b/lib/web_ui/lib/src/engine/surface/recording_canvas.dart similarity index 95% rename from lib/web_ui/lib/src/engine/recording_canvas.dart rename to lib/web_ui/lib/src/engine/surface/recording_canvas.dart index 067fe397eb881..f964323c49142 100644 --- a/lib/web_ui/lib/src/engine/recording_canvas.dart +++ b/lib/web_ui/lib/src/engine/surface/recording_canvas.dart @@ -73,8 +73,9 @@ class RecordingCanvas { print(debugBuf); } else { try { - for (int i = 0; i < _commands.length; i++) { - _commands[i].apply(engineCanvas); + for (int i = 0, len = _commands.length; i < len; i++) { + PaintCommand command = _commands[i]; + command.apply(engineCanvas); } } catch (e) { // commands should never fail, but... @@ -84,6 +85,7 @@ class RecordingCanvas { } } } + engineCanvas.endOfPaint(); } /// Prints recorded commands. @@ -105,7 +107,7 @@ class RecordingCanvas { _saveCount++; } - void saveLayerWithoutBounds(ui.Paint paint) { + void saveLayerWithoutBounds(SurfacePaint paint) { _hasArbitraryPaint = true; // TODO(het): Implement this correctly using another canvas. _commands.add(const PaintSave()); @@ -113,7 +115,7 @@ class RecordingCanvas { _saveCount++; } - void saveLayer(ui.Rect bounds, ui.Paint paint) { + void saveLayer(ui.Rect bounds, SurfacePaint paint) { _hasArbitraryPaint = true; // TODO(het): Implement this correctly using another canvas. _commands.add(const PaintSave()); @@ -186,7 +188,7 @@ class RecordingCanvas { _commands.add(PaintDrawColor(color, blendMode)); } - void drawLine(ui.Offset p1, ui.Offset p2, ui.Paint paint) { + void drawLine(ui.Offset p1, ui.Offset p2, SurfacePaint paint) { final double strokeWidth = math.max(paint.strokeWidth, 1.0); // TODO(yjbanov): This can be optimized. Currently we create a box around // the line and then apply the transform on the box to get @@ -202,17 +204,17 @@ class RecordingCanvas { math.max(p1.dy, p2.dy) + strokeWidth); _hasArbitraryPaint = true; _didDraw = true; - _commands.add(PaintDrawLine(p1, p2, paint.webOnlyPaintData)); + _commands.add(PaintDrawLine(p1, p2, paint.paintData)); } - void drawPaint(ui.Paint paint) { + void drawPaint(SurfacePaint paint) { _hasArbitraryPaint = true; _didDraw = true; _paintBounds.grow(_paintBounds.maxPaintBounds); - _commands.add(PaintDrawPaint(paint.webOnlyPaintData)); + _commands.add(PaintDrawPaint(paint.paintData)); } - void drawRect(ui.Rect rect, ui.Paint paint) { + void drawRect(ui.Rect rect, SurfacePaint paint) { if (paint.shader != null) { _hasArbitraryPaint = true; } @@ -222,10 +224,10 @@ class RecordingCanvas { } else { _paintBounds.grow(rect); } - _commands.add(PaintDrawRect(rect, paint.webOnlyPaintData)); + _commands.add(PaintDrawRect(rect, paint.paintData)); } - void drawRRect(ui.RRect rrect, ui.Paint paint) { + void drawRRect(ui.RRect rrect, SurfacePaint paint) { _hasArbitraryPaint = true; _didDraw = true; final double strokeWidth = @@ -235,10 +237,10 @@ class RecordingCanvas { final double top = math.min(rrect.top, rrect.bottom) - strokeWidth; final double bottom = math.max(rrect.top, rrect.bottom) + strokeWidth; _paintBounds.growLTRB(left, top, right, bottom); - _commands.add(PaintDrawRRect(rrect, paint.webOnlyPaintData)); + _commands.add(PaintDrawRRect(rrect, paint.paintData)); } - void drawDRRect(ui.RRect outer, ui.RRect inner, ui.Paint paint) { + void drawDRRect(ui.RRect outer, ui.RRect inner, SurfacePaint paint) { // Check the inner bounds are contained within the outer bounds // see: https://cs.chromium.org/chromium/src/third_party/skia/src/core/SkCanvas.cpp?l=1787-1789 ui.Rect innerRect = inner.outerRect; @@ -271,10 +273,10 @@ class RecordingCanvas { paint.strokeWidth == null ? 0 : paint.strokeWidth; _paintBounds.growLTRB(outer.left - strokeWidth, outer.top - strokeWidth, outer.right + strokeWidth, outer.bottom + strokeWidth); - _commands.add(PaintDrawDRRect(outer, inner, paint.webOnlyPaintData)); + _commands.add(PaintDrawDRRect(outer, inner, paint.paintData)); } - void drawOval(ui.Rect rect, ui.Paint paint) { + void drawOval(ui.Rect rect, SurfacePaint paint) { _hasArbitraryPaint = true; _didDraw = true; if (paint.strokeWidth != null) { @@ -282,10 +284,10 @@ class RecordingCanvas { } else { _paintBounds.grow(rect); } - _commands.add(PaintDrawOval(rect, paint.webOnlyPaintData)); + _commands.add(PaintDrawOval(rect, paint.paintData)); } - void drawCircle(ui.Offset c, double radius, ui.Paint paint) { + void drawCircle(ui.Offset c, double radius, SurfacePaint paint) { _hasArbitraryPaint = true; _didDraw = true; final double strokeWidth = @@ -295,10 +297,10 @@ class RecordingCanvas { c.dy - radius - strokeWidth, c.dx + radius + strokeWidth, c.dy + radius + strokeWidth); - _commands.add(PaintDrawCircle(c, radius, paint.webOnlyPaintData)); + _commands.add(PaintDrawCircle(c, radius, paint.paintData)); } - void drawPath(ui.Path path, ui.Paint paint) { + void drawPath(ui.Path path, SurfacePaint paint) { _hasArbitraryPaint = true; _didDraw = true; ui.Rect pathBounds = path.getBounds(); @@ -309,23 +311,23 @@ class RecordingCanvas { // Clone path so it can be reused for subsequent draw calls. final ui.Path clone = ui.Path.from(path); clone.fillType = path.fillType; - _commands.add(PaintDrawPath(clone, paint.webOnlyPaintData)); + _commands.add(PaintDrawPath(clone, paint.paintData)); } - void drawImage(ui.Image image, ui.Offset offset, ui.Paint paint) { + void drawImage(ui.Image image, ui.Offset offset, SurfacePaint paint) { _hasArbitraryPaint = true; _didDraw = true; final double left = offset.dx; final double top = offset.dy; _paintBounds.growLTRB(left, top, left + image.width, top + image.height); - _commands.add(PaintDrawImage(image, offset, paint.webOnlyPaintData)); + _commands.add(PaintDrawImage(image, offset, paint.paintData)); } - void drawImageRect(ui.Image image, ui.Rect src, ui.Rect dst, ui.Paint paint) { + void drawImageRect(ui.Image image, ui.Rect src, ui.Rect dst, SurfacePaint paint) { _hasArbitraryPaint = true; _didDraw = true; _paintBounds.grow(dst); - _commands.add(PaintDrawImageRect(image, src, dst, paint.webOnlyPaintData)); + _commands.add(PaintDrawImageRect(image, src, dst, paint.paintData)); } void drawParagraph(ui.Paragraph paragraph, ui.Offset offset) { @@ -357,7 +359,7 @@ class RecordingCanvas { } void drawVertices(ui.Vertices vertices, ui.BlendMode blendMode, - ui.Paint paint) { + SurfacePaint paint) { _hasArbitraryPaint = true; _didDraw = true; final Float32List positions = vertices.positions; @@ -379,7 +381,7 @@ class RecordingCanvas { maxValueY = math.max(maxValueY, y); } _paintBounds.growLTRB(minValueX, minValueY, maxValueX, maxValueY); - _commands.add(PaintVertices(vertices, blendMode, paint.webOnlyPaintData)); + _commands.add(PaintVertices(vertices, blendMode, paint.paintData)); } int _saveCount = 1; @@ -629,7 +631,7 @@ class PaintClipRRect extends PaintCommand { } class PaintClipPath extends PaintCommand { - final ui.Path path; + final SurfacePath path; PaintClipPath(this.path); @@ -682,7 +684,7 @@ class PaintDrawColor extends PaintCommand { class PaintDrawLine extends PaintCommand { final ui.Offset p1; final ui.Offset p2; - final ui.PaintData paint; + final SurfacePaintData paint; PaintDrawLine(this.p1, this.p2, this.paint); @@ -714,7 +716,7 @@ class PaintDrawLine extends PaintCommand { } class PaintDrawPaint extends PaintCommand { - final ui.PaintData paint; + final SurfacePaintData paint; PaintDrawPaint(this.paint); @@ -741,7 +743,7 @@ class PaintDrawPaint extends PaintCommand { class PaintVertices extends PaintCommand { final ui.Vertices vertices; final ui.BlendMode blendMode; - final ui.PaintData paint; + final SurfacePaintData paint; PaintVertices(this.vertices, this.blendMode, this.paint); @override @@ -766,7 +768,7 @@ class PaintVertices extends PaintCommand { class PaintDrawRect extends PaintCommand { final ui.Rect rect; - final ui.PaintData paint; + final SurfacePaintData paint; PaintDrawRect(this.rect, this.paint); @@ -796,7 +798,7 @@ class PaintDrawRect extends PaintCommand { class PaintDrawRRect extends PaintCommand { final ui.RRect rrect; - final ui.PaintData paint; + final SurfacePaintData paint; PaintDrawRRect(this.rrect, this.paint); @@ -827,7 +829,7 @@ class PaintDrawRRect extends PaintCommand { class PaintDrawDRRect extends PaintCommand { final ui.RRect outer; final ui.RRect inner; - final ui.PaintData paint; + final SurfacePaintData paint; PaintDrawDRRect(this.outer, this.inner, this.paint); @@ -858,7 +860,7 @@ class PaintDrawDRRect extends PaintCommand { class PaintDrawOval extends PaintCommand { final ui.Rect rect; - final ui.PaintData paint; + final SurfacePaintData paint; PaintDrawOval(this.rect, this.paint); @@ -889,7 +891,7 @@ class PaintDrawOval extends PaintCommand { class PaintDrawCircle extends PaintCommand { final ui.Offset c; final double radius; - final ui.PaintData paint; + final SurfacePaintData paint; PaintDrawCircle(this.c, this.radius, this.paint); @@ -920,8 +922,8 @@ class PaintDrawCircle extends PaintCommand { } class PaintDrawPath extends PaintCommand { - final ui.Path path; - final ui.PaintData paint; + final SurfacePath path; + final SurfacePaintData paint; PaintDrawPath(this.path, this.paint); @@ -953,7 +955,7 @@ class PaintDrawShadow extends PaintCommand { PaintDrawShadow( this.path, this.color, this.elevation, this.transparentOccluder); - final ui.Path path; + final SurfacePath path; final ui.Color color; final double elevation; final bool transparentOccluder; @@ -992,7 +994,7 @@ class PaintDrawShadow extends PaintCommand { class PaintDrawImage extends PaintCommand { final ui.Image image; final ui.Offset offset; - final ui.PaintData paint; + final SurfacePaintData paint; PaintDrawImage(this.image, this.offset, this.paint); @@ -1022,7 +1024,7 @@ class PaintDrawImageRect extends PaintCommand { final ui.Image image; final ui.Rect src; final ui.Rect dst; - final ui.PaintData paint; + final SurfacePaintData paint; PaintDrawImageRect(this.image, this.src, this.dst, this.paint); @@ -1076,7 +1078,7 @@ class PaintDrawParagraph extends PaintCommand { } } -List _serializePaintToCssPaint(ui.PaintData paint) { +List _serializePaintToCssPaint(SurfacePaintData paint) { final EngineGradient engineShader = paint.shader; return [ paint.blendMode?.index, diff --git a/lib/web_ui/lib/src/engine/surface/scene_builder.dart b/lib/web_ui/lib/src/engine/surface/scene_builder.dart index b29953a30f3c5..b3aaa14d75dad 100644 --- a/lib/web_ui/lib/src/engine/surface/scene_builder.dart +++ b/lib/web_ui/lib/src/engine/surface/scene_builder.dart @@ -9,8 +9,7 @@ class SurfaceSceneBuilder implements ui.SceneBuilder { _surfaceStack.add(PersistedScene(_lastFrameScene)); } - final List _surfaceStack = - []; + final List _surfaceStack = []; /// The scene built by this scene builder. /// @@ -19,8 +18,7 @@ class SurfaceSceneBuilder implements ui.SceneBuilder { assert(() { if (_surfaceStack.length != 1) { final String surfacePrintout = _surfaceStack - .map((PersistedContainerSurface surface) => - surface.runtimeType) + .map((PersistedContainerSurface surface) => surface.runtimeType) .toList() .join(', '); throw Exception('Incorrect sequence of push/pop operations while ' @@ -65,8 +63,11 @@ class SurfaceSceneBuilder implements ui.SceneBuilder { /// /// See [pop] for details about the operation stack. @override - ui.OffsetEngineLayer pushOffset(double dx, double dy, - {ui.OffsetEngineLayer oldLayer}) { + ui.OffsetEngineLayer pushOffset( + double dx, + double dy, { + ui.OffsetEngineLayer oldLayer, + }) { return _pushSurface(PersistedOffset(oldLayer, dx, dy)); } @@ -76,8 +77,10 @@ class SurfaceSceneBuilder implements ui.SceneBuilder { /// /// See [pop] for details about the operation stack. @override - ui.TransformEngineLayer pushTransform(Float64List matrix4, - {ui.TransformEngineLayer oldLayer}) { + ui.TransformEngineLayer pushTransform( + Float64List matrix4, { + ui.TransformEngineLayer oldLayer, + }) { if (matrix4 == null) { throw ArgumentError('"matrix4" argument cannot be null'); } @@ -94,8 +97,11 @@ class SurfaceSceneBuilder implements ui.SceneBuilder { /// See [pop] for details about the operation stack, and [Clip] for different clip modes. /// By default, the clip will be anti-aliased (clip = [Clip.antiAlias]). @override - ui.ClipRectEngineLayer pushClipRect(ui.Rect rect, - {ui.Clip clipBehavior = ui.Clip.antiAlias, ui.ClipRectEngineLayer oldLayer}) { + ui.ClipRectEngineLayer pushClipRect( + ui.Rect rect, { + ui.Clip clipBehavior = ui.Clip.antiAlias, + ui.ClipRectEngineLayer oldLayer, + }) { assert(clipBehavior != null); assert(clipBehavior != ui.Clip.none); return _pushSurface(PersistedClipRect(oldLayer, rect)); @@ -107,10 +113,12 @@ class SurfaceSceneBuilder implements ui.SceneBuilder { /// /// See [pop] for details about the operation stack. @override - ui.ClipRRectEngineLayer pushClipRRect(ui.RRect rrect, - {ui.Clip clipBehavior, ui.ClipRRectEngineLayer oldLayer}) { - return _pushSurface( - PersistedClipRRect(oldLayer, rrect, clipBehavior)); + ui.ClipRRectEngineLayer pushClipRRect( + ui.RRect rrect, { + ui.Clip clipBehavior, + ui.ClipRRectEngineLayer oldLayer, + }) { + return _pushSurface(PersistedClipRRect(oldLayer, rrect, clipBehavior)); } /// Pushes a path clip operation onto the operation stack. @@ -119,8 +127,11 @@ class SurfaceSceneBuilder implements ui.SceneBuilder { /// /// See [pop] for details about the operation stack. @override - ui.ClipPathEngineLayer pushClipPath(ui.Path path, - {ui.Clip clipBehavior = ui.Clip.antiAlias, ui.ClipPathEngineLayer oldLayer}) { + ui.ClipPathEngineLayer pushClipPath( + ui.Path path, { + ui.Clip clipBehavior = ui.Clip.antiAlias, + ui.ClipPathEngineLayer oldLayer, + }) { assert(clipBehavior != null); assert(clipBehavior != ui.Clip.none); return _pushSurface(PersistedClipPath(oldLayer, path, clipBehavior)); @@ -135,8 +146,11 @@ class SurfaceSceneBuilder implements ui.SceneBuilder { /// /// See [pop] for details about the operation stack. @override - ui.OpacityEngineLayer pushOpacity(int alpha, - {ui.Offset offset = ui.Offset.zero, ui.OpacityEngineLayer oldLayer}) { + ui.OpacityEngineLayer pushOpacity( + int alpha, { + ui.Offset offset = ui.Offset.zero, + ui.OpacityEngineLayer oldLayer, + }) { return _pushSurface(PersistedOpacity(oldLayer, alpha, offset)); } @@ -151,12 +165,33 @@ class SurfaceSceneBuilder implements ui.SceneBuilder { /// /// See [pop] for details about the operation stack. @override - ui.ColorFilterEngineLayer pushColorFilter(ui.ColorFilter filter, - {ui.ColorFilterEngineLayer oldLayer}) { + ui.ColorFilterEngineLayer pushColorFilter( + ui.ColorFilter filter, { + ui.ColorFilterEngineLayer oldLayer, + }) { assert(filter != null); throw UnimplementedError(); } + /// Pushes an image filter operation onto the operation stack. + /// + /// The given filter is applied to the children's rasterization before compositing them into + /// the scene. + /// + /// {@macro dart.ui.sceneBuilder.oldLayer} + /// + /// {@macro dart.ui.sceneBuilder.oldLayerVsRetained} + /// + /// See [pop] for details about the operation stack. + @override + ui.ImageFilterEngineLayer pushImageFilter( + ui.ImageFilter filter, { + ui.ImageFilterEngineLayer oldLayer, + }) { + assert(filter != null); + return _pushSurface(PersistedImageFilter(oldLayer, filter)); + } + /// Pushes a backdrop filter operation onto the operation stack. /// /// The given filter is applied to the current contents of the scene prior to @@ -164,8 +199,10 @@ class SurfaceSceneBuilder implements ui.SceneBuilder { /// /// See [pop] for details about the operation stack. @override - ui.BackdropFilterEngineLayer pushBackdropFilter(ui.ImageFilter filter, - {ui.BackdropFilterEngineLayer oldLayer}) { + ui.BackdropFilterEngineLayer pushBackdropFilter( + ui.ImageFilter filter, { + ui.BackdropFilterEngineLayer oldLayer, + }) { return _pushSurface(PersistedBackdropFilter(oldLayer, filter)); } @@ -177,8 +214,11 @@ class SurfaceSceneBuilder implements ui.SceneBuilder { /// See [pop] for details about the operation stack. @override ui.ShaderMaskEngineLayer pushShaderMask( - ui.Shader shader, ui.Rect maskRect, ui.BlendMode blendMode, - {ui.ShaderMaskEngineLayer oldLayer}) { + ui.Shader shader, + ui.Rect maskRect, + ui.BlendMode blendMode, { + ui.ShaderMaskEngineLayer oldLayer, + }) { throw UnimplementedError(); } @@ -266,8 +306,7 @@ class SurfaceSceneBuilder implements ui.SceneBuilder { /// for more details. @override void addPerformanceOverlay(int enabledOptions, ui.Rect bounds) { - _addPerformanceOverlay( - enabledOptions, bounds.left, bounds.right, bounds.top, bounds.bottom); + _addPerformanceOverlay(enabledOptions, bounds.left, bounds.right, bounds.top, bounds.bottom); } /// Whether we've already warned the user about the lack of the performance @@ -276,12 +315,16 @@ class SurfaceSceneBuilder implements ui.SceneBuilder { /// We use this to avoid spamming the console with redundant warning messages. static bool _webOnlyDidWarnAboutPerformanceOverlay = false; - void _addPerformanceOverlay(int enabledOptions, double left, double right, - double top, double bottom) { + void _addPerformanceOverlay( + int enabledOptions, + double left, + double right, + double top, + double bottom, + ) { if (!_webOnlyDidWarnAboutPerformanceOverlay) { _webOnlyDidWarnAboutPerformanceOverlay = true; - html.window.console - .warn('The performance overlay isn\'t supported on the web'); + html.window.console.warn('The performance overlay isn\'t supported on the web'); } } @@ -302,8 +345,7 @@ class SurfaceSceneBuilder implements ui.SceneBuilder { if (willChangeHint) { hints |= 2; } - _addSurface( - persistedPictureFactory(offset.dx, offset.dy, picture, hints)); + _addSurface(persistedPictureFactory(offset.dx, offset.dy, picture, hints)); } /// Adds a backend texture to the scene. @@ -311,17 +353,18 @@ class SurfaceSceneBuilder implements ui.SceneBuilder { /// The texture is scaled to the given size and rasterized at the given /// offset. @override - void addTexture(int textureId, - {ui.Offset offset = ui.Offset.zero, - double width = 0.0, - double height = 0.0, - bool freeze = false}) { + void addTexture( + int textureId, { + ui.Offset offset = ui.Offset.zero, + double width = 0.0, + double height = 0.0, + bool freeze = false, + }) { assert(offset != null, 'Offset argument was null'); _addTexture(offset.dx, offset.dy, width, height, textureId); } - void _addTexture( - double dx, double dy, double width, double height, int textureId) { + void _addTexture(double dx, double dy, double width, double height, int textureId) { // In test mode, allow this to be a no-op. if (!ui.debugEmulateFlutterTesterEnvironment) { throw UnimplementedError('Textures are not supported in Flutter Web'); @@ -368,17 +411,24 @@ class SurfaceSceneBuilder implements ui.SceneBuilder { /// (Fuchsia-only) Adds a scene rendered by another application to the scene /// for this application. @override - void addChildScene( - {ui.Offset offset = ui.Offset.zero, - double width = 0.0, - double height = 0.0, - ui.SceneHost sceneHost, - bool hitTestable = true}) { + void addChildScene({ + ui.Offset offset = ui.Offset.zero, + double width = 0.0, + double height = 0.0, + ui.SceneHost sceneHost, + bool hitTestable = true, + }) { _addChildScene(offset.dx, offset.dy, width, height, sceneHost, hitTestable); } - void _addChildScene(double dx, double dy, double width, double height, - ui.SceneHost sceneHost, bool hitTestable) { + void _addChildScene( + double dx, + double dy, + double width, + double height, + ui.SceneHost sceneHost, + bool hitTestable, + ) { throw UnimplementedError(); } @@ -475,8 +525,22 @@ class SurfaceSceneBuilder implements ui.SceneBuilder { /// Set properties on the linked scene. These properties include its bounds, /// as well as whether it can be the target of focus events or not. @override - void setProperties(double width, double height, double insetTop, - double insetRight, double insetBottom, double insetLeft, bool focusable) { + void setProperties( + double width, + double height, + double insetTop, + double insetRight, + double insetBottom, + double insetLeft, + bool focusable, + ) { throw UnimplementedError(); } } + +// TODO(yjbanov): in HTML the blur looks too aggressive. The current +// implementation was copied from the existing backdrop-filter +// but probably needs a revision. +String _imageFilterToCss(EngineImageFilter filter) { + return 'blur(${math.max(filter.sigmaX, filter.sigmaY) * 2}px)'; +} diff --git a/lib/web_ui/lib/src/engine/surface/surface.dart b/lib/web_ui/lib/src/engine/surface/surface.dart index dc2df2d557416..2aceb0a0f982d 100644 --- a/lib/web_ui/lib/src/engine/surface/surface.dart +++ b/lib/web_ui/lib/src/engine/surface/surface.dart @@ -804,7 +804,12 @@ abstract class PersistedSurface implements ui.EngineLayer { // Matrix only contains local transform (not chain multiplied since root). Matrix4 _localTransformInverse; - Matrix4 get localTransformInverse; + /// The inverse of the local transform that this surface applies to its children. + /// + /// The default implementation is identity transform. Concrete + /// implementations may override this getter to supply a different transform. + Matrix4 get localTransformInverse => + _localTransformInverse ??= Matrix4.identity(); /// Recomputes [transform] and [globalClip] fields. /// diff --git a/lib/web_ui/lib/src/engine/text/font_collection.dart b/lib/web_ui/lib/src/engine/text/font_collection.dart index e33b9c33858dd..ecb0dd42fa032 100644 --- a/lib/web_ui/lib/src/engine/text/font_collection.dart +++ b/lib/web_ui/lib/src/engine/text/font_collection.dart @@ -73,6 +73,10 @@ class FontCollection { } } + Future loadFontFromList(Uint8List list, {String fontFamily}) { + return _assetFontManager._loadFontFaceBytes(fontFamily, list); + } + /// Registers fonts that are used by tests. void debugRegisterTestFonts() { _testFontManager = FontManager(); @@ -189,6 +193,26 @@ class FontManager { } } + // Loads a font from bytes, surfacing errors through the future. + Future _loadFontFaceBytes(String family, Uint8List list) { + // Since these fonts are loaded by user code, surface the error + // through the returned future. + final html.FontFace fontFace = html.FontFace(family, list); + return fontFace.load().then((_) { + html.document.fonts.add(fontFace); + // There might be paragraph measurements for this new font before it is + // loaded. They were measured using fallback font, so we should clear the + // cache. + TextMeasurementService.clearCache(); + }, onError: (dynamic exception) { + // Failures here will throw an html.DomException which confusingly + // does not implement Exception or Error. Rethrow an Exception so it can + // be caught in user code without depending on dart:html or requiring a + // catch block without "on". + throw Exception(exception.toString()); + }); + } + /// Returns a [Future] that completes when all fonts that have been /// registered with this font manager have been loaded and are ready to use. Future ensureFontsLoaded() { diff --git a/lib/web_ui/lib/src/engine/text/measurement.dart b/lib/web_ui/lib/src/engine/text/measurement.dart index 4a067d06f7a8c..43f6078d2e98c 100644 --- a/lib/web_ui/lib/src/engine/text/measurement.dart +++ b/lib/web_ui/lib/src/engine/text/measurement.dart @@ -159,10 +159,17 @@ class RulerManager { /// in [DomTextMeasurementService], or a canvas-based approach in /// [CanvasTextMeasurementService]. abstract class TextMeasurementService { + /// Whether this service uses a canvas to make the text measurements. + /// + /// If [isCanvas] is false, it indicates that this service uses DOM elements + /// to make the text measurements. + bool get isCanvas; + /// Initializes the text measurement service with a specific /// [rulerCacheCapacity] that gets passed to the [RulerManager]. static void initialize({@required int rulerCacheCapacity}) { - clearCache(); + rulerManager?.dispose(); + rulerManager = null; rulerManager = RulerManager(rulerCacheCapacity: rulerCacheCapacity); } @@ -184,7 +191,7 @@ abstract class TextMeasurementService { /// /// This is only used for testing at the moment. Once the implementation is /// complete and production-ready, we'll get rid of this flag. - static bool enableExperimentalCanvasImplementation = false; + static bool enableExperimentalCanvasImplementation = const bool.fromEnvironment('FLUTTER_WEB_USE_EXPERIMENTAL_CANVAS_TEXT', defaultValue: false); /// Gets the appropriate [TextMeasurementService] instance for the given /// [paragraph]. @@ -205,11 +212,10 @@ abstract class TextMeasurementService { return domInstance; } - /// Clears the cache of paragraph rulers. - @visibleForTesting + /// Clears the cache of paragraph rulers that are used for measuring paragraph + /// metrics. static void clearCache() { - rulerManager?.dispose(); - rulerManager = null; + rulerManager?._evictAllRulers(); } static bool _canUseCanvasMeasurement(EngineParagraph paragraph) { @@ -312,6 +318,9 @@ abstract class TextMeasurementService { /// needed for some cases that aren't yet supported in the canvas-based /// implementation such as letter-spacing, word-spacing, etc. class DomTextMeasurementService extends TextMeasurementService { + @override + final bool isCanvas = false; + /// The text measurement service singleton. static DomTextMeasurementService get instance => _instance ??= DomTextMeasurementService(); @@ -388,8 +397,11 @@ class DomTextMeasurementService extends TextMeasurementService { /// value. /// /// This method still needs to measure `minIntrinsicWidth`. - MeasurementResult _measureSingleLineParagraph(ParagraphRuler ruler, - ui.Paragraph paragraph, ui.ParagraphConstraints constraints) { + MeasurementResult _measureSingleLineParagraph( + ParagraphRuler ruler, + EngineParagraph paragraph, + ui.ParagraphConstraints constraints, + ) { final double width = constraints.width; final double minIntrinsicWidth = ruler.minIntrinsicDimensions.width; double maxIntrinsicWidth = ruler.singleLineDimensions.width; @@ -399,6 +411,23 @@ class DomTextMeasurementService extends TextMeasurementService { maxIntrinsicWidth = _applySubPixelRoundingHack(minIntrinsicWidth, maxIntrinsicWidth); final double ideographicBaseline = alphabeticBaseline * _baselineRatioHack; + + final String text = paragraph._plainText; + List lines; + if (text != null) { + final double lineWidth = maxIntrinsicWidth; + lines = [ + EngineLineMetrics.withText( + text, + startIndex: 0, + endIndex: text.length, + hardBreak: true, + width: lineWidth, + lineNumber: 0, + ), + ]; + } + return MeasurementResult( constraints.width, isSingleLine: true, @@ -410,7 +439,7 @@ class DomTextMeasurementService extends TextMeasurementService { maxIntrinsicWidth: maxIntrinsicWidth, alphabeticBaseline: alphabeticBaseline, ideographicBaseline: ideographicBaseline, - lines: null, + lines: lines, ); } @@ -492,6 +521,9 @@ class DomTextMeasurementService extends TextMeasurementService { /// This is a faster implementation than [DomTextMeasurementService] and /// provides line breaks information that can be useful for multi-line text. class CanvasTextMeasurementService extends TextMeasurementService { + @override + final bool isCanvas = true; + /// The text measurement service singleton. static CanvasTextMeasurementService get instance => _instance ??= CanvasTextMeasurementService(); @@ -614,7 +646,9 @@ double _measureSubstring( int start, int end, ) { - assert(0 <= start && start <= end && end <= text.length); + assert(0 <= start); + assert(start <= end); + assert(end <= text.length); if (start == end) { return 0; @@ -624,6 +658,7 @@ double _measureSubstring( end == _lastEnd && text == _lastText && _lastStyle == style) { + // TODO(mdebbar): Explore caching all widths in a map, not only the last one. return _lastWidth; } _lastStart = start; @@ -654,7 +689,9 @@ double _roundWidth(double width) { /// The return value is the new end of the substring after excluding the /// trailing characters. int _excludeTrailing(String text, int start, int end, CharPredicate predicate) { - assert(0 <= start && start <= end && end <= text.length); + assert(0 <= start); + assert(start <= end); + assert(end <= text.length); while (start < end && predicate(text.codeUnitAt(end - 1))) { end--; @@ -676,7 +713,7 @@ class LinesCalculator { final double _maxWidth; /// The lines that have been consumed so far. - List lines = []; + List lines = []; int _lineStart = 0; int _chunkStart = 0; @@ -703,13 +740,8 @@ class LinesCalculator { // A single chunk of text could be force-broken into multiple lines if it // doesn't fit in a single line. That's why we need a loop. while (!_reachedMaxLines) { - final double lineWidth = _measureSubstring( - _canvasContext, - _style, - _text, - _lineStart, - chunkEndWithoutSpace, - ); + final double lineWidth = + measureSubstring(_lineStart, chunkEndWithoutSpace); // The current chunk doesn't reach the maximum width, so we stop here and // wait for the next line break. @@ -731,30 +763,39 @@ class LinesCalculator { // When there's an ellipsis, truncate text to leave enough space for // the ellipsis. final double availableWidth = _maxWidth - _ellipsisWidth; - final int breakingPoint = _forceBreak( - availableWidth, - _text, - _lineStart, - chunkEndWithoutSpace, + final int breakingPoint = forceBreakSubstring( + maxWidth: availableWidth, + start: _lineStart, + end: chunkEndWithoutSpace, ); - lines.add(_text.substring(_lineStart, breakingPoint) + _style.ellipsis); + lines.add(EngineLineMetrics.withText( + _text.substring(_lineStart, breakingPoint) + _style.ellipsis, + startIndex: _lineStart, + endIndex: chunkEnd, + hardBreak: false, + width: measureSubstring(_lineStart, breakingPoint) + _ellipsisWidth, + lineNumber: lines.length, + )); } else if (isChunkTooLong) { - final int breakingPoint = - _forceBreak(_maxWidth, _text, _lineStart, chunkEndWithoutSpace); + final int breakingPoint = forceBreakSubstring( + maxWidth: _maxWidth, + start: _lineStart, + end: chunkEndWithoutSpace, + ); if (breakingPoint == chunkEndWithoutSpace) { - // We could force-break the chunk any further which means we reached + // We couldn't force-break the chunk any further which means we reached // the last character and there isn't enough space for it to fit in // its own line. Since this is the last character in the chunk, we // don't do anything here and we rely on the next iteration (or the // [isHardBreak] check below) to break the line. break; } - _addLineBreak(lineEnd: breakingPoint); + _addLineBreak(lineEnd: breakingPoint, isHardBreak: false); _chunkStart = breakingPoint; } else { // The control case of current line exceeding [_maxWidth], we break the // line. - _addLineBreak(lineEnd: _chunkStart); + _addLineBreak(lineEnd: _chunkStart, isHardBreak: false); } } @@ -763,39 +804,73 @@ class LinesCalculator { } if (isHardBreak) { - _addLineBreak(lineEnd: chunkEnd); + _addLineBreak(lineEnd: chunkEnd, isHardBreak: true); } _chunkStart = chunkEnd; } - void _addLineBreak({@required int lineEnd}) { - final int indexWithoutNewlines = _excludeTrailing( + void _addLineBreak({ + @required int lineEnd, + @required bool isHardBreak, + }) { + final int endWithoutNewlines = _excludeTrailing( _text, _lineStart, lineEnd, _newlinePredicate, ); - lines.add(_text.substring(_lineStart, indexWithoutNewlines)); + final int endWithoutSpace = _excludeTrailing( + _text, + _lineStart, + endWithoutNewlines, + _whitespacePredicate, + ); + final int lineNumber = lines.length; + final EngineLineMetrics metrics = EngineLineMetrics.withText( + _text.substring(_lineStart, endWithoutNewlines), + startIndex: _lineStart, + endIndex: lineEnd, + hardBreak: isHardBreak, + width: measureSubstring(_lineStart, endWithoutSpace), + lineNumber: lineNumber, + ); + lines.add(metrics); _lineStart = lineEnd; if (lines.length == _style.maxLines) { _reachedMaxLines = true; } } + /// Measures the width of a substring of [_text] starting from the index + /// [start] (inclusive) to [end] (exclusive). + /// + /// This method uses [_text], [_style] and [_canvasContext] to perform the + /// measurement. + double measureSubstring(int start, int end) { + return _measureSubstring(_canvasContext, _style, _text, start, end); + } + /// In a continuous block of text, finds the point where text can be broken to /// fit in the given constraint [maxWidth]. /// /// This always returns at least one character even if there isn't enough /// space for it. - int _forceBreak(double maxWidth, String text, int start, int end) { - assert(0 <= start && start < end && end <= text.length); + int forceBreakSubstring({ + @required double maxWidth, + @required int start, + @required int end, + }) { + assert(0 <= start); + assert(start < end); + assert(end <= _text.length); + // When there's no ellipsis, the breaking point should be at least one + // character away from [start]. int low = hasEllipsis ? start : start + 1; int high = end; do { final int mid = (low + high) ~/ 2; - final double width = - _measureSubstring(_canvasContext, _style, text, start, mid); + final double width = measureSubstring(start, mid); if (width < maxWidth) { low = mid; } else if (width > maxWidth) { @@ -805,7 +880,6 @@ class LinesCalculator { } } while (high - low > 1); - // The breaking point should be at least one character away from [start]. return low; } } diff --git a/lib/web_ui/lib/src/engine/text/paragraph.dart b/lib/web_ui/lib/src/engine/text/paragraph.dart index 3214fb897d0df..f9ea3e2140b6b 100644 --- a/lib/web_ui/lib/src/engine/text/paragraph.dart +++ b/lib/web_ui/lib/src/engine/text/paragraph.dart @@ -4,6 +4,119 @@ part of engine; +class EngineLineMetrics implements ui.LineMetrics { + EngineLineMetrics({ + this.hardBreak, + this.ascent, + this.descent, + this.unscaledAscent, + this.height, + this.width, + this.left, + this.baseline, + this.lineNumber, + }) : text = null, + startIndex = -1, + endIndex = -1; + + EngineLineMetrics.withText( + this.text, { + @required this.startIndex, + @required this.endIndex, + @required this.hardBreak, + this.ascent, + this.descent, + this.unscaledAscent, + this.height, + @required this.width, + this.left, + this.baseline, + @required this.lineNumber, + }) : assert(text != null), + assert(hardBreak != null), + assert(width != null), + assert(lineNumber != null && lineNumber >= 0); + + /// The textual content representing this line. + final String text; + + /// The index (inclusive) in the text where this line begins. + final int startIndex; + + /// The index (exclusive) in the text where this line ends. + /// + /// When the line contains an overflow, then [endIndex] goes until the end of + /// the text and doesn't stop at the overflow cutoff. + final int endIndex; + + @override + final bool hardBreak; + + @override + final double ascent; + + @override + final double descent; + + @override + final double unscaledAscent; + + @override + final double height; + + @override + final double width; + + @override + final double left; + + @override + final double baseline; + + @override + final int lineNumber; + + @override + int get hashCode => ui.hashValues( + text, + startIndex, + endIndex, + hardBreak, + ascent, + descent, + unscaledAscent, + height, + width, + left, + baseline, + lineNumber, + ); + + @override + bool operator ==(dynamic other) { + if (identical(this, other)) { + return true; + } + + if (other.runtimeType != runtimeType) { + return false; + } + final EngineLineMetrics typedOther = other; + return text == typedOther.text && + startIndex == typedOther.startIndex && + endIndex == typedOther.endIndex && + hardBreak == typedOther.hardBreak && + ascent == typedOther.ascent && + descent == typedOther.descent && + unscaledAscent == typedOther.unscaledAscent && + height == typedOther.height && + width == typedOther.width && + left == typedOther.left && + baseline == typedOther.baseline && + lineNumber == typedOther.lineNumber; + } +} + /// The web implementation of [ui.Paragraph]. class EngineParagraph implements ui.Paragraph { /// This class is created by the engine, and should not be instantiated @@ -31,10 +144,10 @@ class EngineParagraph implements ui.Paragraph { final html.HtmlElement _paragraphElement; final ParagraphGeometricStyle _geometricStyle; final String _plainText; - final ui.Paint _paint; + final SurfacePaint _paint; final ui.TextAlign _textAlign; final ui.TextDirection _textDirection; - final ui.Paint _background; + final SurfacePaint _background; @visibleForTesting String get plainText => _plainText; @@ -72,9 +185,28 @@ class EngineParagraph implements ui.Paragraph { /// Valid only after [layout] has been called. double get _lineHeight => _measurementResult?.lineHeight ?? 0; - // TODO(flutter_web): see https://github.com/flutter/flutter/issues/33613. @override - double get longestLine => 0; + double get longestLine { + if (_measurementResult.lines != null) { + double maxWidth = 0.0; + for (ui.LineMetrics metrics in _measurementResult.lines) { + if (maxWidth < metrics.width) { + maxWidth = metrics.width; + } + } + return maxWidth; + } + + // In the single-line case, the longest line is equal to the maximum + // intrinsic width of the paragraph. + if (_measurementResult.isSingleLine) { + return _measurementResult.maxIntrinsicWidth; + } + + // If we don't have any line metrics information, there's no way to know the + // longest line in a multi-line paragraph. + return 0.0; + } @override double get minIntrinsicWidth => _measurementResult?.minIntrinsicWidth ?? 0; @@ -99,10 +231,6 @@ class EngineParagraph implements ui.Paragraph { /// directly into a canvas without css text alignment styling. double _alignOffset = 0.0; - /// If not null, this list would contain the strings representing each line - /// in the paragraph. - List get _lines => _measurementResult?.lines; - @override void layout(ui.ParagraphConstraints constraints) { if (constraints == _lastUsedConstraints) { @@ -159,18 +287,21 @@ class EngineParagraph implements ui.Paragraph { /// - Paragraphs that have a non-null word-spacing. /// - Paragraphs with a background. bool get _drawOnCanvas { + if (_measurementResult.lines == null) { + return false; + } + bool canDrawTextOnCanvas; - if (TextMeasurementService.enableExperimentalCanvasImplementation) { - canDrawTextOnCanvas = _lines != null; + if (_measurementService.isCanvas) { + canDrawTextOnCanvas = true; } else { - canDrawTextOnCanvas = _measurementResult.isSingleLine && - _plainText != null && - _geometricStyle.ellipsis == null; + canDrawTextOnCanvas = _geometricStyle.ellipsis == null; } return canDrawTextOnCanvas && _geometricStyle.decoration == null && - _geometricStyle.wordSpacing == null; + _geometricStyle.wordSpacing == null && + _geometricStyle.shadows == null; } /// Whether this paragraph has been laid out. @@ -201,7 +332,7 @@ class EngineParagraph implements ui.Paragraph { }) { assert(boxHeightStyle != null); assert(boxWidthStyle != null); - if (_plainText == null) { + if (_plainText == null || start == end) { return []; } @@ -281,20 +412,37 @@ class EngineParagraph implements ui.Paragraph { } @override - List getWordBoundary(int offset) { + ui.TextRange getWordBoundary(ui.TextPosition position) { + ui.TextPosition textPosition = position; if (_plainText == null) { - return [offset, offset]; + return ui.TextRange(start: textPosition.offset, end: textPosition.offset); } - final int start = WordBreaker.prevBreakIndex(_plainText, offset); - final int end = WordBreaker.nextBreakIndex(_plainText, offset); - return [start, end]; + final int start = + WordBreaker.prevBreakIndex(_plainText, textPosition.offset); + final int end = WordBreaker.nextBreakIndex(_plainText, textPosition.offset); + return ui.TextRange(start: start, end: end); + } + + @override + ui.TextRange getLineBoundary(ui.TextPosition position) { + final List lines = _measurementResult.lines; + if (lines != null) { + final int offset = position.offset; + + for (int i = 0; i < lines.length; i++) { + final EngineLineMetrics line = lines[i]; + if (offset >= line.startIndex && offset < line.endIndex) { + return ui.TextRange(start: line.startIndex, end: line.endIndex); + } + } + } + return ui.TextRange.empty; } @override List computeLineMetrics() { - // TODO(flutter_web): https://github.com/flutter/flutter/issues/39537 - return null; + return _measurementResult.lines; } } @@ -353,7 +501,11 @@ class EngineParagraphStyle implements ui.ParagraphStyle { } double get _lineHeight { - if (_strutStyle == null || _strutStyle._height == null) { + // TODO(mdebbar): Implement proper support for strut styles. + // https://github.com/flutter/flutter/issues/32243 + if (_strutStyle == null || + _strutStyle._height == null || + _strutStyle._height == 0) { // When there's no strut height, always use paragraph style height. return _height; } @@ -790,6 +942,7 @@ class EngineParagraphBuilder implements ui.ParagraphBuilder { ui.Locale locale = _paragraphStyle._locale; ui.Paint background; ui.Paint foreground; + List shadows; int i = 0; @@ -844,6 +997,9 @@ class EngineParagraphBuilder implements ui.ParagraphBuilder { if (style._foreground != null) { foreground = style._foreground; } + if (style._shadows != null) { + shadows = style._shadows; + } i++; } @@ -863,6 +1019,7 @@ class EngineParagraphBuilder implements ui.ParagraphBuilder { locale: locale, background: background, foreground: foreground, + shadows: shadows, ); ui.Paint paint; @@ -892,6 +1049,7 @@ class EngineParagraphBuilder implements ui.ParagraphBuilder { wordSpacing: wordSpacing, decoration: _textDecorationToCssString(decoration, decorationStyle), ellipsis: _paragraphStyle._ellipsis, + shadows: shadows, ), plainText: '', paint: paint, @@ -945,6 +1103,7 @@ class EngineParagraphBuilder implements ui.ParagraphBuilder { wordSpacing: wordSpacing, decoration: _textDecorationToCssString(decoration, decorationStyle), ellipsis: _paragraphStyle._ellipsis, + shadows: shadows, ), plainText: plainText, paint: paint, @@ -1072,14 +1231,14 @@ void _applyParagraphStyleToElement({ style._fontStyle == ui.FontStyle.normal ? 'normal' : 'italic'; } if (style._effectiveFontFamily != null) { - cssStyle.fontFamily = quoteFontFamily(style._effectiveFontFamily); + cssStyle.fontFamily = canonicalizeFontFamily(style._effectiveFontFamily); } } else { if (style._textAlign != previousStyle._textAlign) { cssStyle.textAlign = textAlignToCssValue( style._textAlign, style._textDirection ?? ui.TextDirection.ltr); } - if (style._lineHeight != style._lineHeight) { + if (style._lineHeight != previousStyle._lineHeight) { cssStyle.lineHeight = '${style._lineHeight}'; } if (style._textDirection != previousStyle._textDirection) { @@ -1098,7 +1257,7 @@ void _applyParagraphStyleToElement({ : null; } if (style._fontFamily != previousStyle._fontFamily) { - cssStyle.fontFamily = quoteFontFamily(style._fontFamily); + cssStyle.fontFamily = canonicalizeFontFamily(style._fontFamily); } } } @@ -1138,11 +1297,12 @@ void _applyTextStyleToElement({ // consistently use Ahem font. if (isSpan && !ui.debugEmulateFlutterTesterEnvironment) { if (style._fontFamily != null) { - cssStyle.fontFamily = quoteFontFamily(style._fontFamily); + cssStyle.fontFamily = canonicalizeFontFamily(style._fontFamily); } } else { if (style._effectiveFontFamily != null) { - cssStyle.fontFamily = quoteFontFamily(style._effectiveFontFamily); + cssStyle.fontFamily = + canonicalizeFontFamily(style._effectiveFontFamily); } } if (style._letterSpacing != null) { @@ -1154,6 +1314,9 @@ void _applyTextStyleToElement({ if (style._decoration != null) { updateDecoration = true; } + if (style._shadows != null) { + cssStyle.textShadow = _shadowListToCss(style._shadows); + } } else { if (style._color != previousStyle._color || style._foreground != previousStyle._foreground) { @@ -1176,7 +1339,7 @@ void _applyTextStyleToElement({ : null; } if (style._fontFamily != previousStyle._fontFamily) { - cssStyle.fontFamily = quoteFontFamily(style._fontFamily); + cssStyle.fontFamily = canonicalizeFontFamily(style._fontFamily); } if (style._letterSpacing != previousStyle._letterSpacing) { cssStyle.letterSpacing = '${style._letterSpacing}px'; @@ -1189,6 +1352,9 @@ void _applyTextStyleToElement({ style._decorationColor != previousStyle._decorationColor) { updateDecoration = true; } + if (style._shadows != previousStyle._shadows) { + cssStyle.textShadow = _shadowListToCss(style._shadows); + } } if (updateDecoration) { @@ -1206,6 +1372,27 @@ void _applyTextStyleToElement({ } } +String _shadowListToCss(List shadows) { + if (shadows.isEmpty) { + return ''; + } + // CSS text-shadow is a comma separated list of shadows. + // . + // Shadows are applied front-to-back with first shadow on top. + // Color is optional. offsetx,y are required. blur-radius is optional as well + // and defaults to 0. + StringBuffer sb = new StringBuffer(); + for (int i = 0, len = shadows.length; i < len; i++) { + if (i != 0) { + sb.write(','); + } + ui.Shadow shadow = shadows[i]; + sb.write('${shadow.offset.dx}px ${shadow.offset.dy}px ' + '${shadow.blurRadius}px ${shadow.color.toCssString()}'); + } + return sb.toString(); +} + /// Applies background color properties in text style to paragraph or span /// elements. void _applyTextBackgroundToElement({ diff --git a/lib/web_ui/lib/src/engine/text/ruler.dart b/lib/web_ui/lib/src/engine/text/ruler.dart index 41cbdf0dcb78f..540077601ef34 100644 --- a/lib/web_ui/lib/src/engine/text/ruler.dart +++ b/lib/web_ui/lib/src/engine/text/ruler.dart @@ -17,6 +17,7 @@ class ParagraphGeometricStyle { this.wordSpacing, this.decoration, this.ellipsis, + this.shadows, }); final ui.FontWeight fontWeight; @@ -29,6 +30,7 @@ class ParagraphGeometricStyle { final double wordSpacing; final String decoration; final String ellipsis; + final List shadows; // Since all fields above are primitives, cache hashcode since ruler lookups // use this style as key. @@ -86,7 +88,7 @@ class ParagraphGeometricStyle { result.write(DomRenderer.defaultFontSize); } result.write(' '); - result.write(quoteFontFamily(effectiveFontFamily)); + result.write(canonicalizeFontFamily(effectiveFontFamily)); return result.toString(); } @@ -203,14 +205,30 @@ class TextDimensions { // Rich text: deeply copy contents. This is the slow case that should be // avoided if fast layout performance is desired. final html.Element copy = from._paragraphElement.clone(true); - _element.children.addAll(copy.children); + _element.nodes.addAll(copy.nodes); } } /// Updated element style width. - void updateWidth(String cssWidth) { + void updateConstraintWidth(double width, String ellipsis) { _invalidateBoundsCache(); - _element.style.width = cssWidth; + + if (width.isInfinite) { + _element.style + ..width = null + ..whiteSpace = 'pre'; + } else if (ellipsis != null) { + // Width is finite, but we don't want to let the text soft-wrap when + // ellipsis overflow is enabled. + _element.style + ..width = '${width}px' + ..whiteSpace = 'pre'; + } else { + // Width is finite and there's no ellipsis overflow. + _element.style + ..width = '${width}px' + ..whiteSpace = 'pre-wrap'; + } } void _invalidateBoundsCache() { @@ -227,7 +245,7 @@ class TextDimensions { void applyStyle(ParagraphGeometricStyle style) { _element.style ..fontSize = style.fontSize != null ? '${style.fontSize.floor()}px' : null - ..fontFamily = quoteFontFamily(style.effectiveFontFamily) + ..fontFamily = canonicalizeFontFamily(style.effectiveFontFamily) ..fontWeight = style.fontWeight != null ? fontWeightToCss(style.fontWeight) : null ..fontStyle = style.fontStyle != null @@ -403,6 +421,10 @@ class ParagraphRuler { ..border = '0' ..padding = '0'; + if (assertionsEnabled) { + _singleLineHost.setAttribute('data-ruler', 'single-line'); + } + singleLineDimensions.applyStyle(style); // Force single-line (even if wider than screen) and preserve whitespaces. @@ -425,6 +447,10 @@ class ParagraphRuler { ..border = '0' ..padding = '0'; + if (assertionsEnabled) { + _minIntrinsicHost.setAttribute('data-ruler', 'min-intrinsic'); + } + minIntrinsicDimensions.applyStyle(style); // "flex: 0" causes the paragraph element to shrink horizontally, exposing @@ -454,6 +480,10 @@ class ParagraphRuler { ..border = '0' ..padding = '0'; + if (assertionsEnabled) { + _constrainedHost.setAttribute('data-ruler', 'constrained'); + } + constrainedDimensions.applyStyle(style); final html.CssStyleDeclaration elementStyle = constrainedDimensions._element.style; @@ -461,17 +491,8 @@ class ParagraphRuler { ..display = 'block' ..overflowWrap = 'break-word'; - // TODO(flutter_web): Implement the ellipsis overflow for multi-line text - // too. As a pre-requisite, we need to be able to programmatically find - // line breaks. - if (style.ellipsis == null) { - elementStyle.whiteSpace = 'pre-wrap'; - } else { - // The height measurement is affected by whether the text has the ellipsis - // overflow property or not. This is because when ellipsis is set, we may - // not render all the lines, but stop at the first line that overflows. + if (style.ellipsis != null) { elementStyle - ..whiteSpace = 'pre' ..overflow = 'hidden' ..textOverflow = 'ellipsis'; } @@ -493,6 +514,10 @@ class ParagraphRuler { ..border = '0' ..padding = '0'; + if (assertionsEnabled) { + _lineHeightHost.setAttribute('data-ruler', 'line-height'); + } + lineHeightDimensions.applyStyle(style); // Force single-line (even if wider than screen) and preserve whitespaces. @@ -577,7 +602,10 @@ class ParagraphRuler { // The extra 0.5 is because sometimes the browser needs slightly more space // than the size it reports back. When that happens the text may be wrap // when we thought it didn't. - constrainedDimensions.updateWidth('${constraints.width + 0.5}px'); + constrainedDimensions.updateConstraintWidth( + constraints.width + 0.5, + style.ellipsis, + ); } /// Returns text position in a paragraph that contains multiple @@ -601,30 +629,45 @@ class ParagraphRuler { final double dx = offset.dx; final double dy = offset.dy; if (dx >= bounds.left && - dy < bounds.right && + dx < bounds.right && dy >= bounds.top && dy < bounds.bottom) { // We found the element bounds that contains offset. // Calculate text position for this node. - int textPosition = 0; - for (int nodeIndex = 0; nodeIndex < i; nodeIndex++) { - textPosition += textNodes[nodeIndex].text.length; - } - return textPosition; + return _countTextPosition(el.childNodes, textNodes[i]); } } return 0; } void _collectTextNodes(Iterable nodes, List textNodes) { + if (nodes.isEmpty) { + return; + } + final List childNodes = []; for (html.Node node in nodes) { if (node.nodeType == html.Node.TEXT_NODE) { textNodes.add(node); } - if (node.hasChildNodes()) { - _collectTextNodes(node.childNodes, textNodes); + childNodes.addAll(node.childNodes); + } + _collectTextNodes(childNodes, textNodes); + } + + int _countTextPosition(List nodes, html.Node endNode) { + int position = 0; + final List stack = nodes.reversed.toList(); + while (true) { + var node = stack.removeLast(); + stack.addAll(node.childNodes.reversed); + if (node == endNode) { + break; + } + if (node.nodeType == html.Node.TEXT_NODE) { + position += node.text.length; } } + return position; } /// Performs clean-up after a measurement is done, preparing this ruler for @@ -683,7 +726,7 @@ class ParagraphRuler { ..appendText(before) ..append(rangeSpan) ..appendText(after); - constrainedDimensions.updateWidth('${constraints.width}px'); + constrainedDimensions.updateConstraintWidth(constraints.width, null); // Measure the rects of [rangeSpan]. final List> clientRects = rangeSpan.getClientRects(); @@ -765,7 +808,7 @@ class ParagraphRuler { return null; } final List constraintCache = - _measurementCache[plainText]; + _measurementCache[plainText]; if (constraintCache == null) { return null; } @@ -820,9 +863,9 @@ class MeasurementResult { /// {@macro dart.ui.paragraph.ideographicBaseline} final double ideographicBaseline; - /// Substrings that represent how the text should wrap into multiple lines to - /// satisfy [constraintWidth], - final List lines; + /// The full list of [EngineLineMetrics] that describe in detail the various metrics + /// of each laid out line. + final List lines; const MeasurementResult( this.constraintWidth, { diff --git a/lib/web_ui/lib/src/engine/text_editing/input_type.dart b/lib/web_ui/lib/src/engine/text_editing/input_type.dart index 7df6b2cbb4151..d7620c2f62584 100644 --- a/lib/web_ui/lib/src/engine/text_editing/input_type.dart +++ b/lib/web_ui/lib/src/engine/text_editing/input_type.dart @@ -59,11 +59,14 @@ abstract class EngineInputType { /// . String get inputmodeAttribute; + /// Whether this input type allows the "Enter" key to submit the input action. + bool get submitActionOnEnter => true; + /// Create the appropriate DOM element for this input type. html.HtmlElement createDomElement() => html.InputElement(); /// Given a [domElement], set attributes that are specific to this input type. - void configureDomElement(html.HtmlElement domElement) { + void configureInputMode(html.HtmlElement domElement) { if (inputmodeAttribute == null) { return; } @@ -124,6 +127,9 @@ class MultilineInputType extends EngineInputType { @override final String inputmodeAttribute = null; + @override + bool get submitActionOnEnter => false; + @override html.HtmlElement createDomElement() => html.TextAreaElement(); } 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 17fb8c3eda4d3..4ba710a7b9cf3 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 @@ -17,6 +17,8 @@ void _emptyCallback(dynamic _) {} /// /// They are assigned once during the creation of the DOM element. void _setStaticStyleAttributes(html.HtmlElement domElement) { + domElement.classes.add(HybridTextEditing.textEditingClass); + final html.CssStyleDeclaration elementStyle = domElement.style; elementStyle ..whiteSpace = 'pre-wrap' @@ -46,6 +48,7 @@ void _setStaticStyleAttributes(html.HtmlElement domElement) { } /// The current text and selection state of a text field. +@visibleForTesting class EditingState { EditingState({this.text, this.baseOffset = 0, this.extentOffset = 0}); @@ -68,7 +71,7 @@ class EditingState { /// Flutter Framework can send the [selectionBase] and [selectionExtent] as /// -1, if so 0 assigned to the [baseOffset] and [extentOffset]. -1 is not a /// valid selection range for input DOM elements. - factory EditingState.fromFlutter(Map flutterEditingState) { + factory EditingState.fromFrameworkMessage(Map flutterEditingState) { final int selectionBase = flutterEditingState['selectionBase']; final int selectionExtent = flutterEditingState['selectionExtent']; final String text = flutterEditingState['text']; @@ -102,7 +105,7 @@ class EditingState { } } - /// The counterpart of [EditingState.fromFlutter]. It generates a Map that + /// The counterpart of [EditingState.fromFrameworkMessage]. It generates a Map that /// can be sent to Flutter. // TODO(mdebbar): Should we get `selectionAffinity` and other properties from flutter's editing state? Map toFlutter() => { @@ -177,13 +180,15 @@ class InputConfiguration { @required this.inputType, @required this.inputAction, @required this.obscureText, + @required this.autocorrect, }); - InputConfiguration.fromFlutter(Map flutterInputConfiguration) + InputConfiguration.fromFrameworkMessage(Map flutterInputConfiguration) : inputType = EngineInputType.fromName( flutterInputConfiguration['inputType']['name']), inputAction = flutterInputConfiguration['inputAction'], - obscureText = flutterInputConfiguration['obscureText']; + obscureText = flutterInputConfiguration['obscureText'], + autocorrect = flutterInputConfiguration['autocorrect']; /// The type of information being edited in the input control. final EngineInputType inputType; @@ -193,42 +198,109 @@ class InputConfiguration { /// Whether to hide the text being edited. final bool obscureText; + + /// Whether to enable autocorrection. + /// + /// Definition of autocorrect can be found in: + /// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input + /// + /// For future manual tests, note that autocorrect is an attribute only + /// supported by Safari. + final bool autocorrect; } typedef _OnChangeCallback = void Function(EditingState editingState); typedef _OnActionCallback = void Function(String inputAction); -/// Wraps the DOM element used to provide text editing capabilities. +/// Provides HTML DOM functionality for editable text. /// -/// The backing DOM element could be one of: -/// -/// 1. ``. -/// 2. `