diff --git a/.ci.yaml b/.ci.yaml index fd2435e6c525d..1d894ed4deb55 100644 --- a/.ci.yaml +++ b/.ci.yaml @@ -118,7 +118,7 @@ targets: - DEPS - .ci.yaml - testing/** - - shell/platforms/android/** + - shell/platform/android/** - name: Linux Benchmarks enabled_branches: @@ -294,6 +294,14 @@ targets: jazzy_version: "0.14.1" timeout: 75 + - name: Mac mac_android_aot_engine + recipe: engine_v2/engine_v2 + bringup: true + timeout: 60 + properties: + config_name: mac_android_aot_engine + environment: Staging + - name: Mac mac_host_engine recipe: engine_v2/engine_v2 bringup: true diff --git a/.github/workflows/scorecards-analysis.yml b/.github/workflows/scorecards-analysis.yml index 805b13aa22022..01fcff07b68cd 100644 --- a/.github/workflows/scorecards-analysis.yml +++ b/.github/workflows/scorecards-analysis.yml @@ -26,7 +26,7 @@ jobs: persist-credentials: false - name: setup python - uses: actions/setup-python@c4e89fac7e8767b327bbad6cb4d859eda999cf08 + uses: actions/setup-python@b55428b1882923874294fa556849718a1d7f2ca5 with: python-version: '3.7.7' # install the python version needed @@ -57,6 +57,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@3e7e3b32d0fb8283594bb0a76cc60a00918b0969 + uses: github/codeql-action/upload-sarif@f5d217be74900c6ac8fbbe53f3c10376ba4e64da with: sarif_file: results.sarif diff --git a/BUILD.gn b/BUILD.gn index a4dd6ca59ce58..c775e52733b2e 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -84,9 +84,13 @@ group("flutter") { "//flutter/examples/glfw", "//flutter/examples/vulkan_glfw", ] + + if (!is_mac) { + public_deps += [ "//flutter/examples/glfw_drm" ] + } } - # If enbaled, compile the SDK / snapshot. + # If enabled, compile the SDK / snapshot. if (!is_fuchsia) { public_deps += [ "//flutter/lib/snapshot:generate_snapshot_bins" ] @@ -100,6 +104,9 @@ group("flutter") { # gen_snapshot for the host and not the target. "//third_party/dart/runtime/bin:gen_snapshot", + # Built alongside gen_snapshot for 64 bit targets + "//third_party/dart/runtime/bin:analyze_snapshot", + # Impeller artifacts - compiler and libtessellator "//flutter/impeller/compiler:impellerc", "//flutter/impeller/tessellator:tessellator_shared", diff --git a/DEPS b/DEPS index 615876c8930d0..bb5dbe816c051 100644 --- a/DEPS +++ b/DEPS @@ -18,7 +18,7 @@ vars = { 'llvm_git': 'https://llvm.googlesource.com', # OCMock is for testing only so there is no google clone 'ocmock_git': 'https://github.com/erikdoe/ocmock.git', - 'skia_revision': '3a6550ab89657cbe4c977589cc8c03fe7aad65cc', + 'skia_revision': 'dee2a6ebfcdfff981b8b0a520c23220ab5673089', # WARNING: DO NOT EDIT canvaskit_cipd_instance MANUALLY # See `lib/web_ui/README.md` for how to roll CanvasKit to a new version. @@ -36,7 +36,7 @@ vars = { # Dart is: https://github.com/dart-lang/sdk/blob/main/DEPS. # You can use //tools/dart/create_updated_flutter_deps.py to produce # updated revision list of existing dependencies. - 'dart_revision': '03c6091964b59b587e0cfa5ee822a1aa5947b8b8', + 'dart_revision': '268aa39b1999072555e094059295c5a212152c27', # WARNING: DO NOT EDIT MANUALLY # The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py @@ -46,12 +46,12 @@ vars = { 'dart_clock_rev': '2507a228773c5e877fc9e3330080b234aad965c0', 'dart_collection_rev': '414ffa1bc8ba18bd608bbf916d95715311d89ac1', 'dart_devtools_rev': 'd131d19091f6b89ac89486bd92440a25a523e8b0', - 'dart_protobuf_rev': '9aad6aadcc0fc616051c7e0eaef78c26b3dd7b60', - 'dart_pub_rev': '9bf4289d6fd5d6872a8929d6312bbd7098f3ea9c', + 'dart_protobuf_rev': '8337a7910fef805214e5aae35a98abd481d87a18', + 'dart_pub_rev': 'ac7db6c07318efa4a8712110275eaf70f96a6d00', 'dart_root_certificates_rev': '692f6d6488af68e0121317a9c2c9eb393eb0ee50', 'dart_watcher_rev': 'e00c0ea769e32821d91c0880da8eb736839a6e6d', - 'dart_webdev_rev': '27cc5c9228ca59e721bc41fe7028e0fd6b995748', - 'dart_webkit_inspection_protocol_rev': '57522d6b29d94903b765c757079d906555d5a171', + 'dart_webdev_rev': 'a82a5a900e54584c53cdc0853e6ecf952f5c1d72', + 'dart_webkit_inspection_protocol_rev': '57ebca4b97310a10774c59c0fb056d8f23bd5cee', 'dart_yaml_edit_rev': '01589b3ce447b03aed991db49f1ec6445ad5476d', 'dart_zlib_rev': '27c2f474b71d0d20764f86f60ef8b00da1a16cda', @@ -61,7 +61,7 @@ vars = { 'download_dart_sdk': True, # Checkout Android dependencies only on platforms where we build for Android targets. - 'download_android_deps': 'host_os == "mac" or host_os == "linux"', + 'download_android_deps': 'host_os == "mac" or (host_os == "linux" and host_cpu == "x64")', # Checkout Windows dependencies only if we are building on Windows. 'download_windows_deps' : 'host_os == "win"', @@ -102,7 +102,7 @@ allowed_hosts = [ ] deps = { - 'src': 'https://github.com/flutter/buildroot.git' + '@' + 'cd444824e34d8f735a351f680776bc0479f20677', + 'src': 'https://github.com/flutter/buildroot.git' + '@' + '25e5fd0200ff0bbf4761f3e73cab67a16e928955', # Fuchsia compatibility # @@ -167,7 +167,7 @@ deps = { {'packages': [{'version': 'git_revision:d131d19091f6b89ac89486bd92440a25a523e8b0', 'package': 'dart/third_party/flutter/devtools'}], 'dep_type': 'cipd'}, 'src/third_party/dart/third_party/pkg/args': - Var('dart_git') + '/args.git@73e8d3b55cbedc9765f8e266f3422d8914f8e62a', + Var('dart_git') + '/args.git@80d4abbd6b38b79bcdbc411b4b517628c7611232', 'src/third_party/dart/third_party/pkg/async': Var('dart_git') + '/async.git@f3ed5f690e2ec9dbe1bfc5184705575b4f6480e5', @@ -194,22 +194,22 @@ deps = { Var('dart_git') + '/convert.git@7145da14f9cd730e80fb4c6a10108fcfd205e8e7', 'src/third_party/dart/third_party/pkg/crypto': - Var('dart_git') + '/crypto.git@223e0a62c0f762fd2b510f753861445b52e14fc3', + Var('dart_git') + '/crypto.git@7cf89d35b3d90786d9f7f75211b3b3cd7e4d173f', 'src/third_party/dart/third_party/pkg/csslib': Var('dart_git') + '/csslib.git@ba2eb2d80530eedefadaade338a09c2dd60410f3', 'src/third_party/dart/third_party/pkg/dart_style': - Var('dart_git') + '/dart_style.git@d7b73536a8079331c888b7da539b80e6825270ea', + Var('dart_git') + '/dart_style.git@49bc3ff32b5578b6e19f8fd376d668130941ee29', 'src/third_party/dart/third_party/pkg/dartdoc': - Var('dart_git') + '/dartdoc.git@d7513b2ee0a9bad6c9526f6b0ba970b4fcca17d0', + Var('dart_git') + '/dartdoc.git@b3d4aa002d36c28b0af114abd0997b8521c27b11', 'src/third_party/dart/third_party/pkg/ffi': - Var('dart_git') + '/ffi.git@18b2b549d55009ff594600b04705ff6161681e07', + Var('dart_git') + '/ffi.git@fb5f2667826c0900e551d19101052f84e35f41bf', 'src/third_party/dart/third_party/pkg/file': - Var('dart_git') + '/external/github.com/google/file.dart@0132eeedea2933513bf230513a766a8baeab0c4f', + Var('dart_git') + '/external/github.com/google/file.dart@b2e31cb6ef40b223701dbfa0b907fe58468484d7', 'src/third_party/dart/third_party/pkg/fixnum': Var('dart_git') + '/fixnum.git@e0b17cc1f639c55a9c24947392c64b5a68992535', @@ -221,19 +221,19 @@ deps = { Var('dart_git') + '/html.git@8243e967caad9932c13971af3b2a7c8f028383d5', 'src/third_party/dart/third_party/pkg/http': - Var('dart_git') + '/http.git@5055b684ae45fb141a106ef6ced988aa37ed0ea6', + Var('dart_git') + '/http.git@b0585323f586d56359a8329cc532ad0ee7cbee91', 'src/third_party/dart/third_party/pkg/http_multi_server': Var('dart_git') + '/http_multi_server.git@20bf079c8955d1250a45afb9cb096472a724a551', 'src/third_party/dart/third_party/pkg/http_parser': - Var('dart_git') + '/http_parser.git@d25b3c9e7f23e31ac388a03361737110768597f6', + Var('dart_git') + '/http_parser.git@b968f7ddde0588273a6cbd1d2eb6569f418606ac', 'src/third_party/dart/third_party/pkg/json_rpc_2': Var('dart_git') + '/json_rpc_2.git@805e6536dd961d66f6b8cd46d8f3e61774f957c9', 'src/third_party/dart/third_party/pkg/linter': - Var('dart_git') + '/linter.git@b4afc10055f3009478da1eac1827f9fef42c3759', + Var('dart_git') + '/linter.git@304042aaa933a02d2581ab9598dd2ddaebdbc803', 'src/third_party/dart/third_party/pkg/logging': Var('dart_git') + '/logging.git@d10e24844c2e01d3f6d2b5a1a2bb8717359c6a87', @@ -248,7 +248,7 @@ deps = { Var('dart_git') + '/mime.git@0a75a41445eb642674a0a271eecde78cb025ee60', 'src/third_party/dart/third_party/pkg/mockito': - Var('dart_git') + '/mockito.git@d8a2ddd2054390bd03d34bf64c940884e6f7a596', + Var('dart_git') + '/mockito.git@2acf22f4d400c6e1eee0f6ca595092220fba8b34', 'src/third_party/dart/third_party/pkg/oauth2': Var('dart_git') + '/oauth2.git@199ebf15cbd5b07958438184f32e41c4447a57bf', @@ -269,10 +269,10 @@ deps = { Var('dart_git') + '/pub.git' + '@' + Var('dart_pub_rev'), 'src/third_party/dart/third_party/pkg/pub_semver': - Var('dart_git') + '/pub_semver.git@5c0b4bfd5ca57fe16f1319c581dc8c882e9b8cb2', + Var('dart_git') + '/pub_semver.git@9fd28757ba45961ac5449e0f2b0020670e921475', 'src/third_party/dart/third_party/pkg/shelf': - Var('dart_git') + '/shelf.git@0371a64bd3b99044ee3158bacf8813bba735a9c7', + Var('dart_git') + '/shelf.git@0965d864d0e6c66d5bb6daece400e80484640bd5', 'src/third_party/dart/third_party/pkg/source_map_stack_trace': Var('dart_git') + '/source_map_stack_trace.git@72dbf21a33293b2b8434d0a9751e36f9463981ac', @@ -299,13 +299,13 @@ deps = { Var('dart_git') + '/term_glyph.git@ec7cf7bb51ebb7d55760a1359f6697690dbc06ba', 'src/third_party/dart/third_party/pkg/test': - Var('dart_git') + '/test.git@b144a336776eaa1f9420f3ab38214d8c061c663e', + Var('dart_git') + '/test.git@5f52c524cd10ba3dd9aa5614955c338d50b29d21', 'src/third_party/dart/third_party/pkg/test_reflective_loader': Var('dart_git') + '/test_reflective_loader.git@8d0de01bbe852fea1f8e33aba907abcba50a8a1e', 'src/third_party/dart/third_party/pkg/typed_data': - Var('dart_git') + '/typed_data.git@bb10b64f9a56b8fb49307d4465474bf1c1309f6d', + Var('dart_git') + '/typed_data.git@6369490ede1c87a4a5758304a606a6e4eee364b9', 'src/third_party/dart/third_party/pkg/usage': Var('dart_git') + '/usage.git@e287a72228974886d8a3b40ddcdf12f69d7c6a22', @@ -314,7 +314,7 @@ deps = { Var('dart_git') + '/watcher.git' + '@' + Var('dart_watcher_rev'), 'src/third_party/dart/third_party/pkg/web_socket_channel': - Var('dart_git') + '/web_socket_channel.git@99dbdc5769e19b9eeaf69449a59079153c6a8b1f', + Var('dart_git') + '/web_socket_channel.git@4b46c0c4196a5e76c2b0e2589ed37de247d35938', 'src/third_party/dart/third_party/pkg/webdev': Var('dart_git') + '/webdev.git' + '@' + Var('dart_webdev_rev'), @@ -329,7 +329,7 @@ deps = { Var('dart_git') + '/yaml_edit.git' + '@' + Var('dart_yaml_edit_rev'), 'src/third_party/dart/tools/sdks': - {'packages': [{'version': 'version:2.18.0-271.0.dev', 'package': 'dart/dart-sdk/${{platform}}'}], 'dep_type': 'cipd'}, + {'packages': [{'version': 'version:2.18.0-271.7.beta', '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. @@ -464,7 +464,7 @@ deps = { Var('chromium_git') + '/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator' + '@' + '7de5cc00de50e71a3aab22dea52fbb7ff4efceb6', 'src/third_party/abseil-cpp': - Var('flutter_git') + '/third_party/abseil-cpp.git' + '@' + 'f92f9effc89af7692436c3b9fbb3a67f2239d893', + Var('flutter_git') + '/third_party/abseil-cpp.git' + '@' + '61833f2c057a2b1993d871e8c51156aed1dd4354', # Dart packages 'src/third_party/pkg/archive': @@ -474,7 +474,7 @@ deps = { Var('github_git') + '/felangel/equatable.git' + '@' + '0ba67c72db8bed75877fc1caafa74112ee0bd921', # 2.0.2 'src/third_party/pkg/file': - Var('github_git') + '/google/file.dart.git' + '@' + '427bb20ccc852425d67f2880da2a9b4707c266b4', # 6.1.0 + Var('github_git') + '/google/file.dart.git' + '@' + 'b2e31cb6ef40b223701dbfa0b907fe58468484d7', # 6.1.4 'src/third_party/pkg/flutter_packages': Var('github_git') + '/flutter/packages.git' + '@' + '26990a2f75ab2028c3c77ffc869db11d6d866d18', # various @@ -546,7 +546,7 @@ deps = { 'packages': [ { 'package': 'flutter/android/sdk/all/${{platform}}', - 'version': 'version:32v1' + 'version': 'version:33v6' } ], 'condition': 'download_android_deps', @@ -606,7 +606,7 @@ deps = { 'packages': [ { 'package': 'fuchsia/third_party/clang/mac-amd64', - 'version': 'git_revision:3a20597776a5d2920e511d81653b4d2b6ca0c855' + 'version': 'git_revision:60d276923902051192eba692e5312e605c9d9f65' } ], 'condition': 'host_os == "mac"', @@ -628,7 +628,7 @@ deps = { 'packages': [ { 'package': 'fuchsia/third_party/clang/linux-amd64', - 'version': 'git_revision:3a20597776a5d2920e511d81653b4d2b6ca0c855' + 'version': 'git_revision:41d4067d0b7c3c955b8b6cda328bcb46e268bd6a' } ], 'condition': 'host_os == "linux" and host_cpu == "x64"', @@ -638,8 +638,8 @@ deps = { 'src/buildtools/linux-arm64/clang': { 'packages': [ { - 'package': 'fuchsia/third_party/clang/linux-amd64', - 'version': 'git_revision:3a20597776a5d2920e511d81653b4d2b6ca0c855' + 'package': 'fuchsia/third_party/clang/linux-arm64', + 'version': 'git_revision:d9e02a30b16ea65a7da87913c40af03e22c9571f' } ], 'condition': 'host_os == "linux" and host_cpu == "arm64"', @@ -650,7 +650,7 @@ deps = { 'packages': [ { 'package': 'fuchsia/third_party/clang/windows-amd64', - 'version': 'git_revision:3a20597776a5d2920e511d81653b4d2b6ca0c855' + 'version': 'git_revision:41d4067d0b7c3c955b8b6cda328bcb46e268bd6a' } ], 'condition': 'download_windows_deps', @@ -664,7 +664,7 @@ deps = { 'packages': [ { 'package': 'fuchsia/sdk/core/mac-amd64', - 'version': 'YeMyxhFzoCNrwt0GrUkvlLgP9N_gYEy8dQ6B0Stvp5YC' + 'version': 'Dze6-JhF8LT-exmW_FbhtaUl_QFTlUnQHvtopxkj2H8C' } ], 'condition': 'host_os == "mac" and not download_fuchsia_sdk', @@ -674,7 +674,7 @@ deps = { 'packages': [ { 'package': 'fuchsia/sdk/core/linux-amd64', - 'version': '13FsbGdE97bnZF8KOg3CT33qNvj481lj3Ke4vN4kRMEC' + 'version': '8N2zrCMhImBEPCtuCFCohtRIJRw1UxWRKIee5kvj5JMC' } ], 'condition': 'host_os == "linux" and not download_fuchsia_sdk', diff --git a/analysis_options.yaml b/analysis_options.yaml index 64f8d393da8dd..de29de16689b8 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -5,29 +5,18 @@ # "DIFFERENT FROM FLUTTER/FLUTTER" below. analyzer: - exclude: - # Fixture depends on dart:ui and raises false positives. - - flutter_frontend_server/test/fixtures/lib/main.dart language: strict-casts: true strict-raw-types: true errors: - # treat missing required parameters as a warning (not a hint) - missing_required_param: warning - # treat missing returns as a warning (not a hint) - missing_return: warning - native_function_body_in_non_sdk_code: ignore # DIFFERENT FROM FLUTTER/FLUTTER - # allow having TODO comments in the code - todo: ignore - # allow dart:ui to import dart:_internal - import_internal_library: ignore # DIFFERENT FROM FLUTTER/FLUTTER # allow self-reference to deprecated members (we do this because otherwise we have # to annotate every member in every test, assert, etc, when we deprecate something) deprecated_member_use_from_same_package: ignore # Turned off until null-safe rollout is complete. unnecessary_null_comparison: ignore - # TODO(goderbauer): remove when https://github.com/dart-lang/sdk/issues/49563 is fixed. - ffi_native_unexpected_number_of_parameters: ignore # DIFFERENT FROM FLUTTER/FLUTTER + exclude: # DIFFERENT FROM FLUTTER/FLUTTER + # Fixture depends on dart:ui and raises false positives. + - flutter_frontend_server/test/fixtures/lib/main.dart linter: rules: @@ -65,7 +54,7 @@ linter: - avoid_relative_lib_imports - avoid_renaming_method_parameters - avoid_return_types_on_setters - # - avoid_returning_null # still violated by some pre-nnbd code that we haven't yet migrated + - avoid_returning_null - avoid_returning_null_for_future - avoid_returning_null_for_void # - avoid_returning_this # there are enough valid reasons to return `this` that this lint ends up with too many false positives @@ -89,7 +78,7 @@ linter: # - close_sinks # not reliable enough - combinators_ordering # - comment_references # blocked on https://github.com/dart-lang/linter/issues/1142 - # - conditional_uri_does_not_exist # not yet tested + - conditional_uri_does_not_exist # - constant_identifier_names # needs an opt-out https://github.com/dart-lang/linter/issues/204 - control_flow_in_finally - curly_braces_in_flow_control_structures @@ -151,7 +140,7 @@ linter: # - prefer_double_quotes # opposite of prefer_single_quotes - prefer_equal_for_default_values # - prefer_expression_function_bodies # conflicts with https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#consider-using--for-short-functions-and-methods - # - prefer_final_fields # DIFFERENT FROM FLUTTER/FLUTTER (we do weird things with private fields, especially on the PlatformDispatcher object) + - prefer_final_fields - prefer_final_in_for_each - prefer_final_locals # - prefer_final_parameters # we should enable this one day when it can be auto-fixed (https://github.com/dart-lang/linter/issues/3104), see also parameter_assignments @@ -186,7 +175,7 @@ linter: # - sized_box_shrink_expand # not yet tested - slash_for_doc_comments - sort_child_properties_last - # - sort_constructors_first # DIFFERENT FROM FLUTTER/FLUTTER (we have private fake constructors) + - sort_constructors_first # - sort_pub_dependencies # prevents separating pinned transitive dependencies - sort_unnamed_constructors_first - test_types_in_equals diff --git a/assets/asset_manager.cc b/assets/asset_manager.cc index ef73894d07647..ecf97a4ce5e69 100644 --- a/assets/asset_manager.cc +++ b/assets/asset_manager.cc @@ -13,20 +13,22 @@ AssetManager::AssetManager() = default; AssetManager::~AssetManager() = default; -void AssetManager::PushFront(std::unique_ptr resolver) { +bool AssetManager::PushFront(std::unique_ptr resolver) { if (resolver == nullptr || !resolver->IsValid()) { - return; + return false; } resolvers_.push_front(std::move(resolver)); + return true; } -void AssetManager::PushBack(std::unique_ptr resolver) { +bool AssetManager::PushBack(std::unique_ptr resolver) { if (resolver == nullptr || !resolver->IsValid()) { - return; + return false; } resolvers_.push_back(std::move(resolver)); + return true; } void AssetManager::UpdateResolverByType( diff --git a/assets/asset_manager.h b/assets/asset_manager.h index 335bd84116365..b9e704d58a18f 100644 --- a/assets/asset_manager.h +++ b/assets/asset_manager.h @@ -22,9 +22,23 @@ class AssetManager final : public AssetResolver { ~AssetManager() override; - void PushFront(std::unique_ptr resolver); + //-------------------------------------------------------------------------- + /// @brief Adds an asset resolver to the front of the resolver queue. + /// Assets would be loaded from this resolver before any follwing + /// resolvers. + /// + /// @return Returns whether this resolver is valid and has been added to + /// the resolver queue. + bool PushFront(std::unique_ptr resolver); - void PushBack(std::unique_ptr resolver); + //-------------------------------------------------------------------------- + /// @brief Adds an asset resolver to the end of the resolver queue. + /// Assets would be loaded from this resolver after any previous + /// resolvers. + /// + /// @return Returns whether this resolver is valid and has been added to + /// the resolver queue. + bool PushBack(std::unique_ptr resolver); //-------------------------------------------------------------------------- /// @brief Replaces an asset resolver of the specified `type` with diff --git a/build/archives/BUILD.gn b/build/archives/BUILD.gn index 2282c2d457d02..81f2e537ea268 100644 --- a/build/archives/BUILD.gn +++ b/build/archives/BUILD.gn @@ -172,17 +172,22 @@ if (build_engine_artifacts && !flutter_prebuilt_dart_sdk) { zip_bundle_from_file("dart_sdk_archive") { deps = [ "//third_party/dart:create_sdk" ] output = "dart-sdk-$full_target_platform_name.zip" - if (is_mac) { - # Mac artifacts sometimes use mac and sometimes darwin. Standardizing the - # names will require changes in the list of artifacts the tool is downloading. - output = "dart-sdk-darwin-$target_cpu.zip" - } files = [ { source = rebase_path("$root_build_dir/dart-sdk") destination = "dart-sdk" }, ] + if (is_mac) { + output = "dart-sdk-darwin-$target_cpu.zip" + deps += [ ":dart_sdk_entitlement_config" ] + files += [ + { + source = "$target_gen_dir/dart_sdk_entitlements.txt" + destination = "entitlements.txt" + }, + ] + } } } @@ -241,12 +246,6 @@ if (is_mac) { deps = [ "//flutter/lib/snapshot:create_macos_gen_snapshots" ] } - group("macos_flutter_framework") { - deps = [ - "//flutter/shell/platform/darwin/macos:macos_flutter_framework_archive", - ] - } - zip_bundle("archive_gen_snapshot") { deps = [ ":snapshot_entitlement_config", diff --git a/build/zip.py b/build/zip.py index 9b36dd2e537c1..a6ce362d77f90 100755 --- a/build/zip.py +++ b/build/zip.py @@ -6,15 +6,22 @@ import argparse import json -import zipfile import os import stat import sys +import zipfile def _zip_dir(path, zip_file, prefix): path = path.rstrip('/\\') - for root, _, files in os.walk(path): + for root, directories, files in os.walk(path): + for directory in directories: + if os.path.islink(os.path.join(root, directory)): + add_symlink( + zip_file, + os.path.join(root, directory), + os.path.join(root.replace(path, prefix), directory), + ) for file in files: if os.path.islink(os.path.join(root, file)): add_symlink( @@ -43,7 +50,7 @@ def add_symlink(zip_file, source, target): | stat.S_IWGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IWOTH | stat.S_IXOTH ) zip_info.external_attr = unix_st_mode << 16 - zip_file.writestr(zip_info, source) + zip_file.writestr(zip_info, os.readlink(source)) def main(args): diff --git a/ci/analyze.sh b/ci/analyze.sh index 62886f53165c7..fbfa6b7805d94 100755 --- a/ci/analyze.sh +++ b/ci/analyze.sh @@ -54,29 +54,21 @@ echo "" "$DART" analyze --fatal-infos --fatal-warnings "$FLUTTER_DIR/flutter_frontend_server" -"$DART" analyze --fatal-infos --fatal-warnings "$FLUTTER_DIR/tools/licenses" +"$DART" analyze --fatal-infos --fatal-warnings "$FLUTTER_DIR/tools" -"$DART" analyze --fatal-infos --fatal-warnings "$FLUTTER_DIR/testing/litetest" - -"$DART" analyze --fatal-infos --fatal-warnings "$FLUTTER_DIR/testing/benchmark" - -"$DART" analyze --fatal-infos --fatal-warnings "$FLUTTER_DIR/testing/smoke_test_failure" - -"$DART" analyze --fatal-infos --fatal-warnings "$FLUTTER_DIR/testing/dart" - -"$DART" analyze --fatal-infos --fatal-warnings "$FLUTTER_DIR/testing/scenario_app" - -"$DART" analyze --fatal-infos --fatal-warnings "$FLUTTER_DIR/testing/symbols" - -"$DART" analyze --fatal-infos --fatal-warnings "$FLUTTER_DIR/tools/githooks" - -"$DART" analyze --fatal-infos --fatal-warnings "$FLUTTER_DIR/tools/clang_tidy" +(cd "$FLUTTER_DIR/testing/skia_gold_client"; "$DART" pub get) +"$DART" analyze --fatal-infos --fatal-warnings "$FLUTTER_DIR/testing" echo "" # Check that dart libraries conform. echo "Checking the integrity of the Web SDK" (cd "$FLUTTER_DIR/web_sdk"; "$DART" pub get) +(cd "$FLUTTER_DIR/web_sdk/web_test_utils"; "$DART" pub get) +(cd "$FLUTTER_DIR/web_sdk/web_engine_tester"; "$DART" pub get) + +"$DART" analyze --fatal-infos --fatal-warnings "$FLUTTER_DIR/web_sdk" + WEB_SDK_TEST_FILES="$FLUTTER_DIR/web_sdk/test/*" for testFile in $WEB_SDK_TEST_FILES do diff --git a/ci/builders/mac_android_aot_engine.json b/ci/builders/mac_android_aot_engine.json new file mode 100644 index 0000000000000..6bb034cc6744d --- /dev/null +++ b/ci/builders/mac_android_aot_engine.json @@ -0,0 +1,215 @@ +{ + "builds": [ + { + "archives": [ + { + "base_path": "out/android_profile/zip_archives/", + "type": "gcs", + "include_paths": [ + "out/android_profile/zip_archives/android-arm-profile/darwin-x64.zip" + ], + "name": "android_profile" + } + ], + "drone_dimensions": [ + "device_type=none", + "os=Mac-12", + "cpu=x86" + ], + "gclient_custom_vars": { + "download_android_deps": false + }, + "gn": [ + "--runtime-mode", + "profile", + "--android" + ], + "name": "android_profile", + "ninja": { + "config": "android_profile", + "targets": [ + "flutter/lib/snapshot", + "flutter/shell/platform/android:gen_snapshot" + ] + }, + "tests": [] + }, + { + "archives": [ + { + "base_path": "out/android_profile_arm64/zip_archives/", + "type": "gcs", + "include_paths": [ + "out/android_profile_arm64/zip_archives/android-arm64-profile/darwin-x64.zip" + ], + "name": "android_profile_arm64" + } + ], + "drone_dimensions": [ + "device_type=none", + "os=Mac-12", + "cpu=x86" + ], + "gclient_custom_vars": { + "download_android_deps": false + }, + "gn": [ + "--runtime-mode", + "profile", + "--android", + "--android-cpu=arm64" + ], + "name": "android_profile_arm64", + "ninja": { + "config": "android_profile_arm64", + "targets": [ + "flutter/lib/snapshot", + "flutter/shell/platform/android:gen_snapshot" + ] + }, + "tests": [] + }, + { + "archives": [ + { + "base_path": "out/android_profile_x64/zip_archives/", + "type": "gcs", + "include_paths": [ + "out/android_profile_x64/zip_archives/android-x64-profile/darwin-x64.zip" + ], + "name": "android_profile_x64" + } + ], + "drone_dimensions": [ + "device_type=none", + "os=Mac-12", + "cpu=x86" + ], + "gclient_custom_vars": { + "download_android_deps": false + }, + "gn": [ + "--runtime-mode", + "profile", + "--android", + "--android-cpu=x64" + ], + "name": "android_profile_x64", + "ninja": { + "config": "android_profile_x64", + "targets": [ + "flutter/lib/snapshot", + "flutter/shell/platform/android:gen_snapshot" + ] + }, + "tests": [] + }, + { + "archives": [ + { + "base_path": "out/android_release/zip_archives/", + "type": "gcs", + "include_paths": [ + "out/android_release/zip_archives/android-arm-release/darwin-x64.zip" + ], + "name": "android_release" + } + ], + "drone_dimensions": [ + "device_type=none", + "os=Mac-12", + "cpu=x86" + ], + "gclient_custom_vars": { + "download_android_deps": false + }, + "gn": [ + "--runtime-mode", + "release", + "--android" + ], + "name": "android_release", + "ninja": { + "config": "android_release", + "targets": [ + "flutter/lib/snapshot", + "flutter/shell/platform/android:gen_snapshot" + ] + }, + "tests": [] + }, + { + "archives": [ + { + "base_path": "out/android_release_arm64/zip_archives/", + "type": "gcs", + "include_paths": [ + "out/android_release_arm64/zip_archives/android-arm64-release/darwin-x64.zip" + ], + "name": "android_release_arm64" + } + ], + "drone_dimensions": [ + "device_type=none", + "os=Mac-12", + "cpu=x86" + ], + "gclient_custom_vars": { + "download_android_deps": false + }, + "gn": [ + "--runtime-mode", + "release", + "--android", + "--android-cpu=arm64" + ], + "name": "android_release_arm64", + "ninja": { + "config": "android_release_arm64", + "targets": [ + "flutter/lib/snapshot", + "flutter/shell/platform/android:gen_snapshot" + ] + }, + "tests": [] + }, + { + "archives": [ + { + "base_path": "out/android_release_x64/zip_archives/", + "type": "gcs", + "include_paths": [ + "out/android_release_x64/zip_archives/android-x64-release/darwin-x64.zip" + ], + "name": "android_release_x64" + } + ], + "drone_dimensions": [ + "device_type=none", + "os=Mac-12", + "cpu=x86" + ], + "gclient_custom_vars": { + "download_android_deps": false + }, + "gn": [ + "--runtime-mode", + "release", + "--android", + "--android-cpu=x64" + ], + "name": "android_release_x64", + "ninja": { + "config": "android_release_x64", + "targets": [ + "flutter/lib/snapshot", + "flutter/shell/platform/android:gen_snapshot" + ] + }, + "tests": [] + } + ], + "tests": [], + "generators": [], + "archives": [] +} diff --git a/ci/builders/mac_host_engine.json b/ci/builders/mac_host_engine.json index e0c92f24639b7..9f9c368f48d3f 100644 --- a/ci/builders/mac_host_engine.json +++ b/ci/builders/mac_host_engine.json @@ -4,6 +4,7 @@ "archives": [ { "base_path": "out/host_debug/zip_archives/", + "type": "gcs", "include_paths": [ "out/host_debug/zip_archives/darwin-x64/FlutterMacOS.framework.zip", "out/host_debug/zip_archives/darwin-x64/gen_snapshot.zip", @@ -35,7 +36,7 @@ "ninja": { "config": "host_debug", "targets": [ - "flutter/build/archives:macos_flutter_framework", + "flutter/shell/platform/darwin/macos:zip_macos_flutter_framework", "flutter/build/archives:artifacts", "flutter/build/archives:dart_sdk_archive", "flutter/build/archives:flutter_web_sdk", @@ -49,6 +50,7 @@ "archives": [ { "base_path": "out/host_profile/zip_archives/", + "type": "gcs", "include_paths": [ "out/host_profile/zip_archives/darwin-x64-profile/FlutterMacOS.framework.zip", "out/host_profile/zip_archives/darwin-x64-profile/gen_snapshot.zip", @@ -76,7 +78,7 @@ "ninja": { "config": "host_profile", "targets": [ - "flutter/build/archives:macos_flutter_framework", + "flutter/shell/platform/darwin/macos:zip_macos_flutter_framework", "flutter/build/archives:archive_gen_snapshot", "flutter/build/archives:artifacts", "flutter:unittests" @@ -102,6 +104,7 @@ "archives": [ { "base_path": "out/host_release/zip_archives/", + "type": "gcs", "include_paths": [ "out/host_release/zip_archives/darwin-x64-release/FlutterMacOS.framework.zip", "out/host_release/zip_archives/darwin-x64-release/gen_snapshot.zip", @@ -130,7 +133,7 @@ "ninja": { "config": "host_release", "targets": [ - "flutter/build/archives:macos_flutter_framework", + "flutter/shell/platform/darwin/macos:zip_macos_flutter_framework", "flutter/build/archives:archive_gen_snapshot", "flutter/build/archives:artifacts", "flutter/tools/font-subset" @@ -142,6 +145,7 @@ "archives": [ { "base_path": "out/mac_debug_arm64/zip_archives/", + "type": "gcs", "include_paths": [ "out/mac_debug_arm64/zip_archives/dart-sdk-darwin-arm64.zip", "out/mac_debug_arm64/zip_archives/darwin-x64/gen_snapshot.zip" @@ -172,7 +176,8 @@ "config": "mac_debug_arm64", "targets": [ "flutter/build/archives:dart_sdk_archive", - "flutter/build/archives:archive_gen_snapshot" + "flutter/build/archives:archive_gen_snapshot", + "flutter/shell/platform/darwin/macos:zip_macos_flutter_framework" ] }, "tests": [] @@ -181,6 +186,7 @@ "archives": [ { "base_path": "out/mac_profile_arm64/zip_archives/", + "type": "gcs", "include_paths": [ "out/mac_profile_arm64/zip_archives/darwin-x64-profile/gen_snapshot.zip" ], @@ -208,7 +214,8 @@ "ninja": { "config": "mac_profile_arm64", "targets": [ - "flutter/build/archives:archive_gen_snapshot" + "flutter/build/archives:archive_gen_snapshot", + "flutter/shell/platform/darwin/macos:zip_macos_flutter_framework" ] }, "tests": [] @@ -217,6 +224,7 @@ "archives": [ { "base_path": "out/mac_release_arm64/zip_archives/", + "type": "gcs", "include_paths": [ "out/mac_release_arm64/zip_archives/darwin-x64-release/gen_snapshot.zip" ], @@ -244,11 +252,36 @@ "ninja": { "config": "mac_release_arm64", "targets": [ - "flutter/build/archives:archive_gen_snapshot" + "flutter/build/archives:archive_gen_snapshot", + "flutter/shell/platform/darwin/macos:zip_macos_flutter_framework" ] }, "tests": [] } ], - "tests": [] + "tests": [], + "generators": { + "tasks": [ + { + "name": "Release-FlutterMacOS.framework", + "parameters": [ + "--dst", + "out/release", + "--arm64-out-dir", + "out/mac_release_arm64", + "--x64-out-dir", + "out/host_release", + "--dsym", + "--strip" + ], + "script": "flutter/sky/tools/create_macos_framework.py" + } + ] + }, + "archives": [ + { + "source": "out/release/FlutterMacOS.dSYM.zip", + "destination": "darwin-x64-release/FlutterMacOS.dSYM.zip" + } + ] } diff --git a/ci/builders/mac_ios_engine.json b/ci/builders/mac_ios_engine.json index 21c700836ba70..b3907394140dc 100644 --- a/ci/builders/mac_ios_engine.json +++ b/ci/builders/mac_ios_engine.json @@ -48,8 +48,7 @@ "archives": [ { "base_path": "out/ios_debug/zip_archives/", - "include_paths": [ - ], + "include_paths": [], "name": "ios_debug" } ], @@ -64,23 +63,51 @@ "--runtime-mode", "debug" ], - "generators": { - "tasks": [ - { - "name": "BuildObjcDoc", - "scripts": [ - "flutter/tools/gen_objcdoc.sh" - ] - } - ] - }, "name": "ios_debug", "ninja": { "config": "ios_debug", - "targets": [] + "targets": [ + "flutter/shell/platform/darwin/ios:flutter_framework" + ] }, "tests": [] } ], - "tests": [] + "tests": [], + "generators": { + "tasks": [ + { + "name": "Debug-FlutterMacOS.framework", + "parameters": [ + "--dst", + "out/debug", + "--arm64-out-dir", + "out/ios_debug", + "--simulator-x64-out-dir", + "out/ios_debug_sim", + "--simulator-arm64-out-dir", + "out/ios_debug_sim_arm64" + ], + "script": "flutter/sky/tools/create_full_ios_framework.py", + "language": "python" + }, + { + "name": "obj-c-doc", + "parameters": [ + "out/debug" + ], + "script": "flutter/tools/gen_objcdoc.sh" + } + ] + }, + "archives": [ + { + "source": "out/debug/artifacts.zip", + "destination": "ios/artifacts.zip" + }, + { + "source": "out/debug/ios-objcdoc.zip", + "destination": "ios-objcdoc.zip" + } + ] } diff --git a/ci/builders/mac_ios_engine_profile.json b/ci/builders/mac_ios_engine_profile.json index 530ab2f4ef7a2..591dfc153ef08 100644 --- a/ci/builders/mac_ios_engine_profile.json +++ b/ci/builders/mac_ios_engine_profile.json @@ -61,10 +61,38 @@ "name": "ios_profile", "ninja": { "config": "ios_profile", - "targets": [] + "targets": [ + "flutter/shell/platform/darwin/ios:flutter_framework", + "flutter/lib/snapshot:generate_snapshot_bin" + ] }, "tests": [] } ], - "tests": [] + "tests": [], + "generators": { + "tasks": [ + { + "name": "Profile-FlutterMacOS.framework", + "parameters": [ + "--dst", + "out/profile", + "--arm64-out-dir", + "out/ios_profile", + "--simulator-x64-out-dir", + "out/ios_debug_sim", + "--simulator-arm64-out-dir", + "out/ios_debug_sim_arm64" + ], + "script": "flutter/sky/tools/create_full_ios_framework.py", + "language": "python" + } + ] + }, + "archives": [ + { + "source": "out/profile/artifacts.zip", + "destination": "ios-profile/artifacts.zip" + } + ] } diff --git a/ci/builders/mac_ios_engine_release.json b/ci/builders/mac_ios_engine_release.json index adbd385d465ad..65d02956f2516 100644 --- a/ci/builders/mac_ios_engine_release.json +++ b/ci/builders/mac_ios_engine_release.json @@ -38,7 +38,10 @@ "name": "ios_release", "ninja": { "config": "ios_release", - "targets": [], + "targets": [ + "flutter/shell/platform/darwin/ios:flutter_framework", + "flutter/lib/snapshot:generate_snapshot_bin" + ], "tool": "autoninja" }, "tests": [] @@ -69,5 +72,73 @@ "tests": [] } ], - "tests": [] + "tests": [], + "generators": { + "tasks": [ + { + "name": "release-FlutterMacOS.framework", + "parameters": [ + "--dst", + "out/release", + "--arm64-out-dir", + "out/ios_release", + "--simulator-x64-out-dir", + "out/ios_debug_sim", + "--simulator-arm64-out-dir", + "out/ios_debug_sim_arm64", + "--dsym", + "--strip" + ], + "script": "flutter/sky/tools/create_full_ios_framework.py", + "language": "python" + }, + { + "name": "release-nobitcode-FlutterMacOS.framework", + "parameters": [ + "--dst", + "out/release-nobitcode", + "--arm64-out-dir", + "out/ios_release", + "--simulator-x64-out-dir", + "out/ios_debug_sim", + "--simulator-arm64-out-dir", + "out/ios_debug_sim_arm64", + "--strip-bitcode", + "--dsym", + "--strip" + ], + "script": "flutter/sky/tools/create_full_ios_framework.py", + "language": "python" + }, + { + "name": "Release-macos-gen-snapshots", + "parameters": [ + "--dst", + "out/release", + "--arm64-out-dir", + "out/ios_release" + ], + "script": "flutter/sky/tools/create_macos_gen_snapshots.py", + "language": "python" + } + ] + }, + "archives": [ + { + "source": "out/release/artifacts.zip", + "destination": "ios-release/artifacts.zip" + }, + { + "source": "out/release/Flutter.dSYM.zip", + "destination": "ios-release/Flutter.dSYM.zip" + }, + { + "source": "out/release-nobitcode/artifacts.zip", + "destination": "ios-release-nobitcode/artifacts.zip" + }, + { + "source": "out/release-nobitcode/Flutter.dSYM.zip", + "destination": "ios-release-nobitcode/Flutter.dSYM.zip" + } + ] } diff --git a/ci/firebase_testlab.py b/ci/firebase_testlab.py index 36b5d419b6471..92069a7c1f584 100755 --- a/ci/firebase_testlab.py +++ b/ci/firebase_testlab.py @@ -11,7 +11,16 @@ import subprocess import sys -BUCKET = 'gs://flutter_firebase_testlab' +if 'STORAGE_BUCKET' not in os.environ: + print('The GCP storage bucket must be provided as an environment variable.') + sys.exit(1) +BUCKET = os.environ['STORAGE_BUCKET'] + +if 'GCP_PROJECT' not in os.environ: + print('The GCP project must be provided as an environment variable.') + sys.exit(1) +PROJECT = os.environ['GCP_PROJECT'] + script_dir = os.path.dirname(os.path.realpath(__file__)) buildroot_dir = os.path.abspath(os.path.join(script_dir, '..', '..')) out_dir = os.path.join(buildroot_dir, 'out') @@ -28,7 +37,7 @@ def run_firebase_test(apk, results_dir): [ 'gcloud', '--project', - 'flutter-infra', + PROJECT, 'firebase', 'test', 'android', diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 1c8291fb821a9..fdb7e3c834ee2 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -56,6 +56,8 @@ FILE: ../../../flutter/display_list/display_list_blend_mode.h FILE: ../../../flutter/display_list/display_list_builder.cc FILE: ../../../flutter/display_list/display_list_builder.h FILE: ../../../flutter/display_list/display_list_builder_benchmarks.cc +FILE: ../../../flutter/display_list/display_list_builder_multiplexer.cc +FILE: ../../../flutter/display_list/display_list_builder_multiplexer.h FILE: ../../../flutter/display_list/display_list_canvas_dispatcher.cc FILE: ../../../flutter/display_list/display_list_canvas_dispatcher.h FILE: ../../../flutter/display_list/display_list_canvas_recorder.cc @@ -450,6 +452,8 @@ FILE: ../../../flutter/impeller/base/base_unittests.cc FILE: ../../../flutter/impeller/base/comparable.cc FILE: ../../../flutter/impeller/base/comparable.h FILE: ../../../flutter/impeller/base/config.h +FILE: ../../../flutter/impeller/base/platform/darwin/work_queue_darwin.cc +FILE: ../../../flutter/impeller/base/platform/darwin/work_queue_darwin.h FILE: ../../../flutter/impeller/base/promise.cc FILE: ../../../flutter/impeller/base/promise.h FILE: ../../../flutter/impeller/base/strings.cc @@ -462,10 +466,14 @@ FILE: ../../../flutter/impeller/base/validation.cc FILE: ../../../flutter/impeller/base/validation.h FILE: ../../../flutter/impeller/base/version.cc FILE: ../../../flutter/impeller/base/version.h -FILE: ../../../flutter/impeller/blobcat/blob.cc -FILE: ../../../flutter/impeller/blobcat/blob.h +FILE: ../../../flutter/impeller/base/work_queue.cc +FILE: ../../../flutter/impeller/base/work_queue.h +FILE: ../../../flutter/impeller/base/work_queue_common.cc +FILE: ../../../flutter/impeller/base/work_queue_common.h +FILE: ../../../flutter/impeller/blobcat/blob.fbs FILE: ../../../flutter/impeller/blobcat/blob_library.cc FILE: ../../../flutter/impeller/blobcat/blob_library.h +FILE: ../../../flutter/impeller/blobcat/blob_types.h FILE: ../../../flutter/impeller/blobcat/blob_writer.cc FILE: ../../../flutter/impeller/blobcat/blob_writer.h FILE: ../../../flutter/impeller/blobcat/blobcat_main.cc @@ -487,10 +495,12 @@ FILE: ../../../flutter/impeller/compiler/reflector.cc FILE: ../../../flutter/impeller/compiler/reflector.h FILE: ../../../flutter/impeller/compiler/runtime_stage_data.cc FILE: ../../../flutter/impeller/compiler/runtime_stage_data.h +FILE: ../../../flutter/impeller/compiler/shader_lib/impeller/blending.glsl FILE: ../../../flutter/impeller/compiler/shader_lib/impeller/branching.glsl FILE: ../../../flutter/impeller/compiler/shader_lib/impeller/color.glsl FILE: ../../../flutter/impeller/compiler/shader_lib/impeller/constants.glsl FILE: ../../../flutter/impeller/compiler/shader_lib/impeller/texture.glsl +FILE: ../../../flutter/impeller/compiler/shader_lib/impeller/transform.glsl FILE: ../../../flutter/impeller/compiler/shader_lib/impeller/types.glsl FILE: ../../../flutter/impeller/compiler/source_options.cc FILE: ../../../flutter/impeller/compiler/source_options.h @@ -509,6 +519,8 @@ FILE: ../../../flutter/impeller/display_list/display_list_image_impeller.h FILE: ../../../flutter/impeller/display_list/display_list_playground.cc FILE: ../../../flutter/impeller/display_list/display_list_playground.h FILE: ../../../flutter/impeller/display_list/display_list_unittests.cc +FILE: ../../../flutter/impeller/display_list/nine_patch_converter.cc +FILE: ../../../flutter/impeller/display_list/nine_patch_converter.h FILE: ../../../flutter/impeller/docs/assets/read_frame_captures/image1.png FILE: ../../../flutter/impeller/docs/assets/read_frame_captures/image10.png FILE: ../../../flutter/impeller/docs/assets/read_frame_captures/image11.png @@ -541,8 +553,12 @@ FILE: ../../../flutter/impeller/docs/assets/xcode_frame_capture/image6.png FILE: ../../../flutter/impeller/docs/assets/xcode_frame_capture/image7.png FILE: ../../../flutter/impeller/docs/assets/xcode_frame_capture/image8.png FILE: ../../../flutter/impeller/docs/assets/xcode_frame_capture/image9.png +FILE: ../../../flutter/impeller/entity/contents/atlas_contents.cc +FILE: ../../../flutter/impeller/entity/contents/atlas_contents.h FILE: ../../../flutter/impeller/entity/contents/clip_contents.cc FILE: ../../../flutter/impeller/entity/contents/clip_contents.h +FILE: ../../../flutter/impeller/entity/contents/color_source_contents.cc +FILE: ../../../flutter/impeller/entity/contents/color_source_contents.h FILE: ../../../flutter/impeller/entity/contents/content_context.cc FILE: ../../../flutter/impeller/entity/contents/content_context.h FILE: ../../../flutter/impeller/entity/contents/contents.cc @@ -551,6 +567,8 @@ FILE: ../../../flutter/impeller/entity/contents/filters/blend_filter_contents.cc FILE: ../../../flutter/impeller/entity/contents/filters/blend_filter_contents.h FILE: ../../../flutter/impeller/entity/contents/filters/border_mask_blur_filter_contents.cc FILE: ../../../flutter/impeller/entity/contents/filters/border_mask_blur_filter_contents.h +FILE: ../../../flutter/impeller/entity/contents/filters/color_matrix_filter_contents.cc +FILE: ../../../flutter/impeller/entity/contents/filters/color_matrix_filter_contents.h FILE: ../../../flutter/impeller/entity/contents/filters/filter_contents.cc FILE: ../../../flutter/impeller/entity/contents/filters/filter_contents.h FILE: ../../../flutter/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc @@ -564,20 +582,28 @@ FILE: ../../../flutter/impeller/entity/contents/filters/inputs/filter_input.h FILE: ../../../flutter/impeller/entity/contents/filters/inputs/filter_input_unittests.cc FILE: ../../../flutter/impeller/entity/contents/filters/inputs/texture_filter_input.cc FILE: ../../../flutter/impeller/entity/contents/filters/inputs/texture_filter_input.h +FILE: ../../../flutter/impeller/entity/contents/filters/linear_to_srgb_filter_contents.cc +FILE: ../../../flutter/impeller/entity/contents/filters/linear_to_srgb_filter_contents.h +FILE: ../../../flutter/impeller/entity/contents/filters/srgb_to_linear_filter_contents.cc +FILE: ../../../flutter/impeller/entity/contents/filters/srgb_to_linear_filter_contents.h FILE: ../../../flutter/impeller/entity/contents/linear_gradient_contents.cc FILE: ../../../flutter/impeller/entity/contents/linear_gradient_contents.h -FILE: ../../../flutter/impeller/entity/contents/path_contents.cc -FILE: ../../../flutter/impeller/entity/contents/path_contents.h FILE: ../../../flutter/impeller/entity/contents/radial_gradient_contents.cc FILE: ../../../flutter/impeller/entity/contents/radial_gradient_contents.h +FILE: ../../../flutter/impeller/entity/contents/rrect_shadow_contents.cc +FILE: ../../../flutter/impeller/entity/contents/rrect_shadow_contents.h FILE: ../../../flutter/impeller/entity/contents/solid_color_contents.cc FILE: ../../../flutter/impeller/entity/contents/solid_color_contents.h FILE: ../../../flutter/impeller/entity/contents/solid_stroke_contents.cc FILE: ../../../flutter/impeller/entity/contents/solid_stroke_contents.h +FILE: ../../../flutter/impeller/entity/contents/sweep_gradient_contents.cc +FILE: ../../../flutter/impeller/entity/contents/sweep_gradient_contents.h FILE: ../../../flutter/impeller/entity/contents/text_contents.cc FILE: ../../../flutter/impeller/entity/contents/text_contents.h FILE: ../../../flutter/impeller/entity/contents/texture_contents.cc FILE: ../../../flutter/impeller/entity/contents/texture_contents.h +FILE: ../../../flutter/impeller/entity/contents/tiled_texture_contents.cc +FILE: ../../../flutter/impeller/entity/contents/tiled_texture_contents.h FILE: ../../../flutter/impeller/entity/contents/vertices_contents.cc FILE: ../../../flutter/impeller/entity/contents/vertices_contents.h FILE: ../../../flutter/impeller/entity/entity.cc @@ -591,6 +617,8 @@ FILE: ../../../flutter/impeller/entity/entity_playground.h FILE: ../../../flutter/impeller/entity/entity_unittests.cc FILE: ../../../flutter/impeller/entity/inline_pass_context.cc FILE: ../../../flutter/impeller/entity/inline_pass_context.h +FILE: ../../../flutter/impeller/entity/shaders/atlas_fill.frag +FILE: ../../../flutter/impeller/entity/shaders/atlas_fill.vert FILE: ../../../flutter/impeller/entity/shaders/blending/advanced_blend.glsl FILE: ../../../flutter/impeller/entity/shaders/blending/advanced_blend.vert FILE: ../../../flutter/impeller/entity/shaders/blending/advanced_blend_color.frag @@ -608,25 +636,34 @@ FILE: ../../../flutter/impeller/entity/shaders/blending/advanced_blend_overlay.f FILE: ../../../flutter/impeller/entity/shaders/blending/advanced_blend_saturation.frag FILE: ../../../flutter/impeller/entity/shaders/blending/advanced_blend_screen.frag FILE: ../../../flutter/impeller/entity/shaders/blending/advanced_blend_softlight.frag -FILE: ../../../flutter/impeller/entity/shaders/blending/advanced_blend_utils.glsl FILE: ../../../flutter/impeller/entity/shaders/blending/blend.frag FILE: ../../../flutter/impeller/entity/shaders/blending/blend.vert FILE: ../../../flutter/impeller/entity/shaders/border_mask_blur.frag FILE: ../../../flutter/impeller/entity/shaders/border_mask_blur.vert +FILE: ../../../flutter/impeller/entity/shaders/color_matrix_color_filter.frag +FILE: ../../../flutter/impeller/entity/shaders/color_matrix_color_filter.vert FILE: ../../../flutter/impeller/entity/shaders/gaussian_blur.frag FILE: ../../../flutter/impeller/entity/shaders/gaussian_blur.vert FILE: ../../../flutter/impeller/entity/shaders/glyph_atlas.frag FILE: ../../../flutter/impeller/entity/shaders/glyph_atlas.vert -FILE: ../../../flutter/impeller/entity/shaders/gradient_fill.frag FILE: ../../../flutter/impeller/entity/shaders/gradient_fill.vert +FILE: ../../../flutter/impeller/entity/shaders/linear_gradient_fill.frag +FILE: ../../../flutter/impeller/entity/shaders/linear_to_srgb_filter.frag +FILE: ../../../flutter/impeller/entity/shaders/linear_to_srgb_filter.vert FILE: ../../../flutter/impeller/entity/shaders/radial_gradient_fill.frag -FILE: ../../../flutter/impeller/entity/shaders/radial_gradient_fill.vert +FILE: ../../../flutter/impeller/entity/shaders/rrect_blur.frag +FILE: ../../../flutter/impeller/entity/shaders/rrect_blur.vert FILE: ../../../flutter/impeller/entity/shaders/solid_fill.frag FILE: ../../../flutter/impeller/entity/shaders/solid_fill.vert FILE: ../../../flutter/impeller/entity/shaders/solid_stroke.frag FILE: ../../../flutter/impeller/entity/shaders/solid_stroke.vert +FILE: ../../../flutter/impeller/entity/shaders/srgb_to_linear_filter.frag +FILE: ../../../flutter/impeller/entity/shaders/srgb_to_linear_filter.vert +FILE: ../../../flutter/impeller/entity/shaders/sweep_gradient_fill.frag FILE: ../../../flutter/impeller/entity/shaders/texture_fill.frag FILE: ../../../flutter/impeller/entity/shaders/texture_fill.vert +FILE: ../../../flutter/impeller/entity/shaders/tiled_texture_fill.frag +FILE: ../../../flutter/impeller/entity/shaders/tiled_texture_fill.vert FILE: ../../../flutter/impeller/entity/shaders/vertices.frag FILE: ../../../flutter/impeller/entity/shaders/vertices.vert FILE: ../../../flutter/impeller/geometry/color.cc @@ -654,6 +691,8 @@ FILE: ../../../flutter/impeller/geometry/rect.h FILE: ../../../flutter/impeller/geometry/scalar.h FILE: ../../../flutter/impeller/geometry/shear.cc FILE: ../../../flutter/impeller/geometry/shear.h +FILE: ../../../flutter/impeller/geometry/sigma.cc +FILE: ../../../flutter/impeller/geometry/sigma.h FILE: ../../../flutter/impeller/geometry/size.cc FILE: ../../../flutter/impeller/geometry/size.h FILE: ../../../flutter/impeller/geometry/type_traits.cc @@ -757,6 +796,8 @@ FILE: ../../../flutter/impeller/renderer/backend/vulkan/capabilities_vk.cc FILE: ../../../flutter/impeller/renderer/backend/vulkan/capabilities_vk.h FILE: ../../../flutter/impeller/renderer/backend/vulkan/command_buffer_vk.cc FILE: ../../../flutter/impeller/renderer/backend/vulkan/command_buffer_vk.h +FILE: ../../../flutter/impeller/renderer/backend/vulkan/command_pool_vk.cc +FILE: ../../../flutter/impeller/renderer/backend/vulkan/command_pool_vk.h FILE: ../../../flutter/impeller/renderer/backend/vulkan/context_vk.cc FILE: ../../../flutter/impeller/renderer/backend/vulkan/context_vk.h FILE: ../../../flutter/impeller/renderer/backend/vulkan/device_buffer_vk.cc @@ -777,8 +818,14 @@ FILE: ../../../flutter/impeller/renderer/backend/vulkan/shader_function_vk.cc FILE: ../../../flutter/impeller/renderer/backend/vulkan/shader_function_vk.h FILE: ../../../flutter/impeller/renderer/backend/vulkan/shader_library_vk.cc FILE: ../../../flutter/impeller/renderer/backend/vulkan/shader_library_vk.h +FILE: ../../../flutter/impeller/renderer/backend/vulkan/surface_producer_vk.cc +FILE: ../../../flutter/impeller/renderer/backend/vulkan/surface_producer_vk.h FILE: ../../../flutter/impeller/renderer/backend/vulkan/surface_vk.cc FILE: ../../../flutter/impeller/renderer/backend/vulkan/surface_vk.h +FILE: ../../../flutter/impeller/renderer/backend/vulkan/swapchain_details_vk.cc +FILE: ../../../flutter/impeller/renderer/backend/vulkan/swapchain_details_vk.h +FILE: ../../../flutter/impeller/renderer/backend/vulkan/swapchain_vk.cc +FILE: ../../../flutter/impeller/renderer/backend/vulkan/swapchain_vk.h FILE: ../../../flutter/impeller/renderer/backend/vulkan/texture_vk.cc FILE: ../../../flutter/impeller/renderer/backend/vulkan/texture_vk.h FILE: ../../../flutter/impeller/renderer/backend/vulkan/vertex_descriptor_vk.cc @@ -798,6 +845,7 @@ FILE: ../../../flutter/impeller/renderer/command_buffer.cc FILE: ../../../flutter/impeller/renderer/command_buffer.h FILE: ../../../flutter/impeller/renderer/context.cc FILE: ../../../flutter/impeller/renderer/context.h +FILE: ../../../flutter/impeller/renderer/descriptor_set_layout.h FILE: ../../../flutter/impeller/renderer/device_buffer.cc FILE: ../../../flutter/impeller/renderer/device_buffer.h FILE: ../../../flutter/impeller/renderer/device_buffer_unittests.cc @@ -859,6 +907,7 @@ FILE: ../../../flutter/impeller/runtime_stage/runtime_stage.h FILE: ../../../flutter/impeller/runtime_stage/runtime_stage_playground.cc FILE: ../../../flutter/impeller/runtime_stage/runtime_stage_playground.h FILE: ../../../flutter/impeller/runtime_stage/runtime_stage_unittests.cc +FILE: ../../../flutter/impeller/runtime_stage/runtime_types.h FILE: ../../../flutter/impeller/tessellator/c/tessellator.cc FILE: ../../../flutter/impeller/tessellator/c/tessellator.h FILE: ../../../flutter/impeller/tessellator/dart/lib/tessellator.dart @@ -1051,6 +1100,7 @@ FILE: ../../../flutter/lib/ui/window/platform_message_response.cc FILE: ../../../flutter/lib/ui/window/platform_message_response.h FILE: ../../../flutter/lib/ui/window/platform_message_response_dart.cc FILE: ../../../flutter/lib/ui/window/platform_message_response_dart.h +FILE: ../../../flutter/lib/ui/window/platform_message_response_dart_unittests.cc 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 @@ -1090,19 +1140,20 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/canvaskit_canvas.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/color_filter.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/embedded_views_diff.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/font_fallback_data.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/font_fallbacks.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/fonts.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/image.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/image_filter.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/image_wasm_codecs.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/image_web_codecs.dart -FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/initialization.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/interval_tree.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/layer.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/layer_scene_builder.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/layer_tree.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/mask_filter.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/n_way_canvas.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/noto_font.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/painting.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/path.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/path_metrics.dart @@ -1111,6 +1162,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/picture_recorder.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/platform_message.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/raster_cache.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/rasterizer.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/renderer.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/shader.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/skia_object_cache.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/canvaskit/surface.dart @@ -1126,6 +1178,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/dom.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/embedder.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/engine_canvas.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/font_change_util.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/fonts.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/frame_reference.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/host_node.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/backdrop_filter.dart @@ -1154,6 +1207,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/picture.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/platform_view.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/recording_canvas.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/render_vertices.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/renderer.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/scene.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/scene_builder.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/shader_mask.dart @@ -1188,6 +1242,7 @@ 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/pointer_converter.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/profiler.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/renderer.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/rrect_renderer.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/safe_browser_api.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics.dart @@ -1641,6 +1696,7 @@ FILE: ../../../flutter/shell/platform/common/geometry.h FILE: ../../../flutter/shell/platform/common/geometry_unittests.cc FILE: ../../../flutter/shell/platform/common/incoming_message_dispatcher.cc FILE: ../../../flutter/shell/platform/common/incoming_message_dispatcher.h +FILE: ../../../flutter/shell/platform/common/incoming_message_dispatcher_unittests.cc FILE: ../../../flutter/shell/platform/common/json_message_codec.cc FILE: ../../../flutter/shell/platform/common/json_message_codec.h FILE: ../../../flutter/shell/platform/common/json_message_codec_unittests.cc @@ -1652,6 +1708,7 @@ FILE: ../../../flutter/shell/platform/common/path_utils.h FILE: ../../../flutter/shell/platform/common/path_utils_unittests.cc FILE: ../../../flutter/shell/platform/common/platform_provided_menu.h FILE: ../../../flutter/shell/platform/common/public/flutter_export.h +FILE: ../../../flutter/shell/platform/common/public/flutter_macros.h FILE: ../../../flutter/shell/platform/common/public/flutter_messenger.h FILE: ../../../flutter/shell/platform/common/public/flutter_plugin_registrar.h FILE: ../../../flutter/shell/platform/common/public/flutter_texture_registrar.h @@ -1768,6 +1825,7 @@ FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterView.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterView.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm +FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest_mrc.mm FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterViewResponder.h FILE: ../../../flutter/shell/platform/darwin/ios/framework/Source/FlutterViewTest.mm @@ -2077,18 +2135,6 @@ FILE: ../../../flutter/shell/platform/fuchsia/flutter/gfx_platform_view.cc FILE: ../../../flutter/shell/platform/fuchsia/flutter/gfx_platform_view.h FILE: ../../../flutter/shell/platform/fuchsia/flutter/gfx_session_connection.cc FILE: ../../../flutter/shell/platform/fuchsia/flutter/gfx_session_connection.h -FILE: ../../../flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/child-view2/child_view2.dart -FILE: ../../../flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/child-view2/meta/child-view2.cmx -FILE: ../../../flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/flutter-embedder-test2.cc -FILE: ../../../flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/flutter-embedder-test2.h -FILE: ../../../flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/meta/flutter-embedder-test2.cmx -FILE: ../../../flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/parent-view2/meta/parent-view2.cmx -FILE: ../../../flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/parent-view2/parent_view2.dart -FILE: ../../../flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing/src/lib/ui/base_view/base_view.cc -FILE: ../../../flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing/src/lib/ui/base_view/base_view.h -FILE: ../../../flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing/src/lib/ui/base_view/embedded_view_utils.cc -FILE: ../../../flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing/src/lib/ui/base_view/embedded_view_utils.h -FILE: ../../../flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing/src/lib/ui/base_view/math.h FILE: ../../../flutter/shell/platform/fuchsia/flutter/isolate_configurator.cc FILE: ../../../flutter/shell/platform/fuchsia/flutter/isolate_configurator.h FILE: ../../../flutter/shell/platform/fuchsia/flutter/kernel/extract_far.dart @@ -2300,6 +2346,8 @@ FILE: ../../../flutter/shell/platform/linux/fl_task_runner.h FILE: ../../../flutter/shell/platform/linux/fl_text_input_plugin.cc FILE: ../../../flutter/shell/platform/linux/fl_text_input_plugin.h FILE: ../../../flutter/shell/platform/linux/fl_text_input_plugin_test.cc +FILE: ../../../flutter/shell/platform/linux/fl_text_input_view_delegate.cc +FILE: ../../../flutter/shell/platform/linux/fl_text_input_view_delegate.h FILE: ../../../flutter/shell/platform/linux/fl_texture.cc FILE: ../../../flutter/shell/platform/linux/fl_texture_gl.cc FILE: ../../../flutter/shell/platform/linux/fl_texture_gl_private.h @@ -2390,6 +2438,7 @@ FILE: ../../../flutter/shell/platform/windows/flutter_windows_engine_unittests.c FILE: ../../../flutter/shell/platform/windows/flutter_windows_texture_registrar.cc FILE: ../../../flutter/shell/platform/windows/flutter_windows_texture_registrar.h FILE: ../../../flutter/shell/platform/windows/flutter_windows_texture_registrar_unittests.cc +FILE: ../../../flutter/shell/platform/windows/flutter_windows_unittests.cc FILE: ../../../flutter/shell/platform/windows/flutter_windows_view.cc FILE: ../../../flutter/shell/platform/windows/flutter_windows_view.h FILE: ../../../flutter/shell/platform/windows/flutter_windows_view_unittests.cc @@ -2412,9 +2461,6 @@ FILE: ../../../flutter/shell/platform/windows/keyboard_utils_unittests.cc FILE: ../../../flutter/shell/platform/windows/platform_handler.cc FILE: ../../../flutter/shell/platform/windows/platform_handler.h FILE: ../../../flutter/shell/platform/windows/platform_handler_unittests.cc -FILE: ../../../flutter/shell/platform/windows/platform_handler_win32.cc -FILE: ../../../flutter/shell/platform/windows/platform_handler_win32.h -FILE: ../../../flutter/shell/platform/windows/platform_handler_win32_unittests.cc FILE: ../../../flutter/shell/platform/windows/public/flutter_windows.h FILE: ../../../flutter/shell/platform/windows/sequential_id_generator.cc FILE: ../../../flutter/shell/platform/windows/sequential_id_generator.h @@ -2428,10 +2474,8 @@ FILE: ../../../flutter/shell/platform/windows/system_utils_unittests.cc FILE: ../../../flutter/shell/platform/windows/task_runner.cc FILE: ../../../flutter/shell/platform/windows/task_runner.h FILE: ../../../flutter/shell/platform/windows/task_runner_unittests.cc -FILE: ../../../flutter/shell/platform/windows/task_runner_win32.cc -FILE: ../../../flutter/shell/platform/windows/task_runner_win32.h -FILE: ../../../flutter/shell/platform/windows/task_runner_win32_window.cc -FILE: ../../../flutter/shell/platform/windows/task_runner_win32_window.h +FILE: ../../../flutter/shell/platform/windows/task_runner_window.cc +FILE: ../../../flutter/shell/platform/windows/task_runner_window.h FILE: ../../../flutter/shell/platform/windows/text_input_manager.cc FILE: ../../../flutter/shell/platform/windows/text_input_manager.h FILE: ../../../flutter/shell/platform/windows/text_input_plugin.cc @@ -2447,6 +2491,8 @@ FILE: ../../../flutter/shell/platform/windows/window_proc_delegate_manager.h FILE: ../../../flutter/shell/platform/windows/window_proc_delegate_manager_unittests.cc FILE: ../../../flutter/shell/platform/windows/window_state.h FILE: ../../../flutter/shell/platform/windows/window_unittests.cc +FILE: ../../../flutter/shell/platform/windows/windows_proc_table.cc +FILE: ../../../flutter/shell/platform/windows/windows_proc_table.h FILE: ../../../flutter/shell/profiling/sampling_profiler.cc FILE: ../../../flutter/shell/profiling/sampling_profiler.h FILE: ../../../flutter/shell/profiling/sampling_profiler_unittest.cc @@ -3217,6 +3263,8 @@ ORIGIN: ../../../third_party/icu/scripts/LICENSE TYPE: LicenseType.bsd FILE: ../../../flutter/third_party/accessibility/ax/ax_tree_data.cc FILE: ../../../flutter/third_party/accessibility/ax/ax_tree_data.h +FILE: ../../../flutter/third_party/accessibility/ax/platform/ax_platform_node_mac_unittest.h +FILE: ../../../flutter/third_party/accessibility/ax/platform/ax_platform_node_mac_unittest.mm FILE: ../../../flutter/third_party/accessibility/ax/platform/ax_platform_node_win.cc FILE: ../../../flutter/third_party/accessibility/ax/platform/ax_platform_node_win.h FILE: ../../../flutter/third_party/accessibility/ax/platform/ax_platform_node_win_unittest.cc diff --git a/ci/licenses_golden/licenses_fuchsia b/ci/licenses_golden/licenses_fuchsia index 037552e059f96..9fb23e4a550a3 100644 --- a/ci/licenses_golden/licenses_fuchsia +++ b/ci/licenses_golden/licenses_fuchsia @@ -1,4 +1,4 @@ -Signature: f84c656031e13f699bfe7293ac52fd9e +Signature: 6e8c8027dce5590b0915f65a18ff891f UNUSED LICENSES: @@ -553,14 +553,18 @@ 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.input3/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.lifecycle/meta.json +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.observation.geometry/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.pointer.augment/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.pointer/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.pointerinjector/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.test.input/meta.json +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.test.scene/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.types/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.views/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ultrasound/meta.json +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.unknown/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.update.channel/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.update.channelcontrol/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.update.config/meta.json @@ -1304,14 +1308,18 @@ 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.input3/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.lifecycle/meta.json +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.observation.geometry/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.pointer.augment/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.pointer/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.pointerinjector/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.test.input/meta.json +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.test.scene/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.types/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.views/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ultrasound/meta.json +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.unknown/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.update.channel/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.update.channelcontrol/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.update.config/meta.json @@ -1952,14 +1960,18 @@ 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.input3/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.lifecycle/meta.json +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.observation.geometry/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.pointer.augment/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.pointer/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.pointerinjector/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.test.input/meta.json +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.test.scene/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.types/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.views/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ultrasound/meta.json +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.unknown/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.update.channel/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.update.channelcontrol/meta.json FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.update.config/meta.json @@ -2327,6 +2339,7 @@ FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.settings/keyboard.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.thermal/client_state.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.composition/allocator.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.composition/flatland.fidl +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.observation.geometry/watcher.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.views/flatland_tokens.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.wlan.common/wlan_common.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.wlan.ieee80211/constants.fidl @@ -2395,7 +2408,6 @@ TYPE: LicenseType.bsd FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/boot/bootfs.h FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/boot/driver-config.h FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/boot/image.h -FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/boot/sysconfig.h FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/dlfcn.h FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/features.h FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/hw/pci.h @@ -2407,7 +2419,6 @@ FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/time.h FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/boot/bootfs.h FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/boot/driver-config.h FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/boot/image.h -FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/boot/sysconfig.h FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/dlfcn.h FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/features.h FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/hw/pci.h @@ -2853,174 +2864,6 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================================================== -==================================================================================================== -LIBRARY: fuchsia_sdk -ORIGIN: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/boot/multiboot.h + ../../../fuchsia/sdk/linux/LICENSE -TYPE: LicenseType.bsd -FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/boot/multiboot.h -FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/boot/multiboot.h ----------------------------------------------------------------------------------------------------- -Copyright 2016 The Fuchsia Authors. All rights reserved. -Copyright (c) 2009 Corey Tabaka - -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. - -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: fuchsia_sdk -ORIGIN: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/boot/netboot.h + ../../../fuchsia/sdk/linux/LICENSE -TYPE: LicenseType.bsd -FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/assert.h -FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/boot/netboot.h -FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/compiler.h -FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/errors.h -FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/hw/i2c.h -FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/hw/usb.h -FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/hw/usb/audio.h -FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/hw/usb/hid.h -FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/hw/usb/ums.h -FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/listnode.h -FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/pixelformat.h -FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/processargs.h -FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/status.h -FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/syscalls.h -FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/syscalls/debug.h -FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/syscalls/exception.h -FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/syscalls/log.h -FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/syscalls/object.h -FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/syscalls/pci.h -FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/syscalls/port.h -FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/syscalls/profile.h -FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/syscalls/resource.h -FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/syscalls/types.h -FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/types.h -FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/assert.h -FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/boot/netboot.h -FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/compiler.h -FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/errors.h -FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/hw/i2c.h -FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/hw/usb.h -FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/hw/usb/audio.h -FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/hw/usb/hid.h -FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/hw/usb/ums.h -FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/listnode.h -FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/pixelformat.h -FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/processargs.h -FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/status.h -FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/syscalls.h -FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/syscalls/debug.h -FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/syscalls/exception.h -FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/syscalls/log.h -FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/syscalls/object.h -FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/syscalls/pci.h -FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/syscalls/port.h -FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/syscalls/profile.h -FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/syscalls/resource.h -FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/syscalls/types.h -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.component.runner/component_runner.fidl -FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.fonts/font_provider.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 -FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.media/audio.fidl -FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.modular/module_context.fidl -FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.modular/session_shell.fidl -FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.modular/story_controller.fidl -FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.modular/story_info.fidl -FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.modular/story_provider.fidl -FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.sys/component_controller.fidl -FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.sys/environment.fidl -FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.sys/environment_controller.fidl -FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.sys/launcher.fidl -FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.sys/loader.fidl -FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.sys/runner.fidl -FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.tracing.provider/provider.fidl -FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.policy/presenter.fidl -FILE: ../../../fuchsia/sdk/linux/pkg/fdio/include/lib/fdio/fdio.h -FILE: ../../../fuchsia/sdk/linux/pkg/fdio/include/lib/fdio/io.h -FILE: ../../../fuchsia/sdk/linux/pkg/fdio/include/lib/fdio/vfs.h -FILE: ../../../fuchsia/sdk/linux/pkg/fdio/include/lib/fdio/watcher.h -FILE: ../../../fuchsia/sdk/linux/pkg/fit/include/lib/fit/thread_checker.h -FILE: ../../../fuchsia/sdk/linux/pkg/media_cpp_no_converters/include/lib/media/cpp/timeline_function.h -FILE: ../../../fuchsia/sdk/linux/pkg/media_cpp_no_converters/include/lib/media/cpp/timeline_rate.h -FILE: ../../../fuchsia/sdk/linux/pkg/media_cpp_no_converters/timeline_function.cc -FILE: ../../../fuchsia/sdk/linux/pkg/media_cpp_no_converters/timeline_rate.cc -FILE: ../../../fuchsia/sdk/linux/pkg/sync/include/lib/sync/completion.h -FILE: ../../../fuchsia/sdk/linux/pkg/sys_cpp/include/lib/sys/cpp/termination_reason.h -FILE: ../../../fuchsia/sdk/linux/pkg/zx/channel.cc -FILE: ../../../fuchsia/sdk/linux/pkg/zx/event.cc -FILE: ../../../fuchsia/sdk/linux/pkg/zx/eventpair.cc -FILE: ../../../fuchsia/sdk/linux/pkg/zx/include/lib/zx/channel.h -FILE: ../../../fuchsia/sdk/linux/pkg/zx/include/lib/zx/event.h -FILE: ../../../fuchsia/sdk/linux/pkg/zx/include/lib/zx/eventpair.h -FILE: ../../../fuchsia/sdk/linux/pkg/zx/include/lib/zx/job.h -FILE: ../../../fuchsia/sdk/linux/pkg/zx/include/lib/zx/object.h -FILE: ../../../fuchsia/sdk/linux/pkg/zx/include/lib/zx/object_traits.h -FILE: ../../../fuchsia/sdk/linux/pkg/zx/include/lib/zx/port.h -FILE: ../../../fuchsia/sdk/linux/pkg/zx/include/lib/zx/process.h -FILE: ../../../fuchsia/sdk/linux/pkg/zx/include/lib/zx/socket.h -FILE: ../../../fuchsia/sdk/linux/pkg/zx/include/lib/zx/task.h -FILE: ../../../fuchsia/sdk/linux/pkg/zx/include/lib/zx/thread.h -FILE: ../../../fuchsia/sdk/linux/pkg/zx/include/lib/zx/time.h -FILE: ../../../fuchsia/sdk/linux/pkg/zx/include/lib/zx/vmar.h -FILE: ../../../fuchsia/sdk/linux/pkg/zx/include/lib/zx/vmo.h -FILE: ../../../fuchsia/sdk/linux/pkg/zx/job.cc -FILE: ../../../fuchsia/sdk/linux/pkg/zx/port.cc -FILE: ../../../fuchsia/sdk/linux/pkg/zx/process.cc -FILE: ../../../fuchsia/sdk/linux/pkg/zx/socket.cc -FILE: ../../../fuchsia/sdk/linux/pkg/zx/thread.cc -FILE: ../../../fuchsia/sdk/linux/pkg/zx/vmar.cc -FILE: ../../../fuchsia/sdk/linux/pkg/zx/vmo.cc ----------------------------------------------------------------------------------------------------- -Copyright 2016 The Fuchsia Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - -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: fuchsia_sdk ORIGIN: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/device/audio.h + ../../../fuchsia/sdk/linux/LICENSE @@ -3258,7 +3101,6 @@ FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.bluetooth.sys/access.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.bluetooth.sys/bootstrap.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.bluetooth.sys/host_watcher.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.bluetooth.sys/identity.fidl -FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.bluetooth.sys/pairing_delegate.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.bluetooth.sys/peer.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.bluetooth/address.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.bluetooth/appearance.fidl @@ -3308,13 +3150,12 @@ FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.input.report/touch.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.input.report/units.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.inspect/tree.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.intl/property_provider.fidl -FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.io/connection-info.fidl -FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.io/connection-options.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.io/directory2.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.io/file2.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.io/io.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.io/node-protocols.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.io/node2.fidl +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.io/rights-request.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.kernel/kernel-counter.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.kernel/kernel-debug.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.kernel/kernel-stats.fidl @@ -3475,7 +3316,6 @@ FILE: ../../../fuchsia/sdk/linux/pkg/vfs_cpp/include/lib/vfs/cpp/internal/file_c FILE: ../../../fuchsia/sdk/linux/pkg/vfs_cpp/include/lib/vfs/cpp/internal/node.h FILE: ../../../fuchsia/sdk/linux/pkg/vfs_cpp/include/lib/vfs/cpp/internal/node_connection.h FILE: ../../../fuchsia/sdk/linux/pkg/vfs_cpp/include/lib/vfs/cpp/lazy_dir.h -FILE: ../../../fuchsia/sdk/linux/pkg/vfs_cpp/include/lib/vfs/cpp/node_kind.h FILE: ../../../fuchsia/sdk/linux/pkg/vfs_cpp/include/lib/vfs/cpp/pseudo_dir.h FILE: ../../../fuchsia/sdk/linux/pkg/vfs_cpp/include/lib/vfs/cpp/pseudo_file.h FILE: ../../../fuchsia/sdk/linux/pkg/vfs_cpp/include/lib/vfs/cpp/remote_dir.h @@ -3526,6 +3366,136 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================================================== +==================================================================================================== +LIBRARY: fuchsia_sdk +ORIGIN: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/hw/usb/audio.h + ../../../fuchsia/sdk/linux/LICENSE +TYPE: LicenseType.bsd +FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/assert.h +FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/compiler.h +FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/errors.h +FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/hw/usb.h +FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/hw/usb/audio.h +FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/hw/usb/hid.h +FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/hw/usb/ums.h +FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/listnode.h +FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/pixelformat.h +FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/processargs.h +FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/status.h +FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/syscalls.h +FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/syscalls/debug.h +FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/syscalls/exception.h +FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/syscalls/log.h +FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/syscalls/object.h +FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/syscalls/pci.h +FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/syscalls/port.h +FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/syscalls/profile.h +FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/syscalls/resource.h +FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/syscalls/types.h +FILE: ../../../fuchsia/sdk/linux/arch/arm64/sysroot/include/zircon/types.h +FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/assert.h +FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/compiler.h +FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/errors.h +FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/hw/usb.h +FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/hw/usb/audio.h +FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/hw/usb/hid.h +FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/hw/usb/ums.h +FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/listnode.h +FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/pixelformat.h +FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/processargs.h +FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/status.h +FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/syscalls.h +FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/syscalls/debug.h +FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/syscalls/exception.h +FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/syscalls/log.h +FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/syscalls/object.h +FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/syscalls/pci.h +FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/syscalls/port.h +FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/syscalls/profile.h +FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/syscalls/resource.h +FILE: ../../../fuchsia/sdk/linux/arch/x64/sysroot/include/zircon/syscalls/types.h +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.component.runner/component_runner.fidl +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.fonts/font_provider.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 +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.media/audio.fidl +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.modular/module_context.fidl +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.modular/session_shell.fidl +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.modular/story_controller.fidl +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.modular/story_info.fidl +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.modular/story_provider.fidl +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.sys/component_controller.fidl +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.sys/environment.fidl +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.sys/environment_controller.fidl +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.sys/launcher.fidl +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.sys/loader.fidl +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.sys/runner.fidl +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.tracing.provider/provider.fidl +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.policy/presenter.fidl +FILE: ../../../fuchsia/sdk/linux/pkg/fdio/include/lib/fdio/fdio.h +FILE: ../../../fuchsia/sdk/linux/pkg/fdio/include/lib/fdio/io.h +FILE: ../../../fuchsia/sdk/linux/pkg/fdio/include/lib/fdio/vfs.h +FILE: ../../../fuchsia/sdk/linux/pkg/fdio/include/lib/fdio/watcher.h +FILE: ../../../fuchsia/sdk/linux/pkg/fit/include/lib/fit/thread_checker.h +FILE: ../../../fuchsia/sdk/linux/pkg/media_cpp_no_converters/include/lib/media/cpp/timeline_function.h +FILE: ../../../fuchsia/sdk/linux/pkg/media_cpp_no_converters/include/lib/media/cpp/timeline_rate.h +FILE: ../../../fuchsia/sdk/linux/pkg/media_cpp_no_converters/timeline_function.cc +FILE: ../../../fuchsia/sdk/linux/pkg/media_cpp_no_converters/timeline_rate.cc +FILE: ../../../fuchsia/sdk/linux/pkg/sync/include/lib/sync/completion.h +FILE: ../../../fuchsia/sdk/linux/pkg/sys_cpp/include/lib/sys/cpp/termination_reason.h +FILE: ../../../fuchsia/sdk/linux/pkg/zx/channel.cc +FILE: ../../../fuchsia/sdk/linux/pkg/zx/event.cc +FILE: ../../../fuchsia/sdk/linux/pkg/zx/eventpair.cc +FILE: ../../../fuchsia/sdk/linux/pkg/zx/include/lib/zx/channel.h +FILE: ../../../fuchsia/sdk/linux/pkg/zx/include/lib/zx/event.h +FILE: ../../../fuchsia/sdk/linux/pkg/zx/include/lib/zx/eventpair.h +FILE: ../../../fuchsia/sdk/linux/pkg/zx/include/lib/zx/job.h +FILE: ../../../fuchsia/sdk/linux/pkg/zx/include/lib/zx/object.h +FILE: ../../../fuchsia/sdk/linux/pkg/zx/include/lib/zx/object_traits.h +FILE: ../../../fuchsia/sdk/linux/pkg/zx/include/lib/zx/port.h +FILE: ../../../fuchsia/sdk/linux/pkg/zx/include/lib/zx/process.h +FILE: ../../../fuchsia/sdk/linux/pkg/zx/include/lib/zx/socket.h +FILE: ../../../fuchsia/sdk/linux/pkg/zx/include/lib/zx/task.h +FILE: ../../../fuchsia/sdk/linux/pkg/zx/include/lib/zx/thread.h +FILE: ../../../fuchsia/sdk/linux/pkg/zx/include/lib/zx/time.h +FILE: ../../../fuchsia/sdk/linux/pkg/zx/include/lib/zx/vmar.h +FILE: ../../../fuchsia/sdk/linux/pkg/zx/include/lib/zx/vmo.h +FILE: ../../../fuchsia/sdk/linux/pkg/zx/job.cc +FILE: ../../../fuchsia/sdk/linux/pkg/zx/port.cc +FILE: ../../../fuchsia/sdk/linux/pkg/zx/process.cc +FILE: ../../../fuchsia/sdk/linux/pkg/zx/socket.cc +FILE: ../../../fuchsia/sdk/linux/pkg/zx/thread.cc +FILE: ../../../fuchsia/sdk/linux/pkg/zx/vmar.cc +FILE: ../../../fuchsia/sdk/linux/pkg/zx/vmo.cc +---------------------------------------------------------------------------------------------------- +Copyright 2016 The Fuchsia Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + +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: fuchsia_sdk ORIGIN: ../../../fuchsia/sdk/linux/dart/fidl/lib/fidl.dart + ../../../LICENSE @@ -3617,7 +3587,6 @@ FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.images/overview.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.input.report/overview.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.input/overview.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.intl/overview.fidl -FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.io/overview.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.kernel/overview.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.legacymetrics/overview.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.lightsensor/overview.fidl @@ -3665,11 +3634,18 @@ FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.composition/screenshot.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.gfx/overview.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.input/overview.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.input3/overview.fidl +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.observation.geometry/overview.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.pointer/overview.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.pointerinjector/overview.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.policy/overview.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.scenic/overview.fidl +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.test.input/overview.fidl +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.test.input/registry.fidl +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.test.input/touch.fidl +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.test.scene/controller.fidl +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.test.scene/overview.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.ui.views/overview.fidl +FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.unknown/unknown.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.update.config/config.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.update/overview.fidl FILE: ../../../fuchsia/sdk/linux/fidl/fuchsia.weave/overview.fidl @@ -3686,11 +3662,12 @@ FILE: ../../../fuchsia/sdk/linux/pkg/async/include/lib/async/sequence_id.h FILE: ../../../fuchsia/sdk/linux/pkg/fidl_base/include/lib/fidl/cpp/transaction_header.h FILE: ../../../fuchsia/sdk/linux/pkg/fidl_base/include/lib/fidl/cpp/transport_err.h FILE: ../../../fuchsia/sdk/linux/pkg/fidl_base/include/lib/fidl/cpp/wire_format_metadata.h -FILE: ../../../fuchsia/sdk/linux/pkg/fidl_cpp/include/lib/fidl/cpp/internal/transport_err_hlcpp.h +FILE: ../../../fuchsia/sdk/linux/pkg/fidl_cpp/include/lib/fidl/cpp/unknown_interactions_hlcpp.h FILE: ../../../fuchsia/sdk/linux/pkg/fit/include/lib/fit/inline_any.h FILE: ../../../fuchsia/sdk/linux/pkg/fit/include/lib/fit/inline_any_internal.h FILE: ../../../fuchsia/sdk/linux/pkg/inspect/client.shard.cml FILE: ../../../fuchsia/sdk/linux/pkg/stdcompat/include/lib/stdcompat/internal/linkage.h +FILE: ../../../fuchsia/sdk/linux/pkg/stdcompat/include/lib/stdcompat/internal/variant.h FILE: ../../../fuchsia/sdk/linux/pkg/sys/component/realm_builder_absolute.shard.cml FILE: ../../../fuchsia/sdk/linux/pkg/sys/component/realm_builder_base.shard.cml FILE: ../../../fuchsia/sdk/linux/pkg/sys_component_cpp_testing/internal/convert.cc @@ -3860,4 +3837,4 @@ 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. ==================================================================================================== -Total license count: 17 +Total license count: 16 diff --git a/ci/licenses_golden/licenses_skia b/ci/licenses_golden/licenses_skia index f30bdf768ad22..7fbb2b093d167 100644 --- a/ci/licenses_golden/licenses_skia +++ b/ci/licenses_golden/licenses_skia @@ -1,4 +1,4 @@ -Signature: 55ba1fcdf9cf335e810af15c73b9254e +Signature: 1a0bb0241ba71bb09e074a4cb1724a73 UNUSED LICENSES: @@ -802,7 +802,6 @@ FILE: ../../../third_party/skia/go.sum FILE: ../../../third_party/skia/go_repositories.bzl FILE: ../../../third_party/skia/include/BUILD.bazel FILE: ../../../third_party/skia/include/android/BUILD.bazel -FILE: ../../../third_party/skia/include/c/BUILD.bazel FILE: ../../../third_party/skia/include/codec/BUILD.bazel FILE: ../../../third_party/skia/include/config/BUILD.bazel FILE: ../../../third_party/skia/include/core/BUILD.bazel @@ -826,6 +825,7 @@ FILE: ../../../third_party/skia/include/private/BUILD.bazel FILE: ../../../third_party/skia/include/private/chromium/BUILD.bazel FILE: ../../../third_party/skia/include/private/gpu/BUILD.bazel FILE: ../../../third_party/skia/include/private/gpu/ganesh/BUILD.bazel +FILE: ../../../third_party/skia/include/private/gpu/vk/BUILD.bazel FILE: ../../../third_party/skia/include/sksl/BUILD.bazel FILE: ../../../third_party/skia/include/svg/BUILD.bazel FILE: ../../../third_party/skia/include/utils/BUILD.bazel @@ -839,6 +839,8 @@ FILE: ../../../third_party/skia/infra/bots/assets/armhf_sysroot/VERSION FILE: ../../../third_party/skia/infra/bots/assets/bazel/VERSION FILE: ../../../third_party/skia/infra/bots/assets/bazel_build_task_driver/VERSION FILE: ../../../third_party/skia/infra/bots/assets/bazelisk/VERSION +FILE: ../../../third_party/skia/infra/bots/assets/bazelisk_mac_arm64/VERSION +FILE: ../../../third_party/skia/infra/bots/assets/binutils_linux_x64/VERSION FILE: ../../../third_party/skia/infra/bots/assets/bloaty/VERSION FILE: ../../../third_party/skia/infra/bots/assets/cast_toolchain/VERSION FILE: ../../../third_party/skia/infra/bots/assets/ccache_linux/VERSION @@ -890,6 +892,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-Debian10-Clang-arm64-Debug-Android_FrameworkWorkarounds.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian10-Clang-arm64-Debug-Android_HWASAN.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian10-Clang-arm64-Release-Android_Wuffs.json +FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian10-Clang-x86_64-Debug-AVIF.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian10-Clang-x86_64-Debug-Chromebook_GLES.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian10-Clang-x86_64-Debug-Coverage.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian10-Clang-x86_64-Debug-MSAN.json @@ -903,6 +906,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-Debian10-Clang-x86_64-OptimizeForSize.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian10-Clang-x86_64-Release-ANGLE.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian10-Clang-x86_64-Release-ASAN.json +FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian10-Clang-x86_64-Release-AVIF.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian10-Clang-x86_64-Release-CMake.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian10-Clang-x86_64-Release-Fast.json FILE: ../../../third_party/skia/infra/bots/recipe_modules/build/examples/full.expected/Build-Debian10-Clang-x86_64-Release-NoDEPS.json @@ -1277,7 +1281,6 @@ 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/BUILD.bazel FILE: ../../../third_party/skia/src/android/BUILD.bazel -FILE: ../../../third_party/skia/src/c/BUILD.bazel FILE: ../../../third_party/skia/src/codec/BUILD.bazel FILE: ../../../third_party/skia/src/core/BUILD.bazel FILE: ../../../third_party/skia/src/core/SkOrderedReadBuffer.h @@ -1306,7 +1309,6 @@ FILE: ../../../third_party/skia/src/gpu/ganesh/mtl/BUILD.bazel FILE: ../../../third_party/skia/src/gpu/ganesh/ops/BUILD.bazel FILE: ../../../third_party/skia/src/gpu/ganesh/tessellate/BUILD.bazel FILE: ../../../third_party/skia/src/gpu/ganesh/text/BUILD.bazel -FILE: ../../../third_party/skia/src/gpu/ganesh/v1/BUILD.bazel FILE: ../../../third_party/skia/src/gpu/ganesh/vk/BUILD.bazel FILE: ../../../third_party/skia/src/gpu/tessellate/BUILD.bazel FILE: ../../../third_party/skia/src/gpu/vk/BUILD.bazel @@ -1317,8 +1319,6 @@ FILE: ../../../third_party/skia/src/opts/BUILD.bazel FILE: ../../../third_party/skia/src/pathops/BUILD.bazel FILE: ../../../third_party/skia/src/pdf/BUILD.bazel FILE: ../../../third_party/skia/src/ports/BUILD.bazel -FILE: ../../../third_party/skia/src/ports/SkTLS_pthread.cpp -FILE: ../../../third_party/skia/src/ports/SkTLS_win.cpp FILE: ../../../third_party/skia/src/sfnt/BUILD.bazel FILE: ../../../third_party/skia/src/shaders/BUILD.bazel FILE: ../../../third_party/skia/src/shaders/gradients/BUILD.bazel @@ -1556,6 +1556,7 @@ FILE: ../../../third_party/skia/src/effects/SkColorMatrix.cpp FILE: ../../../third_party/skia/src/effects/SkColorMatrixFilter.cpp FILE: ../../../third_party/skia/src/effects/SkLayerDrawLooper.cpp FILE: ../../../third_party/skia/src/effects/SkTableMaskFilter.cpp +FILE: ../../../third_party/skia/src/gpu/ganesh/Device.cpp FILE: ../../../third_party/skia/src/gpu/ganesh/GrAttachment.cpp FILE: ../../../third_party/skia/src/gpu/ganesh/GrAttachment.h FILE: ../../../third_party/skia/src/gpu/ganesh/GrGpu.h @@ -1567,6 +1568,10 @@ FILE: ../../../third_party/skia/src/gpu/ganesh/GrRenderTarget.h FILE: ../../../third_party/skia/src/gpu/ganesh/GrStencilSettings.cpp FILE: ../../../third_party/skia/src/gpu/ganesh/GrTexture.cpp FILE: ../../../third_party/skia/src/gpu/ganesh/GrTexture.h +FILE: ../../../third_party/skia/src/gpu/ganesh/PathRenderer.cpp +FILE: ../../../third_party/skia/src/gpu/ganesh/PathRenderer.h +FILE: ../../../third_party/skia/src/gpu/ganesh/PathRendererChain.cpp +FILE: ../../../third_party/skia/src/gpu/ganesh/PathRendererChain.h FILE: ../../../third_party/skia/src/gpu/ganesh/geometry/GrPathUtils.cpp FILE: ../../../third_party/skia/src/gpu/ganesh/geometry/GrPathUtils.h FILE: ../../../third_party/skia/src/gpu/ganesh/gl/GrGLAttachment.cpp @@ -1592,11 +1597,6 @@ FILE: ../../../third_party/skia/src/gpu/ganesh/ops/AAHairLinePathRenderer.cpp FILE: ../../../third_party/skia/src/gpu/ganesh/ops/AAHairLinePathRenderer.h FILE: ../../../third_party/skia/src/gpu/ganesh/ops/DefaultPathRenderer.cpp FILE: ../../../third_party/skia/src/gpu/ganesh/ops/DefaultPathRenderer.h -FILE: ../../../third_party/skia/src/gpu/ganesh/v1/Device.cpp -FILE: ../../../third_party/skia/src/gpu/ganesh/v1/PathRenderer.cpp -FILE: ../../../third_party/skia/src/gpu/ganesh/v1/PathRenderer.h -FILE: ../../../third_party/skia/src/gpu/ganesh/v1/PathRendererChain.cpp -FILE: ../../../third_party/skia/src/gpu/ganesh/v1/PathRendererChain.h FILE: ../../../third_party/skia/src/gpu/gl/GrGLDefines.h FILE: ../../../third_party/skia/src/pdf/SkPDFDevice.cpp FILE: ../../../third_party/skia/src/pdf/SkPDFDevice.h @@ -1689,8 +1689,6 @@ FILE: ../../../third_party/skia/dm/DMJsonWriter.h FILE: ../../../third_party/skia/gm/aaa.cpp FILE: ../../../third_party/skia/gm/beziers.cpp FILE: ../../../third_party/skia/gm/blurcircles.cpp -FILE: ../../../third_party/skia/gm/cgm.c -FILE: ../../../third_party/skia/gm/cgms.cpp FILE: ../../../third_party/skia/gm/clipdrawdraw.cpp FILE: ../../../third_party/skia/gm/coloremoji_blendmodes.cpp FILE: ../../../third_party/skia/gm/colorfilters.cpp @@ -1726,17 +1724,6 @@ FILE: ../../../third_party/skia/gm/textblobshader.cpp FILE: ../../../third_party/skia/gm/tiledscaledbitmap.cpp FILE: ../../../third_party/skia/gm/variedtext.cpp FILE: ../../../third_party/skia/gm/yuvtorgbsubset.cpp -FILE: ../../../third_party/skia/include/c/sk_canvas.h -FILE: ../../../third_party/skia/include/c/sk_data.h -FILE: ../../../third_party/skia/include/c/sk_image.h -FILE: ../../../third_party/skia/include/c/sk_maskfilter.h -FILE: ../../../third_party/skia/include/c/sk_matrix.h -FILE: ../../../third_party/skia/include/c/sk_paint.h -FILE: ../../../third_party/skia/include/c/sk_path.h -FILE: ../../../third_party/skia/include/c/sk_picture.h -FILE: ../../../third_party/skia/include/c/sk_shader.h -FILE: ../../../third_party/skia/include/c/sk_surface.h -FILE: ../../../third_party/skia/include/c/sk_types.h FILE: ../../../third_party/skia/include/core/SkBBHFactory.h FILE: ../../../third_party/skia/include/core/SkBlurTypes.h FILE: ../../../third_party/skia/include/core/SkDrawable.h @@ -1749,7 +1736,6 @@ FILE: ../../../third_party/skia/include/ports/SkFontMgr_indirect.h FILE: ../../../third_party/skia/include/ports/SkRemotableFontMgr.h FILE: ../../../third_party/skia/include/private/SkHalf.h FILE: ../../../third_party/skia/samplecode/SampleRectanizer.cpp -FILE: ../../../third_party/skia/src/c/sk_surface.cpp FILE: ../../../third_party/skia/src/core/SkBBHFactory.cpp FILE: ../../../third_party/skia/src/core/SkBitmapCache.cpp FILE: ../../../third_party/skia/src/core/SkBitmapCache.h @@ -2056,7 +2042,6 @@ FILE: ../../../third_party/skia/src/core/SkAutoMalloc.h FILE: ../../../third_party/skia/src/core/SkAutoPixmapStorage.cpp FILE: ../../../third_party/skia/src/core/SkAutoPixmapStorage.h FILE: ../../../third_party/skia/src/core/SkBlendModePriv.h -FILE: ../../../third_party/skia/src/core/SkColorFilter_Matrix.h FILE: ../../../third_party/skia/src/core/SkColorSpace.cpp FILE: ../../../third_party/skia/src/core/SkColorSpacePriv.h FILE: ../../../third_party/skia/src/core/SkCpu.cpp @@ -2071,7 +2056,6 @@ 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/SkMatrixPriv.h -FILE: ../../../third_party/skia/src/core/SkModeColorFilter.h FILE: ../../../third_party/skia/src/core/SkOverdrawCanvas.cpp FILE: ../../../third_party/skia/src/core/SkPathMeasurePriv.h FILE: ../../../third_party/skia/src/core/SkRasterPipeline.cpp @@ -2142,7 +2126,6 @@ FILE: ../../../third_party/skia/src/gpu/ganesh/vk/GrVkDescriptorSet.cpp FILE: ../../../third_party/skia/src/gpu/ganesh/vk/GrVkDescriptorSet.h FILE: ../../../third_party/skia/src/gpu/ganesh/vk/GrVkDescriptorSetManager.cpp FILE: ../../../third_party/skia/src/gpu/ganesh/vk/GrVkDescriptorSetManager.h -FILE: ../../../third_party/skia/src/gpu/ganesh/vk/GrVkExtensions.cpp FILE: ../../../third_party/skia/src/gpu/ganesh/vk/GrVkFramebuffer.cpp FILE: ../../../third_party/skia/src/gpu/ganesh/vk/GrVkFramebuffer.h FILE: ../../../third_party/skia/src/gpu/ganesh/vk/GrVkImageView.cpp @@ -2166,6 +2149,7 @@ FILE: ../../../third_party/skia/src/gpu/ganesh/vk/GrVkUniformHandler.cpp FILE: ../../../third_party/skia/src/gpu/ganesh/vk/GrVkUniformHandler.h FILE: ../../../third_party/skia/src/gpu/ganesh/vk/GrVkVaryingHandler.cpp FILE: ../../../third_party/skia/src/gpu/ganesh/vk/GrVkVaryingHandler.h +FILE: ../../../third_party/skia/src/gpu/vk/VulkanExtensions.cpp FILE: ../../../third_party/skia/src/images/SkImageEncoderPriv.h FILE: ../../../third_party/skia/src/opts/SkChecksum_opts.h FILE: ../../../third_party/skia/src/opts/SkOpts_avx.cpp @@ -2237,7 +2221,6 @@ FILE: ../../../third_party/skia/src/sksl/ir/SkSLTernaryExpression.h FILE: ../../../third_party/skia/src/sksl/ir/SkSLType.cpp FILE: ../../../third_party/skia/src/sksl/ir/SkSLType.h FILE: ../../../third_party/skia/src/sksl/ir/SkSLTypeReference.h -FILE: ../../../third_party/skia/src/sksl/ir/SkSLUnresolvedFunction.h FILE: ../../../third_party/skia/src/sksl/ir/SkSLVarDeclarations.h FILE: ../../../third_party/skia/src/sksl/ir/SkSLVariable.h FILE: ../../../third_party/skia/src/sksl/ir/SkSLVariableReference.h @@ -2530,7 +2513,6 @@ FILE: ../../../third_party/skia/include/gpu/graphite/Context.h FILE: ../../../third_party/skia/include/gpu/graphite/GraphiteTypes.h FILE: ../../../third_party/skia/include/gpu/graphite/Recorder.h FILE: ../../../third_party/skia/include/gpu/graphite/Recording.h -FILE: ../../../third_party/skia/include/gpu/graphite/SkStuff.h FILE: ../../../third_party/skia/include/gpu/graphite/TextureInfo.h FILE: ../../../third_party/skia/include/gpu/graphite/mtl/MtlBackendContext.h FILE: ../../../third_party/skia/include/gpu/graphite/mtl/MtlTypes.h @@ -2560,8 +2542,6 @@ FILE: ../../../third_party/skia/src/effects/imagefilters/SkRuntimeImageFilter.h FILE: ../../../third_party/skia/src/gpu/KeyBuilder.h FILE: ../../../third_party/skia/src/gpu/ResourceKey.cpp FILE: ../../../third_party/skia/src/gpu/ShaderErrorHandler.cpp -FILE: ../../../third_party/skia/src/gpu/ganesh/BaseDevice.cpp -FILE: ../../../third_party/skia/src/gpu/ganesh/BaseDevice.h FILE: ../../../third_party/skia/src/gpu/ganesh/GrDstProxyView.h FILE: ../../../third_party/skia/src/gpu/ganesh/GrEagerVertexAllocator.cpp FILE: ../../../third_party/skia/src/gpu/ganesh/GrMeshDrawTarget.cpp @@ -2593,7 +2573,6 @@ FILE: ../../../third_party/skia/src/gpu/graphite/Caps.h FILE: ../../../third_party/skia/src/gpu/graphite/CommandBuffer.cpp FILE: ../../../third_party/skia/src/gpu/graphite/CommandBuffer.h FILE: ../../../third_party/skia/src/gpu/graphite/Context.cpp -FILE: ../../../third_party/skia/src/gpu/graphite/ContextPriv.cpp FILE: ../../../third_party/skia/src/gpu/graphite/ContextPriv.h FILE: ../../../third_party/skia/src/gpu/graphite/ContextUtils.cpp FILE: ../../../third_party/skia/src/gpu/graphite/ContextUtils.h @@ -2611,8 +2590,6 @@ FILE: ../../../third_party/skia/src/gpu/graphite/DrawPass.h FILE: ../../../third_party/skia/src/gpu/graphite/DrawTypes.h FILE: ../../../third_party/skia/src/gpu/graphite/DrawWriter.cpp FILE: ../../../third_party/skia/src/gpu/graphite/DrawWriter.h -FILE: ../../../third_party/skia/src/gpu/graphite/Gpu.cpp -FILE: ../../../third_party/skia/src/gpu/graphite/Gpu.h FILE: ../../../third_party/skia/src/gpu/graphite/GpuWorkSubmission.h FILE: ../../../third_party/skia/src/gpu/graphite/GraphicsPipeline.cpp FILE: ../../../third_party/skia/src/gpu/graphite/GraphicsPipeline.h @@ -2629,7 +2606,8 @@ FILE: ../../../third_party/skia/src/gpu/graphite/Renderer.h FILE: ../../../third_party/skia/src/gpu/graphite/ResourceProvider.cpp FILE: ../../../third_party/skia/src/gpu/graphite/ResourceProvider.h FILE: ../../../third_party/skia/src/gpu/graphite/ResourceTypes.h -FILE: ../../../third_party/skia/src/gpu/graphite/SkStuff.cpp +FILE: ../../../third_party/skia/src/gpu/graphite/SharedContext.cpp +FILE: ../../../third_party/skia/src/gpu/graphite/SharedContext.h FILE: ../../../third_party/skia/src/gpu/graphite/Surface_Graphite.cpp FILE: ../../../third_party/skia/src/gpu/graphite/Surface_Graphite.h FILE: ../../../third_party/skia/src/gpu/graphite/Task.cpp @@ -2655,12 +2633,12 @@ FILE: ../../../third_party/skia/src/gpu/graphite/mtl/MtlCaps.h FILE: ../../../third_party/skia/src/gpu/graphite/mtl/MtlCaps.mm FILE: ../../../third_party/skia/src/gpu/graphite/mtl/MtlCommandBuffer.h FILE: ../../../third_party/skia/src/gpu/graphite/mtl/MtlCommandBuffer.mm -FILE: ../../../third_party/skia/src/gpu/graphite/mtl/MtlGpu.h -FILE: ../../../third_party/skia/src/gpu/graphite/mtl/MtlGpu.mm FILE: ../../../third_party/skia/src/gpu/graphite/mtl/MtlGraphicsPipeline.h FILE: ../../../third_party/skia/src/gpu/graphite/mtl/MtlGraphicsPipeline.mm FILE: ../../../third_party/skia/src/gpu/graphite/mtl/MtlResourceProvider.h FILE: ../../../third_party/skia/src/gpu/graphite/mtl/MtlResourceProvider.mm +FILE: ../../../third_party/skia/src/gpu/graphite/mtl/MtlSharedContext.h +FILE: ../../../third_party/skia/src/gpu/graphite/mtl/MtlSharedContext.mm FILE: ../../../third_party/skia/src/gpu/graphite/mtl/MtlTexture.h FILE: ../../../third_party/skia/src/gpu/graphite/mtl/MtlTexture.mm FILE: ../../../third_party/skia/src/gpu/graphite/mtl/MtlTypesPriv.mm @@ -2673,8 +2651,6 @@ FILE: ../../../third_party/skia/src/sksl/SkSLContext.cpp FILE: ../../../third_party/skia/src/sksl/SkSLIntrinsicList.h FILE: ../../../third_party/skia/src/sksl/SkSLMangler.h FILE: ../../../third_party/skia/src/sksl/SkSLOperator.cpp -FILE: ../../../third_party/skia/src/sksl/SkSLSharedCompiler.cpp -FILE: ../../../third_party/skia/src/sksl/SkSLSharedCompiler.h FILE: ../../../third_party/skia/src/sksl/SkSLThreadContext.h FILE: ../../../third_party/skia/src/sksl/analysis/SkSLCanExitWithoutReturningValue.cpp FILE: ../../../third_party/skia/src/sksl/analysis/SkSLCheckProgramStructure.cpp @@ -2797,7 +2773,6 @@ FILE: ../../../third_party/skia/client_utils/android/BitmapRegionDecoder.h FILE: ../../../third_party/skia/client_utils/android/BitmapRegionDecoderPriv.h FILE: ../../../third_party/skia/dm/DMSrcSink.cpp FILE: ../../../third_party/skia/dm/DMSrcSink.h -FILE: ../../../third_party/skia/experimental/c-api-example/skia-c-example.c FILE: ../../../third_party/skia/experimental/tools/coreGraphicsPdf2png.cpp FILE: ../../../third_party/skia/gm/aaxfermodes.cpp FILE: ../../../third_party/skia/gm/addarc.cpp @@ -2880,9 +2855,6 @@ FILE: ../../../third_party/skia/samplecode/SampleAnimatedText.cpp FILE: ../../../third_party/skia/samplecode/SampleAtlas.cpp FILE: ../../../third_party/skia/samplecode/SampleShip.cpp FILE: ../../../third_party/skia/samplecode/SampleXfer.cpp -FILE: ../../../third_party/skia/src/c/sk_c_from_to.h -FILE: ../../../third_party/skia/src/c/sk_paint.cpp -FILE: ../../../third_party/skia/src/c/sk_types_priv.h FILE: ../../../third_party/skia/src/codec/SkAndroidCodec.cpp FILE: ../../../third_party/skia/src/codec/SkAndroidCodecAdapter.cpp FILE: ../../../third_party/skia/src/codec/SkAndroidCodecAdapter.h @@ -2928,8 +2900,6 @@ FILE: ../../../third_party/skia/src/core/SkFontMgr.cpp FILE: ../../../third_party/skia/src/core/SkLatticeIter.cpp FILE: ../../../third_party/skia/src/core/SkLatticeIter.h FILE: ../../../third_party/skia/src/core/SkLocalMatrixImageFilter.cpp -FILE: ../../../third_party/skia/src/core/SkMiniRecorder.cpp -FILE: ../../../third_party/skia/src/core/SkMiniRecorder.h FILE: ../../../third_party/skia/src/core/SkMipmapAccessor.cpp FILE: ../../../third_party/skia/src/core/SkMipmapAccessor.h FILE: ../../../third_party/skia/src/core/SkNextID.h @@ -2937,7 +2907,6 @@ FILE: ../../../third_party/skia/src/core/SkOpts.cpp FILE: ../../../third_party/skia/src/core/SkOpts.h FILE: ../../../third_party/skia/src/core/SkPathBuilder.cpp FILE: ../../../third_party/skia/src/core/SkPathPriv.h -FILE: ../../../third_party/skia/src/core/SkPictureCommon.h FILE: ../../../third_party/skia/src/core/SkPictureImageGenerator.cpp FILE: ../../../third_party/skia/src/core/SkPixmap.cpp FILE: ../../../third_party/skia/src/core/SkPixmapPriv.h @@ -2959,6 +2928,7 @@ FILE: ../../../third_party/skia/src/core/SkYUVPlanesCache.h FILE: ../../../third_party/skia/src/effects/SkTableColorFilter.cpp FILE: ../../../third_party/skia/src/effects/imagefilters/SkImageImageFilter.cpp FILE: ../../../third_party/skia/src/gpu/Blend.cpp +FILE: ../../../third_party/skia/src/gpu/ganesh/Device_drawTexture.cpp FILE: ../../../third_party/skia/src/gpu/ganesh/GrAutoLocaleSetter.h FILE: ../../../third_party/skia/src/gpu/ganesh/GrBlurUtils.cpp FILE: ../../../third_party/skia/src/gpu/ganesh/GrBlurUtils.h @@ -2986,6 +2956,8 @@ FILE: ../../../third_party/skia/src/gpu/ganesh/GrTTopoSort.h FILE: ../../../third_party/skia/src/gpu/ganesh/GrTestUtils.cpp FILE: ../../../third_party/skia/src/gpu/ganesh/GrTestUtils.h FILE: ../../../third_party/skia/src/gpu/ganesh/GrXferProcessor.cpp +FILE: ../../../third_party/skia/src/gpu/ganesh/SurfaceDrawContext.cpp +FILE: ../../../third_party/skia/src/gpu/ganesh/SurfaceDrawContext.h FILE: ../../../third_party/skia/src/gpu/ganesh/effects/GrBlendFragmentProcessor.cpp FILE: ../../../third_party/skia/src/gpu/ganesh/effects/GrBlendFragmentProcessor.h FILE: ../../../third_party/skia/src/gpu/ganesh/effects/GrCustomXfermode.cpp @@ -3025,9 +2997,6 @@ FILE: ../../../third_party/skia/src/gpu/ganesh/ops/LatticeOp.cpp FILE: ../../../third_party/skia/src/gpu/ganesh/ops/LatticeOp.h FILE: ../../../third_party/skia/src/gpu/ganesh/ops/TriangulatingPathRenderer.cpp FILE: ../../../third_party/skia/src/gpu/ganesh/ops/TriangulatingPathRenderer.h -FILE: ../../../third_party/skia/src/gpu/ganesh/v1/Device_drawTexture.cpp -FILE: ../../../third_party/skia/src/gpu/ganesh/v1/SurfaceDrawContext.cpp -FILE: ../../../third_party/skia/src/gpu/ganesh/v1/SurfaceDrawContext_v1.h FILE: ../../../third_party/skia/src/gpu/ganesh/vk/GrVkCaps.cpp FILE: ../../../third_party/skia/src/gpu/ganesh/vk/GrVkCaps.h FILE: ../../../third_party/skia/src/gpu/ganesh/vk/GrVkCommandBuffer.cpp @@ -3497,8 +3466,6 @@ 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 @@ -3513,12 +3480,12 @@ 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/SkMacros.h FILE: ../../../third_party/skia/include/private/SkSafe32.h FILE: ../../../third_party/skia/include/private/SkTo.h FILE: ../../../third_party/skia/include/private/gpu/ganesh/GrVkTypesPriv.h +FILE: ../../../third_party/skia/include/private/gpu/vk/SkiaVulkan.h FILE: ../../../third_party/skia/include/utils/SkAnimCodecPlayer.h FILE: ../../../third_party/skia/include/utils/SkTextUtils.h FILE: ../../../third_party/skia/modules/skcms/skcms.cc @@ -3561,7 +3528,6 @@ 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 @@ -3829,7 +3795,6 @@ FILE: ../../../third_party/skia/samplecode/SampleShadowReference.cpp FILE: ../../../third_party/skia/samplecode/SampleShadowUtils.cpp FILE: ../../../third_party/skia/samplecode/SampleStrokeVerb.cpp FILE: ../../../third_party/skia/src/android/SkAndroidFrameworkUtils.cpp -FILE: ../../../third_party/skia/src/c/sk_effects.cpp FILE: ../../../third_party/skia/src/codec/SkBmpBaseCodec.cpp FILE: ../../../third_party/skia/src/codec/SkBmpBaseCodec.h FILE: ../../../third_party/skia/src/codec/SkFrameHolder.h @@ -3883,6 +3848,7 @@ FILE: ../../../third_party/skia/src/gpu/ganesh/GrSurfaceProxyPriv.h FILE: ../../../third_party/skia/src/gpu/ganesh/GrTextureProxyCacheAccess.h FILE: ../../../third_party/skia/src/gpu/ganesh/GrTextureProxyPriv.h FILE: ../../../third_party/skia/src/gpu/ganesh/SkGr.h +FILE: ../../../third_party/skia/src/gpu/ganesh/StencilClip.h FILE: ../../../third_party/skia/src/gpu/ganesh/effects/GrAtlasedShaderHelpers.h FILE: ../../../third_party/skia/src/gpu/ganesh/effects/GrTextureEffect.cpp FILE: ../../../third_party/skia/src/gpu/ganesh/effects/GrTextureEffect.h @@ -3915,7 +3881,6 @@ FILE: ../../../third_party/skia/src/gpu/ganesh/ops/GrSimpleMeshDrawOpHelper.cpp FILE: ../../../third_party/skia/src/gpu/ganesh/ops/GrSimpleMeshDrawOpHelper.h FILE: ../../../third_party/skia/src/gpu/ganesh/ops/TextureOp.cpp FILE: ../../../third_party/skia/src/gpu/ganesh/ops/TextureOp.h -FILE: ../../../third_party/skia/src/gpu/ganesh/v1/StencilClip.h FILE: ../../../third_party/skia/src/gpu/ganesh/vk/GrVkSemaphore.cpp FILE: ../../../third_party/skia/src/gpu/ganesh/vk/GrVkSemaphore.h FILE: ../../../third_party/skia/src/opts/SkUtils_opts.h @@ -4043,6 +4008,8 @@ FILE: ../../../third_party/skia/src/core/SkRuntimeEffectPriv.h FILE: ../../../third_party/skia/src/core/SkVM_fwd.h FILE: ../../../third_party/skia/src/core/SkYUVAInfo.cpp FILE: ../../../third_party/skia/src/core/SkYUVAPixmaps.cpp +FILE: ../../../third_party/skia/src/gpu/ganesh/ClipStack.cpp +FILE: ../../../third_party/skia/src/gpu/ganesh/ClipStack.h FILE: ../../../third_party/skia/src/gpu/ganesh/GrBackendSemaphore.cpp FILE: ../../../third_party/skia/src/gpu/ganesh/GrBackendSurfaceMutableState.cpp FILE: ../../../third_party/skia/src/gpu/ganesh/GrBackendSurfaceMutableStateImpl.h @@ -4062,6 +4029,8 @@ FILE: ../../../third_party/skia/src/gpu/ganesh/GrUniformDataManager.cpp FILE: ../../../third_party/skia/src/gpu/ganesh/GrUniformDataManager.h FILE: ../../../third_party/skia/src/gpu/ganesh/GrUtil.cpp FILE: ../../../third_party/skia/src/gpu/ganesh/GrYUVABackendTextures.cpp +FILE: ../../../third_party/skia/src/gpu/ganesh/StencilMaskHelper.cpp +FILE: ../../../third_party/skia/src/gpu/ganesh/StencilMaskHelper.h FILE: ../../../third_party/skia/src/gpu/ganesh/d3d/GrD3DAMDMemoryAllocator.cpp FILE: ../../../third_party/skia/src/gpu/ganesh/d3d/GrD3DAMDMemoryAllocator.h FILE: ../../../third_party/skia/src/gpu/ganesh/d3d/GrD3DAttachment.cpp @@ -4115,12 +4084,6 @@ FILE: ../../../third_party/skia/src/gpu/ganesh/geometry/GrShape.cpp FILE: ../../../third_party/skia/src/gpu/ganesh/geometry/GrShape.h FILE: ../../../third_party/skia/src/gpu/ganesh/gl/webgl/GrGLMakeNativeInterface_webgl.cpp FILE: ../../../third_party/skia/src/gpu/ganesh/glsl/GrGLSLUniformHandler.cpp -FILE: ../../../third_party/skia/src/gpu/ganesh/v1/ClipStack.cpp -FILE: ../../../third_party/skia/src/gpu/ganesh/v1/ClipStack.h -FILE: ../../../third_party/skia/src/gpu/ganesh/v1/StencilMaskHelper.cpp -FILE: ../../../third_party/skia/src/gpu/ganesh/v1/StencilMaskHelper.h -FILE: ../../../third_party/skia/src/gpu/ganesh/v1/SurfaceFillContext_v1.cpp -FILE: ../../../third_party/skia/src/gpu/ganesh/v1/SurfaceFillContext_v1.h FILE: ../../../third_party/skia/src/gpu/ganesh/vk/GrVkMSAALoadManager.cpp FILE: ../../../third_party/skia/src/gpu/ganesh/vk/GrVkMSAALoadManager.h FILE: ../../../third_party/skia/src/gpu/ganesh/vk/GrVkManagedResource.h @@ -5545,6 +5508,7 @@ FILE: ../../../third_party/skia/include/core/SkColorType.h FILE: ../../../third_party/skia/include/core/SkCombinationBuilder.h FILE: ../../../third_party/skia/include/gpu/GpuTypes.h FILE: ../../../third_party/skia/include/gpu/graphite/ContextOptions.h +FILE: ../../../third_party/skia/include/gpu/graphite/ImageProvider.h FILE: ../../../third_party/skia/include/private/SkUniquePaintParamsID.h FILE: ../../../third_party/skia/include/sksl/SkSLVersion.h FILE: ../../../third_party/skia/infra/bots/task_drivers/bazel_build/bazel_build.go @@ -5573,6 +5537,10 @@ FILE: ../../../third_party/skia/src/core/SkShaderCodeDictionary.h FILE: ../../../third_party/skia/src/gpu/AtlasTypes.cpp FILE: ../../../third_party/skia/src/gpu/AtlasTypes.h FILE: ../../../third_party/skia/src/gpu/RefCntedCallback.h +FILE: ../../../third_party/skia/src/gpu/ganesh/GrBufferTransferRenderTask.cpp +FILE: ../../../third_party/skia/src/gpu/ganesh/GrBufferTransferRenderTask.h +FILE: ../../../third_party/skia/src/gpu/ganesh/GrBufferUpdateRenderTask.cpp +FILE: ../../../third_party/skia/src/gpu/ganesh/GrBufferUpdateRenderTask.h FILE: ../../../third_party/skia/src/gpu/ganesh/GrImageInfo.cpp FILE: ../../../third_party/skia/src/gpu/ganesh/tessellate/PathTessellator.cpp FILE: ../../../third_party/skia/src/gpu/ganesh/tessellate/PathTessellator.h @@ -5583,6 +5551,12 @@ FILE: ../../../third_party/skia/src/gpu/graphite/AttachmentTypes.h FILE: ../../../third_party/skia/src/gpu/graphite/ClipStack.cpp FILE: ../../../third_party/skia/src/gpu/graphite/ClipStack_graphite.h FILE: ../../../third_party/skia/src/gpu/graphite/CommandTypes.h +FILE: ../../../third_party/skia/src/gpu/graphite/ComputePassTask.cpp +FILE: ../../../third_party/skia/src/gpu/graphite/ComputePassTask.h +FILE: ../../../third_party/skia/src/gpu/graphite/ComputePipeline.cpp +FILE: ../../../third_party/skia/src/gpu/graphite/ComputePipeline.h +FILE: ../../../third_party/skia/src/gpu/graphite/ComputePipelineDesc.h +FILE: ../../../third_party/skia/src/gpu/graphite/ComputeTypes.h FILE: ../../../third_party/skia/src/gpu/graphite/DrawAtlas.cpp FILE: ../../../third_party/skia/src/gpu/graphite/DrawAtlas.h FILE: ../../../third_party/skia/src/gpu/graphite/DrawCommands.h @@ -5592,12 +5566,15 @@ FILE: ../../../third_party/skia/src/gpu/graphite/GlobalCache.h FILE: ../../../third_party/skia/src/gpu/graphite/GpuWorkSubmission.cpp FILE: ../../../third_party/skia/src/gpu/graphite/GraphiteResourceKey.cpp FILE: ../../../third_party/skia/src/gpu/graphite/GraphiteResourceKey.h +FILE: ../../../third_party/skia/src/gpu/graphite/ImageUtils.cpp +FILE: ../../../third_party/skia/src/gpu/graphite/ImageUtils.h FILE: ../../../third_party/skia/src/gpu/graphite/Log.h FILE: ../../../third_party/skia/src/gpu/graphite/PaintParams.cpp FILE: ../../../third_party/skia/src/gpu/graphite/PaintParams.h +FILE: ../../../third_party/skia/src/gpu/graphite/PietRenderTask.cpp +FILE: ../../../third_party/skia/src/gpu/graphite/PietRenderTask.h FILE: ../../../third_party/skia/src/gpu/graphite/QueueManager.cpp FILE: ../../../third_party/skia/src/gpu/graphite/QueueManager.h -FILE: ../../../third_party/skia/src/gpu/graphite/RecorderPriv.cpp FILE: ../../../third_party/skia/src/gpu/graphite/RecorderPriv.h FILE: ../../../third_party/skia/src/gpu/graphite/RecordingPriv.h FILE: ../../../third_party/skia/src/gpu/graphite/Resource.cpp @@ -5615,15 +5592,21 @@ FILE: ../../../third_party/skia/src/gpu/graphite/UploadTask.cpp FILE: ../../../third_party/skia/src/gpu/graphite/UploadTask.h FILE: ../../../third_party/skia/src/gpu/graphite/geom/Geometry.h FILE: ../../../third_party/skia/src/gpu/graphite/geom/SubRunData.h +FILE: ../../../third_party/skia/src/gpu/graphite/mtl/MtlComputePipeline.h +FILE: ../../../third_party/skia/src/gpu/graphite/mtl/MtlComputePipeline.mm FILE: ../../../third_party/skia/src/gpu/graphite/mtl/MtlQueueManager.h FILE: ../../../third_party/skia/src/gpu/graphite/mtl/MtlQueueManager.mm FILE: ../../../third_party/skia/src/gpu/graphite/mtl/MtlSampler.h FILE: ../../../third_party/skia/src/gpu/graphite/mtl/MtlSampler.mm +FILE: ../../../third_party/skia/src/gpu/graphite/render/BitmapTextRenderStep.cpp +FILE: ../../../third_party/skia/src/gpu/graphite/render/BitmapTextRenderStep.h FILE: ../../../third_party/skia/src/gpu/graphite/render/CoverBoundsRenderStep.cpp FILE: ../../../third_party/skia/src/gpu/graphite/render/CoverBoundsRenderStep.h FILE: ../../../third_party/skia/src/gpu/graphite/render/DynamicInstancesPatchAllocator.h FILE: ../../../third_party/skia/src/gpu/graphite/render/MiddleOutFanRenderStep.cpp FILE: ../../../third_party/skia/src/gpu/graphite/render/MiddleOutFanRenderStep.h +FILE: ../../../third_party/skia/src/gpu/graphite/render/SDFTextRenderStep.cpp +FILE: ../../../third_party/skia/src/gpu/graphite/render/SDFTextRenderStep.h FILE: ../../../third_party/skia/src/gpu/graphite/render/StencilAndCoverDSS.h FILE: ../../../third_party/skia/src/gpu/graphite/render/TessellateCurvesRenderStep.cpp FILE: ../../../third_party/skia/src/gpu/graphite/render/TessellateCurvesRenderStep.h @@ -5631,10 +5614,6 @@ FILE: ../../../third_party/skia/src/gpu/graphite/render/TessellateStrokesRenderS FILE: ../../../third_party/skia/src/gpu/graphite/render/TessellateStrokesRenderStep.h FILE: ../../../third_party/skia/src/gpu/graphite/render/TessellateWedgesRenderStep.cpp FILE: ../../../third_party/skia/src/gpu/graphite/render/TessellateWedgesRenderStep.h -FILE: ../../../third_party/skia/src/gpu/graphite/render/TextDirectRenderStep.cpp -FILE: ../../../third_party/skia/src/gpu/graphite/render/TextDirectRenderStep.h -FILE: ../../../third_party/skia/src/gpu/graphite/render/TextSDFRenderStep.cpp -FILE: ../../../third_party/skia/src/gpu/graphite/render/TextSDFRenderStep.h FILE: ../../../third_party/skia/src/gpu/graphite/render/VerticesRenderStep.cpp FILE: ../../../third_party/skia/src/gpu/graphite/render/VerticesRenderStep.h FILE: ../../../third_party/skia/src/gpu/graphite/text/AtlasManager.cpp @@ -5644,8 +5623,9 @@ FILE: ../../../third_party/skia/src/gpu/tessellate/FixedCountBufferUtils.h FILE: ../../../third_party/skia/src/gpu/tessellate/LinearTolerances.h FILE: ../../../third_party/skia/src/shaders/SkEmptyShader.cpp FILE: ../../../third_party/skia/src/shaders/gradients/SkGradientShaderBase.cpp +FILE: ../../../third_party/skia/src/sksl/SkSLModuleLoader.cpp +FILE: ../../../third_party/skia/src/sksl/SkSLModuleLoader.h FILE: ../../../third_party/skia/src/sksl/analysis/SkSLFinalizationChecks.cpp -FILE: ../../../third_party/skia/src/sksl/analysis/SkSLGetComputeShaderMainParams.cpp FILE: ../../../third_party/skia/src/sksl/analysis/SkSLIsSameExpressionTree.cpp FILE: ../../../third_party/skia/src/sksl/analysis/SkSLIsTrivialExpression.cpp FILE: ../../../third_party/skia/src/sksl/analysis/SkSLNoOpErrorReporter.h @@ -6495,6 +6475,43 @@ 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/fuzz/oss_fuzz/FuzzCOLRv1.cpp + ../../../third_party/skia/LICENSE +TYPE: LicenseType.bsd +FILE: ../../../third_party/skia/fuzz/oss_fuzz/FuzzCOLRv1.cpp +---------------------------------------------------------------------------------------------------- +Copyright 2022 Google, LLC + +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/fuzz/oss_fuzz/FuzzDDLThreading.cpp + ../../../third_party/skia/LICENSE @@ -6585,9 +6602,15 @@ FILE: ../../../third_party/skia/gm/drawlines_with_local_matrix.cpp FILE: ../../../third_party/skia/gm/palette.cpp FILE: ../../../third_party/skia/include/core/SkOpenTypeSVGDecoder.h FILE: ../../../third_party/skia/modules/skottie/src/BlendModes.cpp +FILE: ../../../third_party/skia/modules/skottie/src/text/Font.cpp +FILE: ../../../third_party/skia/modules/skottie/src/text/Font.h +FILE: ../../../third_party/skia/modules/skunicode/src/SkUnicode_client.cpp FILE: ../../../third_party/skia/modules/svg/include/SkSVGOpenTypeSVGDecoder.h FILE: ../../../third_party/skia/modules/svg/src/SkSVGOpenTypeSVGDecoder.cpp FILE: ../../../third_party/skia/samplecode/SampleSBIX.cpp +FILE: ../../../third_party/skia/src/codec/SkAvifCodec.cpp +FILE: ../../../third_party/skia/src/codec/SkAvifCodec.h +FILE: ../../../third_party/skia/src/gpu/graphite/mtl/MtlComputeCommandEncoder.h FILE: ../../../third_party/skia/src/sfnt/SkOTTable_hmtx.h FILE: ../../../third_party/skia/src/sksl/codegen/SkSLWGSLCodeGenerator.cpp FILE: ../../../third_party/skia/src/sksl/codegen/SkSLWGSLCodeGenerator.h @@ -7044,6 +7067,7 @@ FILE: ../../../third_party/skia/src/gpu/BufferWriter.h FILE: ../../../third_party/skia/src/gpu/GrRectanizer.h FILE: ../../../third_party/skia/src/gpu/Rectanizer.h FILE: ../../../third_party/skia/src/gpu/RectanizerPow2.cpp +FILE: ../../../third_party/skia/src/gpu/ganesh/Device_v1.h FILE: ../../../third_party/skia/src/gpu/ganesh/GrBufferAllocPool.cpp FILE: ../../../third_party/skia/src/gpu/ganesh/GrBufferAllocPool.h FILE: ../../../third_party/skia/src/gpu/ganesh/GrClip.h @@ -7052,7 +7076,6 @@ FILE: ../../../third_party/skia/src/gpu/ganesh/GrFixedClip.cpp FILE: ../../../third_party/skia/src/gpu/ganesh/GrGpu.cpp FILE: ../../../third_party/skia/src/gpu/ganesh/SkGr.cpp FILE: ../../../third_party/skia/src/gpu/ganesh/geometry/GrRect.h -FILE: ../../../third_party/skia/src/gpu/ganesh/v1/Device_v1.h FILE: ../../../third_party/skia/src/ports/SkDebug_win.cpp FILE: ../../../third_party/skia/src/text/gpu/Glyph.h ---------------------------------------------------------------------------------------------------- @@ -7126,6 +7149,53 @@ 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/include/gpu/vk/VulkanExtensions.h + ../../../third_party/skia/LICENSE +TYPE: LicenseType.bsd +FILE: ../../../third_party/skia/include/gpu/vk/VulkanExtensions.h +FILE: ../../../third_party/skia/include/gpu/vk/VulkanTypes.h +FILE: ../../../third_party/skia/src/gpu/ganesh/dawn/GrDawnAsyncWait.cpp +FILE: ../../../third_party/skia/src/gpu/ganesh/dawn/GrDawnAsyncWait.h +FILE: ../../../third_party/skia/src/gpu/piet/PietTypes.h +FILE: ../../../third_party/skia/src/gpu/piet/Render.cpp +FILE: ../../../third_party/skia/src/gpu/piet/Render.h +FILE: ../../../third_party/skia/src/gpu/piet/Scene.cpp +FILE: ../../../third_party/skia/src/gpu/piet/Scene.h +FILE: ../../../third_party/skia/src/gpu/tessellate/MidpointContourParser.h +FILE: ../../../third_party/skia/src/sksl/SkSLPosition.cpp +---------------------------------------------------------------------------------------------------- +Copyright 2022 Google LLC. + +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/include/utils/SkEventTracer.h + ../../../third_party/skia/LICENSE @@ -7569,44 +7639,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/core/SkMatrixImageFilter.cpp + ../../../third_party/skia/LICENSE -TYPE: LicenseType.bsd -FILE: ../../../third_party/skia/src/core/SkMatrixImageFilter.cpp -FILE: ../../../third_party/skia/src/core/SkMatrixImageFilter.h ----------------------------------------------------------------------------------------------------- -Copyright 2014 The Android Open Source Project - -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/core/SkScan_AAAPath.cpp + ../../../third_party/skia/LICENSE @@ -7758,12 +7790,11 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================================================== LIBRARY: skia -ORIGIN: ../../../third_party/skia/src/gpu/ganesh/GrDistanceFieldGenFromVector.cpp + ../../../third_party/skia/LICENSE +ORIGIN: ../../../third_party/skia/src/effects/imagefilters/SkMatrixTransformImageFilter.cpp + ../../../third_party/skia/LICENSE TYPE: LicenseType.bsd -FILE: ../../../third_party/skia/src/gpu/ganesh/GrDistanceFieldGenFromVector.cpp -FILE: ../../../third_party/skia/src/gpu/ganesh/GrDistanceFieldGenFromVector.h +FILE: ../../../third_party/skia/src/effects/imagefilters/SkMatrixTransformImageFilter.cpp ---------------------------------------------------------------------------------------------------- -Copyright 2017 ARM Ltd. +Copyright 2014 The Android Open Source Project Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are @@ -7796,14 +7827,12 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ==================================================================================================== LIBRARY: skia -ORIGIN: ../../../third_party/skia/src/gpu/ganesh/dawn/GrDawnAsyncWait.cpp + ../../../third_party/skia/LICENSE +ORIGIN: ../../../third_party/skia/src/gpu/ganesh/GrDistanceFieldGenFromVector.cpp + ../../../third_party/skia/LICENSE TYPE: LicenseType.bsd -FILE: ../../../third_party/skia/src/gpu/ganesh/dawn/GrDawnAsyncWait.cpp -FILE: ../../../third_party/skia/src/gpu/ganesh/dawn/GrDawnAsyncWait.h -FILE: ../../../third_party/skia/src/gpu/tessellate/MidpointContourParser.h -FILE: ../../../third_party/skia/src/sksl/SkSLPosition.cpp +FILE: ../../../third_party/skia/src/gpu/ganesh/GrDistanceFieldGenFromVector.cpp +FILE: ../../../third_party/skia/src/gpu/ganesh/GrDistanceFieldGenFromVector.h ---------------------------------------------------------------------------------------------------- -Copyright 2022 Google LLC. +Copyright 2017 ARM Ltd. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are @@ -8007,4 +8036,4 @@ Materials are furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Materials. ==================================================================================================== -Total license count: 68 +Total license count: 69 diff --git a/ci/licenses_golden/licenses_third_party b/ci/licenses_golden/licenses_third_party index 46f8bb941583e..97593374fb2bb 100644 --- a/ci/licenses_golden/licenses_third_party +++ b/ci/licenses_golden/licenses_third_party @@ -1,4 +1,4 @@ -Signature: c2f96fc1357f595ff44a0c8e1c6e4cbb +Signature: 17f3f2238676062b964586ee0a786b06 UNUSED LICENSES: @@ -10298,6 +10298,7 @@ FILE: ../../../third_party/dart/runtime/tools/wiki/templates/includes/auto-refre FILE: ../../../third_party/dart/runtime/tools/wiki/templates/includes/favicon.html FILE: ../../../third_party/dart/runtime/tools/wiki/templates/page.html FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/doublex20/arm64_android.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/doublex20/arm64_fuchsia.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/doublex20/arm64_ios.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/doublex20/arm64_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/doublex20/arm64_macos.expect @@ -10309,11 +10310,13 @@ FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/doublex20/ia3 FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/doublex20/ia32_win.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/doublex20/riscv32_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/doublex20/riscv64_linux.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/doublex20/x64_fuchsia.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/doublex20/x64_ios.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/doublex20/x64_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/doublex20/x64_macos.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/doublex20/x64_win.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/floatx20/arm64_android.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/floatx20/arm64_fuchsia.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/floatx20/arm64_ios.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/floatx20/arm64_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/floatx20/arm64_macos.expect @@ -10325,11 +10328,13 @@ FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/floatx20/ia32 FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/floatx20/ia32_win.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/floatx20/riscv32_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/floatx20/riscv64_linux.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/floatx20/x64_fuchsia.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/floatx20/x64_ios.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/floatx20/x64_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/floatx20/x64_macos.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/floatx20/x64_win.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/int8x10/arm64_android.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/int8x10/arm64_fuchsia.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/int8x10/arm64_ios.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/int8x10/arm64_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/int8x10/arm64_macos.expect @@ -10341,11 +10346,13 @@ FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/int8x10/ia32_ FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/int8x10/ia32_win.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/int8x10/riscv32_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/int8x10/riscv64_linux.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/int8x10/x64_fuchsia.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/int8x10/x64_ios.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/int8x10/x64_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/int8x10/x64_macos.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/int8x10/x64_win.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/mixedx20/arm64_android.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/mixedx20/arm64_fuchsia.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/mixedx20/arm64_ios.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/mixedx20/arm64_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/mixedx20/arm64_macos.expect @@ -10357,11 +10364,13 @@ FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/mixedx20/ia32 FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/mixedx20/ia32_win.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/mixedx20/riscv32_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/mixedx20/riscv64_linux.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/mixedx20/x64_fuchsia.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/mixedx20/x64_ios.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/mixedx20/x64_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/mixedx20/x64_macos.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/mixedx20/x64_win.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/regress46127/arm64_android.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/regress46127/arm64_fuchsia.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/regress46127/arm64_ios.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/regress46127/arm64_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/regress46127/arm64_macos.expect @@ -10373,11 +10382,31 @@ FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/regress46127/ FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/regress46127/ia32_win.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/regress46127/riscv32_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/regress46127/riscv64_linux.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/regress46127/x64_fuchsia.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/regress46127/x64_ios.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/regress46127/x64_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/regress46127/x64_macos.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/regress46127/x64_win.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/regress_fuchsia105336/arm64_android.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/regress_fuchsia105336/arm64_fuchsia.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/regress_fuchsia105336/arm64_ios.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/regress_fuchsia105336/arm64_linux.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/regress_fuchsia105336/arm64_macos.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/regress_fuchsia105336/arm_android.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/regress_fuchsia105336/arm_ios.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/regress_fuchsia105336/arm_linux.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/regress_fuchsia105336/ia32_android.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/regress_fuchsia105336/ia32_linux.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/regress_fuchsia105336/ia32_win.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/regress_fuchsia105336/riscv32_linux.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/regress_fuchsia105336/riscv64_linux.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/regress_fuchsia105336/x64_fuchsia.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/regress_fuchsia105336/x64_ios.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/regress_fuchsia105336/x64_linux.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/regress_fuchsia105336/x64_macos.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/regress_fuchsia105336/x64_win.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct128bytesx1/arm64_android.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct128bytesx1/arm64_fuchsia.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct128bytesx1/arm64_ios.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct128bytesx1/arm64_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct128bytesx1/arm64_macos.expect @@ -10389,11 +10418,13 @@ FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct128byte FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct128bytesx1/ia32_win.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct128bytesx1/riscv32_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct128bytesx1/riscv64_linux.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct128bytesx1/x64_fuchsia.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct128bytesx1/x64_ios.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct128bytesx1/x64_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct128bytesx1/x64_macos.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct128bytesx1/x64_win.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct12bytesFloatx6/arm64_android.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct12bytesFloatx6/arm64_fuchsia.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct12bytesFloatx6/arm64_ios.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct12bytesFloatx6/arm64_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct12bytesFloatx6/arm64_macos.expect @@ -10405,11 +10436,13 @@ FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct12bytes FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct12bytesFloatx6/ia32_win.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct12bytesFloatx6/riscv32_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct12bytesFloatx6/riscv64_linux.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct12bytesFloatx6/x64_fuchsia.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct12bytesFloatx6/x64_ios.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct12bytesFloatx6/x64_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct12bytesFloatx6/x64_macos.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct12bytesFloatx6/x64_win.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct16bytesHomogenousx10/arm64_android.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct16bytesHomogenousx10/arm64_fuchsia.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct16bytesHomogenousx10/arm64_ios.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct16bytesHomogenousx10/arm64_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct16bytesHomogenousx10/arm64_macos.expect @@ -10421,23 +10454,28 @@ FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct16bytes FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct16bytesHomogenousx10/ia32_win.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct16bytesHomogenousx10/riscv32_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct16bytesHomogenousx10/riscv64_linux.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct16bytesHomogenousx10/x64_fuchsia.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct16bytesHomogenousx10/x64_ios.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct16bytesHomogenousx10/x64_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct16bytesHomogenousx10/x64_macos.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct16bytesHomogenousx10/x64_win.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct16bytesMixedx10/x64_fuchsia.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct16bytesMixedx10/x64_ios.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct16bytesMixedx10/x64_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct16bytesMixedx10/x64_macos.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct16bytesMixedx10/x64_win.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct16bytesMixedx10_2/x64_fuchsia.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct16bytesMixedx10_2/x64_ios.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct16bytesMixedx10_2/x64_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct16bytesMixedx10_2/x64_macos.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct16bytesMixedx10_2/x64_win.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct16bytesMixedx10_3/x64_fuchsia.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct16bytesMixedx10_3/x64_ios.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct16bytesMixedx10_3/x64_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct16bytesMixedx10_3/x64_macos.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct16bytesMixedx10_3/x64_win.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct3bytesx10/arm64_android.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct3bytesx10/arm64_fuchsia.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct3bytesx10/arm64_ios.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct3bytesx10/arm64_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct3bytesx10/arm64_macos.expect @@ -10449,11 +10487,13 @@ FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct3bytesx FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct3bytesx10/ia32_win.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct3bytesx10/riscv32_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct3bytesx10/riscv64_linux.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct3bytesx10/x64_fuchsia.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct3bytesx10/x64_ios.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct3bytesx10/x64_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct3bytesx10/x64_macos.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct3bytesx10/x64_win.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct8bytesPackedx10/arm64_android.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct8bytesPackedx10/arm64_fuchsia.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct8bytesPackedx10/arm64_ios.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct8bytesPackedx10/arm64_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct8bytesPackedx10/arm64_macos.expect @@ -10465,11 +10505,13 @@ FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct8bytesP FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct8bytesPackedx10/ia32_win.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct8bytesPackedx10/riscv32_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct8bytesPackedx10/riscv64_linux.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct8bytesPackedx10/x64_fuchsia.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct8bytesPackedx10/x64_ios.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct8bytesPackedx10/x64_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct8bytesPackedx10/x64_macos.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct8bytesPackedx10/x64_win.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct8bytesx1/arm64_android.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct8bytesx1/arm64_fuchsia.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct8bytesx1/arm64_ios.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct8bytesx1/arm64_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct8bytesx1/arm64_macos.expect @@ -10481,11 +10523,13 @@ FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct8bytesx FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct8bytesx1/ia32_win.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct8bytesx1/riscv32_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct8bytesx1/riscv64_linux.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct8bytesx1/x64_fuchsia.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct8bytesx1/x64_ios.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct8bytesx1/x64_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct8bytesx1/x64_macos.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct8bytesx1/x64_win.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/structPacked/arm64_android.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/structPacked/arm64_fuchsia.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/structPacked/arm64_ios.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/structPacked/arm64_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/structPacked/arm64_macos.expect @@ -10497,11 +10541,13 @@ FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/structPacked/ FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/structPacked/ia32_win.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/structPacked/riscv32_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/structPacked/riscv64_linux.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/structPacked/x64_fuchsia.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/structPacked/x64_ios.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/structPacked/x64_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/structPacked/x64_macos.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/structPacked/x64_win.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_VeryLargeStruct/arm64_android.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_VeryLargeStruct/arm64_fuchsia.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_VeryLargeStruct/arm64_ios.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_VeryLargeStruct/arm64_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_VeryLargeStruct/arm64_macos.expect @@ -10513,11 +10559,13 @@ FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_VeryLa FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_VeryLargeStruct/ia32_win.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_VeryLargeStruct/riscv32_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_VeryLargeStruct/riscv64_linux.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_VeryLargeStruct/x64_fuchsia.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_VeryLargeStruct/x64_ios.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_VeryLargeStruct/x64_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_VeryLargeStruct/x64_macos.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_VeryLargeStruct/x64_win.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_floatarray/arm64_android.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_floatarray/arm64_fuchsia.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_floatarray/arm64_ios.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_floatarray/arm64_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_floatarray/arm64_macos.expect @@ -10529,11 +10577,13 @@ FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_floata FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_floatarray/ia32_win.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_floatarray/riscv32_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_floatarray/riscv64_linux.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_floatarray/x64_fuchsia.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_floatarray/x64_ios.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_floatarray/x64_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_floatarray/x64_macos.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_floatarray/x64_win.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_floatx4/arm64_android.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_floatx4/arm64_fuchsia.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_floatx4/arm64_ios.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_floatx4/arm64_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_floatx4/arm64_macos.expect @@ -10545,11 +10595,13 @@ FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_floatx FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_floatx4/ia32_win.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_floatx4/riscv32_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_floatx4/riscv64_linux.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_floatx4/x64_fuchsia.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_floatx4/x64_ios.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_floatx4/x64_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_floatx4/x64_macos.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_floatx4/x64_win.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_int8array/arm64_android.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_int8array/arm64_fuchsia.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_int8array/arm64_ios.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_int8array/arm64_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_int8array/arm64_macos.expect @@ -10561,11 +10613,13 @@ FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_int8ar FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_int8array/ia32_win.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_int8array/riscv32_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_int8array/riscv64_linux.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_int8array/x64_fuchsia.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_int8array/x64_ios.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_int8array/x64_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_int8array/x64_macos.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_int8array/x64_win.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_int8x10/arm64_android.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_int8x10/arm64_fuchsia.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_int8x10/arm64_ios.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_int8x10/arm64_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_int8x10/arm64_macos.expect @@ -10577,11 +10631,13 @@ FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_int8x1 FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_int8x10/ia32_win.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_int8x10/riscv32_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_int8x10/riscv64_linux.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_int8x10/x64_fuchsia.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_int8x10/x64_ios.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_int8x10/x64_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_int8x10/x64_macos.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/struct_int8x10/x64_win.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/union16bytesHomogenousx10/arm64_android.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/union16bytesHomogenousx10/arm64_fuchsia.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/union16bytesHomogenousx10/arm64_ios.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/union16bytesHomogenousx10/arm64_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/union16bytesHomogenousx10/arm64_macos.expect @@ -10593,11 +10649,13 @@ FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/union16bytesH FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/union16bytesHomogenousx10/ia32_win.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/union16bytesHomogenousx10/riscv32_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/union16bytesHomogenousx10/riscv64_linux.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/union16bytesHomogenousx10/x64_fuchsia.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/union16bytesHomogenousx10/x64_ios.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/union16bytesHomogenousx10/x64_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/union16bytesHomogenousx10/x64_macos.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/union16bytesHomogenousx10/x64_win.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/union5bytesPackedx10/arm64_android.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/union5bytesPackedx10/arm64_fuchsia.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/union5bytesPackedx10/arm64_ios.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/union5bytesPackedx10/arm64_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/union5bytesPackedx10/arm64_macos.expect @@ -10609,14 +10667,13 @@ FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/union5bytesPa FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/union5bytesPackedx10/ia32_win.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/union5bytesPackedx10/riscv32_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/union5bytesPackedx10/riscv64_linux.expect +FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/union5bytesPackedx10/x64_fuchsia.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/union5bytesPackedx10/x64_ios.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/union5bytesPackedx10/x64_linux.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/union5bytesPackedx10/x64_macos.expect FILE: ../../../third_party/dart/runtime/vm/compiler/ffi/unit_tests/union5bytesPackedx10/x64_win.expect FILE: ../../../third_party/dart/samples/ffi/sqlite/docs/lib/scenario-default.svg FILE: ../../../third_party/dart/samples/ffi/sqlite/docs/lib/scenario-full.svg -FILE: ../../../third_party/dart/samples_2/ffi/sqlite/docs/lib/scenario-default.svg -FILE: ../../../third_party/dart/samples_2/ffi/sqlite/docs/lib/scenario-full.svg FILE: ../../../third_party/dart/sdk/lib/_internal/allowed_experiments.json FILE: ../../../third_party/dart/sdk/lib/html/html_common/conversions_dart2js.dart FILE: ../../../third_party/dart/sdk/lib/html/html_common/html_common.dart @@ -10678,6 +10735,8 @@ FILE: ../../../third_party/dart/runtime/bin/thread_absl.cc FILE: ../../../third_party/dart/runtime/bin/thread_absl.h FILE: ../../../third_party/dart/runtime/platform/mach_o.h FILE: ../../../third_party/dart/runtime/platform/pe.h +FILE: ../../../third_party/dart/runtime/vm/compiler/backend/il_serializer.cc +FILE: ../../../third_party/dart/runtime/vm/compiler/backend/il_serializer.h FILE: ../../../third_party/dart/runtime/vm/heap/gc_shared.cc FILE: ../../../third_party/dart/runtime/vm/heap/gc_shared.h FILE: ../../../third_party/dart/runtime/vm/instructions.cc @@ -10693,6 +10752,7 @@ FILE: ../../../third_party/dart/sdk/lib/_internal/js_shared/lib/synced/embedded_ FILE: ../../../third_party/dart/sdk/lib/_internal/vm/lib/ffi_native_finalizer_patch.dart FILE: ../../../third_party/dart/sdk/lib/_internal/vm/lib/finalizer_patch.dart FILE: ../../../third_party/dart/sdk/lib/_internal/vm/lib/hash_factories.dart +FILE: ../../../third_party/dart/sdk/lib/_internal/wasm/lib/async_patch.dart FILE: ../../../third_party/dart/sdk/lib/_internal/wasm/lib/bool.dart FILE: ../../../third_party/dart/sdk/lib/_internal/wasm/lib/class_id.dart FILE: ../../../third_party/dart/sdk/lib/_internal/wasm/lib/core_patch.dart @@ -10715,6 +10775,8 @@ FILE: ../../../third_party/dart/sdk/lib/_internal/wasm/lib/math_patch.dart FILE: ../../../third_party/dart/sdk/lib/_internal/wasm/lib/object_patch.dart FILE: ../../../third_party/dart/sdk/lib/_internal/wasm/lib/patch.dart FILE: ../../../third_party/dart/sdk/lib/_internal/wasm/lib/print_patch.dart +FILE: ../../../third_party/dart/sdk/lib/_internal/wasm/lib/regexp_helper.dart +FILE: ../../../third_party/dart/sdk/lib/_internal/wasm/lib/regexp_patch.dart FILE: ../../../third_party/dart/sdk/lib/_internal/wasm/lib/stack_trace_patch.dart FILE: ../../../third_party/dart/sdk/lib/_internal/wasm/lib/stopwatch_patch.dart FILE: ../../../third_party/dart/sdk/lib/_internal/wasm/lib/string_buffer_patch.dart @@ -10722,6 +10784,7 @@ FILE: ../../../third_party/dart/sdk/lib/_internal/wasm/lib/string_patch.dart FILE: ../../../third_party/dart/sdk/lib/_internal/wasm/lib/symbol_patch.dart FILE: ../../../third_party/dart/sdk/lib/_internal/wasm/lib/timer_patch.dart FILE: ../../../third_party/dart/sdk/lib/_internal/wasm/lib/type.dart +FILE: ../../../third_party/dart/sdk/lib/_internal/wasm/lib/uri_patch.dart FILE: ../../../third_party/dart/sdk/lib/ffi/c_type.dart FILE: ../../../third_party/dart/sdk/lib/ffi/native_finalizer.dart FILE: ../../../third_party/dart/sdk/lib/js/js_wasm.dart @@ -10921,30 +10984,6 @@ FILE: ../../../third_party/dart/samples/ffi/sqlite/lib/src/bindings/types.dart FILE: ../../../third_party/dart/samples/ffi/sqlite/lib/src/collections/closable_iterator.dart FILE: ../../../third_party/dart/samples/ffi/sqlite/lib/src/database.dart FILE: ../../../third_party/dart/samples/ffi/sqlite/lib/src/ffi/dylib_utils.dart -FILE: ../../../third_party/dart/samples_2/ffi/coordinate.dart -FILE: ../../../third_party/dart/samples_2/ffi/dylib_utils.dart -FILE: ../../../third_party/dart/samples_2/ffi/resource_management/arena_isolate_shutdown_sample.dart -FILE: ../../../third_party/dart/samples_2/ffi/resource_management/arena_sample.dart -FILE: ../../../third_party/dart/samples_2/ffi/resource_management/arena_zoned_sample.dart -FILE: ../../../third_party/dart/samples_2/ffi/resource_management/resource_management_test.dart -FILE: ../../../third_party/dart/samples_2/ffi/resource_management/unmanaged_sample.dart -FILE: ../../../third_party/dart/samples_2/ffi/sample_ffi_bitfield.dart -FILE: ../../../third_party/dart/samples_2/ffi/sample_ffi_data.dart -FILE: ../../../third_party/dart/samples_2/ffi/sample_ffi_dynamic_library.dart -FILE: ../../../third_party/dart/samples_2/ffi/sample_ffi_functions.dart -FILE: ../../../third_party/dart/samples_2/ffi/sample_ffi_functions_callbacks.dart -FILE: ../../../third_party/dart/samples_2/ffi/sample_ffi_functions_structs.dart -FILE: ../../../third_party/dart/samples_2/ffi/sample_ffi_structs.dart -FILE: ../../../third_party/dart/samples_2/ffi/samples_test.dart -FILE: ../../../third_party/dart/samples_2/ffi/sqlite/example/main.dart -FILE: ../../../third_party/dart/samples_2/ffi/sqlite/lib/sqlite.dart -FILE: ../../../third_party/dart/samples_2/ffi/sqlite/lib/src/bindings/bindings.dart -FILE: ../../../third_party/dart/samples_2/ffi/sqlite/lib/src/bindings/constants.dart -FILE: ../../../third_party/dart/samples_2/ffi/sqlite/lib/src/bindings/signatures.dart -FILE: ../../../third_party/dart/samples_2/ffi/sqlite/lib/src/bindings/types.dart -FILE: ../../../third_party/dart/samples_2/ffi/sqlite/lib/src/collections/closable_iterator.dart -FILE: ../../../third_party/dart/samples_2/ffi/sqlite/lib/src/database.dart -FILE: ../../../third_party/dart/samples_2/ffi/sqlite/lib/src/ffi/dylib_utils.dart FILE: ../../../third_party/dart/sdk/lib/_internal/js_shared/lib/rti.dart FILE: ../../../third_party/dart/sdk/lib/_internal/js_shared/lib/synced/recipe_syntax.dart FILE: ../../../third_party/dart/sdk/lib/_internal/vm/lib/ffi_dynamic_library_patch.dart @@ -11068,7 +11107,6 @@ FILE: ../../../third_party/dart/runtime/vm/thread_interrupter_android_arm.S FILE: ../../../third_party/dart/runtime/vm/virtual_memory_compressed.cc FILE: ../../../third_party/dart/runtime/vm/virtual_memory_compressed.h FILE: ../../../third_party/dart/samples/ffi/resource_management/utf8_helpers.dart -FILE: ../../../third_party/dart/samples_2/ffi/resource_management/utf8_helpers.dart FILE: ../../../third_party/dart/sdk/lib/_internal/js_runtime/lib/dart2js_runtime_metrics.dart FILE: ../../../third_party/dart/sdk/lib/_internal/js_runtime/lib/late_helper.dart FILE: ../../../third_party/dart/sdk/lib/_internal/vm/lib/ffi_allocation_patch.dart @@ -11244,10 +11282,6 @@ FILE: ../../../third_party/dart/samples/ffi/async/async_test.dart FILE: ../../../third_party/dart/samples/ffi/async/sample_async_callback.dart FILE: ../../../third_party/dart/samples/ffi/async/sample_native_port_call.dart FILE: ../../../third_party/dart/samples/ffi/sample_ffi_functions_callbacks_closures.dart -FILE: ../../../third_party/dart/samples_2/ffi/async/async_test.dart -FILE: ../../../third_party/dart/samples_2/ffi/async/sample_async_callback.dart -FILE: ../../../third_party/dart/samples_2/ffi/async/sample_native_port_call.dart -FILE: ../../../third_party/dart/samples_2/ffi/sample_ffi_functions_callbacks_closures.dart FILE: ../../../third_party/dart/sdk/lib/_http/embedder_config.dart FILE: ../../../third_party/dart/sdk/lib/_internal/js_dev_runtime/patch/js_patch.dart FILE: ../../../third_party/dart/sdk/lib/_internal/js_runtime/lib/js_patch.dart @@ -11717,7 +11751,6 @@ FILE: ../../../third_party/dart/runtime/vm/zone.cc FILE: ../../../third_party/dart/runtime/vm/zone.h FILE: ../../../third_party/dart/runtime/vm/zone_test.cc FILE: ../../../third_party/dart/samples/samples.status -FILE: ../../../third_party/dart/samples_2/samples_2.status FILE: ../../../third_party/dart/sdk/lib/_http/crypto.dart FILE: ../../../third_party/dart/sdk/lib/_http/http_date.dart FILE: ../../../third_party/dart/sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart diff --git a/common/graphics/persistent_cache.cc b/common/graphics/persistent_cache.cc index ad96510fd588e..a68186688a0ba 100644 --- a/common/graphics/persistent_cache.cc +++ b/common/graphics/persistent_cache.cc @@ -347,6 +347,8 @@ static void PersistentCacheStore(fml::RefPtr worker, std::shared_ptr cache_directory, std::string key, std::unique_ptr value) { + // The static leak checker gets confused by the use of fml::MakeCopyable. + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) auto task = fml::MakeCopyable([cache_directory, // file_name = std::move(key), // mapping = std::move(value) // diff --git a/common/graphics/texture.cc b/common/graphics/texture.cc index c5ad5fdf29af8..f12a5959fc088 100644 --- a/common/graphics/texture.cc +++ b/common/graphics/texture.cc @@ -6,6 +6,10 @@ namespace flutter { +ContextListener::ContextListener() = default; + +ContextListener::~ContextListener() = default; + Texture::Texture(int64_t id) : id_(id) {} Texture::~Texture() = default; @@ -19,6 +23,12 @@ void TextureRegistry::RegisterTexture(std::shared_ptr texture) { mapping_[texture->Id()] = texture; } +void TextureRegistry::RegisterContextListener( + uintptr_t id, + std::weak_ptr image) { + images_[id] = std::move(image); +} + void TextureRegistry::UnregisterTexture(int64_t id) { auto found = mapping_.find(id); if (found == mapping_.end()) { @@ -28,16 +38,36 @@ void TextureRegistry::UnregisterTexture(int64_t id) { mapping_.erase(found); } +void TextureRegistry::UnregisterContextListener(uintptr_t id) { + images_.erase(id); +} + void TextureRegistry::OnGrContextCreated() { for (auto& it : mapping_) { it.second->OnGrContextCreated(); } + + for (const auto& [id, weak_image] : images_) { + if (auto image = weak_image.lock()) { + image->OnGrContextCreated(); + } else { + images_.erase(id); + } + } } void TextureRegistry::OnGrContextDestroyed() { for (auto& it : mapping_) { it.second->OnGrContextDestroyed(); } + + for (const auto& [id, weak_image] : images_) { + if (auto image = weak_image.lock()) { + image->OnGrContextDestroyed(); + } else { + images_.erase(id); + } + } } std::shared_ptr TextureRegistry::GetTexture(int64_t id) { diff --git a/common/graphics/texture.h b/common/graphics/texture.h index 57afa60a6361f..69cfa3ba0f9a4 100644 --- a/common/graphics/texture.h +++ b/common/graphics/texture.h @@ -16,7 +16,22 @@ class GrDirectContext; namespace flutter { -class Texture { +class ContextListener { + public: + ContextListener(); + ~ContextListener(); + + // Called from raster thread. + virtual void OnGrContextCreated() = 0; + + // Called from raster thread. + virtual void OnGrContextDestroyed() = 0; + + private: + FML_DISALLOW_COPY_AND_ASSIGN(ContextListener); +}; + +class Texture : public ContextListener { public: explicit Texture(int64_t id); // Called from UI or raster thread. virtual ~Texture(); // Called from raster thread. @@ -29,12 +44,6 @@ class Texture { const SkSamplingOptions& sampling, const SkPaint* paint = nullptr) = 0; - // Called from raster thread. - virtual void OnGrContextCreated() = 0; - - // Called from raster thread. - virtual void OnGrContextDestroyed() = 0; - // Called on raster thread. virtual void MarkNewFrameAvailable() = 0; @@ -45,7 +54,6 @@ class Texture { private: int64_t id_; - FML_DISALLOW_COPY_AND_ASSIGN(Texture); }; @@ -56,9 +64,16 @@ class TextureRegistry { // Called from raster thread. void RegisterTexture(std::shared_ptr texture); + // Called from raster thread. + void RegisterContextListener(uintptr_t id, + std::weak_ptr image); + // Called from raster thread. void UnregisterTexture(int64_t id); + // Called from the raster thread. + void UnregisterContextListener(uintptr_t id); + // Called from raster thread. std::shared_ptr GetTexture(int64_t id); @@ -70,6 +85,7 @@ class TextureRegistry { private: std::map> mapping_; + std::map> images_; FML_DISALLOW_COPY_AND_ASSIGN(TextureRegistry); }; diff --git a/display_list/BUILD.gn b/display_list/BUILD.gn index 0724e4a997d6b..077209220e69b 100644 --- a/display_list/BUILD.gn +++ b/display_list/BUILD.gn @@ -16,6 +16,8 @@ source_set("display_list") { "display_list_blend_mode.h", "display_list_builder.cc", "display_list_builder.h", + "display_list_builder_multiplexer.cc", + "display_list_builder_multiplexer.h", "display_list_canvas_dispatcher.cc", "display_list_canvas_dispatcher.h", "display_list_canvas_recorder.cc", diff --git a/display_list/display_list.h b/display_list/display_list.h index 3731a575b8cac..8eab4494ef9b6 100644 --- a/display_list/display_list.h +++ b/display_list/display_list.h @@ -90,6 +90,7 @@ namespace flutter { V(SetPodColorSource) \ V(SetSkColorSource) \ V(SetImageColorSource) \ + V(SetRuntimeEffectColorSource) \ \ V(ClearImageFilter) \ V(SetPodImageFilter) \ diff --git a/display_list/display_list_builder.cc b/display_list/display_list_builder.cc index 970e019ff56bf..788938e4b05b7 100644 --- a/display_list/display_list_builder.cc +++ b/display_list/display_list_builder.cc @@ -4,7 +4,9 @@ #include "flutter/display_list/display_list_builder.h" +#include "flutter/display_list/display_list.h" #include "flutter/display_list/display_list_blend_mode.h" +#include "flutter/display_list/display_list_color_source.h" #include "flutter/display_list/display_list_ops.h" namespace flutter { @@ -183,6 +185,12 @@ void DisplayListBuilder::onSetColorSource(const DlColorSource* source) { new (pod) DlSweepGradientColorSource(sweep); break; } + case DlColorSourceType::kRuntimeEffect: { + const DlRuntimeEffectColorSource* effect = source->asRuntimeEffect(); + FML_DCHECK(effect); + Push(0, 0, effect); + break; + } case DlColorSourceType::kUnknown: Push(0, 0, source->skia_object()); break; @@ -225,6 +233,7 @@ void DisplayListBuilder::onSetImageFilter(const DlImageFilter* filter) { break; } case DlImageFilterType::kComposeFilter: + case DlImageFilterType::kLocalMatrixFilter: case DlImageFilterType::kColorFilter: { Push(0, 0, filter); break; @@ -413,19 +422,29 @@ void DisplayListBuilder::setAttributesFromPaint( } } +void DisplayListBuilder::checkForDeferredSave() { + if (current_layer_->has_deferred_save_op_) { + Push(0, 1); + current_layer_->has_deferred_save_op_ = false; + } +} + void DisplayListBuilder::save() { - Push(0, 1); layer_stack_.emplace_back(current_layer_); current_layer_ = &layer_stack_.back(); + current_layer_->has_deferred_save_op_ = true; } + void DisplayListBuilder::restore() { if (layer_stack_.size() > 1) { + if (!current_layer_->has_deferred_save_op_) { + Push(0, 1); + } // Grab the current layer info before we push the restore // on the stack. LayerInfo layer_info = layer_stack_.back(); layer_stack_.pop_back(); current_layer_ = &layer_stack_.back(); - Push(0, 1); if (layer_info.has_layer) { if (layer_info.is_group_opacity_compatible()) { // We are now going to go back and modify the matching saveLayer @@ -504,6 +523,7 @@ void DisplayListBuilder::saveLayer(const SkRect* bounds, void DisplayListBuilder::translate(SkScalar tx, SkScalar ty) { if (SkScalarIsFinite(tx) && SkScalarIsFinite(ty) && (tx != 0.0 || ty != 0.0)) { + checkForDeferredSave(); Push(0, 1, tx, ty); current_layer_->matrix.preTranslate(tx, ty); } @@ -511,12 +531,14 @@ void DisplayListBuilder::translate(SkScalar tx, SkScalar ty) { void DisplayListBuilder::scale(SkScalar sx, SkScalar sy) { if (SkScalarIsFinite(sx) && SkScalarIsFinite(sy) && (sx != 1.0 || sy != 1.0)) { + checkForDeferredSave(); Push(0, 1, sx, sy); current_layer_->matrix.preScale(sx, sy); } } void DisplayListBuilder::rotate(SkScalar degrees) { if (SkScalarMod(degrees, 360.0) != 0.0) { + checkForDeferredSave(); Push(0, 1, degrees); current_layer_->matrix.preConcat(SkMatrix::RotateDeg(degrees)); } @@ -524,6 +546,7 @@ void DisplayListBuilder::rotate(SkScalar degrees) { void DisplayListBuilder::skew(SkScalar sx, SkScalar sy) { if (SkScalarIsFinite(sx) && SkScalarIsFinite(sy) && (sx != 0.0 || sy != 0.0)) { + checkForDeferredSave(); Push(0, 1, sx, sy); current_layer_->matrix.preConcat(SkMatrix::Skew(sx, sy)); } @@ -540,6 +563,7 @@ void DisplayListBuilder::transform2DAffine( SkScalarsAreFinite(mxt, myt) && !(mxx == 1 && mxy == 0 && mxt == 0 && myx == 0 && myy == 1 && myt == 0)) { + checkForDeferredSave(); Push(0, 1, mxx, mxy, mxt, myx, myy, myt); @@ -565,6 +589,7 @@ void DisplayListBuilder::transformFullPerspective( SkScalarsAreFinite(myx, myy) && SkScalarsAreFinite(myz, myt) && SkScalarsAreFinite(mzx, mzy) && SkScalarsAreFinite(mzz, mzt) && SkScalarsAreFinite(mwx, mwy) && SkScalarsAreFinite(mwz, mwt)) { + checkForDeferredSave(); Push(0, 1, mxx, mxy, mxz, mxt, myx, myy, myz, myt, @@ -578,6 +603,7 @@ void DisplayListBuilder::transformFullPerspective( } // clang-format on void DisplayListBuilder::transformReset() { + checkForDeferredSave(); Push(0, 0); current_layer_->matrix.setIdentity(); } @@ -599,6 +625,10 @@ void DisplayListBuilder::transform(const SkM44* m44) { void DisplayListBuilder::clipRect(const SkRect& rect, SkClipOp clip_op, bool is_aa) { + if (!rect.isFinite()) { + return; + } + checkForDeferredSave(); switch (clip_op) { case SkClipOp::kIntersect: Push(0, 1, rect, is_aa); @@ -615,6 +645,7 @@ void DisplayListBuilder::clipRRect(const SkRRect& rrect, if (rrect.isRect()) { clipRect(rrect.rect(), clip_op, is_aa); } else { + checkForDeferredSave(); switch (clip_op) { case SkClipOp::kIntersect: Push(0, 1, rrect, is_aa); @@ -646,6 +677,7 @@ void DisplayListBuilder::clipPath(const SkPath& path, return; } } + checkForDeferredSave(); switch (clip_op) { case SkClipOp::kIntersect: Push(0, 1, path, is_aa); diff --git a/display_list/display_list_builder.h b/display_list/display_list_builder.h index ce785c594b7ad..759b7280ed0f1 100644 --- a/display_list/display_list_builder.h +++ b/display_list/display_list_builder.h @@ -343,6 +343,8 @@ class DisplayListBuilder final : public virtual Dispatcher, sk_sp Build(); private: + void checkForDeferredSave(); + SkAutoTMalloc storage_; size_t used_ = 0; size_t allocated_ = 0; @@ -397,6 +399,8 @@ class DisplayListBuilder final : public virtual Dispatcher, // This offset is only valid if |has_layer| is true. size_t save_layer_offset; + bool has_deferred_save_op_ = false; + bool has_layer; bool cannot_inherit_opacity; bool has_compatible_op; diff --git a/display_list/display_list_builder_benchmarks.cc b/display_list/display_list_builder_benchmarks.cc index 4ebadb4521bc6..129eac3abd497 100644 --- a/display_list/display_list_builder_benchmarks.cc +++ b/display_list/display_list_builder_benchmarks.cc @@ -48,8 +48,8 @@ static void Complete(DisplayListBuilder& builder, } // namespace -static void BM_DisplayListBuiderDefault(benchmark::State& state, - DisplayListBuilderBenchmarkType type) { +static void BM_DisplayListBuilderDefault(benchmark::State& state, + DisplayListBuilderBenchmarkType type) { while (state.KeepRunning()) { DisplayListBuilder builder; InvokeAllRenderingOps(builder); @@ -57,7 +57,7 @@ static void BM_DisplayListBuiderDefault(benchmark::State& state, } } -static void BM_DisplayListBuiderWithScaleAndTranslate( +static void BM_DisplayListBuilderWithScaleAndTranslate( benchmark::State& state, DisplayListBuilderBenchmarkType type) { while (state.KeepRunning()) { @@ -69,7 +69,7 @@ static void BM_DisplayListBuiderWithScaleAndTranslate( } } -static void BM_DisplayListBuiderWithPerspective( +static void BM_DisplayListBuilderWithPerspective( benchmark::State& state, DisplayListBuilderBenchmarkType type) { while (state.KeepRunning()) { @@ -81,7 +81,7 @@ static void BM_DisplayListBuiderWithPerspective( } } -static void BM_DisplayListBuiderWithClipRect( +static void BM_DisplayListBuilderWithClipRect( benchmark::State& state, DisplayListBuilderBenchmarkType type) { SkRect clip_bounds = SkRect::MakeLTRB(6.5, 7.3, 90.2, 85.7); @@ -93,7 +93,7 @@ static void BM_DisplayListBuiderWithClipRect( } } -static void BM_DisplayListBuiderWithSaveLayer( +static void BM_DisplayListBuilderWithSaveLayer( benchmark::State& state, DisplayListBuilderBenchmarkType type) { while (state.KeepRunning()) { @@ -110,7 +110,7 @@ static void BM_DisplayListBuiderWithSaveLayer( } } -static void BM_DisplayListBuiderWithSaveLayerAndImageFilter( +static void BM_DisplayListBuilderWithSaveLayerAndImageFilter( benchmark::State& state, DisplayListBuilderBenchmarkType type) { DlPaint layer_paint; @@ -130,106 +130,106 @@ static void BM_DisplayListBuiderWithSaveLayerAndImageFilter( } } -BENCHMARK_CAPTURE(BM_DisplayListBuiderDefault, +BENCHMARK_CAPTURE(BM_DisplayListBuilderDefault, kDefault, DisplayListBuilderBenchmarkType::kDefault) - ->Unit(benchmark::kMillisecond); -BENCHMARK_CAPTURE(BM_DisplayListBuiderDefault, + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_DisplayListBuilderDefault, kBounds, DisplayListBuilderBenchmarkType::kBounds) - ->Unit(benchmark::kMillisecond); -BENCHMARK_CAPTURE(BM_DisplayListBuiderDefault, + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_DisplayListBuilderDefault, kRtree, DisplayListBuilderBenchmarkType::kRtree) - ->Unit(benchmark::kMillisecond); -BENCHMARK_CAPTURE(BM_DisplayListBuiderDefault, + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_DisplayListBuilderDefault, kBoundsAndRtree, DisplayListBuilderBenchmarkType::kBoundsAndRtree) - ->Unit(benchmark::kMillisecond); + ->Unit(benchmark::kMicrosecond); -BENCHMARK_CAPTURE(BM_DisplayListBuiderWithScaleAndTranslate, +BENCHMARK_CAPTURE(BM_DisplayListBuilderWithScaleAndTranslate, kDefault, DisplayListBuilderBenchmarkType::kDefault) - ->Unit(benchmark::kMillisecond); -BENCHMARK_CAPTURE(BM_DisplayListBuiderWithScaleAndTranslate, + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_DisplayListBuilderWithScaleAndTranslate, kBounds, DisplayListBuilderBenchmarkType::kBounds) - ->Unit(benchmark::kMillisecond); -BENCHMARK_CAPTURE(BM_DisplayListBuiderWithScaleAndTranslate, + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_DisplayListBuilderWithScaleAndTranslate, kRtree, DisplayListBuilderBenchmarkType::kRtree) - ->Unit(benchmark::kMillisecond); -BENCHMARK_CAPTURE(BM_DisplayListBuiderWithScaleAndTranslate, + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_DisplayListBuilderWithScaleAndTranslate, kBoundsAndRtree, DisplayListBuilderBenchmarkType::kBoundsAndRtree) - ->Unit(benchmark::kMillisecond); + ->Unit(benchmark::kMicrosecond); -BENCHMARK_CAPTURE(BM_DisplayListBuiderWithPerspective, +BENCHMARK_CAPTURE(BM_DisplayListBuilderWithPerspective, kDefault, DisplayListBuilderBenchmarkType::kDefault) - ->Unit(benchmark::kMillisecond); -BENCHMARK_CAPTURE(BM_DisplayListBuiderWithPerspective, + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_DisplayListBuilderWithPerspective, kBounds, DisplayListBuilderBenchmarkType::kBounds) - ->Unit(benchmark::kMillisecond); -BENCHMARK_CAPTURE(BM_DisplayListBuiderWithPerspective, + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_DisplayListBuilderWithPerspective, kRtree, DisplayListBuilderBenchmarkType::kRtree) - ->Unit(benchmark::kMillisecond); -BENCHMARK_CAPTURE(BM_DisplayListBuiderWithPerspective, + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_DisplayListBuilderWithPerspective, kBoundsAndRtree, DisplayListBuilderBenchmarkType::kBoundsAndRtree) - ->Unit(benchmark::kMillisecond); + ->Unit(benchmark::kMicrosecond); -BENCHMARK_CAPTURE(BM_DisplayListBuiderWithClipRect, +BENCHMARK_CAPTURE(BM_DisplayListBuilderWithClipRect, kDefault, DisplayListBuilderBenchmarkType::kDefault) - ->Unit(benchmark::kMillisecond); -BENCHMARK_CAPTURE(BM_DisplayListBuiderWithClipRect, + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_DisplayListBuilderWithClipRect, kBounds, DisplayListBuilderBenchmarkType::kBounds) - ->Unit(benchmark::kMillisecond); -BENCHMARK_CAPTURE(BM_DisplayListBuiderWithClipRect, + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_DisplayListBuilderWithClipRect, kRtree, DisplayListBuilderBenchmarkType::kRtree) - ->Unit(benchmark::kMillisecond); -BENCHMARK_CAPTURE(BM_DisplayListBuiderWithClipRect, + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_DisplayListBuilderWithClipRect, kBoundsAndRtree, DisplayListBuilderBenchmarkType::kBoundsAndRtree) - ->Unit(benchmark::kMillisecond); + ->Unit(benchmark::kMicrosecond); -BENCHMARK_CAPTURE(BM_DisplayListBuiderWithSaveLayer, +BENCHMARK_CAPTURE(BM_DisplayListBuilderWithSaveLayer, kDefault, DisplayListBuilderBenchmarkType::kDefault) - ->Unit(benchmark::kMillisecond); -BENCHMARK_CAPTURE(BM_DisplayListBuiderWithSaveLayer, + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_DisplayListBuilderWithSaveLayer, kBounds, DisplayListBuilderBenchmarkType::kBounds) - ->Unit(benchmark::kMillisecond); -BENCHMARK_CAPTURE(BM_DisplayListBuiderWithSaveLayer, + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_DisplayListBuilderWithSaveLayer, kRtree, DisplayListBuilderBenchmarkType::kRtree) - ->Unit(benchmark::kMillisecond); -BENCHMARK_CAPTURE(BM_DisplayListBuiderWithSaveLayer, + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_DisplayListBuilderWithSaveLayer, kBoundsAndRtree, DisplayListBuilderBenchmarkType::kBoundsAndRtree) - ->Unit(benchmark::kMillisecond); + ->Unit(benchmark::kMicrosecond); -BENCHMARK_CAPTURE(BM_DisplayListBuiderWithSaveLayerAndImageFilter, +BENCHMARK_CAPTURE(BM_DisplayListBuilderWithSaveLayerAndImageFilter, kDefault, DisplayListBuilderBenchmarkType::kDefault) - ->Unit(benchmark::kMillisecond); -BENCHMARK_CAPTURE(BM_DisplayListBuiderWithSaveLayerAndImageFilter, + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_DisplayListBuilderWithSaveLayerAndImageFilter, kBounds, DisplayListBuilderBenchmarkType::kBounds) - ->Unit(benchmark::kMillisecond); -BENCHMARK_CAPTURE(BM_DisplayListBuiderWithSaveLayerAndImageFilter, + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_DisplayListBuilderWithSaveLayerAndImageFilter, kRtree, DisplayListBuilderBenchmarkType::kRtree) - ->Unit(benchmark::kMillisecond); -BENCHMARK_CAPTURE(BM_DisplayListBuiderWithSaveLayerAndImageFilter, + ->Unit(benchmark::kMicrosecond); +BENCHMARK_CAPTURE(BM_DisplayListBuilderWithSaveLayerAndImageFilter, kBoundsAndRtree, DisplayListBuilderBenchmarkType::kBoundsAndRtree) - ->Unit(benchmark::kMillisecond); + ->Unit(benchmark::kMicrosecond); } // namespace flutter diff --git a/display_list/display_list_builder_multiplexer.cc b/display_list/display_list_builder_multiplexer.cc new file mode 100644 index 0000000000000..0c029c532f89d --- /dev/null +++ b/display_list/display_list_builder_multiplexer.cc @@ -0,0 +1,28 @@ +// 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/display_list/display_list_builder_multiplexer.h" + +namespace flutter { + +void DisplayListBuilderMultiplexer::addBuilder(DisplayListBuilder* builder) { + builders_.push_back(builder); +} + +void DisplayListBuilderMultiplexer::saveLayer( + const SkRect* bounds, + const DlPaint* paint, + const DlImageFilter* backdrop_filter) { + for (auto* builder : builders_) { + builder->saveLayer(bounds, paint, backdrop_filter); + } +} + +void DisplayListBuilderMultiplexer::restore() { + for (auto* builder : builders_) { + builder->restore(); + } +} + +} // namespace flutter diff --git a/display_list/display_list_builder_multiplexer.h b/display_list/display_list_builder_multiplexer.h new file mode 100644 index 0000000000000..498301d136773 --- /dev/null +++ b/display_list/display_list_builder_multiplexer.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_DISPLAY_LIST_DISPLAY_LIST_BUILDER_MULTIPLEXER_H_ +#define FLUTTER_DISPLAY_LIST_DISPLAY_LIST_BUILDER_MULTIPLEXER_H_ + +#include "flutter/display_list/display_list_builder.h" +#include "flutter/display_list/display_list_image_filter.h" +#include "flutter/display_list/display_list_paint.h" +#include "flutter/fml/macros.h" + +namespace flutter { + +/// A class that mutiplexes some of the DisplayListBuilder calls to multiple +/// other builders. For now it only implements saveLayer and restore as those +/// are needed to create a replacement for PaintContext::internal_nodes_canvas. +class DisplayListBuilderMultiplexer { + public: + DisplayListBuilderMultiplexer() = default; + + void addBuilder(DisplayListBuilder* builder); + + void saveLayer(const SkRect* bounds, + const DlPaint* paint, + const DlImageFilter* backdrop_filter = nullptr); + + void restore(); + + private: + std::vector builders_; +}; + +} // namespace flutter + +#endif // FLUTTER_DISPLAY_LIST_DISPLAY_LIST_BUILDER_MULTIPLEXER_H_ diff --git a/display_list/display_list_canvas_recorder.cc b/display_list/display_list_canvas_recorder.cc index 37b27eb935735..406ee96015bc9 100644 --- a/display_list/display_list_canvas_recorder.cc +++ b/display_list/display_list_canvas_recorder.cc @@ -10,11 +10,21 @@ namespace flutter { +#define CHECK_DISPOSE(ret) \ + do { \ + if (!builder_) { \ + FML_DCHECK(builder_) \ + << "Calling method on DisplayListCanvasRecorder after Build()"; \ + return ret; \ + } \ + } while (0) + DisplayListCanvasRecorder::DisplayListCanvasRecorder(const SkRect& bounds) : SkCanvasVirtualEnforcer(bounds.width(), bounds.height()), builder_(sk_make_sp(bounds)) {} sk_sp DisplayListCanvasRecorder::Build() { + CHECK_DISPOSE(nullptr); sk_sp display_list = builder_->Build(); builder_.reset(); return display_list; @@ -22,44 +32,53 @@ sk_sp DisplayListCanvasRecorder::Build() { // clang-format off void DisplayListCanvasRecorder::didConcat44(const SkM44& m44) { + CHECK_DISPOSE(); builder_->transform(m44); } // clang-format on void DisplayListCanvasRecorder::didSetM44(const SkM44& matrix) { + CHECK_DISPOSE(); builder_->transformReset(); builder_->transform(matrix); } void DisplayListCanvasRecorder::didTranslate(SkScalar tx, SkScalar ty) { + CHECK_DISPOSE(); builder_->translate(tx, ty); } void DisplayListCanvasRecorder::didScale(SkScalar sx, SkScalar sy) { + CHECK_DISPOSE(); builder_->scale(sx, sy); } void DisplayListCanvasRecorder::onClipRect(const SkRect& rect, SkClipOp clip_op, ClipEdgeStyle edge_style) { + CHECK_DISPOSE(); builder_->clipRect(rect, clip_op, edge_style == ClipEdgeStyle::kSoft_ClipEdgeStyle); } void DisplayListCanvasRecorder::onClipRRect(const SkRRect& rrect, SkClipOp clip_op, ClipEdgeStyle edge_style) { + CHECK_DISPOSE(); builder_->clipRRect(rrect, clip_op, edge_style == ClipEdgeStyle::kSoft_ClipEdgeStyle); } void DisplayListCanvasRecorder::onClipPath(const SkPath& path, SkClipOp clip_op, ClipEdgeStyle edge_style) { + CHECK_DISPOSE(); builder_->clipPath(path, clip_op, edge_style == ClipEdgeStyle::kSoft_ClipEdgeStyle); } void DisplayListCanvasRecorder::willSave() { + CHECK_DISPOSE(); builder_->save(); } SkCanvas::SaveLayerStrategy DisplayListCanvasRecorder::getSaveLayerStrategy( const SaveLayerRec& rec) { + CHECK_DISPOSE(SaveLayerStrategy::kNoLayer_SaveLayerStrategy); std::shared_ptr backdrop = DlImageFilter::From(rec.fBackdrop); if (rec.fPaint) { builder_->setAttributesFromPaint(*rec.fPaint, kSaveLayerWithPaintFlags); @@ -72,31 +91,37 @@ SkCanvas::SaveLayerStrategy DisplayListCanvasRecorder::getSaveLayerStrategy( return SaveLayerStrategy::kNoLayer_SaveLayerStrategy; } void DisplayListCanvasRecorder::didRestore() { + CHECK_DISPOSE(); builder_->restore(); } void DisplayListCanvasRecorder::onDrawPaint(const SkPaint& paint) { + CHECK_DISPOSE(); builder_->setAttributesFromPaint(paint, kDrawPaintFlags); builder_->drawPaint(); } void DisplayListCanvasRecorder::onDrawRect(const SkRect& rect, const SkPaint& paint) { + CHECK_DISPOSE(); builder_->setAttributesFromPaint(paint, kDrawRectFlags); builder_->drawRect(rect); } void DisplayListCanvasRecorder::onDrawRRect(const SkRRect& rrect, const SkPaint& paint) { + CHECK_DISPOSE(); builder_->setAttributesFromPaint(paint, kDrawRRectFlags); builder_->drawRRect(rrect); } void DisplayListCanvasRecorder::onDrawDRRect(const SkRRect& outer, const SkRRect& inner, const SkPaint& paint) { + CHECK_DISPOSE(); builder_->setAttributesFromPaint(paint, kDrawDRRectFlags); builder_->drawDRRect(outer, inner); } void DisplayListCanvasRecorder::onDrawOval(const SkRect& rect, const SkPaint& paint) { + CHECK_DISPOSE(); builder_->setAttributesFromPaint(paint, kDrawOvalFlags); builder_->drawOval(rect); } @@ -105,6 +130,7 @@ void DisplayListCanvasRecorder::onDrawArc(const SkRect& rect, SkScalar sweepAngle, bool useCenter, const SkPaint& paint) { + CHECK_DISPOSE(); builder_->setAttributesFromPaint(paint, useCenter // ? kDrawArcWithCenterFlags @@ -113,6 +139,7 @@ void DisplayListCanvasRecorder::onDrawArc(const SkRect& rect, } void DisplayListCanvasRecorder::onDrawPath(const SkPath& path, const SkPaint& paint) { + CHECK_DISPOSE(); builder_->setAttributesFromPaint(paint, kDrawPathFlags); builder_->drawPath(path); } @@ -121,6 +148,7 @@ void DisplayListCanvasRecorder::onDrawPoints(SkCanvas::PointMode mode, size_t count, const SkPoint pts[], const SkPaint& paint) { + CHECK_DISPOSE(); switch (mode) { case SkCanvas::kPoints_PointMode: builder_->setAttributesFromPaint(paint, kDrawPointsAsPointsFlags); @@ -146,6 +174,7 @@ void DisplayListCanvasRecorder::onDrawPoints(SkCanvas::PointMode mode, void DisplayListCanvasRecorder::onDrawVerticesObject(const SkVertices* vertices, SkBlendMode mode, const SkPaint& paint) { + CHECK_DISPOSE(); builder_->setAttributesFromPaint(paint, kDrawVerticesFlags); builder_->drawSkVertices(sk_ref_sp(vertices), mode); } @@ -155,6 +184,7 @@ void DisplayListCanvasRecorder::onDrawImage2(const SkImage* image, SkScalar dy, const SkSamplingOptions& sampling, const SkPaint* paint) { + CHECK_DISPOSE(); if (paint != nullptr) { builder_->setAttributesFromPaint(*paint, kDrawImageWithPaintFlags); } @@ -168,6 +198,7 @@ void DisplayListCanvasRecorder::onDrawImageRect2( const SkSamplingOptions& sampling, const SkPaint* paint, SrcRectConstraint constraint) { + CHECK_DISPOSE(); if (paint != nullptr) { builder_->setAttributesFromPaint(*paint, kDrawImageRectWithPaintFlags); } @@ -179,6 +210,7 @@ void DisplayListCanvasRecorder::onDrawImageLattice2(const SkImage* image, const SkRect& dst, SkFilterMode filter, const SkPaint* paint) { + CHECK_DISPOSE(); if (paint != nullptr) { // SkCanvas will always construct a paint, // though it is a default paint most of the time @@ -201,6 +233,7 @@ void DisplayListCanvasRecorder::onDrawAtlas2(const SkImage* image, const SkSamplingOptions& sampling, const SkRect* cull, const SkPaint* paint) { + CHECK_DISPOSE(); if (paint != nullptr) { builder_->setAttributesFromPaint(*paint, kDrawAtlasWithPaintFlags); } @@ -213,6 +246,7 @@ void DisplayListCanvasRecorder::onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y, const SkPaint& paint) { + CHECK_DISPOSE(); builder_->setAttributesFromPaint(paint, kDrawTextBlobFlags); builder_->drawTextBlob(sk_ref_sp(blob), x, y); } @@ -220,6 +254,7 @@ void DisplayListCanvasRecorder::onDrawTextBlob(const SkTextBlob* blob, void DisplayListCanvasRecorder::onDrawPicture(const SkPicture* picture, const SkMatrix* matrix, const SkPaint* paint) { + CHECK_DISPOSE(); if (paint != nullptr) { builder_->setAttributesFromPaint(*paint, kDrawPictureWithPaintFlags); } @@ -228,6 +263,7 @@ void DisplayListCanvasRecorder::onDrawPicture(const SkPicture* picture, void DisplayListCanvasRecorder::onDrawShadowRec(const SkPath& path, const SkDrawShadowRec& rec) { + CHECK_DISPOSE(); // Skia does not expose the SkDrawShadowRec structure in a public // header file so we cannot record this operation. // See: https://bugs.chromium.org/p/skia/issues/detail?id=12125 @@ -236,12 +272,14 @@ void DisplayListCanvasRecorder::onDrawShadowRec(const SkPath& path, } void DisplayListCanvasRecorder::onDrawBehind(const SkPaint&) { + CHECK_DISPOSE(); FML_DLOG(ERROR) << "Unimplemented DisplayListCanvasRecorder::" << __FUNCTION__; } void DisplayListCanvasRecorder::onDrawRegion(const SkRegion& region, const SkPaint& paint) { + CHECK_DISPOSE(); FML_DLOG(ERROR) << "Unimplemented DisplayListCanvasRecorder::" << __FUNCTION__; } @@ -251,6 +289,7 @@ void DisplayListCanvasRecorder::onDrawPatch(const SkPoint cubics[12], const SkPoint texCoords[4], SkBlendMode mode, const SkPaint& paint) { + CHECK_DISPOSE(); FML_DLOG(ERROR) << "Unimplemented DisplayListCanvasRecorder::" << __FUNCTION__; } @@ -260,6 +299,7 @@ void DisplayListCanvasRecorder::onDrawEdgeAAQuad(const SkRect& rect, SkCanvas::QuadAAFlags aaFlags, const SkColor4f& color, SkBlendMode mode) { + CHECK_DISPOSE(); FML_DLOG(ERROR) << "Unimplemented DisplayListCanvasRecorder::" << __FUNCTION__; } @@ -267,12 +307,14 @@ void DisplayListCanvasRecorder::onDrawEdgeAAQuad(const SkRect& rect, void DisplayListCanvasRecorder::onDrawAnnotation(const SkRect& rect, const char key[], SkData* value) { + CHECK_DISPOSE(); FML_DLOG(ERROR) << "Unimplemented DisplayListCanvasRecorder::" << __FUNCTION__; } void DisplayListCanvasRecorder::onDrawDrawable(SkDrawable* drawable, const SkMatrix* matrix) { + CHECK_DISPOSE(); FML_DLOG(ERROR) << "Unimplemented DisplayListCanvasRecorder::" << __FUNCTION__; } diff --git a/display_list/display_list_canvas_unittests.cc b/display_list/display_list_canvas_unittests.cc index f6e4d6540d0ae..ab120ea686218 100644 --- a/display_list/display_list_canvas_unittests.cc +++ b/display_list/display_list_canvas_unittests.cc @@ -2200,7 +2200,7 @@ BoundsTolerance CanvasCompareTester::DefaultTolerance = const sk_sp CanvasCompareTester::kTestImage = makeTestImage(); const DlImageColorSource CanvasCompareTester::kTestImageColorSource( - kTestImage, + DlImage::Make(kTestImage), DlTileMode::kRepeat, DlTileMode::kRepeat, DlImageSampling::kLinear); diff --git a/display_list/display_list_color_source.cc b/display_list/display_list_color_source.cc index f799f8453806d..4348abbe8b5cf 100644 --- a/display_list/display_list_color_source.cc +++ b/display_list/display_list_color_source.cc @@ -3,6 +3,7 @@ // found in the LICENSE file. #include "flutter/display_list/display_list_color_source.h" +#include "display_list_color_source.h" #include "flutter/display_list/display_list_sampling_options.h" namespace flutter { @@ -17,8 +18,8 @@ std::shared_ptr DlColorSource::From(SkShader* sk_shader) { SkImage* image = sk_shader->isAImage(&local_matrix, xy); if (image) { return std::make_shared( - sk_ref_sp(image), ToDl(xy[0]), ToDl(xy[1]), DlImageSampling::kLinear, - &local_matrix); + DlImage::Make(image), ToDl(xy[0]), ToDl(xy[1]), + DlImageSampling::kLinear, &local_matrix); } } // Skia provides |SkShader->asAGradient(&info)| method to access the @@ -128,4 +129,12 @@ std::shared_ptr DlColorSource::MakeSweep( return std::move(ret); } +std::shared_ptr DlColorSource::MakeRuntimeEffect( + sk_sp runtime_effect, + std::vector> samplers, + sk_sp uniform_data) { + return std::make_shared( + std::move(runtime_effect), std::move(samplers), std::move(uniform_data)); +} + } // namespace flutter diff --git a/display_list/display_list_color_source.h b/display_list/display_list_color_source.h index fafdc7ed183dd..d38217bd25f59 100644 --- a/display_list/display_list_color_source.h +++ b/display_list/display_list_color_source.h @@ -8,12 +8,14 @@ #include "flutter/display_list/display_list.h" #include "flutter/display_list/display_list_attributes.h" #include "flutter/display_list/display_list_color.h" +#include "flutter/display_list/display_list_image.h" #include "flutter/display_list/display_list_sampling_options.h" #include "flutter/display_list/display_list_tile_mode.h" #include "flutter/display_list/types.h" #include "flutter/fml/logging.h" #include "third_party/skia/include/core/SkShader.h" #include "third_party/skia/include/effects/SkGradientShader.h" +#include "third_party/skia/include/effects/SkRuntimeEffect.h" namespace flutter { @@ -23,6 +25,7 @@ class DlLinearGradientColorSource; class DlRadialGradientColorSource; class DlConicalGradientColorSource; class DlSweepGradientColorSource; +class DlRuntimeEffectColorSource; class DlUnknownColorSource; // The DisplayList ColorSource class. This class implements all of the @@ -42,6 +45,7 @@ enum class DlColorSourceType { kRadialGradient, kConicalGradient, kSweepGradient, + kRuntimeEffect, kUnknown }; @@ -103,6 +107,11 @@ class DlColorSource DlTileMode tile_mode, const SkMatrix* matrix = nullptr); + static std::shared_ptr MakeRuntimeEffect( + sk_sp runtime_effect, + std::vector> samplers, + sk_sp uniform_data); + virtual bool is_opaque() const = 0; virtual std::shared_ptr with_sampling( @@ -142,6 +151,19 @@ class DlColorSource return nullptr; } + virtual const DlRuntimeEffectColorSource* asRuntimeEffect() const { + return nullptr; + } + + // If this filter contains images, specifies the owning context for those + // images. + // Images with a DlImage::OwningContext::kRaster must only call skia_object + // on the raster task runner. + // A nullopt return means there is no image. + virtual std::optional owning_context() const { + return std::nullopt; + } + protected: DlColorSource() = default; @@ -201,14 +223,13 @@ class DlMatrixColorSourceBase : public DlColorSource { class DlImageColorSource final : public SkRefCnt, public DlMatrixColorSourceBase { public: - // TODO(100984): Color sources must be DlImages instead of SkImages. - DlImageColorSource(sk_sp image, + DlImageColorSource(sk_sp image, DlTileMode horizontal_tile_mode, DlTileMode vertical_tile_mode, DlImageSampling sampling = DlImageSampling::kLinear, const SkMatrix* matrix = nullptr) : DlMatrixColorSourceBase(matrix), - sk_image_(image), + image_(image), horizontal_tile_mode_(horizontal_tile_mode), vertical_tile_mode_(vertical_tile_mode), sampling_(sampling) {} @@ -221,39 +242,46 @@ class DlImageColorSource final : public SkRefCnt, std::shared_ptr with_sampling( DlImageSampling sampling) const override { - return std::make_shared( - sk_image_, horizontal_tile_mode_, vertical_tile_mode_, sampling, - matrix_ptr()); + return std::make_shared(image_, horizontal_tile_mode_, + vertical_tile_mode_, sampling, + matrix_ptr()); } DlColorSourceType type() const override { return DlColorSourceType::kImage; } size_t size() const override { return sizeof(*this); } - bool is_opaque() const override { return sk_image_->isOpaque(); } + bool is_opaque() const override { return image_->isOpaque(); } - sk_sp image() const { return sk_image_; } + std::optional owning_context() const override { + return image_->owning_context(); + } + + sk_sp image() const { return image_; } DlTileMode horizontal_tile_mode() const { return horizontal_tile_mode_; } DlTileMode vertical_tile_mode() const { return vertical_tile_mode_; } DlImageSampling sampling() const { return sampling_; } virtual sk_sp skia_object() const override { - return sk_image_->makeShader(ToSk(horizontal_tile_mode_), - ToSk(vertical_tile_mode_), ToSk(sampling_), - matrix_ptr()); + if (!image_->skia_image()) { + return nullptr; + } + return image_->skia_image()->makeShader(ToSk(horizontal_tile_mode_), + ToSk(vertical_tile_mode_), + ToSk(sampling_), matrix_ptr()); } protected: bool equals_(DlColorSource const& other) const override { FML_DCHECK(other.type() == DlColorSourceType::kImage); auto that = static_cast(&other); - return (sk_image_ == that->sk_image_ && matrix() == that->matrix() && + return (image_->Equals(that->image_) && matrix() == that->matrix() && horizontal_tile_mode_ == that->horizontal_tile_mode_ && vertical_tile_mode_ == that->vertical_tile_mode_ && sampling_ == that->sampling_); } private: - sk_sp sk_image_; + sk_sp image_; DlTileMode horizontal_tile_mode_; DlTileMode vertical_tile_mode_; DlImageSampling sampling_; @@ -627,6 +655,81 @@ class DlSweepGradientColorSource final : public DlGradientColorSourceBase { FML_DISALLOW_COPY_ASSIGN_AND_MOVE(DlSweepGradientColorSource); }; +class DlRuntimeEffectColorSource final : public DlColorSource { + public: + DlRuntimeEffectColorSource( + sk_sp runtime_effect, + std::vector> samplers, + sk_sp uniform_data) + : runtime_effect_(std::move(runtime_effect)), + samplers_(std::move(samplers)), + uniform_data_(std::move(uniform_data)) {} + + const DlRuntimeEffectColorSource* asRuntimeEffect() const override { + return this; + } + + std::shared_ptr shared() const override { + return std::make_shared( + runtime_effect_, samplers_, uniform_data_); + } + + DlColorSourceType type() const override { + return DlColorSourceType::kRuntimeEffect; + } + size_t size() const override { return sizeof(*this); } + + bool is_opaque() const override { return false; } + + const sk_sp runtime_effect() const { + return runtime_effect_; + } + const std::vector> samplers() const { + return samplers_; + } + const sk_sp uniform_data() const { return uniform_data_; } + + sk_sp skia_object() const override { + if (!runtime_effect_) { + return nullptr; + } + std::vector> sk_samplers(samplers_.size()); + for (size_t i = 0; i < samplers_.size(); i++) { + sk_samplers[i] = samplers_[i]->skia_object(); + } + return runtime_effect_->makeShader(uniform_data_, sk_samplers.data(), + sk_samplers.size()); + } + + protected: + bool equals_(DlColorSource const& other) const override { + FML_DCHECK(other.type() == DlColorSourceType::kRuntimeEffect); + auto that = static_cast(&other); + if (runtime_effect_ != that->runtime_effect_) { + return false; + } + if (uniform_data_ != that->uniform_data_) { + return false; + } + if (samplers_.size() != that->samplers_.size()) { + return false; + } + for (size_t i = 0; i < samplers_.size(); i++) { + if (samplers_[i] != that->samplers_[i]) { + return false; + } + } + return true; + } + + private: + sk_sp runtime_effect_; + std::vector> samplers_; + sk_sp uniform_data_; + + FML_DISALLOW_COPY_ASSIGN_AND_MOVE(DlRuntimeEffectColorSource); +}; + class DlUnknownColorSource final : public DlColorSource { public: DlUnknownColorSource(sk_sp shader) : sk_shader_(shader) {} diff --git a/display_list/display_list_color_source_unittests.cc b/display_list/display_list_color_source_unittests.cc index 6a1440aee24f0..351d94a4f08a6 100644 --- a/display_list/display_list_color_source_unittests.cc +++ b/display_list/display_list_color_source_unittests.cc @@ -5,14 +5,16 @@ #include "flutter/display_list/display_list_attributes_testing.h" #include "flutter/display_list/display_list_builder.h" #include "flutter/display_list/display_list_color_source.h" +#include "flutter/display_list/display_list_image.h" #include "flutter/display_list/display_list_sampling_options.h" #include "flutter/display_list/types.h" +#include "third_party/skia/include/core/SkString.h" #include "third_party/skia/include/core/SkSurface.h" namespace flutter { namespace testing { -static sk_sp MakeTestImage(int w, int h, SkColor color) { +static sk_sp MakeTestImage(int w, int h, SkColor color) { sk_sp surface; if (SkColorGetA(color) < 255) { surface = SkSurface::MakeRasterN32Premul(w, h); @@ -23,11 +25,20 @@ static sk_sp MakeTestImage(int w, int h, SkColor color) { } SkCanvas* canvas = surface->getCanvas(); canvas->drawColor(color); - return surface->makeImageSnapshot(); + return DlImage::Make(surface->makeImageSnapshot()); } -static const sk_sp kTestImage1 = MakeTestImage(10, 10, SK_ColorGREEN); -static const sk_sp kTestAlphaImage1 = +static const sk_sp kTestRuntimeEffect1 = + SkRuntimeEffect::MakeForShader( + SkString("vec4 main(vec2 p) { return vec4(0); }")) + .effect; +static const sk_sp kTestRuntimeEffect2 = + SkRuntimeEffect::MakeForShader( + SkString("vec4 main(vec2 p) { return vec4(1); }")) + .effect; + +static const sk_sp kTestImage1 = MakeTestImage(10, 10, SK_ColorGREEN); +static const sk_sp kTestAlphaImage1 = MakeTestImage(10, 10, SK_ColorTRANSPARENT); // clang-format off static const SkMatrix kTestMatrix1 = @@ -117,15 +128,16 @@ TEST(DisplayListColorSource, FromSkiaColorShader) { } TEST(DisplayListColorSource, FromSkiaImageShader) { - sk_sp shader = - kTestImage1->makeShader(ToSk(DlImageSampling::kLinear), &kTestMatrix1); + sk_sp shader = kTestImage1->skia_image()->makeShader( + ToSk(DlImageSampling::kLinear), &kTestMatrix1); std::shared_ptr source = DlColorSource::From(shader); DlImageColorSource dl_source(kTestImage1, DlTileMode::kClamp, DlTileMode::kClamp, DlImageSampling::kLinear, &kTestMatrix1); ASSERT_EQ(source->type(), DlColorSourceType::kImage); ASSERT_EQ(*source->asImage(), dl_source); - ASSERT_EQ(source->asImage()->image(), kTestImage1); + ASSERT_TRUE(source->asImage()->image()->Equals(kTestImage1)); + ASSERT_TRUE(kTestImage1->Equals(source->asImage()->image())); ASSERT_EQ(source->asImage()->matrix(), kTestMatrix1); ASSERT_EQ(source->asImage()->horizontal_tile_mode(), DlTileMode::kClamp); ASSERT_EQ(source->asImage()->vertical_tile_mode(), DlTileMode::kClamp); @@ -136,6 +148,7 @@ TEST(DisplayListColorSource, FromSkiaImageShader) { ASSERT_EQ(source->asRadialGradient(), nullptr); ASSERT_EQ(source->asConicalGradient(), nullptr); ASSERT_EQ(source->asSweepGradient(), nullptr); + ASSERT_EQ(source->asRuntimeEffect(), nullptr); } TEST(DisplayListColorSource, FromSkiaLinearGradient) { @@ -175,6 +188,7 @@ TEST(DisplayListColorSource, FromSkiaRadialGradient) { ASSERT_EQ(source->asRadialGradient(), nullptr); ASSERT_EQ(source->asConicalGradient(), nullptr); ASSERT_EQ(source->asSweepGradient(), nullptr); + ASSERT_EQ(source->asRuntimeEffect(), nullptr); } TEST(DisplayListColorSource, FromSkiaConicalGradient) { @@ -195,6 +209,7 @@ TEST(DisplayListColorSource, FromSkiaConicalGradient) { ASSERT_EQ(source->asRadialGradient(), nullptr); ASSERT_EQ(source->asConicalGradient(), nullptr); ASSERT_EQ(source->asSweepGradient(), nullptr); + ASSERT_EQ(source->asRuntimeEffect(), nullptr); } TEST(DisplayListColorSource, FromSkiaSweepGradient) { @@ -215,6 +230,7 @@ TEST(DisplayListColorSource, FromSkiaSweepGradient) { ASSERT_EQ(source->asRadialGradient(), nullptr); ASSERT_EQ(source->asConicalGradient(), nullptr); ASSERT_EQ(source->asSweepGradient(), nullptr); + ASSERT_EQ(source->asRuntimeEffect(), nullptr); } TEST(DisplayListColorSource, FromSkiaUnrecognizedShader) { @@ -229,6 +245,7 @@ TEST(DisplayListColorSource, FromSkiaUnrecognizedShader) { ASSERT_EQ(source->asRadialGradient(), nullptr); ASSERT_EQ(source->asConicalGradient(), nullptr); ASSERT_EQ(source->asSweepGradient(), nullptr); + ASSERT_EQ(source->asRuntimeEffect(), nullptr); } TEST(DisplayListColorSource, ColorConstructor) { @@ -251,6 +268,7 @@ TEST(DisplayListColorSource, ColorAsColor) { ASSERT_EQ(source.asRadialGradient(), nullptr); ASSERT_EQ(source.asConicalGradient(), nullptr); ASSERT_EQ(source.asSweepGradient(), nullptr); + ASSERT_EQ(source.asRuntimeEffect(), nullptr); } TEST(DisplayListColorSource, ColorContents) { @@ -398,6 +416,7 @@ TEST(DisplayListColorSource, LinearGradientAsLinear) { ASSERT_EQ(source->asRadialGradient(), nullptr); ASSERT_EQ(source->asConicalGradient(), nullptr); ASSERT_EQ(source->asSweepGradient(), nullptr); + ASSERT_EQ(source->asRuntimeEffect(), nullptr); } TEST(DisplayListColorSource, LinearGradientContents) { @@ -516,6 +535,7 @@ TEST(DisplayListColorSource, RadialGradientAsRadial) { ASSERT_EQ(source->asLinearGradient(), nullptr); ASSERT_EQ(source->asConicalGradient(), nullptr); ASSERT_EQ(source->asSweepGradient(), nullptr); + ASSERT_EQ(source->asRuntimeEffect(), nullptr); } TEST(DisplayListColorSource, RadialGradientContents) { @@ -634,6 +654,7 @@ TEST(DisplayListColorSource, ConicalGradientAsConical) { ASSERT_EQ(source->asLinearGradient(), nullptr); ASSERT_EQ(source->asRadialGradient(), nullptr); ASSERT_EQ(source->asSweepGradient(), nullptr); + ASSERT_EQ(source->asRuntimeEffect(), nullptr); } TEST(DisplayListColorSource, ConicalGradientContents) { @@ -768,6 +789,7 @@ TEST(DisplayListColorSource, SweepGradientAsSweep) { ASSERT_EQ(source->asLinearGradient(), nullptr); ASSERT_EQ(source->asRadialGradient(), nullptr); ASSERT_EQ(source->asConicalGradient(), nullptr); + ASSERT_EQ(source->asRuntimeEffect(), nullptr); } TEST(DisplayListColorSource, SweepGradientContents) { @@ -886,6 +908,7 @@ TEST(DisplayListColorSource, UnknownAsNone) { ASSERT_EQ(source.asRadialGradient(), nullptr); ASSERT_EQ(source.asConicalGradient(), nullptr); ASSERT_EQ(source.asSweepGradient(), nullptr); + ASSERT_EQ(source.asRuntimeEffect(), nullptr); } TEST(DisplayListColorSource, UnknownContents) { @@ -914,5 +937,33 @@ TEST(DisplayListColorSource, UnknownNotEquals) { TestNotEquals(source1, source2, "SkShader differs"); } +TEST(DisplayListColorSource, RuntimeEffect) { + std::shared_ptr source1 = + DlColorSource::MakeRuntimeEffect(kTestRuntimeEffect1, {}, nullptr); + std::shared_ptr source2 = + DlColorSource::MakeRuntimeEffect(kTestRuntimeEffect2, {}, nullptr); + std::shared_ptr source3 = + DlColorSource::MakeRuntimeEffect(nullptr, {}, nullptr); + + ASSERT_EQ(source1->type(), DlColorSourceType::kRuntimeEffect); + ASSERT_EQ(source1->asRuntimeEffect(), source1.get()); + ASSERT_NE(source2->asRuntimeEffect(), source1.get()); + + ASSERT_EQ(source1->asImage(), nullptr); + ASSERT_EQ(source1->asColor(), nullptr); + ASSERT_EQ(source1->asLinearGradient(), nullptr); + ASSERT_EQ(source1->asRadialGradient(), nullptr); + ASSERT_EQ(source1->asConicalGradient(), nullptr); + ASSERT_EQ(source1->asSweepGradient(), nullptr); + + ASSERT_NE(source1->skia_object(), nullptr); + ASSERT_EQ(source3->skia_object(), nullptr); + + TestEquals(source1, source1); + TestEquals(source3, source3); + TestNotEquals(source1, source2, "SkRuntimeEffect differs"); + TestNotEquals(source2, source3, "SkRuntimeEffect differs"); +} + } // namespace testing } // namespace flutter diff --git a/display_list/display_list_image.h b/display_list/display_list_image.h index fafb1504f465b..19ae1d935bf6d 100644 --- a/display_list/display_list_image.h +++ b/display_list/display_list_image.h @@ -54,6 +54,17 @@ class DlImage : public SkRefCnt { /// virtual std::shared_ptr impeller_texture() const = 0; + //---------------------------------------------------------------------------- + /// @brief If the pixel format of this image ignores alpha, this returns + /// true. This method might conservatively return false when it + /// cannot guarnatee an opaque image, for example when the pixel + /// format of the image supports alpha but the image is made up of + /// entirely opaque pixels. + /// + /// @return True if the pixel format of this image ignores alpha. + /// + virtual bool isOpaque() const = 0; + virtual bool isTextureBacked() const = 0; //---------------------------------------------------------------------------- @@ -97,6 +108,21 @@ class DlImage : public SkRefCnt { /// image. virtual std::optional get_error() const; + bool Equals(const DlImage* other) const { + if (!other) { + return false; + } + if (this == other) { + return true; + } + return skia_image() == other->skia_image() && + impeller_texture() == other->impeller_texture(); + } + + bool Equals(const DlImage& other) const { return Equals(&other); } + + bool Equals(sk_sp other) const { return Equals(other.get()); } + protected: DlImage(); }; diff --git a/display_list/display_list_image_filter.cc b/display_list/display_list_image_filter.cc index f9eb71d35cca1..4e43649a208e0 100644 --- a/display_list/display_list_image_filter.cc +++ b/display_list/display_list_image_filter.cc @@ -27,6 +27,33 @@ std::shared_ptr DlImageFilter::From( return std::make_shared(sk_ref_sp(sk_filter)); } +std::shared_ptr DlImageFilter::makeWithLocalMatrix( + const SkMatrix& matrix) const { + if (matrix.isIdentity()) { + return shared(); + } + // Matrix + switch (this->matrix_capability()) { + case MatrixCapability::kTranslate: { + if (!matrix.isTranslate()) { + // Nothing we can do at this point + return nullptr; + } + break; + } + case MatrixCapability::kScaleTranslate: { + if (!matrix.isScaleTranslate()) { + // Nothing we can do at this point + return nullptr; + } + break; + } + default: + break; + } + return std::make_shared(matrix, shared()); +} + SkRect* DlComposeImageFilter::map_local_bounds(const SkRect& input_bounds, SkRect& output_bounds) const { SkRect cur_bounds = input_bounds; diff --git a/display_list/display_list_image_filter.h b/display_list/display_list_image_filter.h index 5cc789e337795..c495c4cf098b2 100644 --- a/display_list/display_list_image_filter.h +++ b/display_list/display_list_image_filter.h @@ -34,6 +34,7 @@ enum class DlImageFilterType { kMatrix, kComposeFilter, kColorFilter, + kLocalMatrixFilter, kUnknown }; @@ -41,12 +42,19 @@ class DlBlurImageFilter; class DlDilateImageFilter; class DlErodeImageFilter; class DlMatrixImageFilter; +class DlLocalMatrixImageFilter; class DlComposeImageFilter; class DlColorFilterImageFilter; class DlImageFilter : public DlAttribute { public: + enum class MatrixCapability { + kTranslate, + kScaleTranslate, + kComplex, + }; + // Return a shared_ptr holding a DlImageFilter representing the indicated // Skia SkImageFilter pointer. // @@ -82,6 +90,13 @@ class DlImageFilter // type of ImageFilter, otherwise return nullptr. virtual const DlMatrixImageFilter* asMatrix() const { return nullptr; } + virtual const DlLocalMatrixImageFilter* asLocalMatrix() const { + return nullptr; + } + + virtual std::shared_ptr makeWithLocalMatrix( + const SkMatrix& matrix) const; + // Return a DlComposeImageFilter pointer to this object iff it is a Compose // type of ImageFilter, otherwise return nullptr. virtual const DlComposeImageFilter* asCompose() const { return nullptr; } @@ -137,6 +152,10 @@ class DlImageFilter const SkMatrix& ctm, SkIRect& input_bounds) const = 0; + virtual MatrixCapability matrix_capability() const { + return MatrixCapability::kScaleTranslate; + } + protected: static SkVector map_vectors_affine(const SkMatrix& ctm, SkScalar x, @@ -534,6 +553,10 @@ class DlComposeImageFilter final : public DlImageFilter { inner_->skia_object()); } + MatrixCapability matrix_capability() const override { + return std::min(outer_->matrix_capability(), inner_->matrix_capability()); + } + protected: bool equals_(const DlImageFilter& other) const override { FML_DCHECK(other.type() == DlImageFilterType::kComposeFilter); @@ -606,6 +629,15 @@ class DlColorFilterImageFilter final : public DlImageFilter { return SkImageFilters::ColorFilter(color_filter_->skia_object(), nullptr); } + MatrixCapability matrix_capability() const override { + return MatrixCapability::kComplex; + } + + std::shared_ptr makeWithLocalMatrix( + const SkMatrix& matrix) const override { + return shared(); + } + protected: bool equals_(const DlImageFilter& other) const override { FML_DCHECK(other.type() == DlImageFilterType::kColorFilter); @@ -617,6 +649,85 @@ class DlColorFilterImageFilter final : public DlImageFilter { std::shared_ptr color_filter_; }; +class DlLocalMatrixImageFilter final : public DlImageFilter { + public: + explicit DlLocalMatrixImageFilter(const SkMatrix& matrix, + std::shared_ptr filter) + : matrix_(matrix), image_filter_(filter) {} + explicit DlLocalMatrixImageFilter(const DlLocalMatrixImageFilter* filter) + : DlLocalMatrixImageFilter(filter->matrix_, filter->image_filter_) {} + DlLocalMatrixImageFilter(const DlLocalMatrixImageFilter& filter) + : DlLocalMatrixImageFilter(&filter) {} + std::shared_ptr shared() const override { + return std::make_shared(this); + } + + DlImageFilterType type() const override { + return DlImageFilterType::kLocalMatrixFilter; + } + size_t size() const override { return sizeof(*this); } + + const SkMatrix& matrix() const { return matrix_; } + + const DlLocalMatrixImageFilter* asLocalMatrix() const override { + return this; + } + + bool modifies_transparent_black() const override { + if (!image_filter_) { + return false; + } + return image_filter_->modifies_transparent_black(); + } + + SkRect* map_local_bounds(const SkRect& input_bounds, + SkRect& output_bounds) const override { + if (!image_filter_) { + return nullptr; + } + return image_filter_->map_local_bounds(input_bounds, output_bounds); + } + + SkIRect* map_device_bounds(const SkIRect& input_bounds, + const SkMatrix& ctm, + SkIRect& output_bounds) const override { + if (!image_filter_) { + return nullptr; + } + return image_filter_->map_device_bounds( + input_bounds, SkMatrix::Concat(ctm, matrix_), output_bounds); + } + + SkIRect* get_input_device_bounds(const SkIRect& output_bounds, + const SkMatrix& ctm, + SkIRect& input_bounds) const override { + if (!image_filter_) { + return nullptr; + } + return image_filter_->get_input_device_bounds( + output_bounds, SkMatrix::Concat(ctm, matrix_), input_bounds); + } + + sk_sp skia_object() const override { + if (!image_filter_) { + return nullptr; + } + return image_filter_->skia_object()->makeWithLocalMatrix(matrix_); + } + + protected: + bool equals_(const DlImageFilter& other) const override { + FML_DCHECK(other.type() == DlImageFilterType::kMatrix); + auto that = static_cast(&other); + return (matrix_ == that->matrix_ && + Equals(image_filter_, that->image_filter_)); + } + + private: + SkMatrix matrix_; + std::shared_ptr image_filter_; +}; + // A wrapper class for a Skia ImageFilter of unknown type. The above 4 types // are the only types that can be constructed by Flutter using the // ui.ImageFilter class so this class should be rarely used. The main use @@ -653,7 +764,7 @@ class DlUnknownImageFilter final : public DlImageFilter { SkRect* map_local_bounds(const SkRect& input_bounds, SkRect& output_bounds) const override { - if (modifies_transparent_black()) { + if (!sk_filter_ || modifies_transparent_black()) { output_bounds = input_bounds; return nullptr; } @@ -664,7 +775,7 @@ class DlUnknownImageFilter final : public DlImageFilter { SkIRect* map_device_bounds(const SkIRect& input_bounds, const SkMatrix& ctm, SkIRect& output_bounds) const override { - if (modifies_transparent_black()) { + if (!sk_filter_ || modifies_transparent_black()) { output_bounds = input_bounds; return nullptr; } @@ -676,7 +787,7 @@ class DlUnknownImageFilter final : public DlImageFilter { SkIRect* get_input_device_bounds(const SkIRect& output_bounds, const SkMatrix& ctm, SkIRect& input_bounds) const override { - if (modifies_transparent_black()) { + if (!sk_filter_ || modifies_transparent_black()) { input_bounds = output_bounds; return nullptr; } diff --git a/display_list/display_list_image_filter_unittests.cc b/display_list/display_list_image_filter_unittests.cc index 346a2f7ff56e3..f3f2507cf3b82 100644 --- a/display_list/display_list_image_filter_unittests.cc +++ b/display_list/display_list_image_filter_unittests.cc @@ -3,10 +3,14 @@ // found in the LICENSE file. #include "flutter/display_list/display_list_attributes_testing.h" +#include "flutter/display_list/display_list_blend_mode.h" #include "flutter/display_list/display_list_builder.h" +#include "flutter/display_list/display_list_color.h" +#include "flutter/display_list/display_list_color_filter.h" #include "flutter/display_list/display_list_comparable.h" #include "flutter/display_list/display_list_image_filter.h" #include "flutter/display_list/display_list_sampling_options.h" +#include "flutter/display_list/display_list_tile_mode.h" #include "flutter/display_list/types.h" #include "gtest/gtest.h" @@ -759,6 +763,89 @@ TEST(DisplayListImageFilter, UnknownContents) { ASSERT_EQ(filter.skia_object().get(), sk_filter.get()); } +TEST(DisplayListImageFilter, LocalImageFilterBounds) { + auto filter_matrix = SkMatrix::MakeAll(2.0, 0.0, 10, // + 0.5, 3.0, 15, // + 0.0, 0.0, 1); + std::vector> sk_filters{ + SkImageFilters::Blur(5.0, 6.0, SkTileMode::kRepeat, nullptr), + SkImageFilters::ColorFilter( + SkColorFilters::Blend(SK_ColorRED, SkBlendMode::kSrcOver), nullptr), + SkImageFilters::Dilate(5.0, 10.0, nullptr), + SkImageFilters::MatrixTransform(filter_matrix, + ToSk(DlImageSampling::kLinear), nullptr), + SkImageFilters::Compose( + SkImageFilters::Blur(5.0, 6.0, SkTileMode::kRepeat, nullptr), + SkImageFilters::ColorFilter( + SkColorFilters::Blend(SK_ColorRED, SkBlendMode::kSrcOver), + nullptr))}; + + DlBlendColorFilter dl_color_filter(DlColor::kRed(), DlBlendMode::kSrcOver); + std::vector> dl_filters{ + std::make_shared(5.0, 6.0, DlTileMode::kRepeat), + std::make_shared(dl_color_filter.shared()), + std::make_shared(5, 10), + std::make_shared(filter_matrix, + DlImageSampling::kLinear), + std::make_shared( + std::make_shared(5.0, 6.0, DlTileMode::kRepeat), + std::make_shared( + dl_color_filter.shared()))}; + + auto persp = SkMatrix::I(); + persp.setPerspY(0.001); + std::vector matrices = { + SkMatrix::Translate(10.0, 10.0), + SkMatrix::Scale(2.0, 2.0).preTranslate(10.0, 10.0), + SkMatrix::RotateDeg(45).preTranslate(5.0, 5.0), persp}; + std::vector bounds_matrices{SkMatrix::Translate(5.0, 10.0), + SkMatrix::Scale(2.0, 2.0)}; + + for (unsigned i = 0; i < sk_filters.size(); i++) { + for (unsigned j = 0; j < matrices.size(); j++) { + for (unsigned k = 0; k < bounds_matrices.size(); k++) { + auto& m = matrices[j]; + auto& bounds_matrix = bounds_matrices[k]; + auto sk_local_filter = sk_filters[i]->makeWithLocalMatrix(m); + auto dl_local_filter = dl_filters[i]->makeWithLocalMatrix(m); + if (!sk_local_filter || !dl_local_filter) { + ASSERT_TRUE(!sk_local_filter); + ASSERT_TRUE(!dl_local_filter); + continue; + } + { + auto inputBounds = SkIRect::MakeLTRB(20, 20, 80, 80); + SkIRect sk_rect, dl_rect; + sk_rect = sk_local_filter->filterBounds( + inputBounds, bounds_matrix, + SkImageFilter::MapDirection::kForward_MapDirection); + dl_local_filter->map_device_bounds(inputBounds, bounds_matrix, + dl_rect); + ASSERT_EQ(sk_rect, dl_rect); + } + { + // Test for: Know the outset bounds to get the inset bounds + // Skia have some bounds calculate error of DilateFilter and + // MatrixFilter + // Skia issue: https://bugs.chromium.org/p/skia/issues/detail?id=13444 + // flutter issue: https://github.com/flutter/flutter/issues/108693 + if (i == 2 || i == 3) { + continue; + } + auto outsetBounds = SkIRect::MakeLTRB(20, 20, 80, 80); + SkIRect sk_rect, dl_rect; + sk_rect = sk_local_filter->filterBounds( + outsetBounds, bounds_matrix, + SkImageFilter::MapDirection::kReverse_MapDirection); + dl_local_filter->get_input_device_bounds(outsetBounds, bounds_matrix, + dl_rect); + ASSERT_EQ(sk_rect, dl_rect); + } + } + } + } +} + TEST(DisplayListImageFilter, UnknownEquals) { sk_sp sk_filter = SkImageFilters::Blur(5.0, 6.0, SkTileMode::kRepeat, nullptr); diff --git a/display_list/display_list_image_skia.cc b/display_list/display_list_image_skia.cc index 2e2dddb5cba59..07fd84143b8fc 100644 --- a/display_list/display_list_image_skia.cc +++ b/display_list/display_list_image_skia.cc @@ -21,6 +21,11 @@ std::shared_ptr DlImageSkia::impeller_texture() const { return nullptr; } +// |DlImage| +bool DlImageSkia::isOpaque() const { + return image_ ? image_->isOpaque() : false; +} + // |DlImage| bool DlImageSkia::isTextureBacked() const { return image_ ? image_->isTextureBacked() : false; diff --git a/display_list/display_list_image_skia.h b/display_list/display_list_image_skia.h index 70f9871823083..7e76de802aa97 100644 --- a/display_list/display_list_image_skia.h +++ b/display_list/display_list_image_skia.h @@ -23,6 +23,9 @@ class DlImageSkia final : public DlImage { // |DlImage| std::shared_ptr impeller_texture() const override; + // |DlImage| + bool isOpaque() const override; + // |DlImage| bool isTextureBacked() const override; diff --git a/display_list/display_list_ops.h b/display_list/display_list_ops.h index 052e7c9343bc9..061f29365df45 100644 --- a/display_list/display_list_ops.h +++ b/display_list/display_list_ops.h @@ -5,6 +5,7 @@ #ifndef FLUTTER_DISPLAY_LIST_DISPLAY_LIST_OPS_H_ #define FLUTTER_DISPLAY_LIST_DISPLAY_LIST_OPS_H_ +#include "display_list_color_source.h" #include "flutter/display_list/display_list.h" #include "flutter/display_list/display_list_blend_mode.h" #include "flutter/display_list/display_list_dispatcher.h" @@ -246,6 +247,28 @@ struct SetImageColorSourceOp : DLOp { } }; +// 56 bytes: 4 byte header, 4 byte padding, 8 for vtable, 8 * 2 for sk_sps, 24 +// for the std::vector. +struct SetRuntimeEffectColorSourceOp : DLOp { + static const auto kType = DisplayListOpType::kSetRuntimeEffectColorSource; + + SetRuntimeEffectColorSourceOp(const DlRuntimeEffectColorSource* source) + : source(source->runtime_effect(), + source->samplers(), + source->uniform_data()) {} + + const DlRuntimeEffectColorSource source; + + void dispatch(Dispatcher& dispatcher) const { + dispatcher.setColorSource(&source); + } + + DisplayListCompare equals(const SetRuntimeEffectColorSourceOp* other) const { + return (source == other->source) ? DisplayListCompare::kEqual + : DisplayListCompare::kNotEqual; + } +}; + // 4 byte header + 16 byte payload uses 24 total bytes (4 bytes unused) struct SetSharedImageFilterOp : DLOp { static const auto kType = DisplayListOpType::kSetSharedImageFilter; diff --git a/display_list/display_list_test_utils.h b/display_list/display_list_test_utils.h index 72d82b68faf82..711b7182a5815 100644 --- a/display_list/display_list_test_utils.h +++ b/display_list/display_list_test_utils.h @@ -99,7 +99,7 @@ static const sk_sp kTestBlender2 = SkBlenders::Arithmetic(0.2, 0.2, 0.2, 0.2, true); static const sk_sp kTestBlender3 = SkBlenders::Arithmetic(0.3, 0.3, 0.3, 0.3, true); -static const DlImageColorSource kTestSource1(TestImage1->skia_image(), +static const DlImageColorSource kTestSource1(TestImage1, DlTileMode::kClamp, DlTileMode::kMirror, kLinearSampling); diff --git a/display_list/display_list_unittests.cc b/display_list/display_list_unittests.cc index 9f6a7675e7d4b..62155636c23de 100644 --- a/display_list/display_list_unittests.cc +++ b/display_list/display_list_unittests.cc @@ -3,14 +3,19 @@ // found in the LICENSE file. #include +#include +#include +#include #include "flutter/display_list/display_list.h" #include "flutter/display_list/display_list_blend_mode.h" #include "flutter/display_list/display_list_builder.h" #include "flutter/display_list/display_list_canvas_recorder.h" +#include "flutter/display_list/display_list_paint.h" #include "flutter/display_list/display_list_rtree.h" #include "flutter/display_list/display_list_test_utils.h" #include "flutter/display_list/display_list_utils.h" +#include "flutter/fml/logging.h" #include "flutter/fml/math.h" #include "flutter/testing/display_list_testing.h" #include "flutter/testing/testing.h" @@ -21,6 +26,17 @@ namespace testing { static std::vector allGroups = CreateAllGroups(); +#ifndef NDEBUG +TEST(DisplayList, CallMethodAfterBuild) { + DisplayListCanvasRecorder recorder(kTestBounds); + recorder.drawRect(kTestBounds, SkPaint()); + recorder.Build(); + EXPECT_DEATH_IF_SUPPORTED( + recorder.drawRect(kTestBounds, SkPaint()), + "Calling method on DisplayListCanvasRecorder after Build\\(\\)"); +} +#endif // NDEBUG + TEST(DisplayList, SingleOpSizes) { for (auto& group : allGroups) { for (size_t i = 0; i < group.variants.size(); i++) { @@ -1633,6 +1649,550 @@ TEST(DisplayList, RTreeOfSaveLayerFilterScene) { test_rtree(rtree, {19, 19, 51, 51}, rects, {0, 1}); } +TEST(DisplayList, RemoveUnnecessarySaveRestorePairs) { + { + DisplayListBuilder builder; + builder.drawRect({10, 10, 20, 20}); + builder.save(); // This save op is unnecessary + builder.drawRect({50, 50, 60, 60}); + builder.restore(); + + DisplayListBuilder builder2; + builder2.drawRect({10, 10, 20, 20}); + builder2.drawRect({50, 50, 60, 60}); + ASSERT_TRUE(DisplayListsEQ_Verbose(builder.Build(), builder2.Build())); + } + + { + DisplayListBuilder builder; + builder.drawRect({10, 10, 20, 20}); + builder.save(); + builder.translate(1.0, 1.0); + { + builder.save(); // unnecessary + builder.drawRect({50, 50, 60, 60}); + builder.restore(); + } + + builder.restore(); + + DisplayListBuilder builder2; + builder2.drawRect({10, 10, 20, 20}); + builder2.save(); + builder2.translate(1.0, 1.0); + { builder2.drawRect({50, 50, 60, 60}); } + builder2.restore(); + ASSERT_TRUE(DisplayListsEQ_Verbose(builder.Build(), builder2.Build())); + } +} + +TEST(DisplayList, CollapseMultipleNestedSaveRestore) { + DisplayListBuilder builder1; + builder1.save(); + builder1.save(); + builder1.save(); + builder1.translate(10, 10); + builder1.scale(2, 2); + builder1.clipRect({10, 10, 20, 20}, SkClipOp::kIntersect, false); + builder1.drawRect({0, 0, 100, 100}); + builder1.restore(); + builder1.restore(); + builder1.restore(); + auto display_list1 = builder1.Build(); + + DisplayListBuilder builder2; + builder2.save(); + builder2.translate(10, 10); + builder2.scale(2, 2); + builder2.clipRect({10, 10, 20, 20}, SkClipOp::kIntersect, false); + builder2.drawRect({0, 0, 100, 100}); + builder2.restore(); + auto display_list2 = builder2.Build(); + + ASSERT_TRUE(DisplayListsEQ_Verbose(display_list1, display_list2)); +} + +TEST(DisplayList, CollapseNestedSaveAndSaveLayerRestore) { + DisplayListBuilder builder1; + builder1.save(); + builder1.saveLayer(nullptr, false); + builder1.drawRect({0, 0, 100, 100}); + builder1.scale(2, 2); + builder1.restore(); + builder1.restore(); + auto display_list1 = builder1.Build(); + + DisplayListBuilder builder2; + builder2.saveLayer(nullptr, false); + builder2.drawRect({0, 0, 100, 100}); + builder2.scale(2, 2); + builder2.restore(); + auto display_list2 = builder2.Build(); + + ASSERT_TRUE(DisplayListsEQ_Verbose(display_list1, display_list2)); +} + +TEST(DisplayList, RemoveUnnecessarySaveRestorePairsInSetPaint) { + SkRect build_bounds = SkRect::MakeLTRB(-100, -100, 200, 200); + SkRect rect = SkRect::MakeLTRB(30, 30, 70, 70); + // clang-format off + const float alpha_matrix[] = { + 0, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 0, 1, + }; + // clang-format on + DlMatrixColorFilter alpha_color_filter(alpha_matrix); + // Making sure hiding a problematic ColorFilter as an ImageFilter + // will generate the same behavior as setting it as a ColorFilter + + DlColorFilterImageFilter color_filter_image_filter(alpha_color_filter); + { + DisplayListBuilder builder(build_bounds); + builder.save(); + DlPaint paint; + paint.setImageFilter(&color_filter_image_filter); + builder.drawRect(rect, paint); + builder.restore(); + sk_sp display_list1 = builder.Build(); + + DisplayListBuilder builder2(build_bounds); + DlPaint paint2; + paint2.setImageFilter(&color_filter_image_filter); + builder2.drawRect(rect, paint2); + sk_sp display_list2 = builder2.Build(); + ASSERT_TRUE(DisplayListsEQ_Verbose(display_list1, display_list2)); + } + + { + DisplayListBuilder builder(build_bounds); + builder.save(); + builder.saveLayer(&build_bounds, true); + DlPaint paint; + paint.setImageFilter(&color_filter_image_filter); + builder.drawRect(rect, paint); + builder.restore(); + builder.restore(); + sk_sp display_list1 = builder.Build(); + + DisplayListBuilder builder2(build_bounds); + builder2.saveLayer(&build_bounds, true); + DlPaint paint2; + paint2.setImageFilter(&color_filter_image_filter); + builder2.drawRect(rect, paint2); + builder2.restore(); + sk_sp display_list2 = builder2.Build(); + ASSERT_TRUE(DisplayListsEQ_Verbose(display_list1, display_list2)); + } +} + +TEST(DisplayList, TransformTriggersDeferredSave) { + DisplayListBuilder builder1; + builder1.save(); + builder1.save(); + builder1.transform(SkM44::Translate(10, 100)); + builder1.drawRect({0, 0, 100, 100}); + builder1.restore(); + builder1.transform(SkM44::Translate(10, 100)); + builder1.drawRect({0, 0, 100, 100}); + builder1.restore(); + auto display_list1 = builder1.Build(); + + DisplayListBuilder builder2; + builder2.save(); + builder2.transform(SkM44::Translate(10, 100)); + builder2.drawRect({0, 0, 100, 100}); + builder2.restore(); + builder2.save(); + builder2.transform(SkM44::Translate(10, 100)); + builder2.drawRect({0, 0, 100, 100}); + builder2.restore(); + auto display_list2 = builder2.Build(); + + ASSERT_TRUE(DisplayListsEQ_Verbose(display_list1, display_list2)); +} + +TEST(DisplayList, Transform2DTriggersDeferredSave) { + DisplayListBuilder builder1; + builder1.save(); + builder1.save(); + builder1.transform2DAffine(0, 1, 12, 1, 0, 33); + builder1.drawRect({0, 0, 100, 100}); + builder1.restore(); + builder1.restore(); + auto display_list1 = builder1.Build(); + + DisplayListBuilder builder2; + builder2.save(); + builder2.transform2DAffine(0, 1, 12, 1, 0, 33); + builder2.drawRect({0, 0, 100, 100}); + builder2.restore(); + auto display_list2 = builder2.Build(); + + ASSERT_TRUE(DisplayListsEQ_Verbose(display_list1, display_list2)); +} + +TEST(DisplayList, TransformPerspectiveTriggersDeferredSave) { + DisplayListBuilder builder1; + builder1.save(); + builder1.save(); + builder1.transformFullPerspective(0, 1, 0, 12, 1, 0, 0, 33, 3, 2, 5, 29, 0, 0, + 0, 12); + builder1.drawRect({0, 0, 100, 100}); + builder1.restore(); + builder1.restore(); + auto display_list1 = builder1.Build(); + + DisplayListBuilder builder2; + builder2.save(); + builder2.transformFullPerspective(0, 1, 0, 12, 1, 0, 0, 33, 3, 2, 5, 29, 0, 0, + 0, 12); + builder2.drawRect({0, 0, 100, 100}); + builder2.restore(); + auto display_list2 = builder2.Build(); + + ASSERT_TRUE(DisplayListsEQ_Verbose(display_list1, display_list2)); +} + +TEST(DisplayList, ResetTransformTriggersDeferredSave) { + DisplayListBuilder builder1; + builder1.save(); + builder1.save(); + builder1.transformReset(); + builder1.drawRect({0, 0, 100, 100}); + builder1.restore(); + builder1.restore(); + auto display_list1 = builder1.Build(); + + DisplayListBuilder builder2; + builder2.save(); + builder2.transformReset(); + builder2.drawRect({0, 0, 100, 100}); + builder2.restore(); + auto display_list2 = builder2.Build(); + + ASSERT_TRUE(DisplayListsEQ_Verbose(display_list1, display_list2)); +} + +TEST(DisplayList, SkewTriggersDeferredSave) { + DisplayListBuilder builder1; + builder1.save(); + builder1.save(); + builder1.skew(10, 10); + builder1.drawRect({0, 0, 100, 100}); + builder1.restore(); + builder1.restore(); + auto display_list1 = builder1.Build(); + + DisplayListBuilder builder2; + builder2.save(); + builder2.skew(10, 10); + builder2.drawRect({0, 0, 100, 100}); + builder2.restore(); + auto display_list2 = builder2.Build(); + + ASSERT_TRUE(DisplayListsEQ_Verbose(display_list1, display_list2)); +} + +TEST(DisplayList, TranslateTriggersDeferredSave) { + DisplayListBuilder builder1; + builder1.save(); + builder1.save(); + builder1.translate(10, 10); + builder1.drawRect({0, 0, 100, 100}); + builder1.restore(); + builder1.restore(); + auto display_list1 = builder1.Build(); + + DisplayListBuilder builder2; + builder2.save(); + builder2.translate(10, 10); + builder2.drawRect({0, 0, 100, 100}); + builder2.restore(); + auto display_list2 = builder2.Build(); + + ASSERT_TRUE(DisplayListsEQ_Verbose(display_list1, display_list2)); +} + +TEST(DisplayList, ScaleTriggersDeferredSave) { + DisplayListBuilder builder1; + builder1.save(); + builder1.save(); + builder1.scale(0.5, 0.5); + builder1.drawRect({0, 0, 100, 100}); + builder1.restore(); + builder1.restore(); + auto display_list1 = builder1.Build(); + + DisplayListBuilder builder2; + builder2.save(); + builder2.scale(0.5, 0.5); + builder2.drawRect({0, 0, 100, 100}); + builder2.restore(); + auto display_list2 = builder2.Build(); + + ASSERT_TRUE(DisplayListsEQ_Verbose(display_list1, display_list2)); +} + +TEST(DisplayList, ClipRectTriggersDeferredSave) { + DisplayListBuilder builder1; + builder1.save(); + builder1.save(); + builder1.clipRect(SkRect::MakeLTRB(0, 0, 100, 100), SkClipOp::kIntersect, + true); + builder1.drawRect({0, 0, 100, 100}); + builder1.restore(); + builder1.transform(SkM44()); + builder1.drawRect({0, 0, 100, 100}); + builder1.restore(); + auto display_list1 = builder1.Build(); + + DisplayListBuilder builder2; + builder2.save(); + builder2.clipRect(SkRect::MakeLTRB(0, 0, 100, 100), SkClipOp::kIntersect, + true); + builder2.drawRect({0, 0, 100, 100}); + builder2.restore(); + builder2.transform(SkM44()); + builder2.drawRect({0, 0, 100, 100}); + auto display_list2 = builder2.Build(); + + ASSERT_TRUE(DisplayListsEQ_Verbose(display_list1, display_list2)); +} + +TEST(DisplayList, ClipRRectTriggersDeferredSave) { + DisplayListBuilder builder1; + builder1.save(); + builder1.save(); + builder1.clipRRect(kTestRRect, SkClipOp::kIntersect, true); + + builder1.drawRect({0, 0, 100, 100}); + builder1.restore(); + builder1.transform(SkM44()); + builder1.drawRect({0, 0, 100, 100}); + builder1.restore(); + auto display_list1 = builder1.Build(); + + DisplayListBuilder builder2; + builder2.save(); + builder2.clipRRect(kTestRRect, SkClipOp::kIntersect, true); + + builder2.drawRect({0, 0, 100, 100}); + builder2.restore(); + builder2.transform(SkM44()); + builder2.drawRect({0, 0, 100, 100}); + auto display_list2 = builder2.Build(); + + ASSERT_TRUE(DisplayListsEQ_Verbose(display_list1, display_list2)); +} + +TEST(DisplayList, ClipPathTriggersDeferredSave) { + DisplayListBuilder builder1; + builder1.save(); + builder1.save(); + builder1.clipPath(kTestPath1, SkClipOp::kIntersect, true); + builder1.drawRect({0, 0, 100, 100}); + builder1.restore(); + builder1.transform(SkM44()); + builder1.drawRect({0, 0, 100, 100}); + builder1.restore(); + auto display_list1 = builder1.Build(); + + DisplayListBuilder builder2; + builder2.save(); + builder2.clipPath(kTestPath1, SkClipOp::kIntersect, true); + builder2.drawRect({0, 0, 100, 100}); + builder2.restore(); + builder2.transform(SkM44()); + builder2.drawRect({0, 0, 100, 100}); + auto display_list2 = builder2.Build(); + + ASSERT_TRUE(DisplayListsEQ_Verbose(display_list1, display_list2)); +} + +TEST(DisplayList, NOPTranslateDoesNotTriggerDeferredSave) { + DisplayListBuilder builder1; + builder1.save(); + builder1.save(); + builder1.translate(0, 0); + builder1.drawRect({0, 0, 100, 100}); + builder1.restore(); + builder1.drawRect({0, 0, 100, 100}); + builder1.restore(); + auto display_list1 = builder1.Build(); + + DisplayListBuilder builder2; + builder2.drawRect({0, 0, 100, 100}); + builder2.drawRect({0, 0, 100, 100}); + auto display_list2 = builder2.Build(); + + ASSERT_TRUE(DisplayListsEQ_Verbose(display_list1, display_list2)); +} + +TEST(DisplayList, NOPScaleDoesNotTriggerDeferredSave) { + DisplayListBuilder builder1; + builder1.save(); + builder1.save(); + builder1.scale(1.0, 1.0); + builder1.drawRect({0, 0, 100, 100}); + builder1.restore(); + builder1.drawRect({0, 0, 100, 100}); + builder1.restore(); + auto display_list1 = builder1.Build(); + + DisplayListBuilder builder2; + builder2.drawRect({0, 0, 100, 100}); + builder2.drawRect({0, 0, 100, 100}); + auto display_list2 = builder2.Build(); + + ASSERT_TRUE(DisplayListsEQ_Verbose(display_list1, display_list2)); +} + +TEST(DisplayList, NOPRotationDoesNotTriggerDeferredSave) { + DisplayListBuilder builder1; + builder1.save(); + builder1.save(); + builder1.rotate(360); + builder1.drawRect({0, 0, 100, 100}); + builder1.restore(); + builder1.drawRect({0, 0, 100, 100}); + builder1.restore(); + auto display_list1 = builder1.Build(); + + DisplayListBuilder builder2; + builder2.drawRect({0, 0, 100, 100}); + builder2.drawRect({0, 0, 100, 100}); + auto display_list2 = builder2.Build(); + + ASSERT_TRUE(DisplayListsEQ_Verbose(display_list1, display_list2)); +} + +TEST(DisplayList, NOPSkewDoesNotTriggerDeferredSave) { + DisplayListBuilder builder1; + builder1.save(); + builder1.save(); + builder1.skew(0, 0); + builder1.drawRect({0, 0, 100, 100}); + builder1.restore(); + builder1.drawRect({0, 0, 100, 100}); + builder1.restore(); + auto display_list1 = builder1.Build(); + + DisplayListBuilder builder2; + builder2.drawRect({0, 0, 100, 100}); + builder2.drawRect({0, 0, 100, 100}); + auto display_list2 = builder2.Build(); + + ASSERT_TRUE(DisplayListsEQ_Verbose(display_list1, display_list2)); +} + +TEST(DisplayList, NOPTransformDoesNotTriggerDeferredSave) { + DisplayListBuilder builder1; + builder1.save(); + builder1.save(); + builder1.transform(SkM44()); + builder1.drawRect({0, 0, 100, 100}); + builder1.restore(); + builder1.transform(SkM44()); + builder1.drawRect({0, 0, 100, 100}); + builder1.restore(); + auto display_list1 = builder1.Build(); + + DisplayListBuilder builder2; + builder2.drawRect({0, 0, 100, 100}); + builder2.drawRect({0, 0, 100, 100}); + auto display_list2 = builder2.Build(); + + ASSERT_TRUE(DisplayListsEQ_Verbose(display_list1, display_list2)); +} + +TEST(DisplayList, NOPTransform2DDoesNotTriggerDeferredSave) { + DisplayListBuilder builder1; + builder1.save(); + builder1.save(); + builder1.transform2DAffine(1, 0, 0, 0, 1, 0); + builder1.drawRect({0, 0, 100, 100}); + builder1.restore(); + builder1.drawRect({0, 0, 100, 100}); + builder1.restore(); + auto display_list1 = builder1.Build(); + + DisplayListBuilder builder2; + builder2.drawRect({0, 0, 100, 100}); + builder2.drawRect({0, 0, 100, 100}); + auto display_list2 = builder2.Build(); + + ASSERT_TRUE(DisplayListsEQ_Verbose(display_list1, display_list2)); +} + +TEST(DisplayList, NOPTransformFullPerspectiveDoesNotTriggerDeferredSave) { + { + DisplayListBuilder builder1; + builder1.save(); + builder1.save(); + builder1.transformFullPerspective(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, + 0, 1); + builder1.drawRect({0, 0, 100, 100}); + builder1.restore(); + builder1.drawRect({0, 0, 100, 100}); + builder1.restore(); + auto display_list1 = builder1.Build(); + + DisplayListBuilder builder2; + builder2.drawRect({0, 0, 100, 100}); + builder2.drawRect({0, 0, 100, 100}); + auto display_list2 = builder2.Build(); + + ASSERT_TRUE(DisplayListsEQ_Verbose(display_list1, display_list2)); + } + + { + DisplayListBuilder builder1; + builder1.save(); + builder1.save(); + builder1.transformFullPerspective(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, + 0, 1); + builder1.transformReset(); + builder1.drawRect({0, 0, 100, 100}); + builder1.restore(); + builder1.drawRect({0, 0, 100, 100}); + builder1.restore(); + auto display_list1 = builder1.Build(); + + DisplayListBuilder builder2; + builder2.save(); + builder2.transformReset(); + builder2.drawRect({0, 0, 100, 100}); + builder2.restore(); + builder2.drawRect({0, 0, 100, 100}); + + auto display_list2 = builder2.Build(); + + ASSERT_TRUE(DisplayListsEQ_Verbose(display_list1, display_list2)); + } +} + +TEST(DisplayList, NOPClipDoesNotTriggerDeferredSave) { + DisplayListBuilder builder1; + builder1.save(); + builder1.save(); + builder1.clipRect(SkRect::MakeLTRB(0, SK_ScalarNaN, SK_ScalarNaN, 0), + SkClipOp::kIntersect, true); + builder1.drawRect({0, 0, 100, 100}); + builder1.restore(); + builder1.drawRect({0, 0, 100, 100}); + builder1.restore(); + auto display_list1 = builder1.Build(); + + DisplayListBuilder builder2; + builder2.drawRect({0, 0, 100, 100}); + builder2.drawRect({0, 0, 100, 100}); + auto display_list2 = builder2.Build(); + + ASSERT_TRUE(DisplayListsEQ_Verbose(display_list1, display_list2)); +} + TEST(DisplayList, RTreeOfClippedSaveLayerFilterScene) { DisplayListBuilder builder; // blur filter with sigma=1 expands by 30 on all sides diff --git a/examples/glfw_drm/.gitignore b/examples/glfw_drm/.gitignore new file mode 100644 index 0000000000000..b7443460fb263 --- /dev/null +++ b/examples/glfw_drm/.gitignore @@ -0,0 +1 @@ +debug/ diff --git a/examples/glfw_drm/BUILD.gn b/examples/glfw_drm/BUILD.gn new file mode 100644 index 0000000000000..784e5654e5e69 --- /dev/null +++ b/examples/glfw_drm/BUILD.gn @@ -0,0 +1,20 @@ +# 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("//flutter/examples/examples.gni") + +if (build_embedder_examples) { + executable("glfw_drm") { + output_name = "embedder_example_drm" + + sources = [ "FlutterEmbedderGLFW.cc" ] + + deps = [ + "//flutter/shell/platform/embedder:embedder", + "//third_party/glfw", + ] + + libs = [ "EGL" ] + } +} diff --git a/examples/glfw_drm/CMakeLists.txt b/examples/glfw_drm/CMakeLists.txt new file mode 100644 index 0000000000000..2563bd8bee05a --- /dev/null +++ b/examples/glfw_drm/CMakeLists.txt @@ -0,0 +1,35 @@ +cmake_minimum_required(VERSION 3.15) +project(FlutterEmbedderGLFW) +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11" ) + +add_executable(flutter_glfw FlutterEmbedderGLFW.cc) + +############################################################ +# GLFW +############################################################ +option(GLFW_BUILD_EXAMPLES "" OFF) +option(GLFW_BUILD_TESTS "" OFF) +option(GLFW_BUILD_DOCS "" OFF) +option(GLFW_INSTALL "" OFF) +find_package(OpenGL REQUIRED COMPONENTS EGL) +include_directories(${OPENGL_INCLUDE_DIRS}) +add_subdirectory(${CMAKE_SOURCE_DIR}/../../../third_party/glfw glfw) +target_link_libraries(flutter_glfw glfw OpenGL::EGL) +include_directories(${CMAKE_SOURCE_DIR}/../../../third_party/glfw/include) + +############################################################ +# Flutter Engine +############################################################ +# This is assuming you've built a local version of the Flutter Engine. If you +# downloaded yours is from the internet you'll have to change this. +include_directories(${CMAKE_SOURCE_DIR}/../../shell/platform/embedder) +find_library(FLUTTER_LIB flutter_engine PATHS ${CMAKE_SOURCE_DIR}/../../../out/host_debug_unopt) +target_link_libraries(flutter_glfw ${FLUTTER_LIB}) + +# Copy the flutter library here since the shared library +# name is `./libflutter_engine.dylib`. +add_custom_command( + TARGET flutter_glfw POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + ${FLUTTER_LIB} + ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/examples/glfw_drm/FlutterEmbedderGLFW.cc b/examples/glfw_drm/FlutterEmbedderGLFW.cc new file mode 100644 index 0000000000000..d75811092f927 --- /dev/null +++ b/examples/glfw_drm/FlutterEmbedderGLFW.cc @@ -0,0 +1,334 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include + +#define GLFW_EXPOSE_NATIVE_EGL +#define GLFW_INCLUDE_GLEXT + +#include +#include +#include +#include +#include +#include +#include "GLFW/glfw3.h" +#include "GLFW/glfw3native.h" +#include "embedder.h" + +// This value is calculated after the window is created. +static double g_pixelRatio = 1.0; +static const size_t kInitialWindowWidth = 800; +static const size_t kInitialWindowHeight = 600; +// Maximum damage history - for triple buffering we need to store damage for +// last two frames; Some Android devices (Pixel 4) use quad buffering. +static const int kMaxHistorySize = 10; + +// Keeps track of the most recent frame damages so that existing damage can +// be easily computed. +std::list damage_history_; + +// Keeps track of the existing damage associated with each FBO ID +std::unordered_map existing_damage_map_; + +EGLDisplay display_; +EGLSurface surface_; + +static_assert(FLUTTER_ENGINE_VERSION == 1, + "This Flutter Embedder was authored against the stable Flutter " + "API at version 1. There has been a serious breakage in the " + "API. Please read the ChangeLog and take appropriate action " + "before updating this assertion"); + +void GLFWcursorPositionCallbackAtPhase(GLFWwindow* window, + FlutterPointerPhase phase, + double x, + double y) { + FlutterPointerEvent event = {}; + event.struct_size = sizeof(event); + event.phase = phase; + event.x = x * g_pixelRatio; + event.y = y * g_pixelRatio; + event.timestamp = + std::chrono::duration_cast( + std::chrono::high_resolution_clock::now().time_since_epoch()) + .count(); + FlutterEngineSendPointerEvent( + reinterpret_cast(glfwGetWindowUserPointer(window)), &event, + 1); +} + +void GLFWcursorPositionCallback(GLFWwindow* window, double x, double y) { + GLFWcursorPositionCallbackAtPhase(window, FlutterPointerPhase::kMove, x, y); +} + +void GLFWmouseButtonCallback(GLFWwindow* window, + int key, + int action, + int mods) { + if (key == GLFW_MOUSE_BUTTON_1 && action == GLFW_PRESS) { + double x, y; + glfwGetCursorPos(window, &x, &y); + GLFWcursorPositionCallbackAtPhase(window, FlutterPointerPhase::kDown, x, y); + glfwSetCursorPosCallback(window, GLFWcursorPositionCallback); + } + + if (key == GLFW_MOUSE_BUTTON_1 && action == GLFW_RELEASE) { + double x, y; + glfwGetCursorPos(window, &x, &y); + GLFWcursorPositionCallbackAtPhase(window, FlutterPointerPhase::kUp, x, y); + glfwSetCursorPosCallback(window, nullptr); + } +} + +static void GLFWKeyCallback(GLFWwindow* window, + int key, + int scancode, + int action, + int mods) { + if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) { + glfwSetWindowShouldClose(window, GLFW_TRUE); + } +} + +void GLFWwindowSizeCallback(GLFWwindow* window, int width, int height) { + FlutterWindowMetricsEvent event = {}; + event.struct_size = sizeof(event); + event.width = width * g_pixelRatio; + event.height = height * g_pixelRatio; + event.pixel_ratio = g_pixelRatio; + FlutterEngineSendWindowMetricsEvent( + reinterpret_cast(glfwGetWindowUserPointer(window)), + &event); +} + +// Auxiliary function used to transform a FlutterRect into the format that is +// expected by the EGL functions (i.e. array of EGLint). +static std::array RectToInts(const FlutterRect rect) { + EGLint height; + eglQuerySurface(display_, surface_, EGL_HEIGHT, &height); + + std::array res{ + static_cast(rect.left), height - static_cast(rect.bottom), + static_cast(rect.right) - static_cast(rect.left), + static_cast(rect.bottom) - static_cast(rect.top)}; + return res; +} + +// Auxiliary function to union the damage regions comprised by two FlutterRect +// element. It saves the result of this join in the rect variable. +static void JoinFlutterRect(FlutterRect* rect, FlutterRect additional_rect) { + rect->left = std::min(rect->left, additional_rect.left); + rect->top = std::min(rect->top, additional_rect.top); + rect->right = std::max(rect->right, additional_rect.right); + rect->bottom = std::max(rect->bottom, additional_rect.bottom); +} + +// Auxiliary function used to check if the given list of extensions contains the +// requested extension name. +static bool HasExtension(const char* extensions, const char* name) { + const char* r = strstr(extensions, name); + auto len = strlen(name); + // check that the extension name is terminated by space or null terminator + return r != nullptr && (r[len] == ' ' || r[len] == 0); +} + +bool RunFlutter(GLFWwindow* window, + const std::string& project_path, + const std::string& icudtl_path) { + FlutterRendererConfig config = {}; + config.type = kOpenGL; + config.open_gl.struct_size = sizeof(config.open_gl); + config.open_gl.make_current = [](void* userdata) -> bool { + glfwMakeContextCurrent(static_cast(userdata)); + return true; + }; + config.open_gl.clear_current = [](void*) -> bool { + glfwMakeContextCurrent(nullptr); // is this even a thing? + return true; + }; + config.open_gl.present_with_info = + [](void* userdata, const FlutterPresentInfo* info) -> bool { + // Free the existing damage that was allocated to this frame. + free(existing_damage_map_[info->fbo_id]); + + // Get list of extensions. + const char* extensions = eglQueryString(display_, EGL_EXTENSIONS); + + // Retrieve the set damage region function. + PFNEGLSETDAMAGEREGIONKHRPROC set_damage_region_ = nullptr; + if (HasExtension(extensions, "EGL_KHR_partial_update")) { + set_damage_region_ = reinterpret_cast( + eglGetProcAddress("eglSetDamageRegionKHR")); + } + + // Retrieve the swap buffers with damage function. + PFNEGLSWAPBUFFERSWITHDAMAGEEXTPROC swap_buffers_with_damage_ = nullptr; + if (HasExtension(extensions, "EGL_EXT_swap_buffers_with_damage")) { + swap_buffers_with_damage_ = + reinterpret_cast( + eglGetProcAddress("eglSwapBuffersWithDamageEXT")); + } else if (HasExtension(extensions, "EGL_KHR_swap_buffers_with_damage")) { + swap_buffers_with_damage_ = + reinterpret_cast( + eglGetProcAddress("eglSwapBuffersWithDamageKHR")); + } + + if (set_damage_region_) { + // Set the buffer damage as the damage region. + auto buffer_rects = RectToInts(info->buffer_damage.damage[0]); + set_damage_region_(display_, surface_, buffer_rects.data(), 1); + } + + // Add frame damage to damage history + damage_history_.push_back(info->frame_damage.damage[0]); + if (damage_history_.size() > kMaxHistorySize) { + damage_history_.pop_front(); + } + + if (swap_buffers_with_damage_) { + // Swap buffers with frame damage. + auto frame_rects = RectToInts(info->frame_damage.damage[0]); + return swap_buffers_with_damage_(display_, surface_, frame_rects.data(), + 1); + } else { + // If the required extensions for partial repaint were not provided, do + // full repaint. + return eglSwapBuffers(display_, surface_); + } + }; + config.open_gl.fbo_callback = [](void*) -> uint32_t { + return 0; // FBO0 + }; + config.open_gl.populate_existing_damage = + [](void* userdata, intptr_t fbo_id, + FlutterDamage* existing_damage) -> void { + // Given the FBO age, create existing damage region by joining all frame + // damages since FBO was last used + EGLint age; + if (glfwExtensionSupported("GL_EXT_buffer_age") == GLFW_TRUE) { + eglQuerySurface(display_, surface_, EGL_BUFFER_AGE_EXT, &age); + } else { + age = 4; // Virtually no driver should have a swapchain length > 4. + } + + existing_damage->num_rects = 1; + + // Allocate the array of rectangles for the existing damage. + existing_damage_map_[fbo_id] = static_cast( + malloc(sizeof(FlutterRect) * existing_damage->num_rects)); + existing_damage_map_[fbo_id][0] = + FlutterRect{0, 0, kInitialWindowWidth, kInitialWindowHeight}; + existing_damage->damage = existing_damage_map_[fbo_id]; + + if (age > 1) { + --age; + // join up to (age - 1) last rects from damage history + for (auto i = damage_history_.rbegin(); + i != damage_history_.rend() && age > 0; ++i, --age) { + if (i == damage_history_.rbegin()) { + if (i != damage_history_.rend()) { + existing_damage->damage[0] = {i->left, i->top, i->right, i->bottom}; + } + } else { + JoinFlutterRect(&(existing_damage->damage[0]), *i); + } + } + } + }; + config.open_gl.gl_proc_resolver = [](void*, const char* name) -> void* { + return reinterpret_cast(glfwGetProcAddress(name)); + }; + config.open_gl.fbo_reset_after_present = true; + + // This directory is generated by `flutter build bundle`. + std::string assets_path = project_path + "/build/flutter_assets"; + FlutterProjectArgs args = { + .struct_size = sizeof(FlutterProjectArgs), + .assets_path = assets_path.c_str(), + .icu_data_path = + icudtl_path.c_str(), // Find this in your bin/cache directory. + }; + FlutterEngine engine = nullptr; + FlutterEngineResult result = + FlutterEngineRun(FLUTTER_ENGINE_VERSION, &config, // renderer + &args, window, &engine); + if (result != kSuccess || engine == nullptr) { + std::cout << "Could not run the Flutter Engine." << std::endl; + return false; + } + + glfwSetWindowUserPointer(window, engine); + GLFWwindowSizeCallback(window, kInitialWindowWidth, kInitialWindowHeight); + + return true; +} + +void printUsage() { + std::cout + << "usage: embedder_example_drm " + << std::endl; +} + +void GLFW_ErrorCallback(int error, const char* description) { + std::cout << "GLFW Error: (" << error << ") " << description << std::endl; +} + +int main(int argc, const char* argv[]) { + if (argc != 3) { + printUsage(); + return 1; + } + + std::string project_path = argv[1]; + std::string icudtl_path = argv[2]; + + glfwSetErrorCallback(GLFW_ErrorCallback); + + int result = glfwInit(); + if (result != GLFW_TRUE) { + std::cout << "Could not initialize GLFW." << std::endl; + return EXIT_FAILURE; + } + +#if defined(__linux__) + glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_EGL_CONTEXT_API); +#endif + + GLFWwindow* window = glfwCreateWindow( + kInitialWindowWidth, kInitialWindowHeight, "Flutter", NULL, NULL); + if (window == nullptr) { + std::cout << "Could not create GLFW window." << std::endl; + return EXIT_FAILURE; + } + + int framebuffer_width, framebuffer_height; + glfwGetFramebufferSize(window, &framebuffer_width, &framebuffer_height); + g_pixelRatio = framebuffer_width / kInitialWindowWidth; + + // Get the display and surface variables. + display_ = glfwGetEGLDisplay(); + surface_ = glfwGetEGLSurface(window); + + bool run_result = RunFlutter(window, project_path, icudtl_path); + if (!run_result) { + std::cout << "Could not run the Flutter engine." << std::endl; + return EXIT_FAILURE; + } + + glfwSetKeyCallback(window, GLFWKeyCallback); + glfwSetWindowSizeCallback(window, GLFWwindowSizeCallback); + glfwSetMouseButtonCallback(window, GLFWmouseButtonCallback); + + while (!glfwWindowShouldClose(window)) { + glfwWaitEvents(); + } + + glfwDestroyWindow(window); + glfwTerminate(); + + return EXIT_SUCCESS; +} diff --git a/examples/glfw_drm/README.md b/examples/glfw_drm/README.md new file mode 100644 index 0000000000000..a6b4018a2a998 --- /dev/null +++ b/examples/glfw_drm/README.md @@ -0,0 +1,37 @@ +# Flutter Embedder Engine GLFW Example +## Description +This is an example of how to use Flutter Engine Embedder in order to get a +Flutter project rendering in a new host environment. The intended audience is +people who need to support host environment other than the ones already provided +by Flutter. This is an advanced topic and not intended for beginners. + +In this example we are demonstrating rendering a Flutter project inside of the GUI +library [GLFW](https://www.glfw.org/). For more information about using the +embedder you can read the wiki article [Custom Flutter Engine Embedders](https://github.com/flutter/flutter/wiki/Custom-Flutter-Engine-Embedders). + +The key difference between this example and the other GLFW example in this +folder is that the present example implements dirty region management (i.e. +rendering only the regions that have changed between frames as opposed to always +rendering the entire frame). For more information on the implementation of +dirty region management within the Embedder API, see [DRM Embedder](https://flutter.dev/go/drm-embedder). + +## Running Instructions +The following example was tested on Linux but with a bit of tweaking should be +able to run on other *nix platforms and Windows. However, because this example +uses the EGL library, it is not compatible with MacOS platforms. + +The example has the following dependencies: + * [GLFW](https://www.glfw.org/) - This can be installed by running `sudo apt-get install libglfw3` + * [CMake](https://cmake.org/) - This can be installed by running `sudo apt-get install cmake` + * [EGL](https://docs.mesa3d.org/egl.html) - This can be installed by running `sudo apt-get install libglfw3-dev` + * [Flutter](https://flutter.dev/) - This can be installed from the [Flutter webpage](https://flutter.dev/docs/get-started/install) + * [Flutter Engine](https://flutter.dev) - This can be built or downloaded, see [Custom Flutter Engine Embedders](https://github.com/flutter/flutter/wiki/Custom-Flutter-Engine-Embedders) for more information. + +In order to **build** and **run** the example you should be able to go into this directory and run +`./run.sh`. + +## Troubleshooting +There are a few things you might have to tweak in order to get your build working: + * Flutter Engine Location - Inside the `CMakeList.txt` file you will see that it is set up to search for the header and library for the Flutter Engine in specific locations, those might not be the location of your Flutter Engine. + * Pixel Ratio - If the project runs but is drawing at the wrong scale you may have to tweak the `kPixelRatio` variable in `FlutterEmbedderGLFW.cc` file. + * GLFW Location - Inside the `CMakeLists.txt` we are searching for the GLFW library, if CMake can't find it you may have to edit that. diff --git a/examples/glfw_drm/main.dart b/examples/glfw_drm/main.dart new file mode 100644 index 0000000000000..d8ad9221f8e4c --- /dev/null +++ b/examples/glfw_drm/main.dart @@ -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. + +import 'package:flutter/material.dart'; +import 'package:flutter/foundation.dart' + show debugDefaultTargetPlatformOverride; +import 'package:flutter_spinkit/flutter_spinkit.dart'; + + +void main() { + // This is a hack to make Flutter think you are running on Google Fuchsia, + // otherwise you will get an error about running from an unsupported platform. + debugDefaultTargetPlatformOverride = TargetPlatform.fuchsia; + runApp(MyApp()); +} + +class MyApp extends StatelessWidget { + // This widget is the root of your application. + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Flutter Demo', + theme: ThemeData( + // This is the theme of your application. + // + // Try running your application with "flutter run". You'll see the + // application has a blue toolbar. Then, without quitting the app, try + // changing the primarySwatch below to Colors.green and then invoke + // "hot reload" (press "r" in the console where you ran "flutter run", + // or simply save your changes to "hot reload" in a Flutter IDE). + // Notice that the counter didn't reset back to zero; the application + // is not restarted. + primarySwatch: Colors.blue, + ), + home: MyHomePage(title: 'Flutter Demo Home Page'), + ); + } +} + +class MyHomePage extends StatefulWidget { + MyHomePage({Key? key, required this.title}) : super(key: key); + + // This widget is the home page of your application. It is stateful, meaning + // that it has a State object (defined below) that contains fields that affect + // how it looks. + + // This class is the configuration for the state. It holds the values (in this + // case the title) provided by the parent (in this case the App widget) and + // used by the build method of the State. Fields in a Widget subclass are + // always marked "final". + + final String title; + + @override + _MyHomePageState createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + @override + Widget build(BuildContext context) { + // The Flutter framework has been optimized to make rerunning build methods + // fast, so that you can just rebuild anything that needs updating rather + // than having to individually change instances of widgets. + return Scaffold( + appBar: AppBar( + // Here we take the value from the MyHomePage object that was created by + // the App.build method, and use it to set our appbar title. + title: Text(widget.title), + ), + body: Center( + // Center is a layout widget. It takes a single child and positions it + // in the middle of the parent. + child: RepaintBoundary( + child: SpinKitRotatingCircle(color: Colors.blue, size: 50.0), + ), + ), + ); + } +} diff --git a/examples/glfw_drm/run.sh b/examples/glfw_drm/run.sh new file mode 100755 index 0000000000000..0ae097e4cc990 --- /dev/null +++ b/examples/glfw_drm/run.sh @@ -0,0 +1,33 @@ +#!/bin/bash +set -e # Exit if any program returns an error. + +################################################################# +# Make the host C++ project. +################################################################# +if [ ! -d debug ]; then + mkdir debug +fi +cd debug +cmake -DCMAKE_BUILD_TYPE=Debug .. +make + +################################################################# +# Make the guest Flutter project. +################################################################# +if [ ! -d myapp ]; then + flutter create myapp + cd myapp + flutter pub add flutter_spinkit + cd .. +fi +cd myapp +cp ../../main.dart lib/main.dart +flutter build bundle \ + --local-engine-src-path ../../../../../ \ + --local-engine=host_debug_unopt +cd - + +################################################################# +# Run the Flutter Engine Embedder +################################################################# +./flutter_glfw ./myapp ../../../../third_party/icu/common/icudtl.dat diff --git a/flow/compositor_context.cc b/flow/compositor_context.cc index 779a42c84eb92..7013bc86ee374 100644 --- a/flow/compositor_context.cc +++ b/flow/compositor_context.cc @@ -46,11 +46,14 @@ std::optional FrameDamage::ComputeClipRect( } CompositorContext::CompositorContext() - : raster_time_(fixed_refresh_rate_updater_), + : texture_registry_(std::make_shared()), + raster_time_(fixed_refresh_rate_updater_), ui_time_(fixed_refresh_rate_updater_) {} CompositorContext::CompositorContext(Stopwatch::RefreshRateUpdater& updater) - : raster_time_(updater), ui_time_(updater) {} + : texture_registry_(std::make_shared()), + raster_time_(updater), + ui_time_(updater) {} CompositorContext::~CompositorContext() = default; @@ -160,12 +163,12 @@ RasterStatus CompositorContext::ScopedFrame::Raster( } void CompositorContext::OnGrContextCreated() { - texture_registry_.OnGrContextCreated(); + texture_registry_->OnGrContextCreated(); raster_cache_.Clear(); } void CompositorContext::OnGrContextDestroyed() { - texture_registry_.OnGrContextDestroyed(); + texture_registry_->OnGrContextDestroyed(); raster_cache_.Clear(); } diff --git a/flow/compositor_context.h b/flow/compositor_context.h index 0981cdd2538b1..f7667640caa71 100644 --- a/flow/compositor_context.h +++ b/flow/compositor_context.h @@ -175,7 +175,9 @@ class CompositorContext { RasterCache& raster_cache() { return raster_cache_; } - TextureRegistry& texture_registry() { return texture_registry_; } + std::shared_ptr texture_registry() { + return texture_registry_; + } const Stopwatch& raster_time() const { return raster_time_; } @@ -185,7 +187,7 @@ class CompositorContext { private: RasterCache raster_cache_; - TextureRegistry texture_registry_; + std::shared_ptr texture_registry_; Stopwatch raster_time_; Stopwatch ui_time_; LayerSnapshotStore layer_snapshot_store_; diff --git a/flow/embedded_views.cc b/flow/embedded_views.cc index 1fb1d244b0e1b..959986dada16f 100644 --- a/flow/embedded_views.cc +++ b/flow/embedded_views.cc @@ -6,6 +6,69 @@ namespace flutter { +SkPictureEmbedderViewSlice::SkPictureEmbedderViewSlice(SkRect view_bounds) { + auto rtree_factory = RTreeFactory(); + rtree_ = rtree_factory.getInstance(); + + recorder_ = std::make_unique(); + recorder_->beginRecording(view_bounds, &rtree_factory); +} + +SkCanvas* SkPictureEmbedderViewSlice::canvas() { + return recorder_->getRecordingCanvas(); +} + +DisplayListBuilder* SkPictureEmbedderViewSlice::builder() { + return nullptr; +} + +void SkPictureEmbedderViewSlice::end_recording() { + picture_ = recorder_->finishRecordingAsPicture(); +} + +std::list SkPictureEmbedderViewSlice::searchNonOverlappingDrawnRects( + const SkRect& query) const { + return rtree_->searchNonOverlappingDrawnRects(query); +} + +void SkPictureEmbedderViewSlice::render_into(SkCanvas* canvas) { + canvas->drawPicture(picture_); +} + +void SkPictureEmbedderViewSlice::render_into(DisplayListBuilder* builder) { + builder->drawPicture(picture_, nullptr, false); +} + +DisplayListEmbedderViewSlice::DisplayListEmbedderViewSlice(SkRect view_bounds) { + recorder_ = std::make_unique(view_bounds); +} + +SkCanvas* DisplayListEmbedderViewSlice::canvas() { + return recorder_ ? recorder_.get() : nullptr; +} + +DisplayListBuilder* DisplayListEmbedderViewSlice::builder() { + return recorder_ ? recorder_->builder().get() : nullptr; +} + +void DisplayListEmbedderViewSlice::end_recording() { + display_list_ = recorder_->Build(); + recorder_ = nullptr; +} + +std::list DisplayListEmbedderViewSlice::searchNonOverlappingDrawnRects( + const SkRect& query) const { + return display_list_->rtree()->searchNonOverlappingDrawnRects(query); +} + +void DisplayListEmbedderViewSlice::render_into(SkCanvas* canvas) { + display_list_->RenderTo(canvas); +} + +void DisplayListEmbedderViewSlice::render_into(DisplayListBuilder* builder) { + builder->drawDisplayList(display_list_); +} + void ExternalViewEmbedder::SubmitFrame(GrDirectContext* context, std::unique_ptr frame) { frame->Submit(); diff --git a/flow/embedded_views.h b/flow/embedded_views.h index 394fa9d829a92..1839a8aa2e3f8 100644 --- a/flow/embedded_views.h +++ b/flow/embedded_views.h @@ -7,11 +7,14 @@ #include +#include "flutter/display_list/display_list_builder.h" +#include "flutter/flow/rtree.h" #include "flutter/flow/surface_frame.h" #include "flutter/fml/memory/ref_counted.h" #include "flutter/fml/raster_thread_merger.h" #include "third_party/skia/include/core/SkCanvas.h" #include "third_party/skia/include/core/SkPath.h" +#include "third_party/skia/include/core/SkPictureRecorder.h" #include "third_party/skia/include/core/SkPoint.h" #include "third_party/skia/include/core/SkRRect.h" #include "third_party/skia/include/core/SkRect.h" @@ -218,10 +221,12 @@ class EmbeddedViewParams { EmbeddedViewParams(SkMatrix matrix, SkSize size_points, - MutatorsStack mutators_stack) + MutatorsStack mutators_stack, + bool display_list_enabled = false) : matrix_(matrix), size_points_(size_points), - mutators_stack_(mutators_stack) { + mutators_stack_(mutators_stack), + display_list_enabled_(display_list_enabled) { SkPath path; SkRect starting_rect = SkRect::MakeSize(size_points); path.addRect(starting_rect); @@ -243,6 +248,10 @@ class EmbeddedViewParams { // Clippings are ignored. const SkRect& finalBoundingRect() const { return final_bounding_rect_; } + // Whether the embedder should construct DisplayList objects to hold the + // rendering commands for each between-view slice of the layer tree. + bool display_list_enabled() const { return display_list_enabled_; } + bool operator==(const EmbeddedViewParams& other) const { return size_points_ == other.size_points_ && mutators_stack_ == other.mutators_stack_ && @@ -255,6 +264,7 @@ class EmbeddedViewParams { SkSize size_points_; MutatorsStack mutators_stack_; SkRect final_bounding_rect_; + bool display_list_enabled_; }; enum class PostPrerollResult { @@ -269,6 +279,70 @@ enum class PostPrerollResult { kSkipAndRetryFrame }; +// The |PlatformViewLayer| calls |CompositeEmbeddedView| in its |Paint| +// method to replace the leaf_nodes_canvas and leaf_nodes_builder in its +// |PaintContext| for subsequent layers in the frame to render into. +// The builder value will only be supplied if the associated ScopedFrame +// is being rendered to DisplayLists. The |EmbedderPaintContext| struct +// allows the method to return both values. +struct EmbedderPaintContext { + SkCanvas* canvas; + DisplayListBuilder* builder; +}; + +// The |EmbedderViewSlice| represents the details of recording all of +// the layer tree rendering operations that appear between before, after +// and between the embedded views. The Slice may be recorded into an +// SkPicture or a DisplayListBuilder depending on the ScopedFrame. +class EmbedderViewSlice { + public: + virtual ~EmbedderViewSlice() = default; + virtual SkCanvas* canvas() = 0; + virtual DisplayListBuilder* builder() = 0; + virtual void end_recording() = 0; + virtual std::list searchNonOverlappingDrawnRects( + const SkRect& query) const = 0; + virtual void render_into(SkCanvas* canvas) = 0; + virtual void render_into(DisplayListBuilder* builder) = 0; +}; + +class SkPictureEmbedderViewSlice : public EmbedderViewSlice { + public: + SkPictureEmbedderViewSlice(SkRect view_bounds); + ~SkPictureEmbedderViewSlice() override = default; + + SkCanvas* canvas() override; + DisplayListBuilder* builder() override; + void end_recording() override; + std::list searchNonOverlappingDrawnRects( + const SkRect& query) const override; + void render_into(SkCanvas* canvas) override; + void render_into(DisplayListBuilder* builder) override; + + private: + std::unique_ptr recorder_; + sk_sp rtree_; + sk_sp picture_; +}; + +class DisplayListEmbedderViewSlice : public EmbedderViewSlice { + public: + DisplayListEmbedderViewSlice(SkRect view_bounds); + ~DisplayListEmbedderViewSlice() override = default; + + SkCanvas* canvas() override; + DisplayListBuilder* builder() override; + void end_recording() override; + std::list searchNonOverlappingDrawnRects( + const SkRect& query) const override; + void render_into(SkCanvas* canvas) override; + void render_into(DisplayListBuilder* builder) override; + + private: + std::unique_ptr recorder_; + sk_sp display_list_; +}; + // Facilitates embedding of platform views within the flow layer tree. // // Used on iOS, Android (hybrid composite mode), and on embedded platforms @@ -315,9 +389,10 @@ class ExternalViewEmbedder { } virtual std::vector GetCurrentCanvases() = 0; + virtual std::vector GetCurrentBuilders() = 0; // Must be called on the UI thread. - virtual SkCanvas* CompositeEmbeddedView(int view_id) = 0; + virtual EmbedderPaintContext CompositeEmbeddedView(int view_id) = 0; // Implementers must submit the frame by calling frame.Submit(). // diff --git a/flow/layers/backdrop_filter_layer.cc b/flow/layers/backdrop_filter_layer.cc index 8b77c77d954ef..d52ac787c44c0 100644 --- a/flow/layers/backdrop_filter_layer.cc +++ b/flow/layers/backdrop_filter_layer.cc @@ -57,6 +57,10 @@ void BackdropFilterLayer::Paint(PaintContext& context) const { AutoCachePaint save_paint(context); save_paint.setBlendMode(blend_mode_); if (context.leaf_nodes_builder) { + // Note that we perform a saveLayer directly on the + // leaf_nodes_builder here similar to how the SkCanvas + // path specifies the kLeafNodesCanvas below. + // See https:://flutter.dev/go/backdrop-filter-with-overlay-canvas context.leaf_nodes_builder->saveLayer(&paint_bounds(), save_paint.dl_paint(), filter_.get()); diff --git a/flow/layers/color_filter_layer.cc b/flow/layers/color_filter_layer.cc index c82ae04c0cd60..02822bf68d612 100644 --- a/flow/layers/color_filter_layer.cc +++ b/flow/layers/color_filter_layer.cc @@ -3,12 +3,15 @@ // found in the LICENSE file. #include "flutter/flow/layers/color_filter_layer.h" + +#include "flutter/display_list/display_list_comparable.h" +#include "flutter/display_list/display_list_paint.h" #include "flutter/flow/raster_cache_item.h" #include "flutter/flow/raster_cache_util.h" namespace flutter { -ColorFilterLayer::ColorFilterLayer(sk_sp filter) +ColorFilterLayer::ColorFilterLayer(std::shared_ptr filter) : CacheableContainerLayer( RasterCacheUtil::kMinimumRendersBeforeCachingFilterLayer, true), @@ -19,7 +22,7 @@ void ColorFilterLayer::Diff(DiffContext* context, const Layer* old_layer) { auto* prev = static_cast(old_layer); if (!context->IsSubtreeDirty()) { FML_DCHECK(prev); - if (filter_ != prev->filter_) { + if (NotEquals(filter_, prev->filter_)) { context->MarkSubtreeDirty(context->GetOldLayerPaintRegion(old_layer)); } } @@ -45,23 +48,29 @@ void ColorFilterLayer::Paint(PaintContext& context) const { TRACE_EVENT0("flutter", "ColorFilterLayer::Paint"); FML_DCHECK(needs_painting(context)); - AutoCachePaint cache_paint(context); - if (context.raster_cache) { + AutoCachePaint cache_paint(context); if (layer_raster_cache_item_->IsCacheChildren()) { - cache_paint.setColorFilter(filter_); + cache_paint.setColorFilter(filter_.get()); } if (layer_raster_cache_item_->Draw(context, cache_paint.sk_paint())) { return; } } - cache_paint.setColorFilter(filter_); - - Layer::AutoSaveLayer save = Layer::AutoSaveLayer::Create( - context, paint_bounds(), cache_paint.sk_paint()); - - PaintChildren(context); + AutoCachePaint cache_paint(context); + cache_paint.setColorFilter(filter_.get()); + if (context.leaf_nodes_builder) { + FML_DCHECK(context.builder_multiplexer); + context.builder_multiplexer->saveLayer(&paint_bounds(), + cache_paint.dl_paint()); + PaintChildren(context); + context.builder_multiplexer->restore(); + } else { + Layer::AutoSaveLayer save = Layer::AutoSaveLayer::Create( + context, paint_bounds(), cache_paint.sk_paint()); + PaintChildren(context); + } } } // namespace flutter diff --git a/flow/layers/color_filter_layer.h b/flow/layers/color_filter_layer.h index d896ff34fb7b8..62f702b33e01a 100644 --- a/flow/layers/color_filter_layer.h +++ b/flow/layers/color_filter_layer.h @@ -5,6 +5,7 @@ #ifndef FLUTTER_FLOW_LAYERS_COLOR_FILTER_LAYER_H_ #define FLUTTER_FLOW_LAYERS_COLOR_FILTER_LAYER_H_ +#include "flutter/display_list/display_list_color_filter.h" #include "flutter/flow/layers/cacheable_layer.h" #include "flutter/flow/layers/layer.h" #include "third_party/skia/include/core/SkColorFilter.h" @@ -12,7 +13,7 @@ namespace flutter { class ColorFilterLayer : public CacheableContainerLayer { public: - explicit ColorFilterLayer(sk_sp filter); + explicit ColorFilterLayer(std::shared_ptr filter); void Diff(DiffContext* context, const Layer* old_layer) override; @@ -21,7 +22,8 @@ class ColorFilterLayer : public CacheableContainerLayer { void Paint(PaintContext& context) const override; private: - sk_sp filter_; + std::shared_ptr filter_; + FML_DISALLOW_COPY_AND_ASSIGN(ColorFilterLayer); }; diff --git a/flow/layers/color_filter_layer_unittests.cc b/flow/layers/color_filter_layer_unittests.cc index 5ec709f1ab54b..63ac582c5b527 100644 --- a/flow/layers/color_filter_layer_unittests.cc +++ b/flow/layers/color_filter_layer_unittests.cc @@ -2,6 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "flutter/display_list/display_list.h" +#include "flutter/display_list/display_list_color.h" +#include "flutter/display_list/display_list_paint.h" #include "flutter/flow/compositor_context.h" #include "flutter/flow/layers/color_filter_layer.h" @@ -14,6 +17,7 @@ #include "flutter/flow/testing/mock_layer.h" #include "flutter/fml/macros.h" #include "flutter/testing/mock_canvas.h" +#include "include/core/SkColor.h" #include "third_party/skia/include/core/SkColorFilter.h" #include "third_party/skia/include/effects/SkColorMatrixFilter.h" @@ -24,7 +28,8 @@ using ColorFilterLayerTest = LayerTest; #ifndef NDEBUG TEST_F(ColorFilterLayerTest, PaintingEmptyLayerDies) { - auto layer = std::make_shared(sk_sp()); + auto layer = std::make_shared( + std::make_shared(sk_sp())); layer->Preroll(preroll_context(), SkMatrix()); EXPECT_EQ(layer->paint_bounds(), kEmptyRect); @@ -39,7 +44,9 @@ 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()); + + auto layer = std::make_shared( + std::make_shared(sk_sp())); layer->Add(mock_layer); EXPECT_EQ(layer->paint_bounds(), kEmptyRect); @@ -82,10 +89,10 @@ TEST_F(ColorFilterLayerTest, SimpleFilter) { 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 dl_color_filter = DlLinearToSrgbGammaColorFilter::instance; auto mock_layer = std::make_shared(child_path, child_paint); - auto layer = std::make_shared(layer_filter); + auto layer = std::make_shared(dl_color_filter); layer->Add(mock_layer); layer->Preroll(preroll_context(), initial_transform); @@ -94,17 +101,23 @@ TEST_F(ColorFilterLayerTest, SimpleFilter) { EXPECT_TRUE(layer->needs_painting(paint_context())); 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}}})); + DisplayListBuilder expected_builder; + /* ColorFilterLayer::Paint() */ { + DlPaint dl_paint; + dl_paint.setColorFilter(dl_color_filter.get()); + expected_builder.saveLayer(&child_bounds, &dl_paint); + { + /* MockLayer::Paint() */ { + expected_builder.drawPath(child_path, + DlPaint().setColor(DlColor::kYellow())); + } + } + } + expected_builder.restore(); + auto expected_display_list = expected_builder.Build(); + + layer->Paint(display_list_paint_context()); + EXPECT_TRUE(DisplayListsEQ_Verbose(display_list(), expected_display_list)); } TEST_F(ColorFilterLayerTest, MultipleChildren) { @@ -115,11 +128,10 @@ TEST_F(ColorFilterLayerTest, MultipleChildren) { 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); + auto dl_color_filter = DlSrgbToLinearGammaColorFilter::instance; + auto layer = std::make_shared(dl_color_filter); layer->Add(mock_layer1); layer->Add(mock_layer2); @@ -136,19 +148,27 @@ TEST_F(ColorFilterLayerTest, MultipleChildren) { 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}}})); + DisplayListBuilder expected_builder; + /* ColorFilterLayer::Paint() */ { + DlPaint dl_paint; + dl_paint.setColorFilter(dl_color_filter.get()); + expected_builder.saveLayer(&children_bounds, &dl_paint); + { + /* MockLayer::Paint() */ { + expected_builder.drawPath(child_path1, + DlPaint().setColor(DlColor::kYellow())); + } + /* MockLayer::Paint() */ { + expected_builder.drawPath(child_path2, + DlPaint().setColor(DlColor::kCyan())); + } + } + } + expected_builder.restore(); + auto expected_display_list = expected_builder.Build(); + + layer->Paint(display_list_paint_context()); + EXPECT_TRUE(DisplayListsEQ_Verbose(display_list(), expected_display_list)); } TEST_F(ColorFilterLayerTest, Nested) { @@ -159,14 +179,12 @@ TEST_F(ColorFilterLayerTest, Nested) { 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); + auto dl_color_filter = DlSrgbToLinearGammaColorFilter::instance; + auto layer1 = std::make_shared(dl_color_filter); + + auto layer2 = std::make_shared(dl_color_filter); layer2->Add(mock_layer2); layer1->Add(mock_layer1); layer1->Add(layer2); @@ -187,32 +205,44 @@ TEST_F(ColorFilterLayerTest, Nested) { 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}}})); + DisplayListBuilder expected_builder; + /* ColorFilterLayer::Paint() */ { + DlPaint dl_paint; + dl_paint.setColorFilter(dl_color_filter.get()); + expected_builder.saveLayer(&children_bounds, &dl_paint); + { + /* MockLayer::Paint() */ { + expected_builder.drawPath(child_path1, + DlPaint().setColor(DlColor::kYellow())); + } + /* ColorFilter::Paint() */ { + DlPaint child_dl_paint; + child_dl_paint.setColor(DlColor::kBlack()); + child_dl_paint.setColorFilter(dl_color_filter.get()); + expected_builder.saveLayer(&child_path2.getBounds(), &child_dl_paint); + + /* MockLayer::Paint() */ { + expected_builder.drawPath(child_path2, + DlPaint().setColor(DlColor::kCyan())); + } + expected_builder.restore(); + } + } + } + expected_builder.restore(); + + auto expected_display_list = expected_builder.Build(); + + layer1->Paint(display_list_paint_context()); + EXPECT_TRUE(DisplayListsEQ_Verbose(display_list(), expected_display_list)); } 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); + auto layer = std::make_shared( + DlLinearToSrgbGammaColorFilter::instance); preroll_context()->surface_needs_readback = false; layer->Preroll(preroll_context(), initial_transform); EXPECT_FALSE(preroll_context()->surface_needs_readback); @@ -227,8 +257,7 @@ TEST_F(ColorFilterLayerTest, Readback) { } TEST_F(ColorFilterLayerTest, CacheChild) { - auto layer_filter = - SkColorMatrixFilter::MakeLightingFilter(SK_ColorGREEN, SK_ColorYELLOW); + auto layer_filter = DlSrgbToLinearGammaColorFilter::instance; auto initial_transform = SkMatrix::Translate(50.0, 25.5); auto other_transform = SkMatrix::Scale(1.0, 2.0); const SkPath child_path = SkPath().addRect(SkRect::MakeWH(5.0f, 5.0f)); @@ -268,8 +297,7 @@ TEST_F(ColorFilterLayerTest, CacheChild) { } TEST_F(ColorFilterLayerTest, CacheChildren) { - auto layer_filter = - SkColorMatrixFilter::MakeLightingFilter(SK_ColorGREEN, SK_ColorYELLOW); + auto layer_filter = DlSrgbToLinearGammaColorFilter::instance; auto initial_transform = SkMatrix::Translate(50.0, 25.5); auto other_transform = SkMatrix::Scale(1.0, 2.0); const SkPath child_path1 = SkPath().addRect(SkRect::MakeWH(5.0f, 5.0f)); @@ -314,8 +342,7 @@ TEST_F(ColorFilterLayerTest, CacheChildren) { } TEST_F(ColorFilterLayerTest, CacheColorFilterLayerSelf) { - auto layer_filter = - SkColorMatrixFilter::MakeLightingFilter(SK_ColorGREEN, SK_ColorYELLOW); + auto layer_filter = DlSrgbToLinearGammaColorFilter::instance; auto initial_transform = SkMatrix::Translate(50.0, 25.5); auto other_transform = SkMatrix::Scale(1.0, 2.0); const SkPath child_path1 = SkPath().addRect(SkRect::MakeWH(5.0f, 5.0f)); @@ -384,8 +411,8 @@ TEST_F(ColorFilterLayerTest, OpacityInheritance) { auto initial_transform = SkMatrix::Translate(50.0, 25.5); const SkPath child_path = SkPath().addRect(SkRect::MakeWH(5.0f, 5.0f)); auto mock_layer = std::make_shared(child_path); - auto color_filter_layer = - std::make_shared(layer_filter.skia_object()); + auto color_filter_layer = std::make_shared( + std::make_shared(matrix)); color_filter_layer->Add(mock_layer); PrerollContext* context = preroll_context(); @@ -409,13 +436,12 @@ TEST_F(ColorFilterLayerTest, OpacityInheritance) { { expected_builder.translate(offset.fX, offset.fY); /* ColorFilterLayer::Paint() */ { - expected_builder.setColor(opacity_alpha << 24); - expected_builder.setColorFilter(&layer_filter); - expected_builder.saveLayer(&child_path.getBounds(), true); + DlPaint dl_paint; + dl_paint.setColor(opacity_alpha << 24); + dl_paint.setColorFilter(&layer_filter); + expected_builder.saveLayer(&child_path.getBounds(), &dl_paint); /* MockLayer::Paint() */ { - expected_builder.setColor(0xFF000000); - expected_builder.setColorFilter(nullptr); - expected_builder.drawPath(child_path); + expected_builder.drawPath(child_path, DlPaint().setColor(0xFF000000)); } expected_builder.restore(); } diff --git a/flow/layers/container_layer.cc b/flow/layers/container_layer.cc index ad6fd9126e200..a37f7d92f4759 100644 --- a/flow/layers/container_layer.cc +++ b/flow/layers/container_layer.cc @@ -134,15 +134,19 @@ void ContainerLayer::PrerollChildren(PrerollContext* context, // Platform views have no children, so context->has_platform_view should // always be false. FML_DCHECK(!context->has_platform_view); + FML_DCHECK(!context->has_texture_layer); + bool child_has_platform_view = false; bool child_has_texture_layer = false; bool subtree_can_inherit_opacity = context->subtree_can_inherit_opacity; 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. + // Reset context->has_platform_view and context->has_texture_layer to false + // so that layers aren't treated as if they have a platform view or texture + // layer based on one being previously found in a sibling tree. context->has_platform_view = false; + context->has_texture_layer = false; + // Initialize the "inherit opacity" flag to false and allow the layer to // override the answer during its |Preroll| context->subtree_can_inherit_opacity = false; diff --git a/flow/layers/container_layer_unittests.cc b/flow/layers/container_layer_unittests.cc index 577ed32ffb493..f1c6df8cdf203 100644 --- a/flow/layers/container_layer_unittests.cc +++ b/flow/layers/container_layer_unittests.cc @@ -29,6 +29,14 @@ TEST_F(ContainerLayerTest, LayerWithParentHasPlatformView) { "!context->has_platform_view"); } +TEST_F(ContainerLayerTest, LayerWithParentHasTextureLayer) { + auto layer = std::make_shared(); + + preroll_context()->has_texture_layer = true; + EXPECT_DEATH_IF_SUPPORTED(layer->Preroll(preroll_context(), SkMatrix()), + "!context->has_texture_layer"); +} + TEST_F(ContainerLayerTest, PaintingEmptyLayerDies) { auto layer = std::make_shared(); @@ -55,6 +63,34 @@ TEST_F(ContainerLayerTest, PaintBeforePrerollDies) { } #endif +TEST_F(ContainerLayerTest, LayerWithParentHasTextureLayerNeedsResetFlag) { + 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); + + auto mock_layer1 = std::make_shared( + child_path1, child_paint1, false /* fake_has_platform_view */, false, + false, true /* fake_has_texture_layer */); + auto mock_layer2 = std::make_shared(child_path2, child_paint2); + + auto root = std::make_shared(); + auto container_layer1 = std::make_shared(); + auto container_layer2 = std::make_shared(); + root->Add(container_layer1); + root->Add(container_layer2); + container_layer1->Add(mock_layer1); + container_layer2->Add(mock_layer2); + + EXPECT_EQ(preroll_context()->has_texture_layer, false); + root->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(preroll_context()->has_texture_layer, true); + // The flag for holding texture layer from parent needs to be clear + EXPECT_EQ(mock_layer2->parent_has_texture_layer(), false); +} + TEST_F(ContainerLayerTest, Simple) { SkPath child_path; child_path.addRect(5.0f, 6.0f, 20.5f, 21.5f); diff --git a/flow/layers/image_filter_layer.cc b/flow/layers/image_filter_layer.cc index 4793bfd8f0d03..addccef60d533 100644 --- a/flow/layers/image_filter_layer.cc +++ b/flow/layers/image_filter_layer.cc @@ -3,12 +3,13 @@ // found in the LICENSE file. #include "flutter/flow/layers/image_filter_layer.h" +#include "flutter/display_list/display_list_comparable.h" #include "flutter/flow/layers/layer.h" #include "flutter/flow/raster_cache_util.h" namespace flutter { -ImageFilterLayer::ImageFilterLayer(sk_sp filter) +ImageFilterLayer::ImageFilterLayer(std::shared_ptr filter) : CacheableContainerLayer( RasterCacheUtil::kMinimumRendersBeforeCachingFilterLayer), filter_(std::move(filter)), @@ -19,7 +20,7 @@ void ImageFilterLayer::Diff(DiffContext* context, const Layer* old_layer) { auto* prev = static_cast(old_layer); if (!context->IsSubtreeDirty()) { FML_DCHECK(prev); - if (filter_ != prev->filter_) { + if (NotEquals(filter_, prev->filter_)) { context->MarkSubtreeDirty(context->GetOldLayerPaintRegion(old_layer)); } } @@ -29,9 +30,10 @@ void ImageFilterLayer::Diff(DiffContext* context, const Layer* old_layer) { if (filter) { // This transform will be applied to every child rect in the subtree context->PushFilterBoundsAdjustment([filter](SkRect rect) { - return SkRect::Make( - filter->filterBounds(rect.roundOut(), SkMatrix::I(), - SkImageFilter::kForward_MapDirection)); + SkIRect filter_out_bounds; + filter->map_device_bounds(rect.roundOut(), SkMatrix::I(), + filter_out_bounds); + return SkRect::Make(filter_out_bounds); }); } } @@ -50,7 +52,6 @@ void ImageFilterLayer::Preroll(PrerollContext* context, SkRect child_bounds = SkRect::MakeEmpty(); PrerollChildren(context, matrix, &child_bounds); - context->subtree_can_inherit_opacity = true; // We always paint with a saveLayer (or a cached rendering), // so we can always apply opacity in any of those cases. @@ -61,10 +62,11 @@ void ImageFilterLayer::Preroll(PrerollContext* context, return; } - const SkIRect filter_input_bounds = child_bounds.roundOut(); - SkIRect filter_output_bounds = filter_->filterBounds( - filter_input_bounds, SkMatrix::I(), SkImageFilter::kForward_MapDirection); - child_bounds = SkRect::Make(filter_output_bounds); + const SkIRect filter_in_bounds = child_bounds.roundOut(); + SkIRect filter_out_bounds; + filter_->map_device_bounds(filter_in_bounds, SkMatrix::I(), + filter_out_bounds); + child_bounds = SkRect::Make(filter_out_bounds); set_paint_bounds(child_bounds); @@ -83,23 +85,31 @@ void ImageFilterLayer::Paint(PaintContext& context) const { FML_DCHECK(needs_painting(context)); AutoCachePaint cache_paint(context); - - if (layer_raster_cache_item_->IsCacheChildren()) { - cache_paint.setImageFilter(transformed_filter_); - } - if (layer_raster_cache_item_->Draw(context, cache_paint.sk_paint())) { - return; + if (context.raster_cache) { + if (layer_raster_cache_item_->IsCacheChildren()) { + cache_paint.setImageFilter(transformed_filter_.get()); + } + if (layer_raster_cache_item_->Draw(context, cache_paint.sk_paint())) { + return; + } } - cache_paint.setImageFilter(filter_); - - // Normally a save_layer is sized to the current layer bounds, but in this - // case the bounds of the child may not be the same as the filtered version - // so we use the bounds of the child container which do not include any - // modifications that the filter might apply. - Layer::AutoSaveLayer save_layer = Layer::AutoSaveLayer::Create( - context, child_paint_bounds(), cache_paint.sk_paint()); - PaintChildren(context); + cache_paint.setImageFilter(filter_.get()); + if (context.leaf_nodes_builder) { + FML_DCHECK(context.builder_multiplexer); + context.builder_multiplexer->saveLayer(&child_paint_bounds(), + cache_paint.dl_paint()); + PaintChildren(context); + context.builder_multiplexer->restore(); + } else { + // Normally a save_layer is sized to the current layer bounds, but in this + // case the bounds of the child may not be the same as the filtered version + // so we use the bounds of the child container which do not include any + // modifications that the filter might apply. + Layer::AutoSaveLayer save_layer = Layer::AutoSaveLayer::Create( + context, child_paint_bounds(), cache_paint.sk_paint()); + PaintChildren(context); + } } } // namespace flutter diff --git a/flow/layers/image_filter_layer.h b/flow/layers/image_filter_layer.h index bb2f3a6d60335..e4987bd9fb75b 100644 --- a/flow/layers/image_filter_layer.h +++ b/flow/layers/image_filter_layer.h @@ -5,15 +5,17 @@ #ifndef FLUTTER_FLOW_LAYERS_IMAGE_FILTER_LAYER_H_ #define FLUTTER_FLOW_LAYERS_IMAGE_FILTER_LAYER_H_ +#include #include "flutter/flow/layers/cacheable_layer.h" #include "flutter/flow/layers/layer.h" +#include "include/core/SkRefCnt.h" #include "third_party/skia/include/core/SkImageFilter.h" namespace flutter { class ImageFilterLayer : public CacheableContainerLayer { public: - explicit ImageFilterLayer(sk_sp filter); + explicit ImageFilterLayer(std::shared_ptr filter); void Diff(DiffContext* context, const Layer* old_layer) override; @@ -22,8 +24,8 @@ class ImageFilterLayer : public CacheableContainerLayer { void Paint(PaintContext& context) const override; private: - sk_sp filter_; - sk_sp transformed_filter_; + std::shared_ptr filter_; + std::shared_ptr transformed_filter_; FML_DISALLOW_COPY_AND_ASSIGN(ImageFilterLayer); }; diff --git a/flow/layers/image_filter_layer_unittests.cc b/flow/layers/image_filter_layer_unittests.cc index 546331452a9fa..2ad4a7e3e86d6 100644 --- a/flow/layers/image_filter_layer_unittests.cc +++ b/flow/layers/image_filter_layer_unittests.cc @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "flutter/display_list/display_list_tile_mode.h" #include "flutter/flow/layers/image_filter_layer.h" #include "flutter/flow/layers/layer_tree.h" @@ -23,7 +24,7 @@ using ImageFilterLayerTest = LayerTest; #ifndef NDEBUG TEST_F(ImageFilterLayerTest, PaintingEmptyLayerDies) { - auto layer = std::make_shared(sk_sp()); + auto layer = std::make_shared(nullptr); layer->Preroll(preroll_context(), SkMatrix()); EXPECT_EQ(layer->paint_bounds(), kEmptyRect); @@ -37,7 +38,7 @@ 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()); + auto layer = std::make_shared(nullptr); layer->Add(mock_layer); EXPECT_EQ(layer->paint_bounds(), kEmptyRect); @@ -81,11 +82,10 @@ TEST_F(ImageFilterLayerTest, SimpleFilter) { 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::MatrixTransform( - SkMatrix(), - SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kLinear), nullptr); + auto dl_image_filter = std::make_shared( + SkMatrix(), DlImageSampling::kMipmapLinear); auto mock_layer = std::make_shared(child_path, child_paint); - auto layer = std::make_shared(layer_filter); + auto layer = std::make_shared(dl_image_filter); layer->Add(mock_layer); const SkRect child_rounded_bounds = @@ -97,18 +97,23 @@ TEST_F(ImageFilterLayerTest, SimpleFilter) { EXPECT_TRUE(layer->needs_painting(paint_context())); 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::SaveLayerData{child_bounds, filter_paint, - nullptr, 1}}, - MockCanvas::DrawCall{ - 1, MockCanvas::DrawPathData{child_path, child_paint}}, - MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}, - })); + DisplayListBuilder expected_builder; + /* ImageFilterLayer::Paint() */ { + DlPaint dl_paint; + dl_paint.setImageFilter(dl_image_filter.get()); + expected_builder.saveLayer(&child_bounds, &dl_paint); + { + /* MockLayer::Paint() */ { + expected_builder.drawPath(child_path, + DlPaint().setColor(DlColor::kYellow())); + } + } + } + expected_builder.restore(); + auto expected_display_list = expected_builder.Build(); + + layer->Paint(display_list_paint_context()); + EXPECT_TRUE(DisplayListsEQ_Verbose(display_list(), expected_display_list)); } TEST_F(ImageFilterLayerTest, SimpleFilterBounds) { @@ -117,11 +122,11 @@ TEST_F(ImageFilterLayerTest, SimpleFilterBounds) { const SkPath child_path = SkPath().addRect(child_bounds); const SkPaint child_paint = SkPaint(SkColors::kYellow); const SkMatrix filter_transform = SkMatrix::Scale(2.0, 2.0); - auto layer_filter = SkImageFilters::MatrixTransform( - filter_transform, - SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kLinear), nullptr); + + auto dl_image_filter = std::make_shared( + filter_transform, DlImageSampling::kMipmapLinear); auto mock_layer = std::make_shared(child_path, child_paint); - auto layer = std::make_shared(layer_filter); + auto layer = std::make_shared(dl_image_filter); layer->Add(mock_layer); const SkRect filter_bounds = SkRect::MakeLTRB(10.0f, 12.0f, 42.0f, 44.0f); @@ -132,18 +137,23 @@ TEST_F(ImageFilterLayerTest, SimpleFilterBounds) { EXPECT_TRUE(layer->needs_painting(paint_context())); 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::SaveLayerData{child_bounds, filter_paint, - nullptr, 1}}, - MockCanvas::DrawCall{ - 1, MockCanvas::DrawPathData{child_path, child_paint}}, - MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}, - })); + DisplayListBuilder expected_builder; + /* ImageFilterLayer::Paint() */ { + DlPaint dl_paint; + dl_paint.setImageFilter(dl_image_filter.get()); + expected_builder.saveLayer(&child_bounds, &dl_paint); + { + /* MockLayer::Paint() */ { + expected_builder.drawPath(child_path, + DlPaint().setColor(DlColor::kYellow())); + } + } + } + expected_builder.restore(); + auto expected_display_list = expected_builder.Build(); + + layer->Paint(display_list_paint_context()); + EXPECT_TRUE(DisplayListsEQ_Verbose(display_list(), expected_display_list)); } TEST_F(ImageFilterLayerTest, MultipleChildren) { @@ -154,12 +164,11 @@ TEST_F(ImageFilterLayerTest, MultipleChildren) { 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::MatrixTransform( - SkMatrix(), - SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kLinear), nullptr); + auto dl_image_filter = std::make_shared( + SkMatrix(), DlImageSampling::kMipmapLinear); 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); + auto layer = std::make_shared(dl_image_filter); layer->Add(mock_layer1); layer->Add(mock_layer2); @@ -178,19 +187,27 @@ TEST_F(ImageFilterLayerTest, MultipleChildren) { 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::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}}})); + DisplayListBuilder expected_builder; + /* ImageFilterLayer::Paint() */ { + DlPaint dl_paint; + dl_paint.setImageFilter(dl_image_filter.get()); + expected_builder.saveLayer(&children_bounds, &dl_paint); + { + /* MockLayer::Paint() */ { + expected_builder.drawPath(child_path1, + DlPaint().setColor(DlColor::kYellow())); + } + /* MockLayer::Paint() */ { + expected_builder.drawPath(child_path2, + DlPaint().setColor(DlColor::kCyan())); + } + } + } + expected_builder.restore(); + auto expected_display_list = expected_builder.Build(); + + layer->Paint(display_list_paint_context()); + EXPECT_TRUE(DisplayListsEQ_Verbose(display_list(), expected_display_list)); } TEST_F(ImageFilterLayerTest, Nested) { @@ -201,16 +218,14 @@ TEST_F(ImageFilterLayerTest, Nested) { 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::MatrixTransform( - SkMatrix(), - SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kLinear), nullptr); - auto layer_filter2 = SkImageFilters::MatrixTransform( - SkMatrix(), - SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kLinear), nullptr); + auto dl_image_filter1 = std::make_shared( + SkMatrix(), DlImageSampling::kMipmapLinear); + auto dl_image_filter2 = std::make_shared( + SkMatrix(), DlImageSampling::kMipmapLinear); 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); + auto layer1 = std::make_shared(dl_image_filter1); + auto layer2 = std::make_shared(dl_image_filter2); layer2->Add(mock_layer2); layer1->Add(mock_layer1); layer1->Add(layer2); @@ -236,35 +251,43 @@ TEST_F(ImageFilterLayerTest, Nested) { 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::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}}, - })); + DisplayListBuilder expected_builder; + /* ImageFilterLayer::Paint() */ { + DlPaint dl_paint; + dl_paint.setImageFilter(dl_image_filter1.get()); + expected_builder.saveLayer(&children_bounds, &dl_paint); + { + /* MockLayer::Paint() */ { + expected_builder.drawPath(child_path1, + DlPaint().setColor(DlColor::kYellow())); + } + /* ImageFilterLayer::Paint() */ { + DlPaint child_paint; + child_paint.setImageFilter(dl_image_filter2.get()); + expected_builder.saveLayer(&child_path2.getBounds(), &child_paint); + /* MockLayer::Paint() */ { + expected_builder.drawPath(child_path2, + DlPaint().setColor(DlColor::kCyan())); + } + expected_builder.restore(); + } + } + } + expected_builder.restore(); + auto expected_display_list = expected_builder.Build(); + + layer1->Paint(display_list_paint_context()); + EXPECT_TRUE(DisplayListsEQ_Verbose(display_list(), expected_display_list)); } TEST_F(ImageFilterLayerTest, Readback) { - auto layer_filter = SkImageFilters::MatrixTransform( - SkMatrix(), - SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kLinear), nullptr); + auto dl_image_filter = std::make_shared( + SkMatrix(), DlImageSampling::kLinear); + auto initial_transform = SkMatrix(); // ImageFilterLayer does not read from surface - auto layer = std::make_shared(layer_filter); + auto layer = std::make_shared(dl_image_filter); preroll_context()->surface_needs_readback = false; layer->Preroll(preroll_context(), initial_transform); EXPECT_FALSE(preroll_context()->surface_needs_readback); @@ -279,14 +302,13 @@ TEST_F(ImageFilterLayerTest, Readback) { } TEST_F(ImageFilterLayerTest, CacheChild) { - auto layer_filter = SkImageFilters::MatrixTransform( - SkMatrix(), - SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kLinear), nullptr); + auto dl_image_filter = std::make_shared( + SkMatrix(), DlImageSampling::kMipmapLinear); auto initial_transform = SkMatrix::Translate(50.0, 25.5); auto other_transform = SkMatrix::Scale(1.0, 2.0); const SkPath child_path = SkPath().addRect(SkRect::MakeWH(5.0f, 5.0f)); auto mock_layer = std::make_shared(child_path); - auto layer = std::make_shared(layer_filter); + auto layer = std::make_shared(dl_image_filter); layer->Add(mock_layer); SkMatrix cache_ctm = initial_transform; @@ -320,9 +342,8 @@ TEST_F(ImageFilterLayerTest, CacheChild) { } TEST_F(ImageFilterLayerTest, CacheChildren) { - auto layer_filter = SkImageFilters::MatrixTransform( - SkMatrix(), - SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kLinear), nullptr); + auto dl_image_filter = std::make_shared( + SkMatrix(), DlImageSampling::kMipmapLinear); auto initial_transform = SkMatrix::Translate(50.0, 25.5); auto other_transform = SkMatrix::Scale(1.0, 2.0); SkPaint paint = SkPaint(); @@ -330,7 +351,7 @@ TEST_F(ImageFilterLayerTest, CacheChildren) { const SkPath child_path2 = SkPath().addRect(SkRect::MakeWH(5.0f, 5.0f)); auto mock_layer1 = std::make_shared(child_path1); auto mock_layer2 = std::make_shared(child_path2); - auto layer = std::make_shared(layer_filter); + auto layer = std::make_shared(dl_image_filter); layer->Add(mock_layer1); layer->Add(mock_layer2); @@ -366,14 +387,14 @@ TEST_F(ImageFilterLayerTest, CacheChildren) { } TEST_F(ImageFilterLayerTest, CacheImageFilterLayerSelf) { - auto layer_filter = SkImageFilters::MatrixTransform( - SkMatrix(), - SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kLinear), nullptr); + auto dl_image_filter = std::make_shared( + SkMatrix(), DlImageSampling::kMipmapLinear); + auto initial_transform = SkMatrix::Translate(50.0, 25.5); auto other_transform = SkMatrix::Scale(1.0, 2.0); const SkPath child_path = SkPath().addRect(SkRect::MakeWH(5.0f, 5.0f)); auto mock_layer = std::make_shared(child_path); - auto layer = std::make_shared(layer_filter); + auto layer = std::make_shared(dl_image_filter); layer->Add(mock_layer); SkMatrix cache_ctm = initial_transform; @@ -416,12 +437,12 @@ TEST_F(ImageFilterLayerTest, OpacityInheritance) { 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::MatrixTransform( - SkMatrix(), - SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kLinear), nullptr); + auto dl_image_filter = std::make_shared( + SkMatrix(), DlImageSampling::kMipmapLinear); + // The mock_layer child will not be compatible with opacity auto mock_layer = MockLayer::Make(child_path, child_paint); - auto image_filter_layer = std::make_shared(layer_filter); + auto image_filter_layer = std::make_shared(dl_image_filter); image_filter_layer->Add(mock_layer); PrerollContext* context = preroll_context(); @@ -439,20 +460,20 @@ TEST_F(ImageFilterLayerTest, OpacityInheritance) { opacity_layer->Preroll(context, SkMatrix::I()); EXPECT_TRUE(opacity_layer->children_can_accept_opacity()); - auto dl_image_filter = DlImageFilter::From(layer_filter); DisplayListBuilder expected_builder; /* OpacityLayer::Paint() */ { expected_builder.save(); { expected_builder.translate(offset.fX, offset.fY); /* ImageFilterLayer::Paint() */ { - expected_builder.setColor(opacity_alpha << 24); - expected_builder.setImageFilter(dl_image_filter.get()); - expected_builder.saveLayer(&child_path.getBounds(), true); + DlPaint image_filter_paint; + image_filter_paint.setColor(opacity_alpha << 24); + image_filter_paint.setImageFilter(dl_image_filter.get()); + expected_builder.saveLayer(&child_path.getBounds(), + &image_filter_paint); /* MockLayer::Paint() */ { - expected_builder.setColor(child_paint.getColor()); - expected_builder.setImageFilter(nullptr); - expected_builder.drawPath(child_path); + expected_builder.drawPath(child_path, + DlPaint().setColor(child_paint.getColor())); } expected_builder.restore(); } @@ -467,18 +488,18 @@ TEST_F(ImageFilterLayerTest, OpacityInheritance) { using ImageFilterLayerDiffTest = DiffContextTest; TEST_F(ImageFilterLayerDiffTest, ImageFilterLayer) { - auto filter = SkImageFilters::Blur(10, 10, SkTileMode::kClamp, nullptr); - + auto dl_blur_filter = + std::make_shared(10, 10, DlTileMode::kClamp); { // tests later assume 30px paint area, fail early if that's not the case - auto paint_rect = - filter->filterBounds(SkIRect::MakeWH(10, 10), SkMatrix::I(), - SkImageFilter::kForward_MapDirection); - EXPECT_EQ(paint_rect, SkIRect::MakeLTRB(-30, -30, 40, 40)); + SkIRect input_bounds; + dl_blur_filter->get_input_device_bounds(SkIRect::MakeWH(10, 10), + SkMatrix::I(), input_bounds); + EXPECT_EQ(input_bounds, SkIRect::MakeLTRB(-30, -30, 40, 40)); } MockLayerTree l1; - auto filter_layer = std::make_shared(filter); + auto filter_layer = std::make_shared(dl_blur_filter); auto path = SkPath().addRect(SkRect::MakeLTRB(100, 100, 110, 110)); filter_layer->Add(std::make_shared(path)); l1.root()->Add(filter_layer); @@ -514,21 +535,22 @@ TEST_F(ImageFilterLayerDiffTest, ImageFilterLayer) { } TEST_F(ImageFilterLayerDiffTest, ImageFilterLayerInflatestChildSize) { - auto filter = SkImageFilters::Blur(10, 10, SkTileMode::kClamp, nullptr); + auto dl_blur_filter = + std::make_shared(10, 10, DlTileMode::kClamp); { // tests later assume 30px paint area, fail early if that's not the case - auto paint_rect = - filter->filterBounds(SkIRect::MakeWH(10, 10), SkMatrix::I(), - SkImageFilter::kForward_MapDirection); - EXPECT_EQ(paint_rect, SkIRect::MakeLTRB(-30, -30, 40, 40)); + SkIRect input_bounds; + dl_blur_filter->get_input_device_bounds(SkIRect::MakeWH(10, 10), + SkMatrix::I(), input_bounds); + EXPECT_EQ(input_bounds, SkIRect::MakeLTRB(-30, -30, 40, 40)); } MockLayerTree l1; // Use nested filter layers to check if both contribute to child bounds - auto filter_layer_1_1 = std::make_shared(filter); - auto filter_layer_1_2 = std::make_shared(filter); + auto filter_layer_1_1 = std::make_shared(dl_blur_filter); + auto filter_layer_1_2 = std::make_shared(dl_blur_filter); filter_layer_1_1->Add(filter_layer_1_2); auto path = SkPath().addRect(SkRect::MakeLTRB(100, 100, 110, 110)); filter_layer_1_2->Add( @@ -537,9 +559,9 @@ TEST_F(ImageFilterLayerDiffTest, ImageFilterLayerInflatestChildSize) { // second layer tree with identical filter layers but different child layer MockLayerTree l2; - auto filter_layer2_1 = std::make_shared(filter); + auto filter_layer2_1 = std::make_shared(dl_blur_filter); filter_layer2_1->AssignOldLayer(filter_layer_1_1.get()); - auto filter_layer2_2 = std::make_shared(filter); + auto filter_layer2_2 = std::make_shared(dl_blur_filter); filter_layer2_2->AssignOldLayer(filter_layer_1_2.get()); filter_layer2_1->Add(filter_layer2_2); filter_layer2_2->Add( diff --git a/flow/layers/layer.h b/flow/layers/layer.h index 63d30c3bb5ebe..bb57b430fa3b0 100644 --- a/flow/layers/layer.h +++ b/flow/layers/layer.h @@ -11,6 +11,7 @@ #include #include "flutter/common/graphics/texture.h" +#include "flutter/display_list/display_list_builder_multiplexer.h" #include "flutter/flow/diff_context.h" #include "flutter/flow/embedded_views.h" #include "flutter/flow/instrumentation.h" @@ -29,7 +30,9 @@ #include "third_party/skia/include/core/SkRRect.h" #include "third_party/skia/include/core/SkRect.h" #include "third_party/skia/include/utils/SkNWayCanvas.h" + namespace flutter { + namespace testing { class MockLayer; } // namespace testing @@ -57,7 +60,7 @@ struct PrerollContext { // These allow us to paint in the end of subtree Preroll. const Stopwatch& raster_time; const Stopwatch& ui_time; - TextureRegistry& texture_registry; + std::shared_ptr texture_registry; const bool checkerboard_offscreen_layers; const float frame_device_pixel_ratio = 1.0f; @@ -106,6 +109,12 @@ struct PrerollContext { bool subtree_can_inherit_opacity = false; std::vector* raster_cached_entries; + + // This flag will be set to true iff the frame will be constructing + // a DisplayList for the layer tree. This flag is mostly of note to + // the embedders that must decide between creating SkPicture or + // DisplayList objects for the inter-view slices of the layer tree. + bool display_list_enabled = false; }; struct PaintContext { @@ -126,7 +135,7 @@ struct PaintContext { ExternalViewEmbedder* view_embedder; const Stopwatch& raster_time; const Stopwatch& ui_time; - TextureRegistry& texture_registry; + std::shared_ptr texture_registry; const RasterCache* raster_cache; const bool checkerboard_offscreen_layers; const float frame_device_pixel_ratio = 1.0f; @@ -144,6 +153,7 @@ struct PaintContext { // a |kSrcOver| blend mode. SkScalar inherited_opacity = SK_Scalar1; DisplayListBuilder* leaf_nodes_builder = nullptr; + DisplayListBuilderMultiplexer* builder_multiplexer = nullptr; }; // Represents a single composited layer. Created on the UI thread but then @@ -217,15 +227,15 @@ class Layer { ~AutoCachePaint() { context_.inherited_opacity = sk_paint_.getAlphaf(); } - void setImageFilter(sk_sp filter) { - sk_paint_.setImageFilter(filter); - dl_paint_.setImageFilter(DlImageFilter::From(filter)); + void setImageFilter(const DlImageFilter* filter) { + sk_paint_.setImageFilter(!filter ? nullptr : filter->skia_object()); + dl_paint_.setImageFilter(filter); update_needs_paint(); } - void setColorFilter(sk_sp filter) { - sk_paint_.setColorFilter(filter); - dl_paint_.setColorFilter(DlColorFilter::From(filter)); + void setColorFilter(const DlColorFilter* filter) { + sk_paint_.setColorFilter(!filter ? nullptr : filter->skia_object()); + dl_paint_.setColorFilter(filter); update_needs_paint(); } diff --git a/flow/layers/layer_tree.cc b/flow/layers/layer_tree.cc index f481a05c65bcb..3b4907b5336bf 100644 --- a/flow/layers/layer_tree.cc +++ b/flow/layers/layer_tree.cc @@ -64,6 +64,7 @@ bool LayerTree::Preroll(CompositorContext::ScopedFrame& frame, .checkerboard_offscreen_layers = checkerboard_offscreen_layers_, .frame_device_pixel_ratio = device_pixel_ratio_, .raster_cached_entries = &raster_cache_items_, + .display_list_enabled = frame.display_list_builder() != nullptr, // clang-format on }; @@ -118,6 +119,16 @@ void LayerTree::Paint(CompositorContext::ScopedFrame& frame, internal_nodes_canvas.addCanvas(overlay_canvases[i]); } } + DisplayListBuilder* builder = frame.display_list_builder(); + DisplayListBuilderMultiplexer builder_multiplexer; + if (builder) { + builder_multiplexer.addBuilder(builder); + if (frame.view_embedder()) { + for (auto* view_builder : frame.view_embedder()->GetCurrentBuilders()) { + builder_multiplexer.addBuilder(view_builder); + } + } + } // clear the previous snapshots. LayerSnapshotStore* snapshot_store = nullptr; @@ -145,7 +156,8 @@ void LayerTree::Paint(CompositorContext::ScopedFrame& frame, .layer_snapshot_store = snapshot_store, .enable_leaf_layer_tracing = enable_leaf_layer_tracing_, .inherited_opacity = SK_Scalar1, - .leaf_nodes_builder = frame.display_list_builder(), + .leaf_nodes_builder = builder, + .builder_multiplexer = builder ? &builder_multiplexer : nullptr, // clang-format on }; @@ -159,22 +171,25 @@ void LayerTree::Paint(CompositorContext::ScopedFrame& frame, } } -sk_sp LayerTree::Flatten(const SkRect& bounds) { +sk_sp LayerTree::Flatten( + const SkRect& bounds, + std::shared_ptr texture_registry, + GrDirectContext* gr_context) { TRACE_EVENT0("flutter", "LayerTree::Flatten"); DisplayListCanvasRecorder builder(bounds); MutatorsStack unused_stack; const FixedRefreshRateStopwatch unused_stopwatch; - TextureRegistry unused_texture_registry; SkMatrix root_surface_transformation; + // No root surface transformation. So assume identity. root_surface_transformation.reset(); PrerollContext preroll_context{ // clang-format off .raster_cache = nullptr, - .gr_context = nullptr, + .gr_context = gr_context, .view_embedder = nullptr, .mutators_stack = unused_stack, .dst_color_space = nullptr, @@ -182,7 +197,7 @@ sk_sp LayerTree::Flatten(const SkRect& bounds) { .surface_needs_readback = false, .raster_time = unused_stopwatch, .ui_time = unused_stopwatch, - .texture_registry = unused_texture_registry, + .texture_registry = texture_registry, .checkerboard_offscreen_layers = false, .frame_device_pixel_ratio = device_pixel_ratio_ // clang-format on @@ -191,23 +206,26 @@ sk_sp LayerTree::Flatten(const SkRect& bounds) { SkISize canvas_size = builder.getBaseLayerSize(); SkNWayCanvas internal_nodes_canvas(canvas_size.width(), canvas_size.height()); internal_nodes_canvas.addCanvas(&builder); + DisplayListBuilderMultiplexer multiplexer; + multiplexer.addBuilder(builder.builder().get()); PaintContext paint_context = { // clang-format off .internal_nodes_canvas = &internal_nodes_canvas, .leaf_nodes_canvas = &builder, - .gr_context = nullptr, + .gr_context = gr_context, .dst_color_space = nullptr, .view_embedder = nullptr, .raster_time = unused_stopwatch, .ui_time = unused_stopwatch, - .texture_registry = unused_texture_registry, + .texture_registry = texture_registry, .raster_cache = nullptr, .checkerboard_offscreen_layers = false, .frame_device_pixel_ratio = device_pixel_ratio_, .layer_snapshot_store = nullptr, .enable_leaf_layer_tracing = false, .leaf_nodes_builder = builder.builder().get(), + .builder_multiplexer = &multiplexer, // clang-format on }; diff --git a/flow/layers/layer_tree.h b/flow/layers/layer_tree.h index c7daa5f809e9f..167500400b758 100644 --- a/flow/layers/layer_tree.h +++ b/flow/layers/layer_tree.h @@ -8,6 +8,7 @@ #include #include +#include "flutter/common/graphics/texture.h" #include "flutter/flow/compositor_context.h" #include "flutter/flow/layers/layer.h" #include "flutter/flow/raster_cache.h" @@ -41,7 +42,10 @@ class LayerTree { void Paint(CompositorContext::ScopedFrame& frame, bool ignore_raster_cache = false) const; - sk_sp Flatten(const SkRect& bounds); + sk_sp Flatten( + const SkRect& bounds, + std::shared_ptr texture_registry = nullptr, + GrDirectContext* gr_context = nullptr); Layer* root_layer() const { return root_layer_.get(); } diff --git a/flow/layers/layer_tree_unittests.cc b/flow/layers/layer_tree_unittests.cc index fa3bb00b455eb..9eeac5a1f77cd 100644 --- a/flow/layers/layer_tree_unittests.cc +++ b/flow/layers/layer_tree_unittests.cc @@ -195,16 +195,10 @@ TEST_F(LayerTreeTest, NeedsSystemComposite) { } TEST_F(LayerTreeTest, PrerollContextInitialization) { - // This EXPECT macro will ensure that if any fields get added to the - // PrerollContext that this test must be revisited and updated. - // If any fields get removed or replaced, then the expect_defaults closure - // will fail to compile, again bringing attention to updating this test. - EXPECT_EQ(sizeof(PrerollContext), size_t(112)); - MutatorsStack mock_mutators; FixedRefreshRateStopwatch mock_raster_time; FixedRefreshRateStopwatch mock_ui_time; - TextureRegistry mock_registry; + std::shared_ptr mock_registry; auto expect_defaults = [&mock_mutators, &mock_raster_time, &mock_ui_time, &mock_registry](const PrerollContext& context) { @@ -218,7 +212,7 @@ TEST_F(LayerTreeTest, PrerollContextInitialization) { EXPECT_EQ(&context.raster_time, &mock_raster_time); EXPECT_EQ(&context.ui_time, &mock_ui_time); - EXPECT_EQ(&context.texture_registry, &mock_registry); + EXPECT_EQ(context.texture_registry.get(), mock_registry.get()); EXPECT_EQ(context.checkerboard_offscreen_layers, false); EXPECT_EQ(context.frame_device_pixel_ratio, 1.0f); @@ -240,15 +234,9 @@ TEST_F(LayerTreeTest, PrerollContextInitialization) { } TEST_F(LayerTreeTest, PaintContextInitialization) { - // This EXPECT macro will ensure that if any fields get added to the - // PaintContext that this test must be revisited and updated. - // If any fields get removed or replaced, then the expect_defaults closure - // will fail to compile, again bringing attention to updating this test. - EXPECT_EQ(sizeof(PaintContext), size_t(104)); - FixedRefreshRateStopwatch mock_raster_time; FixedRefreshRateStopwatch mock_ui_time; - TextureRegistry mock_registry; + std::shared_ptr mock_registry; auto expect_defaults = [&mock_raster_time, &mock_ui_time, &mock_registry](const PaintContext& context) { @@ -258,7 +246,7 @@ TEST_F(LayerTreeTest, PaintContextInitialization) { EXPECT_EQ(context.view_embedder, nullptr); EXPECT_EQ(&context.raster_time, &mock_raster_time); EXPECT_EQ(&context.ui_time, &mock_ui_time); - EXPECT_EQ(&context.texture_registry, &mock_registry); + EXPECT_EQ(context.texture_registry.get(), mock_registry.get()); EXPECT_EQ(context.raster_cache, nullptr); EXPECT_EQ(context.checkerboard_offscreen_layers, false); EXPECT_EQ(context.frame_device_pixel_ratio, 1.0f); @@ -268,6 +256,7 @@ TEST_F(LayerTreeTest, PaintContextInitialization) { EXPECT_EQ(context.inherited_opacity, SK_Scalar1); EXPECT_EQ(context.leaf_nodes_builder, nullptr); + EXPECT_EQ(context.builder_multiplexer, nullptr); }; // These 4 initializers are required because they are handled by reference diff --git a/flow/layers/opacity_layer_unittests.cc b/flow/layers/opacity_layer_unittests.cc index c662e73284ec4..6844b9bc50682 100644 --- a/flow/layers/opacity_layer_unittests.cc +++ b/flow/layers/opacity_layer_unittests.cc @@ -523,7 +523,7 @@ TEST_F(OpacityLayerTest, OpacityInheritanceThroughImageFilter) { auto opacityLayer = std::make_shared(128, SkPoint::Make(20, 20)); auto filterLayer = std::make_shared( - SkImageFilters::Blur(5.0, 5.0, nullptr)); + std::make_shared(5.0, 5.0, DlTileMode::kClamp)); auto mockLayer = MockLayer::MakeOpacityCompatible(SkPath()); filterLayer->Add(mockLayer); opacityLayer->Add(filterLayer); diff --git a/flow/layers/performance_overlay_layer_unittests.cc b/flow/layers/performance_overlay_layer_unittests.cc index ae9eec8b767e4..f7c20d24d2900 100644 --- a/flow/layers/performance_overlay_layer_unittests.cc +++ b/flow/layers/performance_overlay_layer_unittests.cc @@ -58,7 +58,6 @@ static void TestPerformanceOverlayLayerGold(int refresh_rate) { ASSERT_TRUE(surface != nullptr); - flutter::TextureRegistry unused_texture_registry; flutter::PaintContext paintContext = { // clang-format off .internal_nodes_canvas = nullptr, @@ -67,7 +66,7 @@ static void TestPerformanceOverlayLayerGold(int refresh_rate) { .view_embedder = nullptr, .raster_time = mock_stopwatch, .ui_time = mock_stopwatch, - .texture_registry = unused_texture_registry, + .texture_registry = nullptr, .raster_cache = nullptr, .checkerboard_offscreen_layers = false, .frame_device_pixel_ratio = 1.0f, diff --git a/flow/layers/platform_view_layer.cc b/flow/layers/platform_view_layer.cc index 285db390cc91d..8a46cbbce127c 100644 --- a/flow/layers/platform_view_layer.cc +++ b/flow/layers/platform_view_layer.cc @@ -25,7 +25,8 @@ void PlatformViewLayer::Preroll(PrerollContext* context, set_subtree_has_platform_view(true); std::unique_ptr params = std::make_unique(matrix, size_, - context->mutators_stack); + context->mutators_stack, + context->display_list_enabled); context->view_embedder->PrerollCompositeEmbeddedView(view_id_, std::move(params)); } @@ -36,8 +37,10 @@ void PlatformViewLayer::Paint(PaintContext& context) const { "does not support embedding"; return; } - SkCanvas* canvas = context.view_embedder->CompositeEmbeddedView(view_id_); - context.leaf_nodes_canvas = canvas; + EmbedderPaintContext embedder_context = + context.view_embedder->CompositeEmbeddedView(view_id_); + context.leaf_nodes_canvas = embedder_context.canvas; + context.leaf_nodes_builder = embedder_context.builder; } } // namespace flutter diff --git a/flow/layers/shader_mask_layer.cc b/flow/layers/shader_mask_layer.cc index d39d508734c0b..b7dd47e48d99e 100644 --- a/flow/layers/shader_mask_layer.cc +++ b/flow/layers/shader_mask_layer.cc @@ -7,12 +7,12 @@ namespace flutter { -ShaderMaskLayer::ShaderMaskLayer(sk_sp shader, +ShaderMaskLayer::ShaderMaskLayer(std::shared_ptr shader, const SkRect& mask_rect, - SkBlendMode blend_mode) + DlBlendMode blend_mode) : CacheableContainerLayer( RasterCacheUtil::kMinimumRendersBeforeCachingFilterLayer), - shader_(shader), + shader_(std::move(shader)), mask_rect_(mask_rect), blend_mode_(blend_mode) {} @@ -60,8 +60,10 @@ void ShaderMaskLayer::Paint(PaintContext& context) const { PaintChildren(context); SkPaint paint; - paint.setBlendMode(blend_mode_); - paint.setShader(shader_); + paint.setBlendMode(ToSk(blend_mode_)); + if (shader_) { + paint.setShader(shader_->skia_object()); + } context.leaf_nodes_canvas->translate(mask_rect_.left(), mask_rect_.top()); context.leaf_nodes_canvas->drawRect( SkRect::MakeWH(mask_rect_.width(), mask_rect_.height()), paint); diff --git a/flow/layers/shader_mask_layer.h b/flow/layers/shader_mask_layer.h index 0dd44e3dedec7..63ac4421d633e 100644 --- a/flow/layers/shader_mask_layer.h +++ b/flow/layers/shader_mask_layer.h @@ -5,16 +5,16 @@ #ifndef FLUTTER_FLOW_LAYERS_SHADER_MASK_LAYER_H_ #define FLUTTER_FLOW_LAYERS_SHADER_MASK_LAYER_H_ +#include "flutter/display_list/display_list_color_source.h" #include "flutter/flow/layers/cacheable_layer.h" -#include "third_party/skia/include/core/SkShader.h" namespace flutter { class ShaderMaskLayer : public CacheableContainerLayer { public: - ShaderMaskLayer(sk_sp shader, + ShaderMaskLayer(std::shared_ptr shader, const SkRect& mask_rect, - SkBlendMode blend_mode); + DlBlendMode blend_mode); void Diff(DiffContext* context, const Layer* old_layer) override; @@ -23,9 +23,9 @@ class ShaderMaskLayer : public CacheableContainerLayer { void Paint(PaintContext& context) const override; private: - sk_sp shader_; + std::shared_ptr shader_; SkRect mask_rect_; - SkBlendMode blend_mode_; + DlBlendMode blend_mode_; FML_DISALLOW_COPY_AND_ASSIGN(ShaderMaskLayer); }; diff --git a/flow/layers/shader_mask_layer_unittests.cc b/flow/layers/shader_mask_layer_unittests.cc index d57a2753d4bea..5f6c38b76a0f1 100644 --- a/flow/layers/shader_mask_layer_unittests.cc +++ b/flow/layers/shader_mask_layer_unittests.cc @@ -23,7 +23,7 @@ using ShaderMaskLayerTest = LayerTest; #ifndef NDEBUG TEST_F(ShaderMaskLayerTest, PaintingEmptyLayerDies) { auto layer = - std::make_shared(nullptr, kEmptyRect, SkBlendMode::kSrc); + std::make_shared(nullptr, kEmptyRect, DlBlendMode::kSrc); layer->Preroll(preroll_context(), SkMatrix()); EXPECT_EQ(layer->paint_bounds(), kEmptyRect); @@ -39,7 +39,7 @@ TEST_F(ShaderMaskLayerTest, PaintBeforePrerollDies) { 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); + std::make_shared(nullptr, kEmptyRect, DlBlendMode::kSrc); layer->Add(mock_layer); EXPECT_EQ(layer->paint_bounds(), kEmptyRect); @@ -57,7 +57,7 @@ TEST_F(ShaderMaskLayerTest, EmptyFilter) { 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); + DlBlendMode::kSrc); layer->Add(mock_layer); layer->Preroll(preroll_context(), initial_transform); @@ -98,9 +98,10 @@ TEST_F(ShaderMaskLayerTest, SimpleFilter) { const SkPaint child_paint = SkPaint(SkColors::kYellow); auto layer_filter = SkPerlinNoiseShader::MakeFractalNoise(1.0f, 1.0f, 1, 1.0f); + auto dl_filter = DlColorSource::From(layer_filter); auto mock_layer = std::make_shared(child_path, child_paint); - auto layer = std::make_shared(layer_filter, layer_bounds, - SkBlendMode::kSrc); + auto layer = std::make_shared(dl_filter, layer_bounds, + DlBlendMode::kSrc); layer->Add(mock_layer); layer->Preroll(preroll_context(), initial_transform); @@ -142,10 +143,11 @@ TEST_F(ShaderMaskLayerTest, MultipleChildren) { const SkPaint child_paint2 = SkPaint(SkColors::kCyan); auto layer_filter = SkPerlinNoiseShader::MakeFractalNoise(1.0f, 1.0f, 1, 1.0f); + auto dl_filter = DlColorSource::From(layer_filter); 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); + auto layer = std::make_shared(dl_filter, layer_bounds, + DlBlendMode::kSrc); layer->Add(mock_layer1); layer->Add(mock_layer2); @@ -197,14 +199,16 @@ TEST_F(ShaderMaskLayerTest, Nested) { const SkPaint child_paint2 = SkPaint(SkColors::kCyan); auto layer_filter1 = SkPerlinNoiseShader::MakeFractalNoise(1.0f, 1.0f, 1, 1.0f); + auto dl_filter1 = DlColorSource::From(layer_filter1); auto layer_filter2 = SkPerlinNoiseShader::MakeFractalNoise(2.0f, 2.0f, 2, 2.0f); + auto dl_filter2 = DlColorSource::From(layer_filter2); 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); + auto layer1 = std::make_shared(dl_filter1, layer_bounds, + DlBlendMode::kSrc); + auto layer2 = std::make_shared(dl_filter2, layer_bounds, + DlBlendMode::kSrc); layer2->Add(mock_layer2); layer1->Add(mock_layer1); layer1->Add(layer2); @@ -269,8 +273,9 @@ TEST_F(ShaderMaskLayerTest, Readback) { const SkRect layer_bounds = SkRect::MakeLTRB(2.0f, 4.0f, 20.5f, 20.5f); auto layer_filter = SkPerlinNoiseShader::MakeFractalNoise(1.0f, 1.0f, 1, 1.0f); - auto layer = std::make_shared(layer_filter, layer_bounds, - SkBlendMode::kSrc); + auto dl_filter = DlColorSource::From(layer_filter); + auto layer = std::make_shared(dl_filter, layer_bounds, + DlBlendMode::kSrc); // ShaderMaskLayer does not read from surface preroll_context()->surface_needs_readback = false; @@ -289,13 +294,14 @@ TEST_F(ShaderMaskLayerTest, Readback) { TEST_F(ShaderMaskLayerTest, LayerCached) { auto layer_filter = SkPerlinNoiseShader::MakeFractalNoise(1.0f, 1.0f, 1, 1.0f); + auto dl_filter = DlColorSource::From(layer_filter); SkPaint paint; const SkRect layer_bounds = SkRect::MakeLTRB(2.0f, 4.0f, 20.5f, 20.5f); auto initial_transform = SkMatrix::Translate(50.0, 25.5); const SkPath child_path = SkPath().addRect(SkRect::MakeWH(5.0f, 5.0f)); auto mock_layer = std::make_shared(child_path); - auto layer = std::make_shared(layer_filter, layer_bounds, - SkBlendMode::kSrc); + auto layer = std::make_shared(dl_filter, layer_bounds, + DlBlendMode::kSrc); layer->Add(mock_layer); SkMatrix cache_ctm = initial_transform; @@ -344,7 +350,7 @@ TEST_F(ShaderMaskLayerTest, OpacityInheritance) { auto mock_layer = MockLayer::Make(child_path); const SkRect mask_rect = SkRect::MakeLTRB(10, 10, 20, 20); auto shader_mask_layer = - std::make_shared(nullptr, mask_rect, SkBlendMode::kSrc); + std::make_shared(nullptr, mask_rect, DlBlendMode::kSrc); shader_mask_layer->Add(mock_layer); // ShaderMaskLayers can always support opacity despite incompatible children diff --git a/flow/layers/texture_layer.cc b/flow/layers/texture_layer.cc index fa760fe786e83..7ece96ebd4364 100644 --- a/flow/layers/texture_layer.cc +++ b/flow/layers/texture_layer.cc @@ -54,7 +54,9 @@ void TextureLayer::Paint(PaintContext& context) const { FML_DCHECK(needs_painting(context)); std::shared_ptr texture = - context.texture_registry.GetTexture(texture_id_); + context.texture_registry + ? context.texture_registry->GetTexture(texture_id_) + : nullptr; if (!texture) { TRACE_EVENT_INSTANT0("flutter", "null texture"); return; diff --git a/flow/layers/texture_layer_unittests.cc b/flow/layers/texture_layer_unittests.cc index 94e5469db702b..7d45d1d54e825 100644 --- a/flow/layers/texture_layer_unittests.cc +++ b/flow/layers/texture_layer_unittests.cc @@ -43,7 +43,7 @@ TEST_F(TextureLayerTest, PaintingEmptyLayerDies) { false, DlImageSampling::kNearestNeighbor); // Ensure the texture is located by the Layer. - preroll_context()->texture_registry.RegisterTexture(mock_texture); + preroll_context()->texture_registry->RegisterTexture(mock_texture); layer->Preroll(preroll_context(), SkMatrix()); EXPECT_EQ(layer->paint_bounds(), kEmptyRect); @@ -62,7 +62,7 @@ TEST_F(TextureLayerTest, PaintBeforePrerollDies) { layer_offset, layer_size, texture_id, false, DlImageSampling::kLinear); // Ensure the texture is located by the Layer. - preroll_context()->texture_registry.RegisterTexture(mock_texture); + preroll_context()->texture_registry->RegisterTexture(mock_texture); EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), "needs_painting\\(context\\)"); @@ -78,7 +78,7 @@ TEST_F(TextureLayerTest, PaintingWithLinearSampling) { layer_offset, layer_size, texture_id, false, DlImageSampling::kLinear); // Ensure the texture is located by the Layer. - preroll_context()->texture_registry.RegisterTexture(mock_texture); + preroll_context()->texture_registry->RegisterTexture(mock_texture); layer->Preroll(preroll_context(), SkMatrix()); EXPECT_EQ(layer->paint_bounds(), @@ -124,12 +124,12 @@ TEST_F(TextureLayerTest, OpacityInheritance) { layer_offset, layer_size, texture_id, false, DlImageSampling::kLinear); // Ensure the texture is located by the Layer. - preroll_context()->texture_registry.RegisterTexture(mock_texture); + preroll_context()->texture_registry->RegisterTexture(mock_texture); // The texture layer always reports opacity compatibility. PrerollContext* context = preroll_context(); context->subtree_can_inherit_opacity = false; - context->texture_registry.RegisterTexture(mock_texture); + context->texture_registry->RegisterTexture(mock_texture); layer->Preroll(context, SkMatrix::I()); EXPECT_TRUE(context->subtree_can_inherit_opacity); diff --git a/flow/raster_cache.cc b/flow/raster_cache.cc index 8a4e46d58d695..9b6e72d43c2c8 100644 --- a/flow/raster_cache.cc +++ b/flow/raster_cache.cc @@ -47,8 +47,23 @@ void RasterCacheResult::draw(SkCanvas& canvas, const SkPaint* paint) const { #endif canvas.resetMatrix(); flow_.Step(); + + bool exceeds_bounds = bounds.fLeft + image_->dimensions().width() > + SkScalarCeilToScalar(bounds.fRight) || + bounds.fTop + image_->dimensions().height() > + SkScalarCeilToScalar(bounds.fBottom); + + // Make sure raster cache doesn't bleed to physical pixels outside of + // original bounds. https://github.com/flutter/flutter/issues/110002 + if (exceeds_bounds) { + canvas.save(); + canvas.clipRect(SkRect::Make(bounds.roundOut())); + } canvas.drawImage(image_, bounds.fLeft, bounds.fTop, SkSamplingOptions(), paint); + if (exceeds_bounds) { + canvas.restore(); + } } RasterCache::RasterCache(size_t access_threshold, diff --git a/flow/raster_cache_unittests.cc b/flow/raster_cache_unittests.cc index a27b9e45cdfde..9a72da8e698ff 100644 --- a/flow/raster_cache_unittests.cc +++ b/flow/raster_cache_unittests.cc @@ -38,9 +38,13 @@ TEST(RasterCache, MetricsOmitUnpopulatedEntries) { SkCanvas dummy_canvas; SkPaint paint; - PrerollContextHolder preroll_context_holder = - GetSamplePrerollContextHolder(&cache); - PaintContextHolder paint_context_holder = GetSamplePaintContextHolder(&cache); + FixedRefreshRateStopwatch raster_time; + FixedRefreshRateStopwatch ui_time; + MutatorsStack mutators_stack; + PrerollContextHolder preroll_context_holder = GetSamplePrerollContextHolder( + &cache, &raster_time, &ui_time, &mutators_stack); + PaintContextHolder paint_context_holder = + GetSamplePaintContextHolder(&cache, &raster_time, &ui_time); auto& preroll_context = preroll_context_holder.preroll_context; auto& paint_context = paint_context_holder.paint_context; @@ -90,9 +94,13 @@ TEST(RasterCache, ThresholdIsRespectedForDisplayList) { SkCanvas dummy_canvas; SkPaint paint; - PrerollContextHolder preroll_context_holder = - GetSamplePrerollContextHolder(&cache); - PaintContextHolder paint_context_holder = GetSamplePaintContextHolder(&cache); + FixedRefreshRateStopwatch raster_time; + FixedRefreshRateStopwatch ui_time; + MutatorsStack mutators_stack; + PrerollContextHolder preroll_context_holder = GetSamplePrerollContextHolder( + &cache, &raster_time, &ui_time, &mutators_stack); + PaintContextHolder paint_context_holder = + GetSamplePaintContextHolder(&cache, &raster_time, &ui_time); auto& preroll_context = preroll_context_holder.preroll_context; auto& paint_context = paint_context_holder.paint_context; @@ -130,7 +138,10 @@ TEST(RasterCache, SetCheckboardCacheImages) { SkMatrix matrix = SkMatrix::I(); auto display_list = GetSampleDisplayList(); - PaintContextHolder paint_context_holder = GetSamplePaintContextHolder(&cache); + FixedRefreshRateStopwatch raster_time; + FixedRefreshRateStopwatch ui_time; + PaintContextHolder paint_context_holder = + GetSamplePaintContextHolder(&cache, &raster_time, &ui_time); auto& paint_context = paint_context_holder.paint_context; auto dummy_draw_function = [](SkCanvas* canvas) {}; bool did_draw_checkerboard = false; @@ -163,14 +174,17 @@ TEST(RasterCache, AccessThresholdOfZeroDisablesCachingForSkPicture) { SkMatrix matrix = SkMatrix::I(); auto display_list = GetSampleDisplayList(); - ; SkCanvas dummy_canvas; SkPaint paint; - PrerollContextHolder preroll_context_holder = - GetSamplePrerollContextHolder(&cache); - PaintContextHolder paint_context_holder = GetSamplePaintContextHolder(&cache); + FixedRefreshRateStopwatch raster_time; + FixedRefreshRateStopwatch ui_time; + MutatorsStack mutators_stack; + PrerollContextHolder preroll_context_holder = GetSamplePrerollContextHolder( + &cache, &raster_time, &ui_time, &mutators_stack); + PaintContextHolder paint_context_holder = + GetSamplePaintContextHolder(&cache, &raster_time, &ui_time); auto& preroll_context = preroll_context_holder.preroll_context; auto& paint_context = paint_context_holder.paint_context; @@ -193,9 +207,13 @@ TEST(RasterCache, AccessThresholdOfZeroDisablesCachingForDisplayList) { SkCanvas dummy_canvas; SkPaint paint; - PrerollContextHolder preroll_context_holder = - GetSamplePrerollContextHolder(&cache); - PaintContextHolder paint_context_holder = GetSamplePaintContextHolder(&cache); + FixedRefreshRateStopwatch raster_time; + FixedRefreshRateStopwatch ui_time; + MutatorsStack mutators_stack; + PrerollContextHolder preroll_context_holder = GetSamplePrerollContextHolder( + &cache, &raster_time, &ui_time, &mutators_stack); + PaintContextHolder paint_context_holder = + GetSamplePaintContextHolder(&cache, &raster_time, &ui_time); auto& preroll_context = preroll_context_holder.preroll_context; auto& paint_context = paint_context_holder.paint_context; @@ -220,9 +238,13 @@ TEST(RasterCache, PictureCacheLimitPerFrameIsRespectedWhenZeroForSkPicture) { SkCanvas dummy_canvas; SkPaint paint; - PrerollContextHolder preroll_context_holder = - GetSamplePrerollContextHolder(&cache); - PaintContextHolder paint_context_holder = GetSamplePaintContextHolder(&cache); + FixedRefreshRateStopwatch raster_time; + FixedRefreshRateStopwatch ui_time; + MutatorsStack mutators_stack; + PrerollContextHolder preroll_context_holder = GetSamplePrerollContextHolder( + &cache, &raster_time, &ui_time, &mutators_stack); + PaintContextHolder paint_context_holder = + GetSamplePaintContextHolder(&cache, &raster_time, &ui_time); auto& preroll_context = preroll_context_holder.preroll_context; auto& paint_context = paint_context_holder.paint_context; @@ -252,9 +274,13 @@ TEST(RasterCache, PictureCacheLimitPerFrameIsRespectedWhenZeroForDisplayList) { SkCanvas dummy_canvas; SkPaint paint; - PrerollContextHolder preroll_context_holder = - GetSamplePrerollContextHolder(&cache); - PaintContextHolder paint_context_holder = GetSamplePaintContextHolder(&cache); + FixedRefreshRateStopwatch raster_time; + FixedRefreshRateStopwatch ui_time; + MutatorsStack mutators_stack; + PrerollContextHolder preroll_context_holder = GetSamplePrerollContextHolder( + &cache, &raster_time, &ui_time, &mutators_stack); + PaintContextHolder paint_context_holder = + GetSamplePaintContextHolder(&cache, &raster_time, &ui_time); auto& preroll_context = preroll_context_holder.preroll_context; auto& paint_context = paint_context_holder.paint_context; @@ -288,9 +314,13 @@ TEST(RasterCache, EvitUnusedCacheEntries) { SkCanvas dummy_canvas; SkPaint paint; - PrerollContextHolder preroll_context_holder = - GetSamplePrerollContextHolder(&cache); - PaintContextHolder paint_context_holder = GetSamplePaintContextHolder(&cache); + FixedRefreshRateStopwatch raster_time; + FixedRefreshRateStopwatch ui_time; + MutatorsStack mutators_stack; + PrerollContextHolder preroll_context_holder = GetSamplePrerollContextHolder( + &cache, &raster_time, &ui_time, &mutators_stack); + PaintContextHolder paint_context_holder = + GetSamplePaintContextHolder(&cache, &raster_time, &ui_time); auto& preroll_context = preroll_context_holder.preroll_context; auto& paint_context = paint_context_holder.paint_context; @@ -394,9 +424,13 @@ TEST(RasterCache, DeviceRectRoundOutForDisplayList) { SkCanvas canvas(100, 100, nullptr); canvas.setMatrix(ctm); - PrerollContextHolder preroll_context_holder = - GetSamplePrerollContextHolder(&cache); - PaintContextHolder paint_context_holder = GetSamplePaintContextHolder(&cache); + FixedRefreshRateStopwatch raster_time; + FixedRefreshRateStopwatch ui_time; + MutatorsStack mutators_stack; + PrerollContextHolder preroll_context_holder = GetSamplePrerollContextHolder( + &cache, &raster_time, &ui_time, &mutators_stack); + PaintContextHolder paint_context_holder = + GetSamplePaintContextHolder(&cache, &raster_time, &ui_time); auto& preroll_context = preroll_context_holder.preroll_context; auto& paint_context = paint_context_holder.paint_context; @@ -433,9 +467,13 @@ TEST(RasterCache, NestedOpCountMetricUsedForDisplayList) { SkCanvas dummy_canvas; SkPaint paint; - PrerollContextHolder preroll_context_holder = - GetSamplePrerollContextHolder(&cache); - PaintContextHolder paint_context_holder = GetSamplePaintContextHolder(&cache); + FixedRefreshRateStopwatch raster_time; + FixedRefreshRateStopwatch ui_time; + MutatorsStack mutators_stack; + PrerollContextHolder preroll_context_holder = GetSamplePrerollContextHolder( + &cache, &raster_time, &ui_time, &mutators_stack); + PaintContextHolder paint_context_holder = + GetSamplePaintContextHolder(&cache, &raster_time, &ui_time); auto& preroll_context = preroll_context_holder.preroll_context; auto& paint_context = paint_context_holder.paint_context; @@ -476,9 +514,13 @@ TEST(RasterCache, NaiveComplexityScoringDisplayList) { SkCanvas dummy_canvas; SkPaint paint; - PrerollContextHolder preroll_context_holder = - GetSamplePrerollContextHolder(&cache); - PaintContextHolder paint_context_holder = GetSamplePaintContextHolder(&cache); + FixedRefreshRateStopwatch raster_time; + FixedRefreshRateStopwatch ui_time; + MutatorsStack mutators_stack; + PrerollContextHolder preroll_context_holder = GetSamplePrerollContextHolder( + &cache, &raster_time, &ui_time, &mutators_stack); + PaintContextHolder paint_context_holder = + GetSamplePaintContextHolder(&cache, &raster_time, &ui_time); auto& preroll_context = preroll_context_holder.preroll_context; auto& paint_context = paint_context_holder.paint_context; @@ -538,9 +580,13 @@ TEST(RasterCache, DisplayListWithSingularMatrixIsNotCached) { SkCanvas dummy_canvas; SkPaint paint; - PrerollContextHolder preroll_context_holder = - GetSamplePrerollContextHolder(&cache); - PaintContextHolder paint_context_holder = GetSamplePaintContextHolder(&cache); + FixedRefreshRateStopwatch raster_time; + FixedRefreshRateStopwatch ui_time; + MutatorsStack mutators_stack; + PrerollContextHolder preroll_context_holder = GetSamplePrerollContextHolder( + &cache, &raster_time, &ui_time, &mutators_stack); + PaintContextHolder paint_context_holder = + GetSamplePaintContextHolder(&cache, &raster_time, &ui_time); auto& preroll_context = preroll_context_holder.preroll_context; auto& paint_context = paint_context_holder.paint_context; @@ -741,5 +787,67 @@ TEST_F(RasterCacheTest, RasterCacheKeyID_LayerChildrenIds) { ASSERT_EQ(ids, expected_ids); } +TEST_F(RasterCacheTest, RasterCacheBleedingNoClipNeeded) { + SkImageInfo info = + SkImageInfo::MakeN32(40, 40, SkAlphaType::kOpaque_SkAlphaType); + + auto image = SkImage::MakeRasterData( + info, SkData::MakeUninitialized(40 * 40 * 4), 40 * 4); + auto canvas = MockCanvas(); + canvas.setMatrix(SkMatrix::Scale(2, 2)); + // Drawing cached image does not exceeds physical pixels of the original + // bounds and does not need to be clipped. + auto cache_result = + RasterCacheResult(image, SkRect::MakeXYWH(100.3, 100.3, 20, 20), ""); + auto paint = SkPaint(); + cache_result.draw(canvas, &paint); + + EXPECT_EQ(canvas.draw_calls(), + std::vector({ + MockCanvas::DrawCall{ + 0, MockCanvas::SetMatrixData{SkM44::Scale(2, 2)}}, + MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, + MockCanvas::DrawCall{1, MockCanvas::SetMatrixData{SkM44()}}, + MockCanvas::DrawCall{ + 1, MockCanvas::DrawImageData{image, 200.6, 200.6, + SkSamplingOptions(), paint}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}, + })); +} + +TEST_F(RasterCacheTest, RasterCacheBleedingClipNeeded) { + SkImageInfo info = + SkImageInfo::MakeN32(40, 40, SkAlphaType::kOpaque_SkAlphaType); + + auto image = SkImage::MakeRasterData( + info, SkData::MakeUninitialized(40 * 40 * 4), 40 * 4); + auto canvas = MockCanvas(); + canvas.setMatrix(SkMatrix::Scale(2, 2)); + + auto cache_result = + RasterCacheResult(image, SkRect::MakeXYWH(100.3, 100.3, 19.6, 19.6), ""); + auto paint = SkPaint(); + cache_result.draw(canvas, &paint); + + EXPECT_EQ( + canvas.draw_calls(), + std::vector({ + MockCanvas::DrawCall{0, + MockCanvas::SetMatrixData{SkM44::Scale(2, 2)}}, + MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, + MockCanvas::DrawCall{1, MockCanvas::SetMatrixData{SkM44()}}, + MockCanvas::DrawCall{1, MockCanvas::SaveData{2}}, + MockCanvas::DrawCall{ + 2, MockCanvas::ClipRectData{SkRect::MakeLTRB(200, 200, 240, 240), + SkClipOp::kIntersect, + MockCanvas::kHard_ClipEdgeStyle}}, + MockCanvas::DrawCall{ + 2, MockCanvas::DrawImageData{image, 200.6, 200.6, + SkSamplingOptions(), paint}}, + MockCanvas::DrawCall{2, MockCanvas::RestoreData{1}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}, + })); +} + } // namespace testing } // namespace flutter diff --git a/flow/skia_gpu_object.h b/flow/skia_gpu_object.h index e694ef7ffcc2a..16251ec56f823 100644 --- a/flow/skia_gpu_object.h +++ b/flow/skia_gpu_object.h @@ -34,6 +34,16 @@ class UnrefQueue : public fml::RefCountedThreadSafe> { } } + void DeleteTexture(GrBackendTexture texture) { + std::scoped_lock lock(mutex_); + textures_.push_back(texture); + if (!drain_pending_) { + drain_pending_ = true; + task_runner_->PostDelayedTask( + [strong = fml::Ref(this)]() { strong->Drain(); }, drain_delay_); + } + } + // Usually, the drain is called automatically. However, during IO manager // shutdown (when the platform side reference to the OpenGL context is about // to go away), we may need to pre-emptively drain the unref queue. It is the @@ -42,12 +52,14 @@ class UnrefQueue : public fml::RefCountedThreadSafe> { void Drain() { TRACE_EVENT0("flutter", "SkiaUnrefQueue::Drain"); std::deque skia_objects; + std::deque textures; { std::scoped_lock lock(mutex_); objects_.swap(skia_objects); + textures_.swap(textures); drain_pending_ = false; } - DoDrain(skia_objects, context_); + DoDrain(skia_objects, textures, context_); } void UpdateResourceContext(sk_sp context) { @@ -59,6 +71,7 @@ class UnrefQueue : public fml::RefCountedThreadSafe> { const fml::TimeDelta drain_delay_; std::mutex mutex_; std::deque objects_; + std::deque textures_; bool drain_pending_; sk_sp context_; @@ -79,22 +92,30 @@ class UnrefQueue : public fml::RefCountedThreadSafe> { // into a task queued to that thread. ResourceContext* raw_context = context_.release(); fml::TaskRunner::RunNowOrPostTask( - task_runner_, [objects = std::move(objects_), raw_context]() mutable { + task_runner_, [objects = std::move(objects_), + textures = std::move(textures_), raw_context]() mutable { sk_sp context(raw_context); - DoDrain(objects, context); + DoDrain(objects, textures, context); context.reset(); }); } // static static void DoDrain(const std::deque& skia_objects, + const std::deque& textures, sk_sp context) { for (SkRefCnt* skia_object : skia_objects) { skia_object->unref(); } - if (context && !skia_objects.empty()) { - context->performDeferredCleanup(std::chrono::milliseconds(0)); + if (context) { + for (GrBackendTexture texture : textures) { + context->deleteBackendTexture(texture); + } + + if (!skia_objects.empty()) { + context->performDeferredCleanup(std::chrono::milliseconds(0)); + } } } diff --git a/flow/skia_gpu_object_unittests.cc b/flow/skia_gpu_object_unittests.cc index 61127256393a3..4b631fa6fb433 100644 --- a/flow/skia_gpu_object_unittests.cc +++ b/flow/skia_gpu_object_unittests.cc @@ -41,6 +41,7 @@ class TestResourceContext : public TestSkObject { : TestSkObject(latch, dtor_task_queue_id) {} ~TestResourceContext() = default; void performDeferredCleanup(std::chrono::milliseconds msNotUsed) {} + void deleteBackendTexture(GrBackendTexture texture) {} }; class SkiaGpuObjectTest : public ThreadTest { diff --git a/flow/surface_frame.cc b/flow/surface_frame.cc index c8a6bbf6bc3c1..d3133daa07385 100644 --- a/flow/surface_frame.cc +++ b/flow/surface_frame.cc @@ -6,6 +6,7 @@ #include +#include "flutter/flow/layers/layer.h" #include "flutter/fml/logging.h" #include "flutter/fml/trace_event.h" #include "third_party/skia/include/utils/SkNWayCanvas.h" @@ -25,9 +26,7 @@ SurfaceFrame::SurfaceFrame(sk_sp surface, if (surface_) { canvas_ = surface_->getCanvas(); } else if (display_list_fallback) { - dl_recorder_ = sk_make_sp( - SkRect::MakeWH(std::numeric_limits::max(), - std::numeric_limits::max())); + dl_recorder_ = sk_make_sp(kGiantRect); canvas_ = dl_recorder_.get(); } } diff --git a/flow/surface_frame.h b/flow/surface_frame.h index 961bfe5965241..d630ab331be40 100644 --- a/flow/surface_frame.h +++ b/flow/surface_frame.h @@ -51,7 +51,7 @@ class SurfaceFrame { // If existing damage is unspecified (nullopt), entire frame will be // rasterized (no partial redraw). To signal that there is no existing // damage use an empty SkIRect. - std::optional existing_damage; + std::optional existing_damage = std::nullopt; }; SurfaceFrame(sk_sp surface, diff --git a/flow/surface_frame_unittests.cc b/flow/surface_frame_unittests.cc index c7a0bc5132af9..7bc4d07ef564f 100644 --- a/flow/surface_frame_unittests.cc +++ b/flow/surface_frame_unittests.cc @@ -20,4 +20,16 @@ TEST(FlowTest, SurfaceFrameDoesNotSubmitInDtor) { surface_frame.reset(); } +TEST(FlowTest, SurfaceFrameDoesNotHaveEmptyCanvas) { + SurfaceFrame::FramebufferInfo framebuffer_info; + SurfaceFrame frame( + /*surface=*/nullptr, framebuffer_info, + /*submit_callback=*/[](const SurfaceFrame&, SkCanvas*) { return true; }, + /*context_result=*/nullptr, /*display_list_fallback=*/true); + + EXPECT_FALSE(frame.SkiaCanvas()->getLocalClipBounds().isEmpty()); + EXPECT_FALSE( + frame.SkiaCanvas()->quickReject(SkRect::MakeLTRB(10, 10, 50, 50))); +} + } // namespace flutter diff --git a/flow/testing/layer_test.h b/flow/testing/layer_test.h index e5672e1eacdd1..60b4536056ab6 100644 --- a/flow/testing/layer_test.h +++ b/flow/testing/layer_test.h @@ -12,6 +12,7 @@ #include #include +#include "flutter/display_list/display_list_builder_multiplexer.h" #include "flutter/flow/testing/mock_raster_cache.h" #include "flutter/fml/macros.h" #include "flutter/testing/canvas_test.h" @@ -44,7 +45,8 @@ class LayerTestBase : public CanvasTestBase { public: LayerTestBase() - : preroll_context_{ + : texture_registry_(std::make_shared()), + preroll_context_{ // clang-format off .raster_cache = nullptr, .gr_context = nullptr, @@ -91,6 +93,7 @@ class LayerTestBase : public CanvasTestBase { .checkerboard_offscreen_layers = false, .frame_device_pixel_ratio = 1.0f, .leaf_nodes_builder = display_list_recorder_.builder().get(), + .builder_multiplexer = &display_list_multiplexer_, // clang-format on }, check_board_context_{ @@ -108,6 +111,8 @@ class LayerTestBase : public CanvasTestBase { // clang-format on } { internal_display_list_canvas_.addCanvas(&display_list_recorder_); + display_list_multiplexer_.addBuilder( + display_list_recorder_.builder().get()); use_null_raster_cache(); } @@ -162,7 +167,9 @@ class LayerTestBase : public CanvasTestBase { std::vector& cacheable_items() { return cacheable_items_; } - TextureRegistry& texture_regitry() { return texture_registry_; } + std::shared_ptr texture_registry() { + return texture_registry_; + } RasterCache* raster_cache() { return raster_cache_.get(); } PrerollContext* preroll_context() { return &preroll_context_; } PaintContext& paint_context() { return paint_context_; } @@ -180,6 +187,7 @@ class LayerTestBase : public CanvasTestBase { display_list_paint_context_.leaf_nodes_canvas = nullptr; display_list_paint_context_.internal_nodes_canvas = nullptr; display_list_paint_context_.leaf_nodes_builder = nullptr; + display_list_paint_context_.builder_multiplexer = nullptr; } return display_list_; } @@ -205,12 +213,13 @@ class LayerTestBase : public CanvasTestBase { FixedRefreshRateStopwatch raster_time_; FixedRefreshRateStopwatch ui_time_; MutatorsStack mutators_stack_; - TextureRegistry texture_registry_; + std::shared_ptr texture_registry_; std::unique_ptr raster_cache_; PrerollContext preroll_context_; PaintContext paint_context_; DisplayListCanvasRecorder display_list_recorder_; + DisplayListBuilderMultiplexer display_list_multiplexer_; sk_sp display_list_; SkNWayCanvas internal_display_list_canvas_; PaintContext display_list_paint_context_; diff --git a/flow/testing/mock_embedder.cc b/flow/testing/mock_embedder.cc index 11d34ee80beda..8004308d796c2 100644 --- a/flow/testing/mock_embedder.cc +++ b/flow/testing/mock_embedder.cc @@ -37,8 +37,13 @@ std::vector MockViewEmbedder::GetCurrentCanvases() { } // |ExternalViewEmbedder| -SkCanvas* MockViewEmbedder::CompositeEmbeddedView(int view_id) { - return nullptr; +std::vector MockViewEmbedder::GetCurrentBuilders() { + return std::vector({}); +} + +// |ExternalViewEmbedder| +EmbedderPaintContext MockViewEmbedder::CompositeEmbeddedView(int view_id) { + return {nullptr, nullptr}; } } // namespace testing diff --git a/flow/testing/mock_embedder.h b/flow/testing/mock_embedder.h index 2de6ddb51c7b7..abc57d3d28530 100644 --- a/flow/testing/mock_embedder.h +++ b/flow/testing/mock_embedder.h @@ -38,7 +38,10 @@ class MockViewEmbedder : public ExternalViewEmbedder { std::vector GetCurrentCanvases() override; // |ExternalViewEmbedder| - SkCanvas* CompositeEmbeddedView(int view_id) override; + std::vector GetCurrentBuilders() override; + + // |ExternalViewEmbedder| + EmbedderPaintContext CompositeEmbeddedView(int view_id) override; }; } // namespace testing diff --git a/flow/testing/mock_layer.cc b/flow/testing/mock_layer.cc index 6ff049469c2b1..618a1d566798f 100644 --- a/flow/testing/mock_layer.cc +++ b/flow/testing/mock_layer.cc @@ -14,12 +14,14 @@ MockLayer::MockLayer(SkPath path, SkPaint paint, bool fake_has_platform_view, bool fake_reads_surface, - bool fake_opacity_compatible) + bool fake_opacity_compatible, + bool fake_has_texture_layer) : fake_paint_path_(path), fake_paint_(paint), fake_has_platform_view_(fake_has_platform_view), fake_reads_surface_(fake_reads_surface), - fake_opacity_compatible_(fake_opacity_compatible) {} + fake_opacity_compatible_(fake_opacity_compatible), + fake_has_texture_layer_(fake_has_texture_layer) {} bool MockLayer::IsReplacing(DiffContext* context, const Layer* layer) const { // Similar to PictureLayer, only return true for identical mock layers; @@ -41,8 +43,10 @@ void MockLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { parent_matrix_ = matrix; parent_cull_rect_ = context->cull_rect; parent_has_platform_view_ = context->has_platform_view; + parent_has_texture_layer_ = context->has_texture_layer; context->has_platform_view = fake_has_platform_view_; + context->has_texture_layer = fake_has_texture_layer_; set_paint_bounds(fake_paint_path_.getBounds()); if (fake_reads_surface_) { context->surface_needs_readback = true; diff --git a/flow/testing/mock_layer.h b/flow/testing/mock_layer.h index 4e34fa1639d77..ad6bc2cc12b1e 100644 --- a/flow/testing/mock_layer.h +++ b/flow/testing/mock_layer.h @@ -28,7 +28,8 @@ class MockLayer : public Layer { SkPaint paint = SkPaint(), bool fake_has_platform_view = false, bool fake_reads_surface = false, - bool fake_opacity_compatible_ = false); + bool fake_opacity_compatible_ = false, + bool fake_has_texture_layer = false); static std::shared_ptr Make(SkPath path, SkPaint paint = SkPaint()) { @@ -46,6 +47,7 @@ class MockLayer : public Layer { const SkMatrix& parent_matrix() { return parent_matrix_; } const SkRect& parent_cull_rect() { return parent_cull_rect_; } bool parent_has_platform_view() { return parent_has_platform_view_; } + bool parent_has_texture_layer() { return parent_has_texture_layer_; } bool IsReplacing(DiffContext* context, const Layer* layer) const override; void Diff(DiffContext* context, const Layer* old_layer) override; @@ -58,9 +60,11 @@ class MockLayer : public Layer { SkPath fake_paint_path_; SkPaint fake_paint_; bool parent_has_platform_view_ = false; + bool parent_has_texture_layer_ = false; bool fake_has_platform_view_ = false; bool fake_reads_surface_ = false; bool fake_opacity_compatible_ = false; + bool fake_has_texture_layer_ = false; FML_DISALLOW_COPY_AND_ASSIGN(MockLayer); }; diff --git a/flow/testing/mock_raster_cache.cc b/flow/testing/mock_raster_cache.cc index 3ce1be25cfcd8..33b81c15030b3 100644 --- a/flow/testing/mock_raster_cache.cc +++ b/flow/testing/mock_raster_cache.cc @@ -56,7 +56,10 @@ void MockRasterCache::AddMockPicture(int width, int height) { recorder.drawPath(path, SkPaint()); sk_sp display_list = recorder.Build(); - PaintContextHolder holder = GetSamplePaintContextHolder(this); + FixedRefreshRateStopwatch raster_time; + FixedRefreshRateStopwatch ui_time; + PaintContextHolder holder = + GetSamplePaintContextHolder(this, &raster_time, &ui_time); holder.paint_context.dst_color_space = color_space_; DisplayListRasterCacheItem display_list_item(display_list.get(), SkPoint(), @@ -81,31 +84,31 @@ void MockRasterCache::AddMockPicture(int width, int height) { }); } -PrerollContextHolder GetSamplePrerollContextHolder(RasterCache* raster_cache) { - FixedRefreshRateStopwatch raster_time; - FixedRefreshRateStopwatch ui_time; - MutatorsStack mutators_stack; - TextureRegistry texture_registry; +PrerollContextHolder GetSamplePrerollContextHolder( + RasterCache* raster_cache, + FixedRefreshRateStopwatch* raster_time, + FixedRefreshRateStopwatch* ui_time, + MutatorsStack* mutators_stack) { sk_sp srgb = SkColorSpace::MakeSRGB(); PrerollContextHolder holder = { { // clang-format off .raster_cache = raster_cache, - .gr_context = nullptr, - .view_embedder = nullptr, - .mutators_stack = mutators_stack, - .dst_color_space = srgb.get(), - .cull_rect = kGiantRect, + .gr_context = nullptr, + .view_embedder = nullptr, + .mutators_stack = *mutators_stack, + .dst_color_space = srgb.get(), + .cull_rect = kGiantRect, .surface_needs_readback = false, - .raster_time = raster_time, - .ui_time = ui_time, - .texture_registry = texture_registry, - .checkerboard_offscreen_layers = false, - .frame_device_pixel_ratio = 1.0f, - .has_platform_view = false, + .raster_time = *raster_time, + .ui_time = *ui_time, + .texture_registry = nullptr, + .checkerboard_offscreen_layers = false, + .frame_device_pixel_ratio = 1.0f, + .has_platform_view = false, .has_texture_layer = false, - .raster_cached_entries = &raster_cache_items_, + .raster_cached_entries = &raster_cache_items_, // clang-format on }, srgb}; @@ -113,11 +116,10 @@ PrerollContextHolder GetSamplePrerollContextHolder(RasterCache* raster_cache) { return holder; } -PaintContextHolder GetSamplePaintContextHolder(RasterCache* raster_cache) { - FixedRefreshRateStopwatch raster_time; - FixedRefreshRateStopwatch ui_time; - MutatorsStack mutators_stack; - TextureRegistry texture_registry; +PaintContextHolder GetSamplePaintContextHolder( + RasterCache* raster_cache, + FixedRefreshRateStopwatch* raster_time, + FixedRefreshRateStopwatch* ui_time) { sk_sp srgb = SkColorSpace::MakeSRGB(); PaintContextHolder holder = {// clang-format off { @@ -126,9 +128,9 @@ PaintContextHolder GetSamplePaintContextHolder(RasterCache* raster_cache) { .gr_context = nullptr, .dst_color_space = srgb.get(), .view_embedder = nullptr, - .raster_time = raster_time, - .ui_time = ui_time, - .texture_registry = texture_registry, + .raster_time = *raster_time, + .ui_time = *ui_time, + .texture_registry = nullptr, .raster_cache = raster_cache, .checkerboard_offscreen_layers = false, .frame_device_pixel_ratio = 1.0f, diff --git a/flow/testing/mock_raster_cache.h b/flow/testing/mock_raster_cache.h index d6b385b57d389..37742486409bb 100644 --- a/flow/testing/mock_raster_cache.h +++ b/flow/testing/mock_raster_cache.h @@ -72,7 +72,7 @@ class MockRasterCache : public RasterCache { MutatorsStack mutators_stack_; FixedRefreshRateStopwatch raster_time_; FixedRefreshRateStopwatch ui_time_; - TextureRegistry texture_registry_; + std::shared_ptr texture_registry_; PrerollContext preroll_context_ = { // clang-format off .raster_cache = this, @@ -122,10 +122,15 @@ struct PaintContextHolder { }; PrerollContextHolder GetSamplePrerollContextHolder( - RasterCache* raster_cache = nullptr); + RasterCache* raster_cache, + FixedRefreshRateStopwatch* raster_time, + FixedRefreshRateStopwatch* ui_time, + MutatorsStack* mutators_stack); PaintContextHolder GetSamplePaintContextHolder( - RasterCache* raster_cache = nullptr); + RasterCache* raster_cache, + FixedRefreshRateStopwatch* raster_time, + FixedRefreshRateStopwatch* ui_time); bool RasterCacheItemPrerollAndTryToRasterCache( DisplayListRasterCacheItem& display_list_item, diff --git a/fml/logging_unittests.cc b/fml/logging_unittests.cc index 1f6b0f9b83c4e..a64d33e52f3fb 100644 --- a/fml/logging_unittests.cc +++ b/fml/logging_unittests.cc @@ -55,7 +55,6 @@ class LoggingSocketTest : public ::testing::Test { fx_logger_config_t config = { .min_severity = FX_LOG_INFO, - .console_fd = -1, .log_sink_socket = local.release(), .tags = nullptr, .num_tags = 0, diff --git a/impeller/BUILD.gn b/impeller/BUILD.gn index 49c9ace17c74a..2fbc00d125343 100644 --- a/impeller/BUILD.gn +++ b/impeller/BUILD.gn @@ -9,10 +9,6 @@ config("impeller_public_config") { defines = [] - if (impeller_supports_platform) { - defines += [ "IMPELLER_SUPPORTS_PLATFORM=1" ] - } - if (impeller_supports_rendering) { defines += [ "IMPELLER_SUPPORTS_RENDERING=1" ] } @@ -39,8 +35,6 @@ config("impeller_public_config") { if (is_win) { defines += [ - "_USE_MATH_DEFINES", - # TODO(dnfield): https://github.com/flutter/flutter/issues/50053 "_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING", ] diff --git a/impeller/aiks/aiks_unittests.cc b/impeller/aiks/aiks_unittests.cc index 4d0e29b4782d8..8e8c52e2f81ca 100644 --- a/impeller/aiks/aiks_unittests.cc +++ b/impeller/aiks/aiks_unittests.cc @@ -10,10 +10,13 @@ #include "impeller/aiks/aiks_playground.h" #include "impeller/aiks/canvas.h" #include "impeller/aiks/image.h" +#include "impeller/entity/contents/tiled_texture_contents.h" #include "impeller/geometry/color.h" #include "impeller/geometry/geometry_unittests.h" +#include "impeller/geometry/matrix.h" #include "impeller/geometry/path_builder.h" #include "impeller/playground/widgets.h" +#include "impeller/renderer/command_buffer.h" #include "impeller/renderer/snapshot.h" #include "impeller/typographer/backends/skia/text_frame_skia.h" #include "impeller/typographer/backends/skia/text_render_context_skia.h" @@ -70,6 +73,98 @@ TEST_P(AiksTest, CanRenderImage) { ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); } +bool GenerateMipmap(std::shared_ptr context, + std::shared_ptr texture, + std::string label) { + auto buffer = context->CreateCommandBuffer(); + if (!buffer) { + return false; + } + auto pass = buffer->CreateBlitPass(); + if (!pass) { + return false; + } + pass->GenerateMipmap(texture, label); + pass->EncodeCommands(context->GetResourceAllocator()); + return true; +} + +TEST_P(AiksTest, CanRenderTiledTexture) { + auto context = GetContext(); + ASSERT_TRUE(context); + bool first_frame = true; + auto texture = CreateTextureForFixture("table_mountain_nx.png", + /*enable_mipmapping=*/true); + auto callback = [&](AiksContext& renderer, RenderTarget& render_target) { + if (first_frame) { + first_frame = false; + GenerateMipmap(context, texture, "table_mountain_nx"); + ImGui::SetNextWindowSize({480, 100}); + ImGui::SetNextWindowPos({100, 550}); + } + + const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"}; + const Entity::TileMode tile_modes[] = { + Entity::TileMode::kClamp, Entity::TileMode::kRepeat, + Entity::TileMode::kMirror, Entity::TileMode::kDecal}; + const char* mip_filter_names[] = {"None", "Nearest", "Linear"}; + const MipFilter mip_filters[] = {MipFilter::kNone, MipFilter::kNearest, + MipFilter::kLinear}; + const char* min_mag_filter_names[] = {"Nearest", "Linear"}; + const MinMagFilter min_mag_filters[] = {MinMagFilter::kNearest, + MinMagFilter::kLinear}; + static int selected_x_tile_mode = 0; + static int selected_y_tile_mode = 0; + static int selected_mip_filter = 0; + static int selected_min_mag_filter = 0; + ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); + ImGui::Combo("X tile mode", &selected_x_tile_mode, tile_mode_names, + sizeof(tile_mode_names) / sizeof(char*)); + ImGui::Combo("Y tile mode", &selected_y_tile_mode, tile_mode_names, + sizeof(tile_mode_names) / sizeof(char*)); + ImGui::Combo("Mip filter", &selected_mip_filter, mip_filter_names, + sizeof(mip_filter_names) / sizeof(char*)); + ImGui::Combo("Min Mag filter", &selected_min_mag_filter, + min_mag_filter_names, + sizeof(min_mag_filter_names) / sizeof(char*)); + static Matrix matrix = { + 1, 0, 0, 0, // + 0, 1, 0, 0, // + 0, 0, 1, 0, // + 0, 0, 0, 1 // + }; + std::string label = "##1"; + label.c_str(); + for (int i = 0; i < 4; i++) { + ImGui::InputScalarN(label.c_str(), ImGuiDataType_Float, &(matrix.vec[i]), + 4, NULL, NULL, "%.2f", 0); + label[2]++; + } + ImGui::End(); + + Canvas canvas; + Paint paint; + canvas.Translate({100.0, 100.0, 0}); + auto x_tile_mode = tile_modes[selected_x_tile_mode]; + auto y_tile_mode = tile_modes[selected_y_tile_mode]; + SamplerDescriptor descriptor; + descriptor.mip_filter = mip_filters[selected_mip_filter]; + descriptor.min_filter = min_mag_filters[selected_min_mag_filter]; + descriptor.mag_filter = min_mag_filters[selected_min_mag_filter]; + paint.color_source = [texture, x_tile_mode, y_tile_mode, descriptor]() { + auto contents = std::make_shared(); + contents->SetTexture(texture); + contents->SetTileModes(x_tile_mode, y_tile_mode); + contents->SetSamplerDescriptor(descriptor); + contents->SetMatrix(matrix); + return contents; + }; + canvas.DrawRect({0, 0, 600, 600}, paint); + return renderer.Render(canvas.EndRecordingAsPicture(), render_target); + }; + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + TEST_P(AiksTest, CanRenderImageRect) { Canvas canvas; Paint paint; @@ -203,18 +298,210 @@ TEST_P(AiksTest, CanSaveLayerStandalone) { ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); } +TEST_P(AiksTest, CanRenderLinearGradient) { + bool first_frame = true; + auto callback = [&](AiksContext& renderer, RenderTarget& render_target) { + if (first_frame) { + first_frame = false; + ImGui::SetNextWindowSize({480, 100}); + ImGui::SetNextWindowPos({100, 550}); + } + + const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"}; + const Entity::TileMode tile_modes[] = { + Entity::TileMode::kClamp, Entity::TileMode::kRepeat, + Entity::TileMode::kMirror, Entity::TileMode::kDecal}; + + static int selected_tile_mode = 0; + ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); + ImGui::Combo("Tile mode", &selected_tile_mode, tile_mode_names, + sizeof(tile_mode_names) / sizeof(char*)); + static Matrix matrix = { + 1, 0, 0, 0, // + 0, 1, 0, 0, // + 0, 0, 1, 0, // + 0, 0, 0, 1 // + }; + std::string label = "##1"; + label.c_str(); + for (int i = 0; i < 4; i++) { + ImGui::InputScalarN(label.c_str(), ImGuiDataType_Float, &(matrix.vec[i]), + 4, NULL, NULL, "%.2f", 0); + label[2]++; + } + ImGui::End(); + + Canvas canvas; + Paint paint; + canvas.Translate({100.0, 100.0, 0}); + auto tile_mode = tile_modes[selected_tile_mode]; + paint.color_source = [tile_mode]() { + auto contents = std::make_shared(); + contents->SetEndPoints({0, 0}, {200, 200}); + std::vector colors = {Color{0.9568, 0.2627, 0.2118, 1.0}, + Color{0.1294, 0.5882, 0.9529, 1.0}}; + contents->SetColors(std::move(colors)); + contents->SetTileMode(tile_mode); + contents->SetMatrix(matrix); + return contents; + }; + canvas.DrawRect({0, 0, 600, 600}, paint); + return renderer.Render(canvas.EndRecordingAsPicture(), render_target); + }; + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + TEST_P(AiksTest, CanRenderRadialGradient) { + bool first_frame = true; + auto callback = [&](AiksContext& renderer, RenderTarget& render_target) { + if (first_frame) { + first_frame = false; + ImGui::SetNextWindowSize({480, 100}); + ImGui::SetNextWindowPos({100, 550}); + } + + const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"}; + const Entity::TileMode tile_modes[] = { + Entity::TileMode::kClamp, Entity::TileMode::kRepeat, + Entity::TileMode::kMirror, Entity::TileMode::kDecal}; + + static int selected_tile_mode = 0; + ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); + ImGui::Combo("Tile mode", &selected_tile_mode, tile_mode_names, + sizeof(tile_mode_names) / sizeof(char*)); + static Matrix matrix = { + 1, 0, 0, 0, // + 0, 1, 0, 0, // + 0, 0, 1, 0, // + 0, 0, 0, 1 // + }; + std::string label = "##1"; + label.c_str(); + for (int i = 0; i < 4; i++) { + ImGui::InputScalarN(label.c_str(), ImGuiDataType_Float, &(matrix.vec[i]), + 4, NULL, NULL, "%.2f", 0); + label[2]++; + } + ImGui::End(); + + Canvas canvas; + Paint paint; + canvas.Translate({100.0, 100.0, 0}); + auto tile_mode = tile_modes[selected_tile_mode]; + paint.color_source = [tile_mode]() { + auto contents = std::make_shared(); + contents->SetCenterAndRadius({100, 100}, 100); + std::vector colors = {Color{0.9568, 0.2627, 0.2118, 1.0}, + Color{0.1294, 0.5882, 0.9529, 1.0}}; + contents->SetColors(std::move(colors)); + contents->SetTileMode(tile_mode); + contents->SetMatrix(matrix); + return contents; + }; + canvas.DrawRect({0, 0, 600, 600}, paint); + return renderer.Render(canvas.EndRecordingAsPicture(), render_target); + }; + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + +TEST_P(AiksTest, CanRenderSweepGradient) { + bool first_frame = true; + auto callback = [&](AiksContext& renderer, RenderTarget& render_target) { + if (first_frame) { + first_frame = false; + ImGui::SetNextWindowSize({480, 100}); + ImGui::SetNextWindowPos({100, 550}); + } + + const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"}; + const Entity::TileMode tile_modes[] = { + Entity::TileMode::kClamp, Entity::TileMode::kRepeat, + Entity::TileMode::kMirror, Entity::TileMode::kDecal}; + + static int selected_tile_mode = 0; + ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); + ImGui::Combo("Tile mode", &selected_tile_mode, tile_mode_names, + sizeof(tile_mode_names) / sizeof(char*)); + static Matrix matrix = { + 1, 0, 0, 0, // + 0, 1, 0, 0, // + 0, 0, 1, 0, // + 0, 0, 0, 1 // + }; + std::string label = "##1"; + label.c_str(); + for (int i = 0; i < 4; i++) { + ImGui::InputScalarN(label.c_str(), ImGuiDataType_Float, &(matrix.vec[i]), + 4, NULL, NULL, "%.2f", 0); + label[2]++; + } + ImGui::End(); + + Canvas canvas; + Paint paint; + canvas.Translate({100.0, 100.0, 0}); + auto tile_mode = tile_modes[selected_tile_mode]; + paint.color_source = [tile_mode]() { + auto contents = std::make_shared(); + contents->SetCenterAndAngles({100, 100}, Degrees(45), Degrees(135)); + std::vector colors = {Color{0.9568, 0.2627, 0.2118, 1.0}, + Color{0.1294, 0.5882, 0.9529, 1.0}}; + contents->SetColors(std::move(colors)); + contents->SetTileMode(tile_mode); + contents->SetMatrix(matrix); + return contents; + }; + canvas.DrawRect({0, 0, 600, 600}, paint); + return renderer.Render(canvas.EndRecordingAsPicture(), render_target); + }; + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + +TEST_P(AiksTest, CanRenderDifferentShapesWithSameColorSource) { Canvas canvas; + Paint paint; + paint.color_source = []() { + auto contents = std::make_shared(); + contents->SetEndPoints({0, 0}, {100, 100}); + std::vector colors = {Color{0.9568, 0.2627, 0.2118, 1.0}, + Color{0.1294, 0.5882, 0.9529, 1.0}}; + contents->SetColors(std::move(colors)); + contents->SetTileMode(Entity::TileMode::kRepeat); + return contents; + }; + canvas.Save(); + canvas.Translate({100, 100, 0}); + canvas.DrawRect({0, 0, 200, 200}, paint); + canvas.Restore(); + + canvas.Save(); + canvas.Translate({100, 400, 0}); + canvas.DrawCircle({100, 100}, 100, paint); + canvas.Restore(); + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} +TEST_P(AiksTest, BlendModeShouldCoverWholeScreen) { + Canvas canvas; Paint paint; - auto contents = std::make_shared(); - contents->SetCenterAndRadius({125, 125}, 100); - std::vector colors = {Color{0.9019, 0.3921, 0.3960, 1.0}, - Color{0.5686, 0.5960, 0.8980, 1.0}}; - contents->SetColors(std::move(colors)); - paint.contents = contents; - canvas.DrawRect({25, 25, 200, 200}, paint); + paint.color = Color::Red(); + canvas.DrawPaint(paint); + + paint.blend_mode = Entity::BlendMode::kSourceOver; + canvas.SaveLayer(paint); + + paint.color = Color::White(); + canvas.DrawRect({100, 100, 400, 400}, paint); + + paint.blend_mode = Entity::BlendMode::kSource; + canvas.SaveLayer(paint); + + paint.color = Color::Blue(); + canvas.DrawRect({200, 200, 200, 200}, paint); + + canvas.Restore(); + canvas.Restore(); ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); } @@ -940,5 +1227,31 @@ TEST_P(AiksTest, CanRenderClippedLayers) { ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); } +TEST_P(AiksTest, SaveLayerFiltersScaleWithTransform) { + Canvas canvas; + canvas.Scale(GetContentScale()); + canvas.Translate(Vector2(100, 100)); + + auto texture = std::make_shared(CreateTextureForFixture("boston.jpg")); + auto draw_image_layer = [&canvas, &texture](Paint paint) { + canvas.SaveLayer(paint); + canvas.DrawImage(texture, {}, Paint{}); + canvas.Restore(); + }; + + Paint effect_paint; + effect_paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{ + .style = FilterContents::BlurStyle::kNormal, + .sigma = Sigma{6}, + }; + draw_image_layer(effect_paint); + + canvas.Translate(Vector2(300, 300)); + canvas.Scale(Vector2(3, 3)); + draw_image_layer(effect_paint); + + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + } // namespace testing } // namespace impeller diff --git a/impeller/aiks/canvas.cc b/impeller/aiks/canvas.cc index dfa6371392585..b19e042c7d929 100644 --- a/impeller/aiks/canvas.cc +++ b/impeller/aiks/canvas.cc @@ -5,10 +5,13 @@ #include "impeller/aiks/canvas.h" #include +#include #include "flutter/fml/logging.h" #include "impeller/aiks/paint_pass_delegate.h" +#include "impeller/entity/contents/atlas_contents.h" #include "impeller/entity/contents/clip_contents.h" +#include "impeller/entity/contents/rrect_shadow_contents.h" #include "impeller/entity/contents/text_contents.h" #include "impeller/entity/contents/texture_contents.h" #include "impeller/entity/contents/vertices_contents.h" @@ -80,6 +83,10 @@ void Canvas::Concat(const Matrix& xformation) { xformation_stack_.back().xformation = GetCurrentTransformation() * xformation; } +void Canvas::PreConcat(const Matrix& xformation) { + xformation_stack_.back().xformation = xformation * GetCurrentTransformation(); +} + void Canvas::ResetTransform() { xformation_stack_.back().xformation = {}; } @@ -145,10 +152,51 @@ void Canvas::DrawPaint(Paint paint) { GetCurrentPass().AddEntity(std::move(entity)); } +bool Canvas::AttemptDrawBlurredRRect(const Rect& rect, + Scalar corner_radius, + Paint& paint) { + if (!paint.mask_blur_descriptor.has_value() || + paint.mask_blur_descriptor->style != FilterContents::BlurStyle::kNormal || + paint.style != Paint::Style::kFill) { + return false; + } + + // For symmetrically mask blurred solid RRects, absorb the mask blur and use + // a faster SDF approximation. + + auto contents = std::make_shared(); + contents->SetColor(paint.color); + contents->SetSigma(paint.mask_blur_descriptor->sigma); + contents->SetRRect(rect, corner_radius); + + paint.mask_blur_descriptor = std::nullopt; + + Entity entity; + entity.SetTransformation(GetCurrentTransformation()); + entity.SetStencilDepth(GetStencilDepth()); + entity.SetBlendMode(paint.blend_mode); + entity.SetContents(paint.WithFilters(std::move(contents))); + + GetCurrentPass().AddEntity(std::move(entity)); + + return true; +} + void Canvas::DrawRect(Rect rect, Paint paint) { + if (AttemptDrawBlurredRRect(rect, 0, paint)) { + return; + } DrawPath(PathBuilder{}.AddRect(rect).TakePath(), std::move(paint)); } +void Canvas::DrawRRect(Rect rect, Scalar corner_radius, Paint paint) { + if (AttemptDrawBlurredRRect(rect, corner_radius, paint)) { + return; + } + DrawPath(PathBuilder{}.AddRoundedRect(rect, corner_radius).TakePath(), + std::move(paint)); +} + void Canvas::DrawCircle(Point center, Scalar radius, Paint paint) { DrawPath(PathBuilder{}.AddCircle(center, radius).TakePath(), std::move(paint)); @@ -183,8 +231,6 @@ void Canvas::RestoreClip() { GetCurrentPass().AddEntity(std::move(entity)); } -void Canvas::DrawShadow(Path path, Color color, Scalar elevation) {} - void Canvas::DrawPicture(Picture picture) { if (!picture.pass) { return; @@ -230,8 +276,7 @@ void Canvas::DrawImageRect(std::shared_ptr image, return; } - auto contents = std::make_shared(); - contents->SetPath(PathBuilder{}.AddRect(dest).TakePath()); + auto contents = TextureContents::MakeRect(dest); contents->SetTexture(image->GetTexture()); contents->SetSourceRect(source); contents->SetSamplerDescriptor(std::move(sampler)); @@ -321,4 +366,40 @@ void Canvas::DrawVertices(Vertices vertices, GetCurrentPass().AddEntity(std::move(entity)); } +void Canvas::DrawAtlas(std::shared_ptr atlas, + std::vector transforms, + std::vector texture_coordinates, + std::vector colors, + Entity::BlendMode blend_mode, + SamplerDescriptor sampler, + std::optional cull_rect, + Paint paint) { + if (!atlas) { + return; + } + auto size = atlas->GetSize(); + + if (size.IsEmpty()) { + return; + } + + std::shared_ptr contents = std::make_shared(); + contents->SetColors(std::move(colors)); + contents->SetTransforms(std::move(transforms)); + contents->SetTextureCoordinates(std::move(texture_coordinates)); + contents->SetTexture(atlas->GetTexture()); + contents->SetSamplerDescriptor(std::move(sampler)); + contents->SetBlendMode(blend_mode); + contents->SetCullRect(cull_rect); + contents->SetAlpha(paint.color.alpha); + + Entity entity; + entity.SetTransformation(GetCurrentTransformation()); + entity.SetStencilDepth(GetStencilDepth()); + entity.SetBlendMode(paint.blend_mode); + entity.SetContents(paint.WithFilters(contents, false)); + + GetCurrentPass().AddEntity(std::move(entity)); +} + } // namespace impeller diff --git a/impeller/aiks/canvas.h b/impeller/aiks/canvas.h index 2e7abc61468f3..780fccefbcbcc 100644 --- a/impeller/aiks/canvas.h +++ b/impeller/aiks/canvas.h @@ -54,6 +54,8 @@ class Canvas { void Concat(const Matrix& xformation); + void PreConcat(const Matrix& xformation); + void Translate(const Vector3& offset); void Scale(const Vector2& scale); @@ -70,6 +72,8 @@ class Canvas { void DrawRect(Rect rect, Paint paint); + void DrawRRect(Rect rect, Scalar corner_radius, Paint paint); + void DrawCircle(Point center, Scalar radius, Paint paint); void DrawImage(std::shared_ptr image, @@ -87,8 +91,6 @@ class Canvas { Path path, Entity::ClipOperation clip_op = Entity::ClipOperation::kIntersect); - void DrawShadow(Path path, Color color, Scalar elevation); - void DrawPicture(Picture picture); void DrawTextFrame(TextFrame text_frame, Point position, Paint paint); @@ -97,6 +99,15 @@ class Canvas { Entity::BlendMode blend_mode, Paint paint); + void DrawAtlas(std::shared_ptr atlas, + std::vector transforms, + std::vector texture_coordinates, + std::vector colors, + Entity::BlendMode blend_mode, + SamplerDescriptor sampler, + std::optional cull_rect, + Paint paint); + Picture EndRecordingAsPicture(); private: @@ -117,6 +128,10 @@ class Canvas { void RestoreClip(); + bool AttemptDrawBlurredRRect(const Rect& rect, + Scalar corner_radius, + Paint& paint); + FML_DISALLOW_COPY_AND_ASSIGN(Canvas); }; diff --git a/impeller/aiks/paint.cc b/impeller/aiks/paint.cc index 6a71dc46d7379..1702cb9f72b3c 100644 --- a/impeller/aiks/paint.cc +++ b/impeller/aiks/paint.cc @@ -10,7 +10,9 @@ namespace impeller { std::shared_ptr Paint::CreateContentsForEntity(Path path, bool cover) const { - if (contents) { + if (color_source.has_value()) { + auto& source = color_source.value(); + auto contents = source(); contents->SetPath(std::move(path)); return contents; } @@ -40,17 +42,18 @@ std::shared_ptr Paint::CreateContentsForEntity(Path path, std::shared_ptr Paint::WithFilters( std::shared_ptr input, - std::optional is_solid_color) const { - bool is_solid_color_val = is_solid_color.value_or(!contents); + std::optional is_solid_color, + const Matrix& effect_transform) const { + bool is_solid_color_val = is_solid_color.value_or(!color_source); - if (mask_filter.has_value()) { - const MaskFilterProc& filter = mask_filter.value(); - input = filter(FilterInput::Make(input), is_solid_color_val); + if (mask_blur_descriptor.has_value()) { + input = mask_blur_descriptor->CreateMaskBlur( + FilterInput::Make(input), is_solid_color_val, effect_transform); } if (image_filter.has_value()) { const ImageFilterProc& filter = image_filter.value(); - input = filter(FilterInput::Make(input)); + input = filter(FilterInput::Make(input), effect_transform); } if (color_filter.has_value()) { @@ -61,4 +64,16 @@ std::shared_ptr Paint::WithFilters( return input; } +std::shared_ptr Paint::MaskBlurDescriptor::CreateMaskBlur( + FilterInput::Ref input, + bool is_solid_color, + const Matrix& effect_transform) const { + if (is_solid_color) { + return FilterContents::MakeGaussianBlur( + input, sigma, sigma, style, Entity::TileMode::kDecal, effect_transform); + } + return FilterContents::MakeBorderMaskBlur(input, sigma, sigma, style, + effect_transform); +} + } // namespace impeller diff --git a/impeller/aiks/paint.h b/impeller/aiks/paint.h index edf9b197668c4..02da68de57f87 100644 --- a/impeller/aiks/paint.h +++ b/impeller/aiks/paint.h @@ -12,26 +12,41 @@ #include "impeller/entity/contents/linear_gradient_contents.h" #include "impeller/entity/contents/radial_gradient_contents.h" #include "impeller/entity/contents/solid_stroke_contents.h" +#include "impeller/entity/contents/sweep_gradient_contents.h" #include "impeller/entity/entity.h" #include "impeller/geometry/color.h" namespace impeller { struct Paint { - using ImageFilterProc = + using ImageFilterProc = std::function( + FilterInput::Ref, + const Matrix& effect_transform)>; + using ColorFilterProc = std::function(FilterInput::Ref)>; - using ColorFilterProc = ImageFilterProc; - using MaskFilterProc = - std::function(FilterInput::Ref, - bool is_solid_color)>; + using MaskFilterProc = std::function( + FilterInput::Ref, + bool is_solid_color, + const Matrix& effect_transform)>; + using ColorSourceProc = std::function()>; enum class Style { kFill, kStroke, }; + struct MaskBlurDescriptor { + FilterContents::BlurStyle style; + Sigma sigma; + + std::shared_ptr CreateMaskBlur( + FilterInput::Ref input, + bool is_solid_color, + const Matrix& effect_matrix) const; + }; + Color color = Color::Black(); - std::shared_ptr contents; + std::optional color_source; Scalar stroke_width = 0.0; SolidStrokeContents::Cap stroke_cap = SolidStrokeContents::Cap::kButt; @@ -42,7 +57,7 @@ struct Paint { std::optional image_filter; std::optional color_filter; - std::optional mask_filter; + std::optional mask_blur_descriptor; /// @brief Wrap this paint's configured filters to the given contents. /// @param[in] input The contents to wrap with paint's filters. @@ -57,7 +72,8 @@ struct Paint { /// original contents is returned. std::shared_ptr WithFilters( std::shared_ptr input, - std::optional is_solid_color = std::nullopt) const; + std::optional is_solid_color = std::nullopt, + const Matrix& effect_transform = Matrix()) const; std::shared_ptr CreateContentsForEntity(Path path = {}, bool cover = false) const; diff --git a/impeller/aiks/paint_pass_delegate.cc b/impeller/aiks/paint_pass_delegate.cc index f5d6598309f61..453da016714bf 100644 --- a/impeller/aiks/paint_pass_delegate.cc +++ b/impeller/aiks/paint_pass_delegate.cc @@ -33,14 +33,14 @@ bool PaintPassDelegate::CanCollapseIntoParentPass() { // |EntityPassDelgate| std::shared_ptr PaintPassDelegate::CreateContentsForSubpassTarget( - std::shared_ptr target) { - auto contents = std::make_shared(); - contents->SetPath( - PathBuilder{}.AddRect(Rect::MakeSize(target->GetSize())).TakePath()); + std::shared_ptr target, + const Matrix& effect_transform) { + auto contents = TextureContents::MakeRect(Rect::MakeSize(target->GetSize())); contents->SetTexture(target); contents->SetSourceRect(Rect::MakeSize(target->GetSize())); contents->SetOpacity(paint_.color.alpha); - return contents; + + return paint_.WithFilters(std::move(contents), false, effect_transform); } } // namespace impeller diff --git a/impeller/aiks/paint_pass_delegate.h b/impeller/aiks/paint_pass_delegate.h index dcd94d16d8258..128f7b6abb14a 100644 --- a/impeller/aiks/paint_pass_delegate.h +++ b/impeller/aiks/paint_pass_delegate.h @@ -30,7 +30,8 @@ class PaintPassDelegate final : public EntityPassDelegate { // |EntityPassDelgate| std::shared_ptr CreateContentsForSubpassTarget( - std::shared_ptr target) override; + std::shared_ptr target, + const Matrix& effect_transform) override; private: const Paint paint_; diff --git a/impeller/base/BUILD.gn b/impeller/base/BUILD.gn index 577d34d78f2b7..4edd4e1a154f9 100644 --- a/impeller/base/BUILD.gn +++ b/impeller/base/BUILD.gn @@ -24,8 +24,19 @@ impeller_component("base") { "validation.h", "version.cc", "version.h", + "work_queue.cc", + "work_queue.h", + "work_queue_common.cc", + "work_queue_common.h", ] + if (is_ios || is_mac) { + sources += [ + "platform/darwin/work_queue_darwin.cc", + "platform/darwin/work_queue_darwin.h", + ] + } + deps = [ "//flutter/fml" ] } diff --git a/impeller/base/platform/darwin/work_queue_darwin.cc b/impeller/base/platform/darwin/work_queue_darwin.cc new file mode 100644 index 0000000000000..6ac76520530ed --- /dev/null +++ b/impeller/base/platform/darwin/work_queue_darwin.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 "impeller/base/platform/darwin/work_queue_darwin.h" + +namespace impeller { + +std::shared_ptr WorkQueueDarwin::Create() { + auto queue = std::shared_ptr(new WorkQueueDarwin()); + if (!queue->IsValid()) { + return nullptr; + } + return queue; +} + +WorkQueueDarwin::WorkQueueDarwin() + : queue_(::dispatch_queue_create( + "io.flutter.impeller.wq", + ::dispatch_queue_attr_make_with_qos_class( + DISPATCH_QUEUE_CONCURRENT_WITH_AUTORELEASE_POOL, + QOS_CLASS_USER_INITIATED, + -1))) {} + +WorkQueueDarwin::~WorkQueueDarwin() = default; + +bool WorkQueueDarwin::IsValid() const { + return queue_ != NULL; +} + +// |WorkQueue| +void WorkQueueDarwin::PostTask(fml::closure task) { + dispatch_async(queue_, ^() { + task(); + }); +} + +} // namespace impeller diff --git a/impeller/base/platform/darwin/work_queue_darwin.h b/impeller/base/platform/darwin/work_queue_darwin.h new file mode 100644 index 0000000000000..c82aca9eb285f --- /dev/null +++ b/impeller/base/platform/darwin/work_queue_darwin.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. + +#pragma once + +#include + +#include + +#include "flutter/fml/macros.h" +#include "impeller/base/work_queue.h" + +namespace impeller { + +class WorkQueueDarwin final : public WorkQueue { + public: + static std::shared_ptr Create(); + + // |WorkQueue| + ~WorkQueueDarwin(); + + bool IsValid() const; + + private: + dispatch_queue_t queue_ = NULL; + + WorkQueueDarwin(); + + // |WorkQueue| + void PostTask(fml::closure task) override; + + FML_DISALLOW_COPY_AND_ASSIGN(WorkQueueDarwin); +}; + +} // namespace impeller diff --git a/impeller/entity/contents/path_contents.cc b/impeller/base/work_queue.cc similarity index 65% rename from impeller/entity/contents/path_contents.cc rename to impeller/base/work_queue.cc index afa956a8cdb10..f4a375496207a 100644 --- a/impeller/entity/contents/path_contents.cc +++ b/impeller/base/work_queue.cc @@ -2,12 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "path_contents.h" +#include "flutter/impeller/base/work_queue.h" namespace impeller { -PathContents::PathContents() = default; +WorkQueue::WorkQueue() = default; -PathContents::~PathContents() = default; +WorkQueue::~WorkQueue() = default; } // namespace impeller diff --git a/impeller/entity/contents/path_contents.h b/impeller/base/work_queue.h similarity index 50% rename from impeller/entity/contents/path_contents.h rename to impeller/base/work_queue.h index f02430570f6c8..afd31c7acd53c 100644 --- a/impeller/entity/contents/path_contents.h +++ b/impeller/base/work_queue.h @@ -4,21 +4,24 @@ #pragma once +#include + +#include "flutter/fml/closure.h" #include "flutter/fml/macros.h" -#include "impeller/entity/contents/contents.h" -#include "impeller/geometry/path.h" namespace impeller { -class PathContents : public Contents { +class WorkQueue : public std::enable_shared_from_this { public: - PathContents(); + virtual ~WorkQueue(); - ~PathContents() override; + virtual void PostTask(fml::closure task) = 0; - virtual void SetPath(Path path) = 0; + protected: + WorkQueue(); - FML_DISALLOW_COPY_AND_ASSIGN(PathContents); + private: + FML_DISALLOW_COPY_AND_ASSIGN(WorkQueue); }; } // namespace impeller diff --git a/impeller/base/work_queue_common.cc b/impeller/base/work_queue_common.cc new file mode 100644 index 0000000000000..9a1f0620148ec --- /dev/null +++ b/impeller/base/work_queue_common.cc @@ -0,0 +1,25 @@ +// 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 "impeller/base/work_queue_common.h" + +namespace impeller { + +std::shared_ptr WorkQueueCommon::Create() { + return std::shared_ptr(new WorkQueueCommon()); +} + +WorkQueueCommon::WorkQueueCommon() + : loop_(fml::ConcurrentMessageLoop::Create(2u)) {} + +WorkQueueCommon::~WorkQueueCommon() { + loop_->Terminate(); +} + +// |WorkQueue| +void WorkQueueCommon::PostTask(fml::closure task) { + loop_->GetTaskRunner()->PostTask(std::move(task)); +} + +} // namespace impeller diff --git a/impeller/base/work_queue_common.h b/impeller/base/work_queue_common.h new file mode 100644 index 0000000000000..952f6ecbadacd --- /dev/null +++ b/impeller/base/work_queue_common.h @@ -0,0 +1,33 @@ +// 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. + +#pragma once + +#include + +#include "flutter/fml/concurrent_message_loop.h" +#include "flutter/fml/macros.h" +#include "impeller/base/work_queue.h" + +namespace impeller { + +class WorkQueueCommon : public WorkQueue { + public: + static std::shared_ptr Create(); + + // |WorkQueue| + ~WorkQueueCommon(); + + private: + std::shared_ptr loop_; + + WorkQueueCommon(); + + // |WorkQueue| + void PostTask(fml::closure task) override; + + FML_DISALLOW_COPY_AND_ASSIGN(WorkQueueCommon); +}; + +} // namespace impeller diff --git a/impeller/blobcat/BUILD.gn b/impeller/blobcat/BUILD.gn index b38d29bdcfe9d..a70f4a4ec10ff 100644 --- a/impeller/blobcat/BUILD.gn +++ b/impeller/blobcat/BUILD.gn @@ -2,19 +2,30 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. +import("//third_party/flatbuffers/flatbuffers.gni") import("../tools/impeller.gni") +config("blobcat_config") { + configs = [ "//flutter/impeller:impeller_public_config" ] + include_dirs = [ "$root_gen_dir/flutter" ] +} + +flatbuffers("blobcat_flatbuffers") { + flatbuffers = [ "blob.fbs" ] + public_configs = [ ":blobcat_config" ] + public_deps = [ "//third_party/flatbuffers" ] +} + impeller_component("blobcat_lib") { sources = [ - "blob.cc", - "blob.h", "blob_library.cc", "blob_library.h", "blob_writer.cc", "blob_writer.h", ] - deps = [ + public_deps = [ + ":blobcat_flatbuffers", "../base", "//flutter/fml", ] diff --git a/impeller/blobcat/blob.fbs b/impeller/blobcat/blob.fbs new file mode 100644 index 0000000000000..741c6bfca567d --- /dev/null +++ b/impeller/blobcat/blob.fbs @@ -0,0 +1,23 @@ +// 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. + +namespace impeller.fb; + +enum Stage:byte { + kVertex, + kFragment, +} + +table Blob { + stage: Stage; + name: string; + mapping: [ubyte]; +} + +table BlobLibrary { + items: [Blob]; +} + +root_type BlobLibrary; +file_identifier "BCAT"; diff --git a/impeller/blobcat/blob.h b/impeller/blobcat/blob.h deleted file mode 100644 index dc8ec7e472c15..0000000000000 --- a/impeller/blobcat/blob.h +++ /dev/null @@ -1,43 +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. - -#pragma once - -#include -#include -#include -#include - -#include "flutter/fml/macros.h" -#include "flutter/fml/mapping.h" - -namespace impeller { - -constexpr const uint32_t kBlobCatMagic = 0x0B10BCA7; -struct BlobHeader { - uint32_t magic = kBlobCatMagic; - uint32_t blob_count = 0u; -}; - -struct Blob { - enum class ShaderType : uint8_t { - kVertex, - kFragment, - }; - - static constexpr size_t kMaxNameLength = 32u; - - ShaderType type = ShaderType::kVertex; - uint64_t offset = 0; - uint64_t length = 0; - uint8_t name[kMaxNameLength] = {}; -}; - -struct BlobDescription { - Blob::ShaderType type; - std::string name; - std::shared_ptr mapping; -}; - -} // namespace impeller diff --git a/impeller/blobcat/blob_library.cc b/impeller/blobcat/blob_library.cc index 94af2799f9252..8282f2cb475df 100644 --- a/impeller/blobcat/blob_library.cc +++ b/impeller/blobcat/blob_library.cc @@ -4,68 +4,52 @@ #include "impeller/blobcat/blob_library.h" +#include #include +#include "impeller/base/validation.h" +#include "impeller/blobcat/blob_flatbuffers.h" + namespace impeller { -BlobLibrary::BlobLibrary(std::shared_ptr mapping) - : mapping_(std::move(mapping)) { - if (!mapping_ || mapping_->GetMapping() == nullptr) { - FML_LOG(ERROR) << "Invalid mapping."; - return; +constexpr BlobShaderType ToShaderType(fb::Stage stage) { + switch (stage) { + case fb::Stage::kVertex: + return BlobShaderType::kVertex; + case fb::Stage::kFragment: + return BlobShaderType::kFragment; } + FML_UNREACHABLE(); +} - BlobHeader header; - std::vector blobs; - - size_t offset = 0u; - - // Read the header. - { - const size_t read_size = sizeof(BlobHeader); - if (mapping_->GetSize() < offset + read_size) { - return; - } - std::memcpy(&header, mapping_->GetMapping() + offset, read_size); - offset += read_size; - - // Validate the header. - if (header.magic != kBlobCatMagic) { - FML_LOG(ERROR) << "Invalid blob magic."; - return; - } - - blobs.resize(header.blob_count); +BlobLibrary::BlobLibrary(std::shared_ptr payload) + : payload_(std::move(payload)) { + if (!payload_ || payload_->GetMapping() == nullptr) { + VALIDATION_LOG << "Blob mapping was absent."; + return; } - // Read the blob descriptions. - { - const size_t read_size = sizeof(Blob) * header.blob_count; - ::memcpy(blobs.data(), mapping_->GetMapping() + offset, read_size); - offset += read_size; // NOLINT(clang-analyzer-deadcode.DeadStores) + if (!fb::BlobLibraryBufferHasIdentifier(payload_->GetMapping())) { + VALIDATION_LOG << "Invalid blob magic."; + return; } - // Read the blobs. - { - for (size_t i = 0; i < header.blob_count; i++) { - const auto& blob = blobs[i]; + auto blob_library = fb::GetBlobLibrary(payload_->GetMapping()); + if (!blob_library) { + return; + } + if (auto items = blob_library->items()) { + for (auto i = items->begin(), end = items->end(); i != end; i++) { BlobKey key; - key.type = blob.type; - key.name = std::string{reinterpret_cast(blob.name)}; - auto mapping = std::make_shared( - mapping_->GetMapping() + blob.offset, // offset - blob.length, // length - [mapping = mapping_](const uint8_t* data, size_t size) {} - // release proc - ); - - auto inserted = blobs_.insert({key, mapping}); - if (!inserted.second) { - FML_LOG(ERROR) << "Shader library had duplicate shader named " - << key.name; - return; - } + key.name = i->name()->str(); + key.type = ToShaderType(i->stage()); + blobs_[key] = std::make_shared( + i->mapping()->Data(), i->mapping()->size(), + [payload = payload_](auto, auto) { + // The pointers are into the base payload. Instead of copying the + // data, just hold onto the payload. + }); } } @@ -84,7 +68,7 @@ size_t BlobLibrary::GetShaderCount() const { return blobs_.size(); } -std::shared_ptr BlobLibrary::GetMapping(Blob::ShaderType type, +std::shared_ptr BlobLibrary::GetMapping(BlobShaderType type, std::string name) const { BlobKey key; key.type = type; @@ -94,7 +78,7 @@ std::shared_ptr BlobLibrary::GetMapping(Blob::ShaderType type, } size_t BlobLibrary::IterateAllBlobs( - std::function& mapping)> callback) const { diff --git a/impeller/blobcat/blob_library.h b/impeller/blobcat/blob_library.h index c548ec95315d1..7d5a8b598f282 100644 --- a/impeller/blobcat/blob_library.h +++ b/impeller/blobcat/blob_library.h @@ -11,13 +11,13 @@ #include "flutter/fml/hash_combine.h" #include "flutter/fml/macros.h" #include "flutter/fml/mapping.h" -#include "impeller/blobcat/blob.h" +#include "impeller/blobcat/blob_types.h" namespace impeller { class BlobLibrary { public: - BlobLibrary(std::shared_ptr mapping); + explicit BlobLibrary(std::shared_ptr payload); BlobLibrary(BlobLibrary&&); @@ -27,17 +27,17 @@ class BlobLibrary { size_t GetShaderCount() const; - std::shared_ptr GetMapping(Blob::ShaderType type, + std::shared_ptr GetMapping(BlobShaderType type, std::string name) const; size_t IterateAllBlobs( - std::function& mapping)>) const; private: struct BlobKey { - Blob::ShaderType type = Blob::ShaderType::kFragment; + BlobShaderType type = BlobShaderType::kFragment; std::string name; struct Hash { @@ -60,7 +60,7 @@ class BlobLibrary { BlobKey::Hash, BlobKey::Equal>; - std::shared_ptr mapping_; + std::shared_ptr payload_; Blobs blobs_; bool is_valid_ = false; diff --git a/impeller/blobcat/blob.cc b/impeller/blobcat/blob_types.h similarity index 75% rename from impeller/blobcat/blob.cc rename to impeller/blobcat/blob_types.h index e7f97177af870..7c22d907d06ab 100644 --- a/impeller/blobcat/blob.cc +++ b/impeller/blobcat/blob_types.h @@ -2,10 +2,13 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "impeller/blobcat/blob.h" +#pragma once namespace impeller { -// +enum class BlobShaderType { + kVertex, + kFragment, +}; } // namespace impeller diff --git a/impeller/blobcat/blob_writer.cc b/impeller/blobcat/blob_writer.cc index be500f5e9f64b..cb242041cffbb 100644 --- a/impeller/blobcat/blob_writer.cc +++ b/impeller/blobcat/blob_writer.cc @@ -4,21 +4,24 @@ #include "impeller/blobcat/blob_writer.h" +#include #include #include +#include "impeller/blobcat/blob_flatbuffers.h" + namespace impeller { BlobWriter::BlobWriter() = default; BlobWriter::~BlobWriter() = default; -std::optional InferShaderTypefromFileExtension( +std::optional InferShaderTypefromFileExtension( const std::filesystem::path& path) { if (path == ".vert") { - return Blob::ShaderType::kVertex; + return BlobShaderType::kVertex; } else if (path == ".frag") { - return Blob::ShaderType::kFragment; + return BlobShaderType::kFragment; } return std::nullopt; } @@ -67,79 +70,48 @@ bool BlobWriter::AddBlobAtPath(const std::string& std_path) { std::move(file_mapping)); } -bool BlobWriter::AddBlob(Blob::ShaderType type, +bool BlobWriter::AddBlob(BlobShaderType type, std::string name, std::shared_ptr mapping) { if (name.empty() || !mapping || mapping->GetMapping() == nullptr) { return false; } - if (name.length() >= Blob::kMaxNameLength) { - FML_LOG(ERROR) << "Blob name length was too long."; - return false; - } - blob_descriptions_.emplace_back( BlobDescription{type, std::move(name), std::move(mapping)}); return true; } -std::shared_ptr BlobWriter::CreateMapping() const { - BlobHeader header; - header.blob_count = blob_descriptions_.size(); - - uint64_t offset = sizeof(BlobHeader) + (sizeof(Blob) * header.blob_count); - - std::vector blobs; - { - blobs.resize(header.blob_count); - for (size_t i = 0; i < header.blob_count; i++) { - const auto& desc = blob_descriptions_[i]; - blobs[i].type = desc.type; - blobs[i].offset = offset; - blobs[i].length = desc.mapping->GetSize(); - std::memcpy(reinterpret_cast(blobs[i].name), desc.name.data(), - desc.name.size()); - offset += blobs[i].length; - } +constexpr fb::Stage ToStage(BlobShaderType type) { + switch (type) { + case BlobShaderType::kVertex: + return fb::Stage::kVertex; + case BlobShaderType::kFragment: + return fb::Stage::kFragment; } + FML_UNREACHABLE(); +} - { - auto buffer = std::make_shared>(); - buffer->resize(offset, 0); - - size_t write_offset = 0u; - - // Write the header. - { - const size_t write_length = sizeof(header); - std::memcpy(buffer->data() + write_offset, &header, write_length); - write_offset += write_length; - } - - // Write the blob descriptions. - { - const size_t write_length = blobs.size() * sizeof(Blob); - std::memcpy(buffer->data() + write_offset, blobs.data(), write_length); - write_offset += write_length; - } - - // Write the blobs themselves. - { - for (size_t i = 0; i < header.blob_count; i++) { - const auto& desc = blob_descriptions_[i]; - const size_t write_length = desc.mapping->GetSize(); - std::memcpy(buffer->data() + write_offset, desc.mapping->GetMapping(), - write_length); - write_offset += write_length; - } +std::shared_ptr BlobWriter::CreateMapping() const { + fb::BlobLibraryT blobs; + for (const auto& blob_description : blob_descriptions_) { + auto mapping = blob_description.mapping; + if (!mapping) { + return nullptr; } - FML_CHECK(write_offset == offset); - return std::make_shared( - buffer->data(), buffer->size(), - [buffer](const uint8_t* data, size_t size) {}); + auto desc = std::make_unique(); + desc->name = blob_description.name; + desc->stage = ToStage(blob_description.type); + desc->mapping = {mapping->GetMapping(), + mapping->GetMapping() + mapping->GetSize()}; + blobs.items.emplace_back(std::move(desc)); } - return nullptr; + auto builder = std::make_shared(); + builder->Finish(fb::BlobLibrary::Pack(*builder.get(), &blobs), + fb::BlobLibraryIdentifier()); + return std::make_shared(builder->GetBufferPointer(), + builder->GetSize(), + [builder](auto, auto) {}); } } // namespace impeller diff --git a/impeller/blobcat/blob_writer.h b/impeller/blobcat/blob_writer.h index bbf60213ac73a..eb2f80c3e4a17 100644 --- a/impeller/blobcat/blob_writer.h +++ b/impeller/blobcat/blob_writer.h @@ -10,7 +10,7 @@ #include "flutter/fml/macros.h" #include "flutter/fml/mapping.h" -#include "impeller/blobcat/blob.h" +#include "impeller/blobcat/blob_types.h" namespace impeller { @@ -22,13 +22,19 @@ class BlobWriter { [[nodiscard]] bool AddBlobAtPath(const std::string& path); - [[nodiscard]] bool AddBlob(Blob::ShaderType type, + [[nodiscard]] bool AddBlob(BlobShaderType type, std::string name, std::shared_ptr mapping); std::shared_ptr CreateMapping() const; private: + struct BlobDescription { + BlobShaderType type; + std::string name; + std::shared_ptr mapping; + }; + std::vector blob_descriptions_; FML_DISALLOW_COPY_AND_ASSIGN(BlobWriter); diff --git a/impeller/blobcat/blobcat_unittests.cc b/impeller/blobcat/blobcat_unittests.cc index bc5bab227d667..6fc8783f652bd 100644 --- a/impeller/blobcat/blobcat_unittests.cc +++ b/impeller/blobcat/blobcat_unittests.cc @@ -27,15 +27,15 @@ const std::string CreateStringFromMapping(const fml::Mapping& mapping) { TEST(BlobTest, CanReadAndWriteBlobs) { BlobWriter writer; - ASSERT_TRUE(writer.AddBlob(Blob::ShaderType::kVertex, "Hello", + ASSERT_TRUE(writer.AddBlob(BlobShaderType::kVertex, "Hello", CreateMappingFromString("World"))); - ASSERT_TRUE(writer.AddBlob(Blob::ShaderType::kFragment, "Foo", + ASSERT_TRUE(writer.AddBlob(BlobShaderType::kFragment, "Foo", CreateMappingFromString("Bar"))); - ASSERT_TRUE(writer.AddBlob(Blob::ShaderType::kVertex, "Baz", + ASSERT_TRUE(writer.AddBlob(BlobShaderType::kVertex, "Baz", CreateMappingFromString("Bang"))); - ASSERT_TRUE(writer.AddBlob(Blob::ShaderType::kVertex, "Ping", + ASSERT_TRUE(writer.AddBlob(BlobShaderType::kVertex, "Ping", CreateMappingFromString("Pong"))); - ASSERT_TRUE(writer.AddBlob(Blob::ShaderType::kFragment, "Pang", + ASSERT_TRUE(writer.AddBlob(BlobShaderType::kFragment, "Pang", CreateMappingFromString("World"))); auto mapping = writer.CreateMapping(); @@ -46,9 +46,9 @@ TEST(BlobTest, CanReadAndWriteBlobs) { ASSERT_EQ(library.GetShaderCount(), 5u); // Wrong type. - ASSERT_EQ(library.GetMapping(Blob::ShaderType::kFragment, "Hello"), nullptr); + ASSERT_EQ(library.GetMapping(BlobShaderType::kFragment, "Hello"), nullptr); - auto hello_vtx = library.GetMapping(Blob::ShaderType::kVertex, "Hello"); + auto hello_vtx = library.GetMapping(BlobShaderType::kVertex, "Hello"); ASSERT_NE(hello_vtx, nullptr); ASSERT_EQ(CreateStringFromMapping(*hello_vtx), "World"); } diff --git a/impeller/compiler/code_gen_template.h b/impeller/compiler/code_gen_template.h index 4a061fcadb5ad..58b9b0a7b9464 100644 --- a/impeller/compiler/code_gen_template.h +++ b/impeller/compiler/code_gen_template.h @@ -16,15 +16,17 @@ constexpr std::string_view kReflectionHeaderTemplate = {# Note: The nogncheck decorations are only to make GN not mad at the template#} {# this file is generated from. There are no GN rule violations in the generated#} {# file itself and the no-check declarations will be stripped in generated files.#} -#include "impeller/renderer/buffer_view.h" {# // nogncheck #} +#include "impeller/renderer/buffer_view.h" {# // nogncheck #} -#include "impeller/renderer/command.h" {# // nogncheck #} +#include "impeller/renderer/command.h" {# // nogncheck #} -#include "impeller/renderer/sampler.h" {# // nogncheck #} +#include "impeller/renderer/descriptor_set_layout.h" {# // nogncheck #} -#include "impeller/renderer/shader_types.h" {# // nogncheck #} +#include "impeller/renderer/sampler.h" {# // nogncheck #} -#include "impeller/renderer/texture.h" {# // nogncheck #} +#include "impeller/renderer/shader_types.h" {# // nogncheck #} + +#include "impeller/renderer/texture.h" {# // nogncheck #} namespace impeller { @@ -150,30 +152,24 @@ std::move({{ arg.argument_name }}){% if not loop.is_last %}, {% endif %} // =========================================================================== // Metadata for Vulkan ======================================================= // =========================================================================== -#ifdef IMPELLER_ENABLE_VULKAN_REFLECTION -{% if length(buffers)+length(sampled_images) > 0 %} - static constexpr std::array kDescriptorSetLayouts{ + static constexpr std::array kDescriptorSetLayouts{ {% for buffer in buffers %} - VkDescriptorSetLayoutBinding{ + DescriptorSetLayout{ {{buffer.binding}}, // binding = {{buffer.binding}} - VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, // descriptorType = Uniform Buffer + DescriptorType::kUniformBuffer, // descriptorType = Uniform Buffer 1, // descriptorCount = 1 - {{to_vk_shader_stage_flag_bits(shader_stage)}}, // stageFlags = {{to_shader_stage(shader_stage)}} - nullptr, // pImmutableSamplers = NULL + {{to_shader_stage(shader_stage)}}, // stageFlags = {{to_shader_stage(shader_stage)}} }, {% endfor %} {% for sampled_image in sampled_images %} - VkDescriptorSetLayoutBinding{ + DescriptorSetLayout{ {{sampled_image.binding}}, // binding = {{sampled_image.binding}} - VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, // descriptorType = Sampled Image + DescriptorType::kSampledImage, // descriptorType = Sampled Image 1, // descriptorCount = 1 - {{to_vk_shader_stage_flag_bits(shader_stage)}},// stageFlags = {{to_shader_stage(shader_stage)}} - nullptr, // pImmutableSamplers = NULL + {{to_shader_stage(shader_stage)}}, // stageFlags = {{to_shader_stage(shader_stage)}} }, {% endfor %} }; -{% endif %} -#endif }; // struct {{camel_case(shader_name)}}{{camel_case(shader_stage)}}Shader diff --git a/impeller/compiler/compiler.cc b/impeller/compiler/compiler.cc index e5b2018fd267f..1cd435c06e5df 100644 --- a/impeller/compiler/compiler.cc +++ b/impeller/compiler/compiler.cc @@ -4,6 +4,7 @@ #include "impeller/compiler/compiler.h" +#include #include #include #include @@ -13,10 +14,15 @@ #include "impeller/compiler/compiler_backend.h" #include "impeller/compiler/includer.h" #include "impeller/compiler/logger.h" +#include "impeller/compiler/types.h" namespace impeller { namespace compiler { +const uint32_t kFragBindingBase = 128; +const size_t kNumUniformKinds = + int(shaderc_uniform_kind::shaderc_uniform_kind_buffer) + 1; + static CompilerBackend CreateMSLCompiler(const spirv_cross::ParsedIR& ir, const SourceOptions& source_options) { auto sl_compiler = std::make_shared(ir); @@ -104,6 +110,133 @@ static CompilerBackend CreateCompiler(const spirv_cross::ParsedIR& ir, return compiler; } +static void SetLimitations(shaderc::CompileOptions& compiler_opts) { + using Limit = std::pair; + static constexpr std::array limits = { + Limit{shaderc_limit::shaderc_limit_max_lights, 8}, + Limit{shaderc_limit::shaderc_limit_max_clip_planes, 6}, + Limit{shaderc_limit::shaderc_limit_max_texture_units, 2}, + Limit{shaderc_limit::shaderc_limit_max_texture_coords, 8}, + Limit{shaderc_limit::shaderc_limit_max_vertex_attribs, 16}, + Limit{shaderc_limit::shaderc_limit_max_vertex_uniform_components, 4096}, + Limit{shaderc_limit::shaderc_limit_max_varying_floats, 60}, + Limit{shaderc_limit::shaderc_limit_max_vertex_texture_image_units, 16}, + Limit{shaderc_limit::shaderc_limit_max_combined_texture_image_units, 80}, + Limit{shaderc_limit::shaderc_limit_max_texture_image_units, 16}, + Limit{shaderc_limit::shaderc_limit_max_fragment_uniform_components, 1024}, + Limit{shaderc_limit::shaderc_limit_max_draw_buffers, 8}, + Limit{shaderc_limit::shaderc_limit_max_vertex_uniform_vectors, 256}, + Limit{shaderc_limit::shaderc_limit_max_varying_vectors, 15}, + Limit{shaderc_limit::shaderc_limit_max_fragment_uniform_vectors, 256}, + Limit{shaderc_limit::shaderc_limit_max_vertex_output_vectors, 16}, + Limit{shaderc_limit::shaderc_limit_max_fragment_input_vectors, 15}, + Limit{shaderc_limit::shaderc_limit_min_program_texel_offset, -8}, + Limit{shaderc_limit::shaderc_limit_max_program_texel_offset, 7}, + Limit{shaderc_limit::shaderc_limit_max_clip_distances, 8}, + Limit{shaderc_limit::shaderc_limit_max_compute_work_group_count_x, 65535}, + Limit{shaderc_limit::shaderc_limit_max_compute_work_group_count_y, 65535}, + Limit{shaderc_limit::shaderc_limit_max_compute_work_group_count_z, 65535}, + Limit{shaderc_limit::shaderc_limit_max_compute_work_group_size_x, 1024}, + Limit{shaderc_limit::shaderc_limit_max_compute_work_group_size_y, 1024}, + Limit{shaderc_limit::shaderc_limit_max_compute_work_group_size_z, 64}, + Limit{shaderc_limit::shaderc_limit_max_compute_uniform_components, 512}, + Limit{shaderc_limit::shaderc_limit_max_compute_texture_image_units, 16}, + Limit{shaderc_limit::shaderc_limit_max_compute_image_uniforms, 8}, + Limit{shaderc_limit::shaderc_limit_max_compute_atomic_counters, 8}, + Limit{shaderc_limit::shaderc_limit_max_compute_atomic_counter_buffers, 1}, + Limit{shaderc_limit::shaderc_limit_max_varying_components, 60}, + Limit{shaderc_limit::shaderc_limit_max_vertex_output_components, 64}, + Limit{shaderc_limit::shaderc_limit_max_geometry_input_components, 64}, + Limit{shaderc_limit::shaderc_limit_max_geometry_output_components, 128}, + Limit{shaderc_limit::shaderc_limit_max_fragment_input_components, 128}, + Limit{shaderc_limit::shaderc_limit_max_image_units, 8}, + Limit{shaderc_limit:: + shaderc_limit_max_combined_image_units_and_fragment_outputs, + 8}, + Limit{shaderc_limit::shaderc_limit_max_combined_shader_output_resources, + 8}, + Limit{shaderc_limit::shaderc_limit_max_image_samples, 0}, + Limit{shaderc_limit::shaderc_limit_max_vertex_image_uniforms, 0}, + Limit{shaderc_limit::shaderc_limit_max_tess_control_image_uniforms, 0}, + Limit{shaderc_limit::shaderc_limit_max_tess_evaluation_image_uniforms, 0}, + Limit{shaderc_limit::shaderc_limit_max_geometry_image_uniforms, 0}, + Limit{shaderc_limit::shaderc_limit_max_fragment_image_uniforms, 8}, + Limit{shaderc_limit::shaderc_limit_max_combined_image_uniforms, 8}, + Limit{shaderc_limit::shaderc_limit_max_geometry_texture_image_units, 16}, + Limit{shaderc_limit::shaderc_limit_max_geometry_output_vertices, 256}, + Limit{shaderc_limit::shaderc_limit_max_geometry_total_output_components, + 1024}, + Limit{shaderc_limit::shaderc_limit_max_geometry_uniform_components, 512}, + Limit{shaderc_limit::shaderc_limit_max_geometry_varying_components, 60}, + Limit{shaderc_limit::shaderc_limit_max_tess_control_input_components, + 128}, + Limit{shaderc_limit::shaderc_limit_max_tess_control_output_components, + 128}, + Limit{shaderc_limit::shaderc_limit_max_tess_control_texture_image_units, + 16}, + Limit{shaderc_limit::shaderc_limit_max_tess_control_uniform_components, + 1024}, + Limit{ + shaderc_limit::shaderc_limit_max_tess_control_total_output_components, + 4096}, + Limit{shaderc_limit::shaderc_limit_max_tess_evaluation_input_components, + 128}, + Limit{shaderc_limit::shaderc_limit_max_tess_evaluation_output_components, + 128}, + Limit{ + shaderc_limit::shaderc_limit_max_tess_evaluation_texture_image_units, + 16}, + Limit{shaderc_limit::shaderc_limit_max_tess_evaluation_uniform_components, + 1024}, + Limit{shaderc_limit::shaderc_limit_max_tess_patch_components, 120}, + Limit{shaderc_limit::shaderc_limit_max_patch_vertices, 32}, + Limit{shaderc_limit::shaderc_limit_max_tess_gen_level, 64}, + Limit{shaderc_limit::shaderc_limit_max_viewports, 16}, + Limit{shaderc_limit::shaderc_limit_max_vertex_atomic_counters, 0}, + Limit{shaderc_limit::shaderc_limit_max_tess_control_atomic_counters, 0}, + Limit{shaderc_limit::shaderc_limit_max_tess_evaluation_atomic_counters, + 0}, + Limit{shaderc_limit::shaderc_limit_max_geometry_atomic_counters, 0}, + Limit{shaderc_limit::shaderc_limit_max_fragment_atomic_counters, 8}, + Limit{shaderc_limit::shaderc_limit_max_combined_atomic_counters, 8}, + Limit{shaderc_limit::shaderc_limit_max_atomic_counter_bindings, 1}, + Limit{shaderc_limit::shaderc_limit_max_vertex_atomic_counter_buffers, 0}, + Limit{ + shaderc_limit::shaderc_limit_max_tess_control_atomic_counter_buffers, + 0}, + Limit{shaderc_limit:: + shaderc_limit_max_tess_evaluation_atomic_counter_buffers, + 0}, + Limit{shaderc_limit::shaderc_limit_max_geometry_atomic_counter_buffers, + 0}, + Limit{shaderc_limit::shaderc_limit_max_fragment_atomic_counter_buffers, + 0}, + Limit{shaderc_limit::shaderc_limit_max_combined_atomic_counter_buffers, + 1}, + Limit{shaderc_limit::shaderc_limit_max_atomic_counter_buffer_size, 32}, + Limit{shaderc_limit::shaderc_limit_max_transform_feedback_buffers, 4}, + Limit{shaderc_limit:: + shaderc_limit_max_transform_feedback_interleaved_components, + 64}, + Limit{shaderc_limit::shaderc_limit_max_cull_distances, 8}, + Limit{shaderc_limit::shaderc_limit_max_combined_clip_and_cull_distances, + 8}, + Limit{shaderc_limit::shaderc_limit_max_samples, 4}, + }; + for (auto& [limit, value] : limits) { + compiler_opts.SetLimit(limit, value); + } +} + +void Compiler::SetBindingBase(shaderc::CompileOptions& compiler_opts) const { + for (size_t uniform_kind = 0; uniform_kind < kNumUniformKinds; + uniform_kind++) { + compiler_opts.SetBindingBaseForStage( + ToShaderCShaderKind(SourceType::kFragmentShader), + static_cast(uniform_kind), kFragBindingBase); + } +} + Compiler::Compiler(const fml::Mapping& source_mapping, SourceOptions source_options, Reflector::Options reflector_options) @@ -139,6 +272,8 @@ Compiler::Compiler(const fml::Mapping& source_mapping, shaderc_source_language::shaderc_source_language_glsl); spirv_options.SetForcedVersionProfile(460, shaderc_profile::shaderc_profile_core); + SetLimitations(spirv_options); + switch (source_options.target_platform) { case TargetPlatform::kMetalDesktop: case TargetPlatform::kMetalIOS: @@ -212,6 +347,9 @@ Compiler::Compiler(const fml::Mapping& source_mapping, } spirv_options.SetAutoBindUniforms(true); +#ifdef IMPELLER_ENABLE_VULKAN + SetBindingBase(spirv_options); +#endif spirv_options.SetAutoMapLocations(true); std::vector included_file_names; @@ -365,6 +503,7 @@ std::string Compiler::GetDependencyNames(std::string separator) const { std::unique_ptr Compiler::CreateDepfileContents( std::initializer_list targets_names) const { + // https://github.com/ninja-build/ninja/blob/master/src/depfile_parser.cc#L28 const auto targets = JoinStrings(targets_names, " "); const auto dependencies = GetDependencyNames(" "); diff --git a/impeller/compiler/compiler.h b/impeller/compiler/compiler.h index 9dd3d901c3dde..379f610fd2286 100644 --- a/impeller/compiler/compiler.h +++ b/impeller/compiler/compiler.h @@ -57,6 +57,8 @@ class Compiler { std::string GetDependencyNames(std::string separator) const; + void SetBindingBase(shaderc::CompileOptions& compiler_opts) const; + FML_DISALLOW_COPY_AND_ASSIGN(Compiler); }; diff --git a/impeller/compiler/compiler_test.cc b/impeller/compiler/compiler_test.cc index 85b3abaa5f9c5..a0031fbb5c088 100644 --- a/impeller/compiler/compiler_test.cc +++ b/impeller/compiler/compiler_test.cc @@ -58,6 +58,13 @@ static std::string SLFileName(const char* fixture_name, return stream.str(); } +std::unique_ptr CompilerTest::GetReflectionJson( + const char* fixture_name) const { + auto filename = ReflectionJSONName(fixture_name); + auto fd = fml::OpenFileReadOnly(intermediates_directory_, filename.c_str()); + return fml::FileMapping::CreateReadOnly(fd); +} + bool CompilerTest::CanCompileAndReflect(const char* fixture_name, SourceType source_type) const { auto fixture = flutter::testing::OpenFixtureAsMapping(fixture_name); @@ -159,7 +166,6 @@ bool CompilerTest::CanCompileAndReflect(const char* fixture_name, return false; } } - return true; } diff --git a/impeller/compiler/compiler_test.h b/impeller/compiler/compiler_test.h index a7be1c688b198..0831a95bf13e3 100644 --- a/impeller/compiler/compiler_test.h +++ b/impeller/compiler/compiler_test.h @@ -21,6 +21,9 @@ class CompilerTest : public ::testing::TestWithParam { ~CompilerTest(); + std::unique_ptr GetReflectionJson( + const char* fixture_name) const; + bool CanCompileAndReflect( const char* fixture_name, SourceType source_type = SourceType::kUnknown) const; diff --git a/impeller/compiler/compiler_unittests.cc b/impeller/compiler/compiler_unittests.cc index d19d9f00c98c0..f8d1c45beb268 100644 --- a/impeller/compiler/compiler_unittests.cc +++ b/impeller/compiler/compiler_unittests.cc @@ -50,6 +50,12 @@ TEST_P(CompilerTest, CanCompileComputeShader) { ASSERT_TRUE(CanCompileAndReflect("sample.comp", SourceType::kComputeShader)); } +TEST_P(CompilerTest, MustFailDueToExceedingResourcesLimit) { + ScopedValidationDisable disable_validation; + ASSERT_FALSE( + CanCompileAndReflect("resources_limit.vert", SourceType::kVertexShader)); +} + TEST_P(CompilerTest, MustFailDueToMultipleLocationPerStructMember) { if (GetParam() == TargetPlatform::kFlutterSPIRV) { // This is a failure of reflection which this target doesn't perform. @@ -59,6 +65,31 @@ TEST_P(CompilerTest, MustFailDueToMultipleLocationPerStructMember) { ASSERT_FALSE(CanCompileAndReflect("struct_def_bug.vert")); } +TEST_P(CompilerTest, BindingBaseForFragShader) { + if (GetParam() == TargetPlatform::kFlutterSPIRV) { + // This is a failure of reflection which this target doesn't perform. + GTEST_SKIP(); + } + +#ifndef IMPELLER_ENABLE_VULKAN + GTEST_SKIP(); +#endif + + ASSERT_TRUE(CanCompileAndReflect("sample.vert", SourceType::kVertexShader)); + ASSERT_TRUE(CanCompileAndReflect("sample.frag", SourceType::kFragmentShader)); + + auto get_binding = [&](const char* fixture) -> uint32_t { + auto json_fd = GetReflectionJson(fixture); + nlohmann::json shader_json = nlohmann::json::parse(json_fd->GetMapping()); + return shader_json["buffers"][0]["binding"].get(); + }; + + auto vert_uniform_binding = get_binding("sample.vert"); + auto frag_uniform_binding = get_binding("sample.frag"); + + ASSERT_GT(frag_uniform_binding, vert_uniform_binding); +} + #define INSTANTIATE_TARGET_PLATFORM_TEST_SUITE_P(suite_name) \ INSTANTIATE_TEST_SUITE_P( \ suite_name, CompilerTest, \ diff --git a/impeller/compiler/impellerc_main.cc b/impeller/compiler/impellerc_main.cc index 76d844c070c0c..b78807699f890 100644 --- a/impeller/compiler/impellerc_main.cc +++ b/impeller/compiler/impellerc_main.cc @@ -3,6 +3,7 @@ // found in the LICENSE file. #include +#include #include "flutter/fml/backtrace.h" #include "flutter/fml/command_line.h" @@ -20,6 +21,21 @@ namespace impeller { namespace compiler { +// Sets the file access mode of the file at path 'p' to 0644. +static bool SetPermissiveAccess(const std::filesystem::path& p) { + auto permissions = + std::filesystem::perms::owner_read | std::filesystem::perms::owner_write | + std::filesystem::perms::group_read | std::filesystem::perms::others_read; + std::error_code error; + std::filesystem::permissions(p, permissions, error); + if (error) { + std::cerr << "Failed to set access on file '" << p + << "': " << error.message() << std::endl; + return false; + } + return true; +} + bool Main(const fml::CommandLine& command_line) { fml::InstallCrashHandler(); if (command_line.HasOption("help")) { @@ -110,6 +126,11 @@ bool Main(const fml::CommandLine& command_line) { << std::endl; return false; } + // Tools that consume the runtime stage data expect the access mode to + // be 0644. + if (!SetPermissiveAccess(sl_file_name)) { + return false; + } } else { if (!fml::WriteAtomically(*switches.working_directory, sl_file_name.string().c_str(), diff --git a/impeller/compiler/reflector.cc b/impeller/compiler/reflector.cc index 69d61719e08b7..0df6098086df6 100644 --- a/impeller/compiler/reflector.cc +++ b/impeller/compiler/reflector.cc @@ -354,11 +354,24 @@ std::shared_ptr Reflector::GenerateRuntimeStageData() const { uniform_description.rows = spir_type.vecsize; uniform_description.columns = spir_type.columns; uniform_description.bit_width = spir_type.width; + uniform_description.array_elements = GetArrayElements(spir_type); data->AddUniformDescription(std::move(uniform_description)); }); return data; } +uint32_t Reflector::GetArrayElements(const spirv_cross::SPIRType& type) const { + if (type.array.empty()) { + return 0; + } + uint32_t elements = 1; + for (size_t i = 0; i < type.array.size(); i++) { + FML_CHECK(type.array_size_literal[i]); + elements *= type.array[i]; + } + return elements; +} + static std::string ToString(CompilerBackend::Type type) { switch (type) { case CompilerBackend::Type::kMSL: diff --git a/impeller/compiler/reflector.h b/impeller/compiler/reflector.h index 5a5be695034d9..808ce56d3b223 100644 --- a/impeller/compiler/reflector.h +++ b/impeller/compiler/reflector.h @@ -138,6 +138,8 @@ class Reflector { std::vector ReadStructMembers( const spirv_cross::TypeID& type_id) const; + uint32_t GetArrayElements(const spirv_cross::SPIRType& type) const; + FML_DISALLOW_COPY_AND_ASSIGN(Reflector); }; diff --git a/impeller/compiler/runtime_stage_data.cc b/impeller/compiler/runtime_stage_data.cc index af4c61118538d..70ebb3079a2a0 100644 --- a/impeller/compiler/runtime_stage_data.cc +++ b/impeller/compiler/runtime_stage_data.cc @@ -157,6 +157,7 @@ std::shared_ptr RuntimeStageData::CreateMapping() const { } desc->type = uniform_type.value(); desc->bit_width = uniform.bit_width; + desc->array_elements = uniform.array_elements; runtime_stage.uniforms.emplace_back(std::move(desc)); } diff --git a/impeller/compiler/runtime_stage_data.h b/impeller/compiler/runtime_stage_data.h index b0022a1402d42..d5da09dd02c0e 100644 --- a/impeller/compiler/runtime_stage_data.h +++ b/impeller/compiler/runtime_stage_data.h @@ -22,6 +22,7 @@ struct UniformDescription { size_t rows = 0u; size_t columns = 0u; size_t bit_width = 0u; + size_t array_elements = 0u; }; class RuntimeStageData { diff --git a/impeller/compiler/shader_lib/impeller/blending.glsl b/impeller/compiler/shader_lib/impeller/blending.glsl new file mode 100644 index 0000000000000..f92e6bc4ab9a0 --- /dev/null +++ b/impeller/compiler/shader_lib/impeller/blending.glsl @@ -0,0 +1,193 @@ +// 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 BLENDING_GLSL_ +#define BLENDING_GLSL_ + +#include +#include + +//------------------------------------------------------------------------------ +/// HSV utilities. +/// + +float IPLuminosity(vec3 color) { + return color.r * 0.3 + color.g * 0.59 + color.b * 0.11; +} + +/// Scales the color's luma by the amount necessary to place the color +/// components in a 1-0 range. +vec3 IPClipColor(vec3 color) { + float lum = IPLuminosity(color); + float mn = min(min(color.r, color.g), color.b); + float mx = max(max(color.r, color.g), color.b); + // `lum - mn` and `mx - lum` will always be >= 0 in the following conditions, + // so adding a tiny value is enough to make these divisions safe. + if (mn < 0) { + color = lum + (((color - lum) * lum) / (lum - mn + kEhCloseEnough)); + } + if (mx > 1) { + color = lum + (((color - lum) * (1 - lum)) / (mx - lum + kEhCloseEnough)); + } + return color; +} + +vec3 IPSetLuminosity(vec3 color, float luminosity) { + float relative_lum = luminosity - IPLuminosity(color); + return IPClipColor(color + relative_lum); +} + +float IPSaturation(vec3 color) { + return max(max(color.r, color.g), color.b) - + min(min(color.r, color.g), color.b); +} + +vec3 IPSetSaturation(vec3 color, float saturation) { + float mn = min(min(color.r, color.g), color.b); + float mx = max(max(color.r, color.g), color.b); + return (mn < mx) ? ((color - mn) * saturation) / (mx - mn) : vec3(0); +} + +//------------------------------------------------------------------------------ +/// Color blend functions. +/// +/// These routines take two unpremultiplied RGB colors and output a third color. +/// They can be combined with any alpha compositing operation. When these blend +/// functions are used for drawing Entities in Impeller, the output is always +/// applied to the destination using `SourceOver` alpha compositing. +/// + +vec3 IPBlendScreen(vec3 dst, vec3 src) { + // https://www.w3.org/TR/compositing-1/#blendingscreen + return dst + src - (dst * src); +} + +vec3 IPBlendHardLight(vec3 dst, vec3 src) { + // https://www.w3.org/TR/compositing-1/#blendinghardlight + return IPVec3Choose(dst * (2 * src), IPBlendScreen(dst, 2 * src - 1), src); +} + +vec3 IPBlendOverlay(vec3 dst, vec3 src) { + // https://www.w3.org/TR/compositing-1/#blendingoverlay + // HardLight, but with reversed parameters. + return IPBlendHardLight(src, dst); +} + +vec3 IPBlendDarken(vec3 dst, vec3 src) { + // https://www.w3.org/TR/compositing-1/#blendingdarken + return min(dst, src); +} + +vec3 IPBlendLighten(vec3 dst, vec3 src) { + // https://www.w3.org/TR/compositing-1/#blendinglighten + return max(dst, src); +} + +vec3 IPBlendColorDodge(vec3 dst, vec3 src) { + // https://www.w3.org/TR/compositing-1/#blendingcolordodge + + vec3 color = min(vec3(1), dst / (1 - src)); + + if (dst.r < kEhCloseEnough) { + color.r = 0; + } + if (dst.g < kEhCloseEnough) { + color.g = 0; + } + if (dst.b < kEhCloseEnough) { + color.b = 0; + } + + if (1 - src.r < kEhCloseEnough) { + color.r = 1; + } + if (1 - src.g < kEhCloseEnough) { + color.g = 1; + } + if (1 - src.b < kEhCloseEnough) { + color.b = 1; + } + + return color; +} + +vec3 IPBlendColorBurn(vec3 dst, vec3 src) { + // https://www.w3.org/TR/compositing-1/#blendingcolorburn + + vec3 color = 1 - min(vec3(1), (1 - dst) / src); + + if (1 - dst.r < kEhCloseEnough) { + color.r = 1; + } + if (1 - dst.g < kEhCloseEnough) { + color.g = 1; + } + if (1 - dst.b < kEhCloseEnough) { + color.b = 1; + } + + if (src.r < kEhCloseEnough) { + color.r = 0; + } + if (src.g < kEhCloseEnough) { + color.g = 0; + } + if (src.b < kEhCloseEnough) { + color.b = 0; + } + + return color; +} + +vec3 IPBlendSoftLight(vec3 dst, vec3 src) { + // https://www.w3.org/TR/compositing-1/#blendingsoftlight + + vec3 D = IPVec3ChooseCutoff(((16 * dst - 12) * dst + 4) * dst, // + sqrt(dst), // + dst, // + 0.25); + + return IPVec3Choose(dst - (1 - 2 * src) * dst * (1 - dst), // + dst + (2 * src - 1) * (D - dst), // + src); +} + +vec3 IPBlendDifference(vec3 dst, vec3 src) { + // https://www.w3.org/TR/compositing-1/#blendingdifference + return abs(dst - src); +} + +vec3 IPBlendExclusion(vec3 dst, vec3 src) { + // https://www.w3.org/TR/compositing-1/#blendingexclusion + return dst + src - 2 * dst * src; +} + +vec3 IPBlendMultiply(vec3 dst, vec3 src) { + // https://www.w3.org/TR/compositing-1/#blendingmultiply + return dst * src; +} + +vec3 IPBlendHue(vec3 dst, vec3 src) { + // https://www.w3.org/TR/compositing-1/#blendinghue + return IPSetLuminosity(IPSetSaturation(src, IPSaturation(dst)), + IPLuminosity(dst)); +} + +vec3 IPBlendSaturation(vec3 dst, vec3 src) { + // https://www.w3.org/TR/compositing-1/#blendingsaturation + return IPSetLuminosity(IPSetSaturation(dst, IPSaturation(src)), + IPLuminosity(dst)); +} + +vec3 IPBlendColor(vec3 dst, vec3 src) { + // https://www.w3.org/TR/compositing-1/#blendingcolor + return IPSetLuminosity(src, IPLuminosity(dst)); +} + +vec3 IPBlendLuminosity(vec3 dst, vec3 src) { + // https://www.w3.org/TR/compositing-1/#blendingluminosity + return IPSetLuminosity(dst, IPLuminosity(src)); +} + +#endif diff --git a/impeller/compiler/shader_lib/impeller/constants.glsl b/impeller/compiler/shader_lib/impeller/constants.glsl index 5ccc5b298c661..0ead4981f6411 100644 --- a/impeller/compiler/shader_lib/impeller/constants.glsl +++ b/impeller/compiler/shader_lib/impeller/constants.glsl @@ -7,4 +7,10 @@ const float kEhCloseEnough = 0.000001; +// 1 / (2 * pi) +const float k1Over2Pi = 0.1591549430918; + +// sqrt(2 * pi) +const float kSqrtTwoPi = 2.50662827463; + #endif diff --git a/impeller/compiler/shader_lib/impeller/texture.glsl b/impeller/compiler/shader_lib/impeller/texture.glsl index 5d2498377147c..633af1cd2819a 100644 --- a/impeller/compiler/shader_lib/impeller/texture.glsl +++ b/impeller/compiler/shader_lib/impeller/texture.glsl @@ -19,13 +19,66 @@ vec4 IPSample(sampler2D texture_sampler, vec2 coords, float y_coord_scale) { return texture(texture_sampler, coords); } -/// Sample a texture, emulating SamplerAddressMode::ClampToBorder. +// These values must correspond to the order of the items in the +// 'Entity::TileMode' enum class. +const float kTileModeClamp = 0; +const float kTileModeRepeat = 1; +const float kTileModeMirror = 2; +const float kTileModeDecal = 3; + +/// Remap a float using a tiling mode. +/// +/// When `tile_mode` is `kTileModeDecal`, no tiling is applied and `t` is +/// returned. In all other cases, a value between 0 and 1 is returned by tiling +/// `t`. +/// When `t` is between [0 to 1), the original unchanged `t` is always returned. +float IPFloatTile(float t, float tile_mode) { + if (tile_mode == kTileModeClamp) { + t = clamp(t, 0.0, 1.0); + } else if (tile_mode == kTileModeRepeat) { + t = fract(t); + } else if (tile_mode == kTileModeMirror) { + float t1 = t - 1; + float t2 = t1 - 2 * floor(t1 * 0.5) - 1; + t = abs(t2); + } + return t; +} + +/// Remap a vec2 using a tiling mode. +/// +/// Runs each component of the vec2 through `IPFloatTile`. +vec2 IPVec2Tile(vec2 coords, float x_tile_mode, float y_tile_mode) { + return vec2(IPFloatTile(coords.x, x_tile_mode), + IPFloatTile(coords.y, y_tile_mode)); +} + +/// Sample a texture, emulating a specific tile mode. +/// +/// This is useful for Impeller graphics backend that don't have native support +/// for Decal. +vec4 IPSampleWithTileMode(sampler2D tex, + vec2 coords, + float y_coord_scale, + float x_tile_mode, + float y_tile_mode) { + if (x_tile_mode == kTileModeDecal && (coords.x < 0 || coords.x >= 1) || + y_tile_mode == kTileModeDecal && (coords.y < 0 || coords.y >= 1)) { + return vec4(0); + } + + return IPSample(tex, IPVec2Tile(coords, x_tile_mode, y_tile_mode), y_coord_scale); +} + +/// Sample a texture, emulating a specific tile mode. /// -/// This is useful for Impeller graphics backend that don't support -/// ClampToBorder. -vec4 IPSampleClampToBorder(sampler2D tex, vec2 uv) { - float within_bounds = float(uv.x > 0 && uv.y > 0 && uv.x < 1 && uv.y < 1); - return texture(tex, uv) * within_bounds; +/// This is useful for Impeller graphics backend that don't have native support +/// for Decal. +vec4 IPSampleWithTileMode(sampler2D tex, + vec2 coords, + float y_coord_scale, + float tile_mode) { + return IPSampleWithTileMode(tex, coords, y_coord_scale, tile_mode, tile_mode); } #endif diff --git a/impeller/compiler/shader_lib/impeller/transform.glsl b/impeller/compiler/shader_lib/impeller/transform.glsl new file mode 100644 index 0000000000000..e57bdf0b14194 --- /dev/null +++ b/impeller/compiler/shader_lib/impeller/transform.glsl @@ -0,0 +1,14 @@ +// 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 TRANSFORM_GLSL_ +#define TRANSFORM_GLSL_ + +/// Returns the Cartesian coordinates of `position` in `transform` space. +vec2 IPVec2TransformPosition(mat4 matrix, vec2 point) { + vec4 transformed = matrix * vec4(point, 0, 1); + return transformed.xy / transformed.w; +} + +#endif diff --git a/impeller/compiler/spirv_sksl.cc b/impeller/compiler/spirv_sksl.cc index 977f593ccbd64..ab5727ded9d92 100644 --- a/impeller/compiler/spirv_sksl.cc +++ b/impeller/compiler/spirv_sksl.cc @@ -22,6 +22,7 @@ std::string CompilerSkSL::compile() { options.version = 100; options.vulkan_semantics = false; options.enable_420pack_extension = false; + options.flatten_multidimensional_arrays = true; backend.allow_precision_qualifiers = false; backend.basic_int16_type = "short"; @@ -38,6 +39,8 @@ std::string CompilerSkSL::compile() { backend.use_array_constructor = true; backend.workgroup_size_is_hidden = true; + fixup_user_functions(); + fixup_anonymous_struct_names(); fixup_type_alias(); reorder_type_alias(); @@ -72,6 +75,30 @@ std::string CompilerSkSL::compile() { return buffer.str(); } +void CompilerSkSL::fixup_user_functions() { + const std::string prefix = "__flutter_local_"; + ir.for_each_typed_id([&](uint32_t, const SPIRFunction& func) { + const auto& original_name = get_name(func.self); + // Just in case. Don't add the prefix a second time. + if (original_name.rfind(prefix, 0) == 0) { + return; + } + std::string new_name = prefix + original_name; + set_name(func.self, new_name); + }); + + ir.for_each_typed_id( + [&](uint32_t, const SPIRFunctionPrototype& func) { + const auto& original_name = get_name(func.self); + // Just in case. Don't add the prefix a second time. + if (original_name.rfind(prefix, 0) == 0) { + return; + } + std::string new_name = prefix + original_name; + set_name(func.self, new_name); + }); +} + void CompilerSkSL::emit_header() { statement("// This SkSL shader is autogenerated by spirv-cross."); statement(""); diff --git a/impeller/compiler/spirv_sksl.h b/impeller/compiler/spirv_sksl.h index 3fbe4cc1fdcae..7469ba37df4c2 100644 --- a/impeller/compiler/spirv_sksl.h +++ b/impeller/compiler/spirv_sksl.h @@ -38,6 +38,8 @@ class CompilerSkSL : public spirv_cross::CompilerGLSL { void emit_uniform(const spirv_cross::SPIRVariable& var) override; + void fixup_user_functions(); + void detect_unsupported_resources(); bool emit_constant_resources(); bool emit_struct_resources(); diff --git a/impeller/display_list/BUILD.gn b/impeller/display_list/BUILD.gn index a7a51ae9b393f..d68ae5b9ddc7b 100644 --- a/impeller/display_list/BUILD.gn +++ b/impeller/display_list/BUILD.gn @@ -10,6 +10,8 @@ impeller_component("display_list") { "display_list_dispatcher.h", "display_list_image_impeller.cc", "display_list_image_impeller.h", + "nine_patch_converter.cc", + "nine_patch_converter.h", ] public_deps = [ diff --git a/impeller/display_list/display_list_dispatcher.cc b/impeller/display_list/display_list_dispatcher.cc index b06e0ca57a776..378c960fe01c0 100644 --- a/impeller/display_list/display_list_dispatcher.cc +++ b/impeller/display_list/display_list_dispatcher.cc @@ -4,23 +4,30 @@ #include "impeller/display_list/display_list_dispatcher.h" +#include #include #include #include "display_list/display_list_blend_mode.h" +#include "display_list/display_list_color_filter.h" #include "display_list/display_list_path_effect.h" #include "display_list/display_list_tile_mode.h" #include "flutter/fml/logging.h" #include "flutter/fml/trace_event.h" #include "impeller/display_list/display_list_image_impeller.h" +#include "impeller/display_list/nine_patch_converter.h" #include "impeller/entity/contents/filters/filter_contents.h" +#include "impeller/entity/contents/filters/inputs/filter_input.h" #include "impeller/entity/contents/linear_gradient_contents.h" #include "impeller/entity/contents/radial_gradient_contents.h" #include "impeller/entity/contents/solid_stroke_contents.h" +#include "impeller/entity/contents/sweep_gradient_contents.h" +#include "impeller/entity/contents/tiled_texture_contents.h" #include "impeller/entity/entity.h" #include "impeller/geometry/path.h" #include "impeller/geometry/path_builder.h" #include "impeller/geometry/scalar.h" +#include "impeller/geometry/sigma.h" #include "impeller/geometry/vertices.h" #include "impeller/renderer/formats.h" #include "impeller/typographer/backends/skia/text_frame_skia.h" @@ -100,6 +107,71 @@ static Entity::BlendMode ToBlendMode(flutter::DlBlendMode mode) { FML_UNREACHABLE(); } +static Entity::TileMode ToTileMode(flutter::DlTileMode tile_mode) { + switch (tile_mode) { + case flutter::DlTileMode::kClamp: + return Entity::TileMode::kClamp; + case flutter::DlTileMode::kRepeat: + return Entity::TileMode::kRepeat; + case flutter::DlTileMode::kMirror: + return Entity::TileMode::kMirror; + case flutter::DlTileMode::kDecal: + return Entity::TileMode::kDecal; + } +} + +static impeller::SamplerDescriptor ToSamplerDescriptor( + const flutter::DlImageSampling options) { + impeller::SamplerDescriptor desc; + switch (options) { + case flutter::DlImageSampling::kNearestNeighbor: + desc.min_filter = desc.mag_filter = impeller::MinMagFilter::kNearest; + desc.label = "Nearest Sampler"; + break; + case flutter::DlImageSampling::kLinear: + desc.min_filter = desc.mag_filter = impeller::MinMagFilter::kLinear; + desc.label = "Linear Sampler"; + break; + case flutter::DlImageSampling::kMipmapLinear: + desc.min_filter = desc.mag_filter = impeller::MinMagFilter::kLinear; + desc.mip_filter = impeller::MipFilter::kLinear; + desc.label = "Mipmap Linear Sampler"; + break; + default: + break; + } + return desc; +} + +static impeller::SamplerDescriptor ToSamplerDescriptor( + const flutter::DlFilterMode options) { + impeller::SamplerDescriptor desc; + switch (options) { + case flutter::DlFilterMode::kNearest: + desc.min_filter = desc.mag_filter = impeller::MinMagFilter::kNearest; + desc.label = "Nearest Sampler"; + break; + case flutter::DlFilterMode::kLinear: + desc.min_filter = desc.mag_filter = impeller::MinMagFilter::kLinear; + desc.label = "Linear Sampler"; + break; + default: + break; + } + return desc; +} + +static Matrix ToMatrix(const SkMatrix& m) { + return Matrix{ + // clang-format off + m[0], m[3], 0, m[6], + m[1], m[4], 0, m[7], + 0, 0, 1, 0, + m[2], m[5], 0, m[8], + // clang-format on + }; +} + // |flutter::Dispatcher| void DisplayListDispatcher::setAntiAlias(bool aa) { // Nothing to do because AA is implicit. @@ -189,18 +261,46 @@ static Color ToColor(const SkColor& color) { }; } +static std::vector ToColors(const flutter::DlColor colors[], int count) { + auto result = std::vector(); + if (colors == nullptr) { + return result; + } + for (int i = 0; i < count; i++) { + result.push_back(ToColor(colors[i])); + } + return result; +} + +static std::vector ToRSXForms(const SkRSXform xform[], int count) { + auto result = std::vector(); + for (int i = 0; i < count; i++) { + auto form = xform[i]; + // clang-format off + auto matrix = Matrix{ + form.fSCos, form.fSSin, 0, 0, + -form.fSSin, form.fSCos, 0, 0, + 0, 0, 1, 0, + form.fTx, form.fTy, 0, 1 + }; + // clang-format on + result.push_back(matrix); + } + return result; +} + // |flutter::Dispatcher| void DisplayListDispatcher::setColorSource( const flutter::DlColorSource* source) { if (!source) { - paint_.contents = nullptr; + paint_.color_source = std::nullopt; return; } switch (source->type()) { case flutter::DlColorSourceType::kColor: { const flutter::DlColorColorSource* color = source->asColor(); - paint_.contents = nullptr; + paint_.color_source = std::nullopt; setColor(color->color()); FML_DCHECK(color); return; @@ -209,35 +309,95 @@ void DisplayListDispatcher::setColorSource( const flutter::DlLinearGradientColorSource* linear = source->asLinearGradient(); FML_DCHECK(linear); - auto contents = std::make_shared(); - contents->SetEndPoints(ToPoint(linear->start_point()), - ToPoint(linear->end_point())); + auto start_point = ToPoint(linear->start_point()); + auto end_point = ToPoint(linear->end_point()); std::vector colors; for (auto i = 0; i < linear->stop_count(); i++) { colors.emplace_back(ToColor(linear->colors()[i])); } - contents->SetColors(std::move(colors)); - paint_.contents = std::move(contents); + auto tile_mode = ToTileMode(linear->tile_mode()); + auto matrix = ToMatrix(linear->matrix()); + paint_.color_source = [start_point, end_point, colors = std::move(colors), + tile_mode, matrix]() { + auto contents = std::make_shared(); + contents->SetEndPoints(start_point, end_point); + contents->SetColors(std::move(colors)); + contents->SetTileMode(tile_mode); + contents->SetMatrix(matrix); + return contents; + }; return; } case flutter::DlColorSourceType::kRadialGradient: { const flutter::DlRadialGradientColorSource* radialGradient = source->asRadialGradient(); - FML_CHECK(radialGradient); - auto contents = std::make_shared(); - contents->SetCenterAndRadius(ToPoint(radialGradient->center()), - radialGradient->radius()); + FML_DCHECK(radialGradient); + auto center = ToPoint(radialGradient->center()); + auto radius = radialGradient->radius(); std::vector colors; for (auto i = 0; i < radialGradient->stop_count(); i++) { colors.emplace_back(ToColor(radialGradient->colors()[i])); } - contents->SetColors(std::move(colors)); - paint_.contents = std::move(contents); + auto tile_mode = ToTileMode(radialGradient->tile_mode()); + auto matrix = ToMatrix(radialGradient->matrix()); + paint_.color_source = [center, radius, colors = std::move(colors), + tile_mode, matrix]() { + auto contents = std::make_shared(); + contents->SetCenterAndRadius(center, radius); + contents->SetColors(std::move(colors)); + contents->SetTileMode(tile_mode); + contents->SetMatrix(matrix); + return contents; + }; + return; + } + case flutter::DlColorSourceType::kSweepGradient: { + const flutter::DlSweepGradientColorSource* sweepGradient = + source->asSweepGradient(); + FML_DCHECK(sweepGradient); + + auto center = ToPoint(sweepGradient->center()); + auto start_angle = Degrees(sweepGradient->start()); + auto end_angle = Degrees(sweepGradient->end()); + std::vector colors; + for (auto i = 0; i < sweepGradient->stop_count(); i++) { + colors.emplace_back(ToColor(sweepGradient->colors()[i])); + } + auto tile_mode = ToTileMode(sweepGradient->tile_mode()); + auto matrix = ToMatrix(sweepGradient->matrix()); + paint_.color_source = [center, start_angle, end_angle, + colors = std::move(colors), tile_mode, matrix]() { + auto contents = std::make_shared(); + contents->SetCenterAndAngles(center, start_angle, end_angle); + contents->SetColors(std::move(colors)); + contents->SetTileMode(tile_mode); + contents->SetMatrix(matrix); + return contents; + }; + return; + } + case flutter::DlColorSourceType::kImage: { + const flutter::DlImageColorSource* image_color_source = source->asImage(); + FML_DCHECK(image_color_source && + image_color_source->image()->impeller_texture()); + auto texture = image_color_source->image()->impeller_texture(); + auto x_tile_mode = ToTileMode(image_color_source->horizontal_tile_mode()); + auto y_tile_mode = ToTileMode(image_color_source->vertical_tile_mode()); + auto desc = ToSamplerDescriptor(image_color_source->sampling()); + auto matrix = ToMatrix(image_color_source->matrix()); + paint_.color_source = [texture, x_tile_mode, y_tile_mode, desc, + matrix]() { + auto contents = std::make_shared(); + contents->SetTexture(texture); + contents->SetTileModes(x_tile_mode, y_tile_mode); + contents->SetSamplerDescriptor(desc); + contents->SetMatrix(matrix); + return contents; + }; return; } - case flutter::DlColorSourceType::kImage: case flutter::DlColorSourceType::kConicalGradient: - case flutter::DlColorSourceType::kSweepGradient: + case flutter::DlColorSourceType::kRuntimeEffect: case flutter::DlColorSourceType::kUnknown: UNIMPLEMENTED; break; @@ -268,10 +428,27 @@ void DisplayListDispatcher::setColorFilter( }; return; } - case flutter::DlColorFilterType::kMatrix: + case flutter::DlColorFilterType::kMatrix: { + const flutter::DlMatrixColorFilter* dl_matrix = filter->asMatrix(); + impeller::FilterContents::ColorMatrix color_matrix; + dl_matrix->get_matrix(color_matrix.array); + paint_.color_filter = [color_matrix](FilterInput::Ref input) { + return FilterContents::MakeColorMatrix({input}, color_matrix); + }; + return; + } case flutter::DlColorFilterType::kSrgbToLinearGamma: + paint_.color_filter = [](FilterInput::Ref input) { + return FilterContents::MakeSrgbToLinearFilter({input}); + }; + return; case flutter::DlColorFilterType::kLinearToSrgbGamma: + paint_.color_filter = [](FilterInput::Ref input) { + return FilterContents::MakeLinearToSrgbFilter({input}); + }; + return; case flutter::DlColorFilterType::kUnknown: + FML_LOG(ERROR) << "requested DlColorFilterType::kUnknown"; UNIMPLEMENTED; break; } @@ -316,22 +493,16 @@ static FilterContents::BlurStyle ToBlurStyle(SkBlurStyle blur_style) { void DisplayListDispatcher::setMaskFilter(const flutter::DlMaskFilter* filter) { // Needs https://github.com/flutter/flutter/issues/95434 if (filter == nullptr) { - paint_.mask_filter = std::nullopt; + paint_.mask_blur_descriptor = std::nullopt; return; } switch (filter->type()) { case flutter::DlMaskFilterType::kBlur: { auto blur = filter->asBlur(); - auto style = ToBlurStyle(blur->style()); - auto sigma = FilterContents::Sigma(blur->sigma()); - - paint_.mask_filter = [style, sigma](FilterInput::Ref input, - bool is_solid_color) { - if (is_solid_color) { - return FilterContents::MakeGaussianBlur(input, sigma, sigma, style); - } - return FilterContents::MakeBorderMaskBlur(input, sigma, sigma, style); + paint_.mask_blur_descriptor = { + .style = ToBlurStyle(blur->style()), + .sigma = Sigma(blur->sigma()), }; break; } @@ -350,16 +521,15 @@ static std::optional ToImageFilterProc( switch (filter->type()) { case flutter::DlImageFilterType::kBlur: { auto blur = filter->asBlur(); - auto sigma_x = FilterContents::Sigma(blur->sigma_x()); - auto sigma_y = FilterContents::Sigma(blur->sigma_y()); - - if (blur->tile_mode() != flutter::DlTileMode::kClamp) { - // TODO(105072): Implement tile mode for blur filter. - UNIMPLEMENTED; - } - - return [sigma_x, sigma_y](FilterInput::Ref input) { - return FilterContents::MakeGaussianBlur(input, sigma_x, sigma_y); + auto sigma_x = Sigma(blur->sigma_x()); + auto sigma_y = Sigma(blur->sigma_y()); + auto tile_mode = ToTileMode(blur->tile_mode()); + + return [sigma_x, sigma_y, tile_mode](FilterInput::Ref input, + const Matrix& effect_transform) { + return FilterContents::MakeGaussianBlur( + input, sigma_x, sigma_y, FilterContents::BlurStyle::kNormal, + tile_mode, effect_transform); }; break; @@ -367,6 +537,7 @@ static std::optional ToImageFilterProc( case flutter::DlImageFilterType::kDilate: case flutter::DlImageFilterType::kErode: case flutter::DlImageFilterType::kMatrix: + case flutter::DlImageFilterType::kLocalMatrixFilter: case flutter::DlImageFilterType::kComposeFilter: case flutter::DlImageFilterType::kColorFilter: case flutter::DlImageFilterType::kUnknown: @@ -392,6 +563,14 @@ static std::optional ToRect(const SkRect* rect) { return Rect::MakeLTRB(rect->fLeft, rect->fTop, rect->fRight, rect->fBottom); } +static std::vector ToRects(const SkRect tex[], int count) { + auto result = std::vector(); + for (int i = 0; i < count; i++) { + result.push_back(ToRect(&tex[i]).value()); + } + return result; +} + // |flutter::Dispatcher| void DisplayListDispatcher::saveLayer(const SkRect* bounds, const flutter::SaveLayerOptions options, @@ -575,8 +754,8 @@ static Path ToPath(const SkPath& path) { break; case SkPathFillType::kInverseWinding: case SkPathFillType::kInverseEvenOdd: - // TODO(104848): Support the inverse winding modes. - UNIMPLEMENTED; + // Flutter doesn't expose these path fill types. These are only visible + // via the dispatcher interface. We should never get here. fill_type = FillType::kNonZero; break; } @@ -667,13 +846,14 @@ void DisplayListDispatcher::drawPaint() { // |flutter::Dispatcher| void DisplayListDispatcher::drawLine(const SkPoint& p0, const SkPoint& p1) { auto path = PathBuilder{}.AddLine(ToPoint(p0), ToPoint(p1)).TakePath(); - canvas_.DrawPath(std::move(path), paint_); + Paint paint = paint_; + paint.style = Paint::Style::kStroke; + canvas_.DrawPath(std::move(path), std::move(paint)); } // |flutter::Dispatcher| void DisplayListDispatcher::drawRect(const SkRect& rect) { - auto path = PathBuilder{}.AddRect(ToRect(rect)).TakePath(); - canvas_.DrawPath(std::move(path), paint_); + canvas_.DrawRect(ToRect(rect), paint_); } // |flutter::Dispatcher| @@ -690,7 +870,11 @@ void DisplayListDispatcher::drawCircle(const SkPoint& center, SkScalar radius) { // |flutter::Dispatcher| void DisplayListDispatcher::drawRRect(const SkRRect& rrect) { - canvas_.DrawPath(ToPath(rrect), paint_); + if (rrect.isSimple()) { + canvas_.DrawRRect(ToRect(rrect.rect()), rrect.getSimpleRadii().fX, paint_); + } else { + canvas_.DrawPath(ToPath(rrect), paint_); + } } // |flutter::Dispatcher| @@ -722,7 +906,39 @@ void DisplayListDispatcher::drawArc(const SkRect& oval_bounds, void DisplayListDispatcher::drawPoints(SkCanvas::PointMode mode, uint32_t count, const SkPoint points[]) { - UNIMPLEMENTED; + Paint paint = paint_; + paint.style = Paint::Style::kStroke; + switch (mode) { + case SkCanvas::kPoints_PointMode: + if (paint.stroke_cap == SolidStrokeContents::Cap::kButt) { + paint.stroke_cap = SolidStrokeContents::Cap::kSquare; + } + for (uint32_t i = 0; i < count; i++) { + Point p0 = ToPoint(points[i]); + auto path = PathBuilder{}.AddLine(p0, p0).TakePath(); + canvas_.DrawPath(std::move(path), paint); + } + break; + case SkCanvas::kLines_PointMode: + for (uint32_t i = 1; i < count; i += 2) { + Point p0 = ToPoint(points[i - 1]); + Point p1 = ToPoint(points[i]); + auto path = PathBuilder{}.AddLine(p0, p1).TakePath(); + canvas_.DrawPath(std::move(path), paint); + } + break; + case SkCanvas::kPolygon_PointMode: + if (count > 1) { + Point p0 = ToPoint(points[0]); + for (uint32_t i = 1; i < count; i++) { + Point p1 = ToPoint(points[i]); + auto path = PathBuilder{}.AddLine(p0, p1).TakePath(); + canvas_.DrawPath(std::move(path), paint); + p0 = p1; + } + } + break; + } } // |flutter::Dispatcher| @@ -767,29 +983,6 @@ void DisplayListDispatcher::drawImage(const sk_sp image, ); } -static impeller::SamplerDescriptor ToSamplerDescriptor( - const flutter::DlImageSampling options) { - impeller::SamplerDescriptor desc; - switch (options) { - case flutter::DlImageSampling::kNearestNeighbor: - desc.min_filter = desc.mag_filter = impeller::MinMagFilter::kNearest; - desc.label = "Nearest Sampler"; - break; - case flutter::DlImageSampling::kLinear: - desc.min_filter = desc.mag_filter = impeller::MinMagFilter::kLinear; - desc.label = "Linear Sampler"; - break; - case flutter::DlImageSampling::kMipmapLinear: - desc.min_filter = desc.mag_filter = impeller::MinMagFilter::kLinear; - desc.mip_filter = impeller::MipFilter::kLinear; - desc.label = "Mipmap Linear Sampler"; - break; - default: - break; - } - return desc; -} - // |flutter::Dispatcher| void DisplayListDispatcher::drawImageRect( const sk_sp image, @@ -800,7 +993,7 @@ void DisplayListDispatcher::drawImageRect( SkCanvas::SrcRectConstraint constraint) { canvas_.DrawImageRect( std::make_shared(image->impeller_texture()), // image - ToRect(src), // source rect + ToRect(src), // source rect ToRect(dst), // destination rect paint_, // paint ToSamplerDescriptor(sampling) // sampling @@ -813,8 +1006,11 @@ void DisplayListDispatcher::drawImageNine(const sk_sp image, const SkRect& dst, flutter::DlFilterMode filter, bool render_with_attributes) { - // Needs https://github.com/flutter/flutter/issues/95434 - UNIMPLEMENTED; + NinePatchConverter converter = {}; + converter.DrawNinePatch( + std::make_shared(image->impeller_texture()), + Rect::MakeLTRB(center.fLeft, center.fTop, center.fRight, center.fBottom), + ToRect(dst), ToSamplerDescriptor(filter), &canvas_, &paint_); } // |flutter::Dispatcher| @@ -824,8 +1020,9 @@ void DisplayListDispatcher::drawImageLattice( const SkRect& dst, flutter::DlFilterMode filter, bool render_with_attributes) { - // Needs https://github.com/flutter/flutter/issues/95434 - UNIMPLEMENTED; + // Don't implement this one since it is not exposed by flutter, + // Skia internally converts calls to drawImageNine into this method, + // which is then converted back to drawImageNine by display list. } // |flutter::Dispatcher| @@ -838,8 +1035,10 @@ void DisplayListDispatcher::drawAtlas(const sk_sp atlas, flutter::DlImageSampling sampling, const SkRect* cull_rect, bool render_with_attributes) { - // Needs https://github.com/flutter/flutter/issues/95434 - UNIMPLEMENTED; + canvas_.DrawAtlas(std::make_shared(atlas->impeller_texture()), + ToRSXForms(xform, count), ToRects(tex, count), + ToColors(colors, count), ToBlendMode(mode), + ToSamplerDescriptor(sampling), ToRect(cull_rect), paint_); } // |flutter::Dispatcher| @@ -878,7 +1077,66 @@ void DisplayListDispatcher::drawShadow(const SkPath& path, const SkScalar elevation, bool transparent_occluder, SkScalar dpr) { - UNIMPLEMENTED; + Color spot_color = ToColor(color); + spot_color.alpha *= 0.25; + + // Compute the spot color -- ported from SkShadowUtils::ComputeTonalColors. + { + Scalar max = + std::max(std::max(spot_color.red, spot_color.green), spot_color.blue); + Scalar min = + std::min(std::min(spot_color.red, spot_color.green), spot_color.blue); + Scalar luminance = (min + max) * 0.5; + + Scalar alpha_adjust = + (2.6f + (-2.66667f + 1.06667f * spot_color.alpha) * spot_color.alpha) * + spot_color.alpha; + Scalar color_alpha = + (3.544762f + (-4.891428f + 2.3466f * luminance) * luminance) * + luminance; + color_alpha = std::clamp(alpha_adjust * color_alpha, 0.0f, 1.0f); + + Scalar greyscale_alpha = + std::clamp(spot_color.alpha * (1 - 0.4f * luminance), 0.0f, 1.0f); + + Scalar color_scale = color_alpha * (1 - greyscale_alpha); + Scalar tonal_alpha = color_scale + greyscale_alpha; + Scalar unpremul_scale = color_scale / tonal_alpha; + spot_color = Color(unpremul_scale * spot_color.red, + unpremul_scale * spot_color.green, + unpremul_scale * spot_color.blue, tonal_alpha); + } + + Vector3 light_position(0, -1, 1); + Scalar occluder_z = dpr * elevation; + + constexpr Scalar kLightRadius = 800 / 600; // Light radius / light height + + Paint paint; + paint.style = Paint::Style::kFill; + paint.color = spot_color; + paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{ + .style = FilterContents::BlurStyle::kNormal, + .sigma = Radius{kLightRadius * occluder_z / + canvas_.GetCurrentTransformation().GetScale().y}, + }; + + canvas_.Save(); + canvas_.PreConcat( + Matrix::MakeTranslation(Vector2(0, -occluder_z * light_position.y))); + + SkRect rect; + SkRRect rrect; + if (path.isRect(&rect)) { + canvas_.DrawRect(ToRect(rect), std::move(paint)); + } else if (path.isRRect(&rrect) && rrect.isSimple()) { + canvas_.DrawRRect(ToRect(rrect.rect()), rrect.getSimpleRadii().fX, + std::move(paint)); + } else { + canvas_.DrawPath(ToPath(path), std::move(paint)); + } + + canvas_.Restore(); } Picture DisplayListDispatcher::EndRecordingAsPicture() { diff --git a/impeller/display_list/display_list_image_impeller.cc b/impeller/display_list/display_list_image_impeller.cc index b4627c0da6776..f964010b9ae90 100644 --- a/impeller/display_list/display_list_image_impeller.cc +++ b/impeller/display_list/display_list_image_impeller.cc @@ -29,6 +29,12 @@ std::shared_ptr DlImageImpeller::impeller_texture() const { return texture_; } +// |DlImage| +bool DlImageImpeller::isOpaque() const { + // Impeller doesn't currently implement opaque alpha types. + return false; +} + // |DlImage| bool DlImageImpeller::isTextureBacked() const { // Impeller textures are always ... textures :/ diff --git a/impeller/display_list/display_list_image_impeller.h b/impeller/display_list/display_list_image_impeller.h index 652b861f85c77..cf7ae7501eb69 100644 --- a/impeller/display_list/display_list_image_impeller.h +++ b/impeller/display_list/display_list_image_impeller.h @@ -23,6 +23,9 @@ class DlImageImpeller final : public flutter::DlImage { // |DlImage| std::shared_ptr impeller_texture() const override; + // |DlImage| + bool isOpaque() const override; + // |DlImage| bool isTextureBacked() const override; diff --git a/impeller/display_list/display_list_unittests.cc b/impeller/display_list/display_list_unittests.cc index 65207ec24eea5..c29a999cc6c12 100644 --- a/impeller/display_list/display_list_unittests.cc +++ b/impeller/display_list/display_list_unittests.cc @@ -16,6 +16,7 @@ #include "impeller/display_list/display_list_playground.h" #include "impeller/geometry/point.h" #include "impeller/playground/widgets.h" +#include "include/core/SkRRect.h" #include "third_party/imgui/imgui.h" #include "third_party/skia/include/core/SkClipOp.h" #include "third_party/skia/include/core/SkColor.h" @@ -297,12 +298,14 @@ TEST_P(DisplayListTest, CanDrawBackdropFilter) { } static float sigma[] = {10, 10}; + static float ctm_scale = 1; static bool use_bounds = true; static bool draw_circle = true; static bool add_clip = true; ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); ImGui::SliderFloat2("Sigma", sigma, 0, 100); + ImGui::SliderFloat("Scale", &ctm_scale, 0, 10); ImGui::NewLine(); ImGui::TextWrapped( "If everything is working correctly, none of the options below should " @@ -314,7 +317,7 @@ TEST_P(DisplayListTest, CanDrawBackdropFilter) { flutter::DisplayListBuilder builder; - Vector2 scale = GetContentScale(); + Vector2 scale = ctm_scale * GetContentScale(); builder.scale(scale.x, scale.y); auto filter = flutter::DlBlurImageFilter(sigma[0], sigma[1], @@ -357,5 +360,200 @@ TEST_P(DisplayListTest, CanDrawBackdropFilter) { ASSERT_TRUE(OpenPlaygroundHere(callback)); } +TEST_P(DisplayListTest, CanDrawNinePatchImage) { + // Image is drawn with corners to scale and center pieces stretched to fit. + auto texture = CreateTextureForFixture("embarcadero.jpg"); + flutter::DisplayListBuilder builder; + auto size = texture->GetSize(); + builder.drawImageNine( + DlImageImpeller::Make(texture), + SkIRect::MakeLTRB(size.width / 4, size.height / 4, size.width * 3 / 4, + size.height * 3 / 4), + SkRect::MakeLTRB(0, 0, size.width * 2, size.height * 2), + flutter::DlFilterMode::kNearest, true); + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(DisplayListTest, CanDrawNinePatchImageCenterWidthBiggerThanDest) { + // Edge case, the width of the corners does not leave any room for the + // center slice. The center (across the vertical axis) is folded out of the + // resulting image. + auto texture = CreateTextureForFixture("embarcadero.jpg"); + flutter::DisplayListBuilder builder; + auto size = texture->GetSize(); + builder.drawImageNine( + DlImageImpeller::Make(texture), + SkIRect::MakeLTRB(size.width / 4, size.height / 4, size.width * 3 / 4, + size.height * 3 / 4), + SkRect::MakeLTRB(0, 0, size.width / 2, size.height), + flutter::DlFilterMode::kNearest, true); + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(DisplayListTest, CanDrawNinePatchImageCenterHeightBiggerThanDest) { + // Edge case, the height of the corners does not leave any room for the + // center slice. The center (across the horizontal axis) is folded out of the + // resulting image. + auto texture = CreateTextureForFixture("embarcadero.jpg"); + flutter::DisplayListBuilder builder; + auto size = texture->GetSize(); + builder.drawImageNine( + DlImageImpeller::Make(texture), + SkIRect::MakeLTRB(size.width / 4, size.height / 4, size.width * 3 / 4, + size.height * 3 / 4), + SkRect::MakeLTRB(0, 0, size.width, size.height / 2), + flutter::DlFilterMode::kNearest, true); + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(DisplayListTest, CanDrawNinePatchImageCenterBiggerThanDest) { + // Edge case, the width and height of the corners does not leave any + // room for the center slices. Only the corners are displayed. + auto texture = CreateTextureForFixture("embarcadero.jpg"); + flutter::DisplayListBuilder builder; + auto size = texture->GetSize(); + builder.drawImageNine( + DlImageImpeller::Make(texture), + SkIRect::MakeLTRB(size.width / 4, size.height / 4, size.width * 3 / 4, + size.height * 3 / 4), + SkRect::MakeLTRB(0, 0, size.width / 2, size.height / 2), + flutter::DlFilterMode::kNearest, true); + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(DisplayListTest, CanDrawNinePatchImageCornersScaledDown) { + // Edge case, there is not enough room for the corners to be drawn + // without scaling them down. + auto texture = CreateTextureForFixture("embarcadero.jpg"); + flutter::DisplayListBuilder builder; + auto size = texture->GetSize(); + builder.drawImageNine( + DlImageImpeller::Make(texture), + SkIRect::MakeLTRB(size.width / 4, size.height / 4, size.width * 3 / 4, + size.height * 3 / 4), + SkRect::MakeLTRB(0, 0, size.width / 4, size.height / 4), + flutter::DlFilterMode::kNearest, true); + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(DisplayListTest, CanDrawPoints) { + flutter::DisplayListBuilder builder; + SkPoint points[7] = { + {0, 0}, // + {100, 100}, // + {100, 0}, // + {0, 100}, // + {0, 0}, // + {48, 48}, // + {52, 52}, // + }; + std::vector caps = { + flutter::DlStrokeCap::kButt, + flutter::DlStrokeCap::kRound, + flutter::DlStrokeCap::kSquare, + }; + flutter::DlPaint paint = + flutter::DlPaint() // + .setColor(flutter::DlColor::kYellow().withAlpha(127)) // + .setStrokeWidth(20); + builder.translate(50, 50); + for (auto cap : caps) { + paint.setStrokeCap(cap); + builder.save(); + builder.drawPoints(SkCanvas::kPoints_PointMode, 7, points, paint); + builder.translate(150, 0); + builder.drawPoints(SkCanvas::kLines_PointMode, 5, points, paint); + builder.translate(150, 0); + builder.drawPoints(SkCanvas::kPolygon_PointMode, 5, points, paint); + builder.restore(); + builder.translate(0, 150); + } + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(DisplayListTest, CanDrawZeroLengthLine) { + flutter::DisplayListBuilder builder; + std::vector caps = { + flutter::DlStrokeCap::kButt, + flutter::DlStrokeCap::kRound, + flutter::DlStrokeCap::kSquare, + }; + flutter::DlPaint paint = + flutter::DlPaint() // + .setColor(flutter::DlColor::kYellow().withAlpha(127)) // + .setDrawStyle(flutter::DlDrawStyle::kStroke) // + .setStrokeCap(flutter::DlStrokeCap::kButt) // + .setStrokeWidth(20); + SkPath path = SkPath().addPoly({{150, 50}, {150, 50}}, false); + for (auto cap : caps) { + paint.setStrokeCap(cap); + builder.drawLine({50, 50}, {50, 50}, paint); + builder.drawPath(path, paint); + builder.translate(0, 150); + } + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(DisplayListTest, CanDrawShadow) { + flutter::DisplayListBuilder builder; + std::array paths = { + SkPath{}.addRect(SkRect::MakeXYWH(0, 0, 200, 100)), + SkPath{}.addRRect( + SkRRect::MakeRectXY(SkRect::MakeXYWH(0, 0, 200, 100), 30, 30)), + SkPath{}.addCircle(100, 50, 50), + }; + builder.setColor(flutter::DlColor::kWhite()); + builder.drawPaint(); + builder.setColor(flutter::DlColor::kCyan()); + builder.translate(100, 100); + for (size_t x = 0; x < paths.size(); x++) { + builder.save(); + for (size_t y = 0; y < 5; y++) { + builder.drawShadow(paths[x], flutter::DlColor::kBlack(), 3 + y * 5, false, + 1); + builder.drawPath(paths[x]); + builder.translate(0, 200); + } + builder.restore(); + builder.translate(300, 0); + } + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(DisplayListTest, CanDrawZeroWidthLine) { + flutter::DisplayListBuilder builder; + std::vector caps = { + flutter::DlStrokeCap::kButt, + flutter::DlStrokeCap::kRound, + flutter::DlStrokeCap::kSquare, + }; + flutter::DlPaint paint = // + flutter::DlPaint() // + .setColor(flutter::DlColor::kWhite()) // + .setDrawStyle(flutter::DlDrawStyle::kStroke) // + .setStrokeWidth(0); + flutter::DlPaint outline_paint = // + flutter::DlPaint() // + .setColor(flutter::DlColor::kYellow()) // + .setDrawStyle(flutter::DlDrawStyle::kStroke) // + .setStrokeCap(flutter::DlStrokeCap::kSquare) // + .setStrokeWidth(1); + SkPath path = SkPath().addPoly({{150, 50}, {160, 50}}, false); + for (auto cap : caps) { + paint.setStrokeCap(cap); + builder.drawLine({50, 50}, {60, 50}, paint); + builder.drawRect({45, 45, 65, 55}, outline_paint); + builder.drawLine({100, 50}, {100, 50}, paint); + if (cap != flutter::DlStrokeCap::kButt) { + builder.drawRect({95, 45, 105, 55}, outline_paint); + } + builder.drawPath(path, paint); + builder.drawRect(path.getBounds().makeOutset(5, 5), outline_paint); + builder.translate(0, 150); + } + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + } // namespace testing } // namespace impeller diff --git a/impeller/display_list/nine_patch_converter.cc b/impeller/display_list/nine_patch_converter.cc new file mode 100644 index 0000000000000..2a9872e0ea8a3 --- /dev/null +++ b/impeller/display_list/nine_patch_converter.cc @@ -0,0 +1,94 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "impeller/display_list/nine_patch_converter.h" + +namespace impeller { + +NinePatchConverter::NinePatchConverter() = default; + +NinePatchConverter::~NinePatchConverter() = default; + +std::vector NinePatchConverter::InitSlices(double img0, + double imgC0, + double imgC1, + double img1, + double dst0, + double dst1) { + auto imageDim = img1 - img0; + auto destDim = dst1 - dst0; + + if (imageDim == destDim) { + // If the src and dest are the same size then we do not need scaling + // We return 4 values for a single slice + return {img0, dst0, img1, dst1}; + } + + auto edge0Dim = imgC0 - img0; + auto edge1Dim = img1 - imgC1; + auto edgesDim = edge0Dim + edge1Dim; + + if (edgesDim >= destDim) { + // the center portion has disappeared, leaving only the edges to scale to a + // common center position in the destination this produces only 2 slices + // which is 8 values + auto dstC = dst0 + destDim * edge0Dim / edgesDim; + // clang-format off + return { + img0, dst0, imgC0, dstC, + imgC1, dstC, img1, dst1, + }; + // clang-format on + } + + // center portion is nonEmpty and only that part is scaled + // we need 3 slices which is 12 values + auto dstC0 = dst0 + edge0Dim; + auto dstC1 = dst1 - edge1Dim; + // clang-format off + return { + img0, dst0, imgC0, dstC0, + imgC0, dstC0, imgC1, dstC1, + imgC1, dstC1, img1, dst1, + }; + // clang-format on +} + +void NinePatchConverter::DrawNinePatch(std::shared_ptr image, + Rect center, + Rect dst, + SamplerDescriptor sampler, + Canvas* canvas, + Paint* paint) { + if (dst.IsEmpty()) { + return; + } + auto image_size = image->GetSize(); + auto hSlices = InitSlices(0, center.GetLeft(), center.GetRight(), + image_size.width, dst.GetLeft(), dst.GetRight()); + auto vSlices = InitSlices(0, center.GetTop(), center.GetBottom(), + image_size.height, dst.GetTop(), dst.GetBottom()); + + for (size_t yi = 0; yi < vSlices.size(); yi += 4) { + auto srcY0 = vSlices[yi]; + auto dstY0 = vSlices[yi + 1]; + auto srcY1 = vSlices[yi + 2]; + auto dstY1 = vSlices[yi + 3]; + for (size_t xi = 0; xi < hSlices.size(); xi += 4) { + auto srcX0 = hSlices[xi]; + auto dstX0 = hSlices[xi + 1]; + auto srcX1 = hSlices[xi + 2]; + auto dstX1 = hSlices[xi + 3]; + // TODO(jonahwilliams): consider converting this into a single call to + // DrawImageAtlas. + canvas->DrawImageRect(image, Rect::MakeLTRB(srcX0, srcY0, srcX1, srcY1), + Rect::MakeLTRB(dstX0, dstY0, dstX1, dstY1), *paint, + sampler); + } + } +} + +} // namespace impeller diff --git a/impeller/display_list/nine_patch_converter.h b/impeller/display_list/nine_patch_converter.h new file mode 100644 index 0000000000000..a9250099d4250 --- /dev/null +++ b/impeller/display_list/nine_patch_converter.h @@ -0,0 +1,47 @@ +// 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. + +#pragma once + +#include + +#include "flutter/fml/macros.h" +#include "impeller/aiks/canvas.h" +#include "impeller/aiks/image.h" +#include "impeller/aiks/paint.h" +#include "impeller/geometry/path.h" +#include "impeller/renderer/sampler_descriptor.h" + +namespace impeller { + +// Converts a call to draw a nine patch image into a draw atlas call. +class NinePatchConverter { + public: + NinePatchConverter(); + + ~NinePatchConverter(); + + void DrawNinePatch(std::shared_ptr image, + Rect center, + Rect dst, + SamplerDescriptor sampler, + Canvas* canvas, + Paint* paint); + + private: + // Return a list of slice coordinates based on the size of the nine-slice + // parameters in one dimension. Each set of slice coordinates contains a + // begin/end pair for each of the source (image) and dest (screen) in the + // order (src0, dst0, src1, dst1). The area from src0 => src1 of the image is + // painted on the screen from dst0 => dst1 The slices for each dimension are + // generated independently. + std::vector InitSlices(double img0, + double imgC0, + double imgC1, + double img1, + double dst0, + double dst1); +}; + +} // namespace impeller diff --git a/impeller/entity/BUILD.gn b/impeller/entity/BUILD.gn index 0c1dfefc6ba6e..ec16b8e5849ae 100644 --- a/impeller/entity/BUILD.gn +++ b/impeller/entity/BUILD.gn @@ -8,6 +8,8 @@ impeller_shaders("entity_shaders") { name = "entity" shaders = [ + "shaders/atlas_fill.frag", + "shaders/atlas_fill.vert", "shaders/blending/advanced_blend.vert", "shaders/blending/advanced_blend_color.frag", "shaders/blending/advanced_blend_colorburn.frag", @@ -28,29 +30,43 @@ impeller_shaders("entity_shaders") { "shaders/blending/blend.vert", "shaders/border_mask_blur.frag", "shaders/border_mask_blur.vert", + "shaders/color_matrix_color_filter.frag", + "shaders/color_matrix_color_filter.vert", "shaders/gaussian_blur.frag", "shaders/gaussian_blur.vert", "shaders/glyph_atlas.frag", "shaders/glyph_atlas.vert", - "shaders/gradient_fill.frag", "shaders/gradient_fill.vert", + "shaders/linear_to_srgb_filter.frag", + "shaders/linear_to_srgb_filter.vert", + "shaders/linear_gradient_fill.frag", + "shaders/radial_gradient_fill.frag", + "shaders/rrect_blur.vert", + "shaders/rrect_blur.frag", "shaders/solid_fill.frag", "shaders/solid_fill.vert", "shaders/solid_stroke.frag", "shaders/solid_stroke.vert", + "shaders/srgb_to_linear_filter.frag", + "shaders/srgb_to_linear_filter.vert", + "shaders/sweep_gradient_fill.frag", "shaders/texture_fill.frag", "shaders/texture_fill.vert", - "shaders/vertices.vert", + "shaders/tiled_texture_fill.frag", + "shaders/tiled_texture_fill.vert", "shaders/vertices.frag", - "shaders/radial_gradient_fill.vert", - "shaders/radial_gradient_fill.frag", + "shaders/vertices.vert", ] } impeller_component("entity") { sources = [ + "contents/atlas_contents.cc", + "contents/atlas_contents.h", "contents/clip_contents.cc", "contents/clip_contents.h", + "contents/color_source_contents.cc", + "contents/color_source_contents.h", "contents/content_context.cc", "contents/content_context.h", "contents/contents.cc", @@ -59,6 +75,8 @@ impeller_component("entity") { "contents/filters/blend_filter_contents.h", "contents/filters/border_mask_blur_filter_contents.cc", "contents/filters/border_mask_blur_filter_contents.h", + "contents/filters/color_matrix_filter_contents.cc", + "contents/filters/color_matrix_filter_contents.h", "contents/filters/filter_contents.cc", "contents/filters/filter_contents.h", "contents/filters/gaussian_blur_filter_contents.cc", @@ -71,20 +89,28 @@ impeller_component("entity") { "contents/filters/inputs/filter_input.h", "contents/filters/inputs/texture_filter_input.cc", "contents/filters/inputs/texture_filter_input.h", + "contents/filters/linear_to_srgb_filter_contents.cc", + "contents/filters/linear_to_srgb_filter_contents.h", + "contents/filters/srgb_to_linear_filter_contents.cc", + "contents/filters/srgb_to_linear_filter_contents.h", "contents/linear_gradient_contents.cc", "contents/linear_gradient_contents.h", - "contents/path_contents.cc", - "contents/path_contents.h", "contents/radial_gradient_contents.cc", "contents/radial_gradient_contents.h", + "contents/rrect_shadow_contents.cc", + "contents/rrect_shadow_contents.h", "contents/solid_color_contents.cc", "contents/solid_color_contents.h", "contents/solid_stroke_contents.cc", "contents/solid_stroke_contents.h", + "contents/sweep_gradient_contents.cc", + "contents/sweep_gradient_contents.h", "contents/text_contents.cc", "contents/text_contents.h", "contents/texture_contents.cc", "contents/texture_contents.h", + "contents/tiled_texture_contents.cc", + "contents/tiled_texture_contents.h", "contents/vertices_contents.cc", "contents/vertices_contents.h", "entity.cc", diff --git a/impeller/entity/contents/atlas_contents.cc b/impeller/entity/contents/atlas_contents.cc new file mode 100644 index 0000000000000..8e82a568c1fcd --- /dev/null +++ b/impeller/entity/contents/atlas_contents.cc @@ -0,0 +1,150 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "impeller/renderer/formats.h" +#include "impeller/renderer/sampler_library.h" +#include "impeller/renderer/vertex_buffer_builder.h" + +#include "impeller/entity/atlas_fill.frag.h" +#include "impeller/entity/atlas_fill.vert.h" +#include "impeller/entity/contents/atlas_contents.h" +#include "impeller/entity/contents/content_context.h" +#include "impeller/entity/entity.h" +#include "impeller/renderer/render_pass.h" + +namespace impeller { + +AtlasContents::AtlasContents() = default; + +AtlasContents::~AtlasContents() = default; + +void AtlasContents::SetTexture(std::shared_ptr texture) { + texture_ = std::move(texture); +} + +std::shared_ptr AtlasContents::GetTexture() const { + return texture_; +} + +void AtlasContents::SetTransforms(std::vector transforms) { + transforms_ = transforms; +} + +void AtlasContents::SetTextureCoordinates(std::vector texture_coords) { + texture_coords_ = texture_coords; +} + +void AtlasContents::SetColors(std::vector colors) { + colors_ = colors; +} + +void AtlasContents::SetAlpha(Scalar alpha) { + alpha_ = alpha; +} + +void AtlasContents::SetBlendMode(Entity::BlendMode blend_mode) { + // TODO(jonahwilliams): blending of colors with texture. + blend_mode_ = blend_mode; +} + +void AtlasContents::SetCullRect(std::optional cull_rect) { + cull_rect_ = cull_rect; +} + +std::optional AtlasContents::GetCoverage(const Entity& entity) const { + if (cull_rect_.has_value()) { + return cull_rect_.value().TransformBounds(entity.GetTransformation()); + } + + Rect bounding_box = {}; + for (size_t i = 0; i < texture_coords_.size(); i++) { + auto matrix = transforms_[i]; + auto sample_rect = texture_coords_[i]; + auto bounds = Rect::MakeSize(sample_rect.size).TransformBounds(matrix); + bounding_box = bounds.Union(bounding_box); + } + return bounding_box.TransformBounds(entity.GetTransformation()); +} + +void AtlasContents::SetSamplerDescriptor(SamplerDescriptor desc) { + sampler_descriptor_ = std::move(desc); +} + +const SamplerDescriptor& AtlasContents::GetSamplerDescriptor() const { + return sampler_descriptor_; +} + +bool AtlasContents::Render(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const { + if (texture_ == nullptr) { + return true; + } + + using VS = AtlasFillVertexShader; + using FS = AtlasFillFragmentShader; + + const auto texture_size = texture_->GetSize(); + if (texture_size.IsEmpty()) { + return true; + } + + VertexBufferBuilder vertex_builder; + vertex_builder.Reserve(texture_coords_.size() * 6); + constexpr size_t indices[6] = {0, 1, 2, 1, 2, 3}; + constexpr Scalar width[6] = {0, 1, 0, 1, 0, 1}; + constexpr Scalar height[6] = {0, 0, 1, 0, 1, 1}; + for (size_t i = 0; i < texture_coords_.size(); i++) { + auto sample_rect = texture_coords_[i]; + auto matrix = transforms_[i]; + auto color = (colors_.size() > 0 ? colors_[i] : Color::Black()); + auto transformed_points = + Rect::MakeSize(sample_rect.size).GetTransformedPoints(matrix); + + for (size_t j = 0; j < 6; j++) { + VS::PerVertexData data; + data.position = transformed_points[indices[j]]; + data.texture_coords = + (sample_rect.origin + Point(sample_rect.size.width * width[j], + sample_rect.size.height * height[j])) / + texture_size; + data.color = color.Premultiply(); + vertex_builder.AppendVertex(data); + } + } + + if (!vertex_builder.HasVertices()) { + return true; + } + + auto& host_buffer = pass.GetTransientsBuffer(); + + VS::VertInfo vert_info; + vert_info.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * + entity.GetTransformation(); + + FS::FragInfo frag_info; + frag_info.texture_sampler_y_coord_scale = texture_->GetYCoordScale(); + frag_info.has_vertex_color = colors_.size() > 0 ? 1.0 : 0.0; + frag_info.alpha = alpha_; + + Command cmd; + cmd.label = "DrawAtlas"; + cmd.pipeline = + renderer.GetAtlasPipeline(OptionsFromPassAndEntity(pass, entity)); + cmd.stencil_reference = entity.GetStencilDepth(); + cmd.BindVertices(vertex_builder.CreateVertexBuffer(host_buffer)); + VS::BindVertInfo(cmd, host_buffer.EmplaceUniform(vert_info)); + FS::BindFragInfo(cmd, host_buffer.EmplaceUniform(frag_info)); + FS::BindTextureSampler(cmd, texture_, + renderer.GetContext()->GetSamplerLibrary()->GetSampler( + sampler_descriptor_)); + pass.AddCommand(std::move(cmd)); + + return true; +} + +} // namespace impeller diff --git a/impeller/entity/contents/atlas_contents.h b/impeller/entity/contents/atlas_contents.h new file mode 100644 index 0000000000000..2ff284c1a5143 --- /dev/null +++ b/impeller/entity/contents/atlas_contents.h @@ -0,0 +1,65 @@ +// 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. + +#pragma once + +#include +#include +#include + +#include "flutter/fml/macros.h" +#include "impeller/entity/contents/contents.h" +#include "impeller/entity/entity.h" +#include "impeller/renderer/sampler_descriptor.h" + +namespace impeller { + +class AtlasContents final : public Contents { + public: + explicit AtlasContents(); + + ~AtlasContents() override; + + void SetTexture(std::shared_ptr texture); + + std::shared_ptr GetTexture() const; + + void SetTransforms(std::vector transforms); + + void SetBlendMode(Entity::BlendMode blend_mode); + + void SetTextureCoordinates(std::vector texture_coords); + + void SetColors(std::vector colors); + + void SetCullRect(std::optional cull_rect); + + void SetSamplerDescriptor(SamplerDescriptor desc); + + void SetAlpha(Scalar alpha); + + const SamplerDescriptor& GetSamplerDescriptor() const; + + // |Contents| + std::optional GetCoverage(const Entity& entity) const override; + + // |Contents| + bool Render(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const override; + + private: + std::shared_ptr texture_; + std::vector texture_coords_; + std::vector colors_; + std::vector transforms_; + Entity::BlendMode blend_mode_; + std::optional cull_rect_; + Scalar alpha_ = 1.0; + SamplerDescriptor sampler_descriptor_ = {}; + + FML_DISALLOW_COPY_AND_ASSIGN(AtlasContents); +}; + +} // namespace impeller diff --git a/impeller/entity/contents/color_source_contents.cc b/impeller/entity/contents/color_source_contents.cc new file mode 100644 index 0000000000000..3a8f26a29e7aa --- /dev/null +++ b/impeller/entity/contents/color_source_contents.cc @@ -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. + +#include "color_source_contents.h" + +#include "impeller/entity/entity.h" +#include "impeller/geometry/matrix.h" + +namespace impeller { + +ColorSourceContents::ColorSourceContents() = default; + +ColorSourceContents::~ColorSourceContents() = default; + +void ColorSourceContents::SetPath(Path path) { + path_ = path; +} + +const Path& ColorSourceContents::GetPath() const { + return path_; +} + +void ColorSourceContents::SetMatrix(Matrix matrix) { + inverse_matrix_ = matrix.Invert(); +} + +const Matrix& ColorSourceContents::GetInverseMatrix() const { + return inverse_matrix_; +} + +std::optional ColorSourceContents::GetCoverage( + const Entity& entity) const { + return path_.GetTransformedBoundingBox(entity.GetTransformation()); +}; + +} // namespace impeller diff --git a/impeller/entity/contents/color_source_contents.h b/impeller/entity/contents/color_source_contents.h new file mode 100644 index 0000000000000..94386bd111ed5 --- /dev/null +++ b/impeller/entity/contents/color_source_contents.h @@ -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. + +#pragma once + +#include "flutter/fml/macros.h" +#include "impeller/entity/contents/contents.h" +#include "impeller/geometry/matrix.h" +#include "impeller/geometry/path.h" + +namespace impeller { + +class ColorSourceContents : public Contents { + public: + ColorSourceContents(); + + ~ColorSourceContents() override; + + void SetPath(Path path); + + void SetMatrix(Matrix matrix); + + // |Contents| + std::optional GetCoverage(const Entity& entity) const override; + + protected: + const Path& GetPath() const; + + const Matrix& GetInverseMatrix() const; + + private: + Path path_; + Matrix inverse_matrix_; + + FML_DISALLOW_COPY_AND_ASSIGN(ColorSourceContents); +}; + +} // namespace impeller diff --git a/impeller/entity/contents/content_context.cc b/impeller/entity/contents/content_context.cc index 60c6a062db168..707a3bf5a79a3 100644 --- a/impeller/entity/contents/content_context.cc +++ b/impeller/entity/contents/content_context.cc @@ -149,12 +149,16 @@ ContentContext::ContentContext(std::shared_ptr context) return; } - gradient_fill_pipelines_[{}] = - CreateDefaultPipeline(*context_); solid_fill_pipelines_[{}] = CreateDefaultPipeline(*context_); + linear_gradient_fill_pipelines_[{}] = + CreateDefaultPipeline(*context_); radial_gradient_fill_pipelines_[{}] = CreateDefaultPipeline(*context_); + sweep_gradient_fill_pipelines_[{}] = + CreateDefaultPipeline(*context_); + rrect_blur_pipelines_[{}] = + CreateDefaultPipeline(*context_); texture_blend_pipelines_[{}] = CreateDefaultPipeline(*context_); blend_color_pipelines_[{}] = @@ -187,15 +191,24 @@ ContentContext::ContentContext(std::shared_ptr context) blend_softlight_pipelines_[{}] = CreateDefaultPipeline(*context_); texture_pipelines_[{}] = CreateDefaultPipeline(*context_); + tiled_texture_pipelines_[{}] = + CreateDefaultPipeline(*context_); gaussian_blur_pipelines_[{}] = CreateDefaultPipeline(*context_); border_mask_blur_pipelines_[{}] = CreateDefaultPipeline(*context_); + color_matrix_color_filter_pipelines_[{}] = + CreateDefaultPipeline(*context_); + linear_to_srgb_filter_pipelines_[{}] = + CreateDefaultPipeline(*context_); + srgb_to_linear_filter_pipelines_[{}] = + CreateDefaultPipeline(*context_); solid_stroke_pipelines_[{}] = CreateDefaultPipeline(*context_); glyph_atlas_pipelines_[{}] = CreateDefaultPipeline(*context_); vertices_pipelines_[{}] = CreateDefaultPipeline(*context_); + atlas_pipelines_[{}] = CreateDefaultPipeline(*context_); // Pipelines that are variants of the base pipelines with custom descriptors. // TODO(98684): Rework this API to allow fetching the descriptor without @@ -232,13 +245,18 @@ std::shared_ptr ContentContext::MakeSubpass( SubpassCallback subpass_callback) const { auto context = GetContext(); - auto subpass_target = RenderTarget::CreateOffscreen(*context, texture_size); + RenderTarget subpass_target; + if (context->SupportsOffscreenMSAA()) { + subpass_target = RenderTarget::CreateOffscreenMSAA(*context, texture_size); + } else { + subpass_target = RenderTarget::CreateOffscreen(*context, texture_size); + } auto subpass_texture = subpass_target.GetRenderTargetTexture(); if (!subpass_texture) { return nullptr; } - auto sub_command_buffer = context->CreateRenderCommandBuffer(); + auto sub_command_buffer = context->CreateCommandBuffer(); sub_command_buffer->SetLabel("Offscreen Contents Command Buffer"); if (!sub_command_buffer) { return nullptr; @@ -254,7 +272,7 @@ std::shared_ptr ContentContext::MakeSubpass( return nullptr; } - if (!sub_renderpass->EncodeCommands(context->GetTransientsAllocator())) { + if (!sub_renderpass->EncodeCommands()) { return nullptr; } diff --git a/impeller/entity/contents/content_context.h b/impeller/entity/contents/content_context.h index fb627bad171d9..7c79b72e746a7 100644 --- a/impeller/entity/contents/content_context.h +++ b/impeller/entity/contents/content_context.h @@ -27,37 +27,54 @@ #include "impeller/entity/advanced_blend_saturation.frag.h" #include "impeller/entity/advanced_blend_screen.frag.h" #include "impeller/entity/advanced_blend_softlight.frag.h" +#include "impeller/entity/atlas_fill.frag.h" +#include "impeller/entity/atlas_fill.vert.h" #include "impeller/entity/blend.frag.h" #include "impeller/entity/blend.vert.h" #include "impeller/entity/border_mask_blur.frag.h" #include "impeller/entity/border_mask_blur.vert.h" +#include "impeller/entity/color_matrix_color_filter.frag.h" +#include "impeller/entity/color_matrix_color_filter.vert.h" #include "impeller/entity/entity.h" #include "impeller/entity/gaussian_blur.frag.h" #include "impeller/entity/gaussian_blur.vert.h" #include "impeller/entity/glyph_atlas.frag.h" #include "impeller/entity/glyph_atlas.vert.h" -#include "impeller/entity/gradient_fill.frag.h" #include "impeller/entity/gradient_fill.vert.h" +#include "impeller/entity/linear_gradient_fill.frag.h" +#include "impeller/entity/linear_to_srgb_filter.frag.h" +#include "impeller/entity/linear_to_srgb_filter.vert.h" #include "impeller/entity/radial_gradient_fill.frag.h" -#include "impeller/entity/radial_gradient_fill.vert.h" +#include "impeller/entity/rrect_blur.frag.h" +#include "impeller/entity/rrect_blur.vert.h" #include "impeller/entity/solid_fill.frag.h" #include "impeller/entity/solid_fill.vert.h" #include "impeller/entity/solid_stroke.frag.h" #include "impeller/entity/solid_stroke.vert.h" +#include "impeller/entity/srgb_to_linear_filter.frag.h" +#include "impeller/entity/srgb_to_linear_filter.vert.h" +#include "impeller/entity/sweep_gradient_fill.frag.h" #include "impeller/entity/texture_fill.frag.h" #include "impeller/entity/texture_fill.vert.h" +#include "impeller/entity/tiled_texture_fill.frag.h" +#include "impeller/entity/tiled_texture_fill.vert.h" #include "impeller/entity/vertices.frag.h" #include "impeller/entity/vertices.vert.h" #include "impeller/renderer/formats.h" namespace impeller { -using GradientFillPipeline = - PipelineT; +using LinearGradientFillPipeline = + PipelineT; using SolidFillPipeline = PipelineT; using RadialGradientFillPipeline = - PipelineT; + PipelineT; +using SweepGradientFillPipeline = + PipelineT; +using BlendPipeline = PipelineT; +using RRectBlurPipeline = + PipelineT; using BlendPipeline = PipelineT; using BlendColorPipeline = PipelineT; @@ -91,16 +108,26 @@ using BlendSoftLightPipeline = PipelineT; using TexturePipeline = PipelineT; +using TiledTexturePipeline = + PipelineT; using GaussianBlurPipeline = PipelineT; using BorderMaskBlurPipeline = PipelineT; +using ColorMatrixColorFilterPipeline = + PipelineT; +using LinearToSrgbFilterPipeline = + PipelineT; +using SrgbToLinearFilterPipeline = + PipelineT; using SolidStrokePipeline = PipelineT; using GlyphAtlasPipeline = PipelineT; using VerticesPipeline = PipelineT; +using AtlasPipeline = PipelineT; // Instead of requiring new shaders for clips, the solid fill stages are used // to redirect writing to the stencil instead of color attachments. using ClipPipeline = PipelineT; @@ -133,20 +160,30 @@ struct ContentContextOptions { class ContentContext { public: - ContentContext(std::shared_ptr context); + explicit ContentContext(std::shared_ptr context); ~ContentContext(); bool IsValid() const; - std::shared_ptr GetGradientFillPipeline( + std::shared_ptr GetLinearGradientFillPipeline( ContentContextOptions opts) const { - return GetPipeline(gradient_fill_pipelines_, opts); + return GetPipeline(linear_gradient_fill_pipelines_, opts); } + std::shared_ptr GetRadialGradientFillPipeline( ContentContextOptions opts) const { return GetPipeline(radial_gradient_fill_pipelines_, opts); } + std::shared_ptr GetRRectBlurPipeline( + ContentContextOptions opts) const { + return GetPipeline(rrect_blur_pipelines_, opts); + } + + std::shared_ptr GetSweepGradientFillPipeline( + ContentContextOptions opts) const { + return GetPipeline(sweep_gradient_fill_pipelines_, opts); + } std::shared_ptr GetSolidFillPipeline( ContentContextOptions opts) const { @@ -162,6 +199,11 @@ class ContentContext { return GetPipeline(texture_pipelines_, opts); } + std::shared_ptr GetTiledTexturePipeline( + ContentContextOptions opts) const { + return GetPipeline(tiled_texture_pipelines_, opts); + } + std::shared_ptr GetGaussianBlurPipeline( ContentContextOptions opts) const { return GetPipeline(gaussian_blur_pipelines_, opts); @@ -172,6 +214,21 @@ class ContentContext { return GetPipeline(border_mask_blur_pipelines_, opts); } + std::shared_ptr GetColorMatrixColorFilterPipeline( + ContentContextOptions opts) const { + return GetPipeline(color_matrix_color_filter_pipelines_, opts); + } + + std::shared_ptr GetLinearToSrgbFilterPipeline( + ContentContextOptions opts) const { + return GetPipeline(linear_to_srgb_filter_pipelines_, opts); + } + + std::shared_ptr GetSrgbToLinearFilterPipeline( + ContentContextOptions opts) const { + return GetPipeline(srgb_to_linear_filter_pipelines_, opts); + } + std::shared_ptr GetSolidStrokePipeline( ContentContextOptions opts) const { return GetPipeline(solid_stroke_pipelines_, opts); @@ -191,6 +248,10 @@ class ContentContext { return GetPipeline(vertices_pipelines_, opts); } + std::shared_ptr GetAtlasPipeline(ContentContextOptions opts) const { + return GetPipeline(atlas_pipelines_, opts); + } + // Advanced blends. std::shared_ptr GetBlendColorPipeline( @@ -290,17 +351,25 @@ class ContentContext { // These are mutable because while the prototypes are created eagerly, any // variants requested from that are lazily created and cached in the variants // map. - mutable Variants gradient_fill_pipelines_; mutable Variants solid_fill_pipelines_; + mutable Variants linear_gradient_fill_pipelines_; mutable Variants radial_gradient_fill_pipelines_; + mutable Variants sweep_gradient_fill_pipelines_; + mutable Variants rrect_blur_pipelines_; mutable Variants texture_blend_pipelines_; mutable Variants texture_pipelines_; + mutable Variants tiled_texture_pipelines_; mutable Variants gaussian_blur_pipelines_; mutable Variants border_mask_blur_pipelines_; + mutable Variants + color_matrix_color_filter_pipelines_; + mutable Variants linear_to_srgb_filter_pipelines_; + mutable Variants srgb_to_linear_filter_pipelines_; mutable Variants solid_stroke_pipelines_; mutable Variants clip_pipelines_; mutable Variants glyph_atlas_pipelines_; mutable Variants vertices_pipelines_; + mutable Variants atlas_pipelines_; // Advanced blends. mutable Variants blend_color_pipelines_; mutable Variants blend_colorburn_pipelines_; diff --git a/impeller/entity/contents/contents.cc b/impeller/entity/contents/contents.cc index c0f4831f1e442..a6c77db7bb5fa 100644 --- a/impeller/entity/contents/contents.cc +++ b/impeller/entity/contents/contents.cc @@ -32,19 +32,19 @@ Contents::~Contents() = default; std::optional Contents::RenderToSnapshot( const ContentContext& renderer, const Entity& entity) const { - auto bounds = GetCoverage(entity); - if (!bounds.has_value()) { + auto coverage = GetCoverage(entity); + if (!coverage.has_value()) { return std::nullopt; } auto texture = renderer.MakeSubpass( - ISize::Ceil(bounds->size), - [&contents = *this, &entity, &bounds](const ContentContext& renderer, - RenderPass& pass) -> bool { + ISize::Ceil(coverage->size), + [&contents = *this, &entity, &coverage](const ContentContext& renderer, + RenderPass& pass) -> bool { Entity sub_entity; sub_entity.SetBlendMode(Entity::BlendMode::kSourceOver); sub_entity.SetTransformation( - Matrix::MakeTranslation(Vector3(-bounds->origin)) * + Matrix::MakeTranslation(Vector3(-coverage->origin)) * entity.GetTransformation()); return contents.Render(renderer, sub_entity, pass); }); @@ -54,7 +54,7 @@ std::optional Contents::RenderToSnapshot( } return Snapshot{.texture = texture, - .transform = Matrix::MakeTranslation(bounds->origin)}; + .transform = Matrix::MakeTranslation(coverage->origin)}; } bool Contents::ShouldRender(const Entity& entity, diff --git a/impeller/entity/contents/filters/blend_filter_contents.cc b/impeller/entity/contents/filters/blend_filter_contents.cc index 5d9dc691c8326..bc115086d291c 100644 --- a/impeller/entity/contents/filters/blend_filter_contents.cc +++ b/impeller/entity/contents/filters/blend_filter_contents.cc @@ -28,29 +28,33 @@ using PipelineProc = std::shared_ptr (ContentContext::*)(ContentContextOptions) const; template -static bool AdvancedBlend(const FilterInput::Vector& inputs, - const ContentContext& renderer, - const Entity& entity, - RenderPass& pass, - const Rect& coverage, - std::optional foreground_color, - PipelineProc pipeline_proc) { +static std::optional AdvancedBlend( + const FilterInput::Vector& inputs, + const ContentContext& renderer, + const Entity& entity, + const Rect& coverage, + std::optional foreground_color, + PipelineProc pipeline_proc) { using VS = typename TPipeline::VertexShader; using FS = typename TPipeline::FragmentShader; + //---------------------------------------------------------------------------- + /// Handle inputs. + /// + const size_t total_inputs = inputs.size() + (foreground_color.has_value() ? 1 : 0); if (total_inputs < 2) { - return false; + return std::nullopt; } auto dst_snapshot = inputs[0]->GetSnapshot(renderer, entity); if (!dst_snapshot.has_value()) { - return true; + return std::nullopt; } auto maybe_dst_uvs = dst_snapshot->GetCoverageUVs(coverage); if (!maybe_dst_uvs.has_value()) { - return true; + return std::nullopt; } auto dst_uvs = maybe_dst_uvs.value(); @@ -59,172 +63,208 @@ static bool AdvancedBlend(const FilterInput::Vector& inputs, if (!foreground_color.has_value()) { src_snapshot = inputs[1]->GetSnapshot(renderer, entity); if (!src_snapshot.has_value()) { - return true; + return std::nullopt; } auto maybe_src_uvs = src_snapshot->GetCoverageUVs(coverage); if (!maybe_src_uvs.has_value()) { - return true; + return std::nullopt; } src_uvs = maybe_src_uvs.value(); } - auto& host_buffer = pass.GetTransientsBuffer(); - - auto size = pass.GetRenderTargetSize(); - VertexBufferBuilder vtx_builder; - vtx_builder.AddVertices({ - {Point(0, 0), dst_uvs[0], src_uvs[0]}, - {Point(size.width, 0), dst_uvs[1], src_uvs[1]}, - {Point(size.width, size.height), dst_uvs[3], src_uvs[3]}, - {Point(0, 0), dst_uvs[0], src_uvs[0]}, - {Point(size.width, size.height), dst_uvs[3], src_uvs[3]}, - {Point(0, size.height), dst_uvs[2], src_uvs[2]}, - }); - auto vtx_buffer = vtx_builder.CreateVertexBuffer(host_buffer); - - auto options = OptionsFromPassAndEntity(pass, entity); - std::shared_ptr pipeline = - std::invoke(pipeline_proc, renderer, options); - - Command cmd; - cmd.label = "Advanced Blend Filter"; - cmd.BindVertices(vtx_buffer); - cmd.pipeline = std::move(pipeline); - - auto sampler = renderer.GetContext()->GetSamplerLibrary()->GetSampler({}); - FS::BindTextureSamplerDst(cmd, dst_snapshot->texture, sampler); - - typename FS::BlendInfo blend_info; - if (foreground_color.has_value()) { - blend_info.color_factor = 1; - blend_info.color = foreground_color.value(); - // This texture will not be sampled from due to the color factor. But this - // is present so that validation doesn't trip on a missing binding. - FS::BindTextureSamplerSrc(cmd, dst_snapshot->texture, sampler); - } else { - blend_info.color_factor = 0; - FS::BindTextureSamplerSrc(cmd, src_snapshot->texture, sampler); - } - auto blend_uniform = host_buffer.EmplaceUniform(blend_info); - FS::BindBlendInfo(cmd, blend_uniform); - - typename VS::FrameInfo frame_info; - frame_info.mvp = Matrix::MakeOrthographic(size); - - auto uniform_view = host_buffer.EmplaceUniform(frame_info); - VS::BindFrameInfo(cmd, uniform_view); - pass.AddCommand(cmd); - - return true; -} - -static bool PipelineBlend(const FilterInput::Vector& inputs, - const ContentContext& renderer, - const Entity& entity, - RenderPass& pass, - const Rect& coverage, - Entity::BlendMode pipeline_blend, - std::optional foreground_color) { - using VS = BlendPipeline::VertexShader; - using FS = BlendPipeline::FragmentShader; - - auto& host_buffer = pass.GetTransientsBuffer(); - - auto sampler = renderer.GetContext()->GetSamplerLibrary()->GetSampler({}); + //---------------------------------------------------------------------------- + /// Render to texture. + /// - Command cmd; - cmd.label = "Pipeline Blend Filter"; - auto options = OptionsFromPass(pass); + ContentContext::SubpassCallback callback = [&](const ContentContext& renderer, + RenderPass& pass) { + auto& host_buffer = pass.GetTransientsBuffer(); - auto add_blend_command = [&](std::optional input) { - if (!input.has_value()) { - return false; - } - auto input_coverage = input->GetCoverage(); - if (!input_coverage.has_value()) { - return false; - } - - FS::BindTextureSamplerSrc(cmd, input->texture, sampler); - - auto size = input->texture->GetSize(); - VertexBufferBuilder vtx_builder; + auto size = pass.GetRenderTargetSize(); + VertexBufferBuilder vtx_builder; vtx_builder.AddVertices({ - {Point(0, 0), Point(0, 0)}, - {Point(size.width, 0), Point(1, 0)}, - {Point(size.width, size.height), Point(1, 1)}, - {Point(0, 0), Point(0, 0)}, - {Point(size.width, size.height), Point(1, 1)}, - {Point(0, size.height), Point(0, 1)}, + {Point(0, 0), dst_uvs[0], src_uvs[0]}, + {Point(size.width, 0), dst_uvs[1], src_uvs[1]}, + {Point(size.width, size.height), dst_uvs[3], src_uvs[3]}, + {Point(0, 0), dst_uvs[0], src_uvs[0]}, + {Point(size.width, size.height), dst_uvs[3], src_uvs[3]}, + {Point(0, size.height), dst_uvs[2], src_uvs[2]}, }); auto vtx_buffer = vtx_builder.CreateVertexBuffer(host_buffer); + + auto options = OptionsFromPassAndEntity(pass, entity); + std::shared_ptr pipeline = + std::invoke(pipeline_proc, renderer, options); + + Command cmd; + cmd.label = "Advanced Blend Filter"; cmd.BindVertices(vtx_buffer); + cmd.pipeline = std::move(pipeline); + + typename FS::BlendInfo blend_info; + + auto sampler = renderer.GetContext()->GetSamplerLibrary()->GetSampler({}); + FS::BindTextureSamplerDst(cmd, dst_snapshot->texture, sampler); + blend_info.dst_y_coord_scale = dst_snapshot->texture->GetYCoordScale(); + + if (foreground_color.has_value()) { + blend_info.color_factor = 1; + blend_info.color = foreground_color.value(); + // This texture will not be sampled from due to the color factor. But + // this is present so that validation doesn't trip on a missing + // binding. + FS::BindTextureSamplerSrc(cmd, dst_snapshot->texture, sampler); + } else { + blend_info.color_factor = 0; + FS::BindTextureSamplerSrc(cmd, src_snapshot->texture, sampler); + blend_info.src_y_coord_scale = src_snapshot->texture->GetYCoordScale(); + } + auto blend_uniform = host_buffer.EmplaceUniform(blend_info); + FS::BindBlendInfo(cmd, blend_uniform); - VS::FrameInfo frame_info; - frame_info.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * - Matrix::MakeTranslation(-coverage.origin) * - input->transform; + typename VS::FrameInfo frame_info; + frame_info.mvp = Matrix::MakeOrthographic(size); auto uniform_view = host_buffer.EmplaceUniform(frame_info); VS::BindFrameInfo(cmd, uniform_view); - pass.AddCommand(cmd); + return true; }; - // Draw the first texture using kSource. - - options.blend_mode = Entity::BlendMode::kSource; - cmd.pipeline = renderer.GetBlendPipeline(options); - if (!add_blend_command(inputs[0]->GetSnapshot(renderer, entity))) { - return true; + auto out_texture = renderer.MakeSubpass(ISize(coverage.size), callback); + if (!out_texture) { + return std::nullopt; } + out_texture->SetLabel("Advanced Blend Filter Texture"); - // Write subsequent textures using the selected blend mode. + return Snapshot{.texture = out_texture, + .transform = Matrix::MakeTranslation(coverage.origin), + .sampler_descriptor = dst_snapshot->sampler_descriptor}; +} + +static std::optional PipelineBlend( + const FilterInput::Vector& inputs, + const ContentContext& renderer, + const Entity& entity, + const Rect& coverage, + Entity::BlendMode pipeline_blend, + std::optional foreground_color) { + using VS = BlendPipeline::VertexShader; + using FS = BlendPipeline::FragmentShader; + + ContentContext::SubpassCallback callback = [&](const ContentContext& renderer, + RenderPass& pass) { + auto& host_buffer = pass.GetTransientsBuffer(); + + auto sampler = renderer.GetContext()->GetSamplerLibrary()->GetSampler({}); + + Command cmd; + cmd.label = "Pipeline Blend Filter"; + auto options = OptionsFromPass(pass); + + auto add_blend_command = [&](std::optional input) { + if (!input.has_value()) { + return false; + } + auto input_coverage = input->GetCoverage(); + if (!input_coverage.has_value()) { + return false; + } + + FS::BindTextureSamplerSrc(cmd, input->texture, sampler); + + auto size = input->texture->GetSize(); + VertexBufferBuilder vtx_builder; + vtx_builder.AddVertices({ + {Point(0, 0), Point(0, 0)}, + {Point(size.width, 0), Point(1, 0)}, + {Point(size.width, size.height), Point(1, 1)}, + {Point(0, 0), Point(0, 0)}, + {Point(size.width, size.height), Point(1, 1)}, + {Point(0, size.height), Point(0, 1)}, + }); + auto vtx_buffer = vtx_builder.CreateVertexBuffer(host_buffer); + cmd.BindVertices(vtx_buffer); + + VS::FrameInfo frame_info; + frame_info.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * + Matrix::MakeTranslation(-coverage.origin) * + input->transform; + + auto uniform_view = host_buffer.EmplaceUniform(frame_info); + VS::BindFrameInfo(cmd, uniform_view); + + pass.AddCommand(cmd); + return true; + }; + + // Draw the first texture using kSource. - if (inputs.size() >= 2) { - options.blend_mode = pipeline_blend; + options.blend_mode = Entity::BlendMode::kSource; cmd.pipeline = renderer.GetBlendPipeline(options); + if (!add_blend_command(inputs[0]->GetSnapshot(renderer, entity))) { + return true; + } + + // Write subsequent textures using the selected blend mode. - for (auto texture_i = inputs.begin() + 1; texture_i < inputs.end(); - texture_i++) { - auto input = texture_i->get()->GetSnapshot(renderer, entity); - if (!add_blend_command(input)) { - return true; + if (inputs.size() >= 2) { + options.blend_mode = pipeline_blend; + cmd.pipeline = renderer.GetBlendPipeline(options); + + for (auto texture_i = inputs.begin() + 1; texture_i < inputs.end(); + texture_i++) { + auto input = texture_i->get()->GetSnapshot(renderer, entity); + if (!add_blend_command(input)) { + return true; + } } } - } - // If a foreground color is set, blend it in. + // If a foreground color is set, blend it in. - if (foreground_color.has_value()) { - auto contents = std::make_shared(); - contents->SetPath(PathBuilder{} - .AddRect(Rect::MakeSize(pass.GetRenderTargetSize())) - .TakePath()); - contents->SetColor(foreground_color.value()); + if (foreground_color.has_value()) { + auto contents = std::make_shared(); + contents->SetPath(PathBuilder{} + .AddRect(Rect::MakeSize(pass.GetRenderTargetSize())) + .TakePath()); + contents->SetColor(foreground_color.value()); - Entity foreground_entity; - foreground_entity.SetBlendMode(pipeline_blend); - foreground_entity.SetContents(contents); - if (!foreground_entity.Render(renderer, pass)) { - return false; + Entity foreground_entity; + foreground_entity.SetBlendMode(pipeline_blend); + foreground_entity.SetContents(contents); + if (!foreground_entity.Render(renderer, pass)) { + return false; + } } + + return true; + }; + + auto out_texture = renderer.MakeSubpass(ISize(coverage.size), callback); + if (!out_texture) { + return std::nullopt; } + out_texture->SetLabel("Pipeline Blend Filter Texture"); - return true; + return Snapshot{ + .texture = out_texture, + .transform = Matrix::MakeTranslation(coverage.origin), + .sampler_descriptor = + inputs[0]->GetSnapshot(renderer, entity)->sampler_descriptor}; } -#define BLEND_CASE(mode) \ - case Entity::BlendMode::k##mode: \ - advanced_blend_proc_ = \ - [](const FilterInput::Vector& inputs, const ContentContext& renderer, \ - const Entity& entity, RenderPass& pass, const Rect& coverage, \ - std::optional fg_color) { \ - PipelineProc p = &ContentContext::GetBlend##mode##Pipeline; \ - return AdvancedBlend( \ - inputs, renderer, entity, pass, coverage, fg_color, p); \ - }; \ +#define BLEND_CASE(mode) \ + case Entity::BlendMode::k##mode: \ + advanced_blend_proc_ = [](const FilterInput::Vector& inputs, \ + const ContentContext& renderer, \ + const Entity& entity, const Rect& coverage, \ + std::optional fg_color) { \ + PipelineProc p = &ContentContext::GetBlend##mode##Pipeline; \ + return AdvancedBlend(inputs, renderer, entity, \ + coverage, fg_color, p); \ + }; \ break; void BlendFilterContents::SetBlendMode(Entity::BlendMode blend_mode) { @@ -265,31 +305,31 @@ void BlendFilterContents::SetForegroundColor(std::optional color) { foreground_color_ = color; } -bool BlendFilterContents::RenderFilter(const FilterInput::Vector& inputs, - const ContentContext& renderer, - const Entity& entity, - RenderPass& pass, - const Rect& coverage) const { +std::optional BlendFilterContents::RenderFilter( + const FilterInput::Vector& inputs, + const ContentContext& renderer, + const Entity& entity, + const Matrix& effect_transform, + const Rect& coverage) const { if (inputs.empty()) { - return true; + return std::nullopt; } if (inputs.size() == 1 && !foreground_color_.has_value()) { // Nothing to blend. - return PipelineBlend(inputs, renderer, entity, pass, coverage, + return PipelineBlend(inputs, renderer, entity, coverage, Entity::BlendMode::kSource, std::nullopt); } if (blend_mode_ <= Entity::BlendMode::kLastPipelineBlendMode) { - return PipelineBlend(inputs, renderer, entity, pass, coverage, blend_mode_, + return PipelineBlend(inputs, renderer, entity, coverage, blend_mode_, foreground_color_); } if (blend_mode_ <= Entity::BlendMode::kLastAdvancedBlendMode) { - return advanced_blend_proc_(inputs, renderer, entity, pass, coverage, + return advanced_blend_proc_(inputs, renderer, entity, coverage, foreground_color_); } - FML_UNREACHABLE(); } diff --git a/impeller/entity/contents/filters/blend_filter_contents.h b/impeller/entity/contents/filters/blend_filter_contents.h index a52b7bf31a133..b0010f2db717e 100644 --- a/impeller/entity/contents/filters/blend_filter_contents.h +++ b/impeller/entity/contents/filters/blend_filter_contents.h @@ -11,13 +11,12 @@ namespace impeller { class BlendFilterContents : public FilterContents { public: - using AdvancedBlendProc = - std::function foreground_color)>; + using AdvancedBlendProc = std::function( + const FilterInput::Vector& inputs, + const ContentContext& renderer, + const Entity& entity, + const Rect& coverage, + std::optional foreground_color)>; BlendFilterContents(); @@ -31,11 +30,11 @@ class BlendFilterContents : public FilterContents { private: // |FilterContents| - bool RenderFilter(const FilterInput::Vector& inputs, - const ContentContext& renderer, - const Entity& entity, - RenderPass& pass, - const Rect& coverage) const override; + std::optional RenderFilter(const FilterInput::Vector& inputs, + const ContentContext& renderer, + const Entity& entity, + const Matrix& effect_transform, + const Rect& coverage) const override; Entity::BlendMode blend_mode_ = Entity::BlendMode::kSourceOver; AdvancedBlendProc advanced_blend_proc_; diff --git a/impeller/entity/contents/filters/border_mask_blur_filter_contents.cc b/impeller/entity/contents/filters/border_mask_blur_filter_contents.cc index 2901b76060bf1..619f457a4e563 100644 --- a/impeller/entity/contents/filters/border_mask_blur_filter_contents.cc +++ b/impeller/entity/contents/filters/border_mask_blur_filter_contents.cc @@ -47,68 +47,90 @@ void BorderMaskBlurFilterContents::SetBlurStyle(BlurStyle blur_style) { } } -bool BorderMaskBlurFilterContents::RenderFilter( +std::optional BorderMaskBlurFilterContents::RenderFilter( const FilterInput::Vector& inputs, const ContentContext& renderer, const Entity& entity, - RenderPass& pass, + const Matrix& effect_transform, const Rect& coverage) const { - if (inputs.empty()) { - return true; - } - using VS = BorderMaskBlurPipeline::VertexShader; using FS = BorderMaskBlurPipeline::FragmentShader; - auto& host_buffer = pass.GetTransientsBuffer(); + //---------------------------------------------------------------------------- + /// Handle inputs. + /// + + if (inputs.empty()) { + return std::nullopt; + } auto input_snapshot = inputs[0]->GetSnapshot(renderer, entity); if (!input_snapshot.has_value()) { - return true; + return std::nullopt; } auto maybe_input_uvs = input_snapshot->GetCoverageUVs(coverage); if (!maybe_input_uvs.has_value()) { - return true; + return std::nullopt; } auto input_uvs = maybe_input_uvs.value(); - VertexBufferBuilder vtx_builder; - vtx_builder.AddVertices({ - {Point(0, 0), input_uvs[0]}, - {Point(1, 0), input_uvs[1]}, - {Point(1, 1), input_uvs[3]}, - {Point(0, 0), input_uvs[0]}, - {Point(1, 1), input_uvs[3]}, - {Point(0, 1), input_uvs[2]}, - }); - auto vtx_buffer = vtx_builder.CreateVertexBuffer(host_buffer); - - Command cmd; - cmd.label = "Border Mask Blur Filter"; - auto options = OptionsFromPass(pass); - options.blend_mode = Entity::BlendMode::kSource; - cmd.pipeline = renderer.GetBorderMaskBlurPipeline(options); - cmd.BindVertices(vtx_buffer); - - VS::FrameInfo frame_info; - frame_info.mvp = Matrix::MakeOrthographic(ISize(1, 1)); - frame_info.sigma_uv = Vector2(sigma_x_.sigma, sigma_y_.sigma).Abs() / - input_snapshot->texture->GetSize(); - frame_info.src_factor = src_color_factor_; - frame_info.inner_blur_factor = inner_blur_factor_; - frame_info.outer_blur_factor = outer_blur_factor_; - auto uniform_view = host_buffer.EmplaceUniform(frame_info); - VS::BindFrameInfo(cmd, uniform_view); - - auto sampler = renderer.GetContext()->GetSamplerLibrary()->GetSampler({}); - FS::BindTextureSampler(cmd, input_snapshot->texture, sampler); - - return pass.AddCommand(std::move(cmd)); + //---------------------------------------------------------------------------- + /// Render to texture. + /// + + ContentContext::SubpassCallback callback = [&](const ContentContext& renderer, + RenderPass& pass) { + auto& host_buffer = pass.GetTransientsBuffer(); + + VertexBufferBuilder vtx_builder; + vtx_builder.AddVertices({ + {Point(0, 0), input_uvs[0]}, + {Point(1, 0), input_uvs[1]}, + {Point(1, 1), input_uvs[3]}, + {Point(0, 0), input_uvs[0]}, + {Point(1, 1), input_uvs[3]}, + {Point(0, 1), input_uvs[2]}, + }); + auto vtx_buffer = vtx_builder.CreateVertexBuffer(host_buffer); + + Command cmd; + cmd.label = "Border Mask Blur Filter"; + auto options = OptionsFromPass(pass); + options.blend_mode = Entity::BlendMode::kSource; + cmd.pipeline = renderer.GetBorderMaskBlurPipeline(options); + cmd.BindVertices(vtx_buffer); + + VS::FrameInfo frame_info; + frame_info.mvp = Matrix::MakeOrthographic(ISize(1, 1)); + + auto sigma = effect_transform * Vector2(sigma_x_.sigma, sigma_y_.sigma); + frame_info.sigma_uv = sigma.Abs() / input_snapshot->texture->GetSize(); + frame_info.src_factor = src_color_factor_; + frame_info.inner_blur_factor = inner_blur_factor_; + frame_info.outer_blur_factor = outer_blur_factor_; + auto uniform_view = host_buffer.EmplaceUniform(frame_info); + VS::BindFrameInfo(cmd, uniform_view); + + auto sampler = renderer.GetContext()->GetSamplerLibrary()->GetSampler({}); + FS::BindTextureSampler(cmd, input_snapshot->texture, sampler); + + return pass.AddCommand(std::move(cmd)); + }; + + auto out_texture = renderer.MakeSubpass(ISize(coverage.size), callback); + if (!out_texture) { + return std::nullopt; + } + out_texture->SetLabel("BorderMaskBlurFilter Texture"); + + return Snapshot{.texture = out_texture, + .transform = Matrix::MakeTranslation(coverage.origin)}; } std::optional BorderMaskBlurFilterContents::GetFilterCoverage( const FilterInput::Vector& inputs, - const Entity& entity) const { + const Entity& entity, + const Matrix& effect_transform) const { if (inputs.empty()) { return std::nullopt; } @@ -117,7 +139,7 @@ std::optional BorderMaskBlurFilterContents::GetFilterCoverage( if (!coverage.has_value()) { return std::nullopt; } - auto transform = inputs[0]->GetTransform(entity); + auto transform = inputs[0]->GetTransform(entity) * effect_transform; auto transformed_blur_vector = transform.TransformDirection(Vector2(Radius{sigma_x_}.radius, 0)).Abs() + transform.TransformDirection(Vector2(0, Radius{sigma_y_}.radius)).Abs(); diff --git a/impeller/entity/contents/filters/border_mask_blur_filter_contents.h b/impeller/entity/contents/filters/border_mask_blur_filter_contents.h index 6aa95d87046e4..9d971f5a11fdf 100644 --- a/impeller/entity/contents/filters/border_mask_blur_filter_contents.h +++ b/impeller/entity/contents/filters/border_mask_blur_filter_contents.h @@ -22,16 +22,19 @@ class BorderMaskBlurFilterContents final : public FilterContents { void SetBlurStyle(BlurStyle blur_style); // |FilterContents| - std::optional GetFilterCoverage(const FilterInput::Vector& inputs, - const Entity& entity) const override; + std::optional GetFilterCoverage( + const FilterInput::Vector& inputs, + const Entity& entity, + const Matrix& effect_transform) const override; private: // |FilterContents| - bool RenderFilter(const FilterInput::Vector& input_textures, - const ContentContext& renderer, - const Entity& entity, - RenderPass& pass, - const Rect& coverage) const override; + std::optional RenderFilter( + const FilterInput::Vector& input_textures, + const ContentContext& renderer, + const Entity& entity, + const Matrix& effect_transform, + const Rect& coverage) const override; Sigma sigma_x_; Sigma sigma_y_; diff --git a/impeller/entity/contents/filters/color_matrix_filter_contents.cc b/impeller/entity/contents/filters/color_matrix_filter_contents.cc new file mode 100644 index 0000000000000..78409bad759fd --- /dev/null +++ b/impeller/entity/contents/filters/color_matrix_filter_contents.cc @@ -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. + +#include "impeller/entity/contents/filters/color_matrix_filter_contents.h" + +#include + +#include "impeller/entity/contents/content_context.h" +#include "impeller/entity/contents/contents.h" +#include "impeller/geometry/point.h" +#include "impeller/geometry/vector.h" +#include "impeller/renderer/render_pass.h" +#include "impeller/renderer/sampler_library.h" + +namespace impeller { + +ColorMatrixFilterContents::ColorMatrixFilterContents() = default; + +ColorMatrixFilterContents::~ColorMatrixFilterContents() = default; + +void ColorMatrixFilterContents::SetMatrix(const ColorMatrix& matrix) { + matrix_ = matrix; +} + +std::optional ColorMatrixFilterContents::RenderFilter( + const FilterInput::Vector& inputs, + const ContentContext& renderer, + const Entity& entity, + const Matrix& effect_transform, + const Rect& coverage) const { + using VS = ColorMatrixColorFilterPipeline::VertexShader; + using FS = ColorMatrixColorFilterPipeline::FragmentShader; + + //---------------------------------------------------------------------------- + /// Handle inputs. + /// + + if (inputs.empty()) { + return std::nullopt; + } + + auto input_snapshot = inputs[0]->GetSnapshot(renderer, entity); + if (!input_snapshot.has_value()) { + return std::nullopt; + } + + //---------------------------------------------------------------------------- + /// Render to texture. + /// + + ContentContext::SubpassCallback callback = [&](const ContentContext& renderer, + RenderPass& pass) { + Command cmd; + cmd.label = "Color Matrix Filter"; + + auto options = OptionsFromPass(pass); + options.blend_mode = Entity::BlendMode::kSource; + cmd.pipeline = renderer.GetColorMatrixColorFilterPipeline(options); + + VertexBufferBuilder vtx_builder; + vtx_builder.AddVertices({ + {Point(0, 0)}, + {Point(1, 0)}, + {Point(1, 1)}, + {Point(0, 0)}, + {Point(1, 1)}, + {Point(0, 1)}, + }); + auto& host_buffer = pass.GetTransientsBuffer(); + auto vtx_buffer = vtx_builder.CreateVertexBuffer(host_buffer); + cmd.BindVertices(vtx_buffer); + + VS::FrameInfo frame_info; + frame_info.mvp = Matrix::MakeOrthographic(ISize(1, 1)); + + FS::FragInfo frag_info; + const float* matrix = matrix_.array; + frag_info.color_v = Vector4(matrix[4], matrix[9], matrix[14], matrix[19]); + // clang-format off + frag_info.color_m = + Matrix( + matrix[ 0], matrix[ 1], matrix[ 2], matrix[ 3], + matrix[ 5], matrix[ 6], matrix[ 7], matrix[ 8], + matrix[10], matrix[11], matrix[12], matrix[13], + matrix[15], matrix[16], matrix[17], matrix[18] + ); + // clang-format on + + auto sampler = renderer.GetContext()->GetSamplerLibrary()->GetSampler({}); + FS::BindInputTexture(cmd, input_snapshot->texture, sampler); + FS::BindFragInfo(cmd, host_buffer.EmplaceUniform(frag_info)); + + VS::BindFrameInfo(cmd, host_buffer.EmplaceUniform(frame_info)); + + return pass.AddCommand(std::move(cmd)); + }; + + auto out_texture = + renderer.MakeSubpass(input_snapshot->texture->GetSize(), callback); + if (!out_texture) { + return std::nullopt; + } + out_texture->SetLabel("ColorMatrixFilter Texture"); + + return Snapshot{.texture = out_texture, + .transform = input_snapshot->transform, + .sampler_descriptor = input_snapshot->sampler_descriptor}; +} + +} // namespace impeller diff --git a/impeller/entity/contents/filters/color_matrix_filter_contents.h b/impeller/entity/contents/filters/color_matrix_filter_contents.h new file mode 100644 index 0000000000000..b725249019106 --- /dev/null +++ b/impeller/entity/contents/filters/color_matrix_filter_contents.h @@ -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. + +#pragma once + +#include +#include + +#include "impeller/entity/contents/filters/filter_contents.h" +#include "impeller/entity/contents/filters/inputs/filter_input.h" + +namespace impeller { + +// Look at example at: https://github.com/flutter/impeller/pull/132 + +class ColorMatrixFilterContents final : public FilterContents { + public: + ColorMatrixFilterContents(); + + ~ColorMatrixFilterContents() override; + + void SetMatrix(const ColorMatrix& matrix); + + private: + // |FilterContents| + std::optional RenderFilter( + const FilterInput::Vector& input_textures, + const ContentContext& renderer, + const Entity& entity, + const Matrix& effect_transform, + const Rect& coverage) const override; + + ColorMatrix matrix_; + + FML_DISALLOW_COPY_AND_ASSIGN(ColorMatrixFilterContents); +}; + +} // namespace impeller diff --git a/impeller/entity/contents/filters/filter_contents.cc b/impeller/entity/contents/filters/filter_contents.cc index 36cc3ee94e718..ef5047966e71d 100644 --- a/impeller/entity/contents/filters/filter_contents.cc +++ b/impeller/entity/contents/filters/filter_contents.cc @@ -16,8 +16,11 @@ #include "impeller/entity/contents/content_context.h" #include "impeller/entity/contents/filters/blend_filter_contents.h" #include "impeller/entity/contents/filters/border_mask_blur_filter_contents.h" +#include "impeller/entity/contents/filters/color_matrix_filter_contents.h" #include "impeller/entity/contents/filters/gaussian_blur_filter_contents.h" #include "impeller/entity/contents/filters/inputs/filter_input.h" +#include "impeller/entity/contents/filters/linear_to_srgb_filter_contents.h" +#include "impeller/entity/contents/filters/srgb_to_linear_filter_contents.h" #include "impeller/entity/contents/texture_contents.h" #include "impeller/entity/entity.h" #include "impeller/geometry/path_builder.h" @@ -74,13 +77,19 @@ std::shared_ptr FilterContents::MakeDirectionalGaussianBlur( Sigma sigma, Vector2 direction, BlurStyle blur_style, - FilterInput::Ref source_override) { + Entity::TileMode tile_mode, + FilterInput::Ref source_override, + Sigma secondary_sigma, + const Matrix& effect_transform) { auto blur = std::make_shared(); blur->SetInputs({input}); blur->SetSigma(sigma); blur->SetDirection(direction); blur->SetBlurStyle(blur_style); + blur->SetTileMode(tile_mode); blur->SetSourceOverride(source_override); + blur->SetSecondarySigma(secondary_sigma); + blur->SetEffectTransform(effect_transform); return blur; } @@ -88,11 +97,15 @@ std::shared_ptr FilterContents::MakeGaussianBlur( FilterInput::Ref input, Sigma sigma_x, Sigma sigma_y, - BlurStyle blur_style) { + BlurStyle blur_style, + Entity::TileMode tile_mode, + const Matrix& effect_transform) { auto x_blur = MakeDirectionalGaussianBlur(input, sigma_x, Point(1, 0), - BlurStyle::kNormal); + BlurStyle::kNormal, tile_mode, + nullptr, {}, effect_transform); auto y_blur = MakeDirectionalGaussianBlur(FilterInput::Make(x_blur), sigma_y, - Point(0, 1), blur_style, input); + Point(0, 1), blur_style, tile_mode, + input, sigma_x, effect_transform); return y_blur; } @@ -100,11 +113,36 @@ std::shared_ptr FilterContents::MakeBorderMaskBlur( FilterInput::Ref input, Sigma sigma_x, Sigma sigma_y, - BlurStyle blur_style) { + BlurStyle blur_style, + const Matrix& effect_transform) { auto filter = std::make_shared(); filter->SetInputs({input}); filter->SetSigma(sigma_x, sigma_y); filter->SetBlurStyle(blur_style); + filter->SetEffectTransform(effect_transform); + return filter; +} + +std::shared_ptr FilterContents::MakeColorMatrix( + FilterInput::Ref input, + const ColorMatrix& matrix) { + auto filter = std::make_shared(); + filter->SetInputs({input}); + filter->SetMatrix(matrix); + return filter; +} + +std::shared_ptr FilterContents::MakeLinearToSrgbFilter( + FilterInput::Ref input) { + auto filter = std::make_shared(); + filter->SetInputs({input}); + return filter; +} + +std::shared_ptr FilterContents::MakeSrgbToLinearFilter( + FilterInput::Ref input) { + auto filter = std::make_shared(); + filter->SetInputs({input}); return filter; } @@ -120,6 +158,10 @@ void FilterContents::SetCoverageCrop(std::optional coverage_crop) { coverage_crop_ = coverage_crop; } +void FilterContents::SetEffectTransform(Matrix effect_transform) { + effect_transform_ = effect_transform.Basis(); +} + bool FilterContents::Render(const ContentContext& renderer, const Entity& entity, RenderPass& pass) const { @@ -138,21 +180,22 @@ bool FilterContents::Render(const ContentContext& renderer, // Draw the result texture, respecting the transform and clip stack. - auto contents = std::make_shared(); - contents->SetPath( - PathBuilder{}.AddRect(filter_coverage.value()).GetCurrentPath()); + auto texture_rect = Rect::MakeSize(snapshot.texture->GetSize()); + auto contents = TextureContents::MakeRect(texture_rect); contents->SetTexture(snapshot.texture); - contents->SetSourceRect(Rect::MakeSize(snapshot.texture->GetSize())); + contents->SetSamplerDescriptor(snapshot.sampler_descriptor); + contents->SetSourceRect(texture_rect); Entity e; e.SetBlendMode(entity.GetBlendMode()); e.SetStencilDepth(entity.GetStencilDepth()); + e.SetTransformation(snapshot.transform); return contents->Render(renderer, e, pass); } std::optional FilterContents::GetLocalCoverage( const Entity& local_entity) const { - auto coverage = GetFilterCoverage(inputs_, local_entity); + auto coverage = GetFilterCoverage(inputs_, local_entity, effect_transform_); if (coverage_crop_.has_value() && coverage.has_value()) { coverage = coverage->Intersection(coverage_crop_.value()); } @@ -170,7 +213,8 @@ std::optional FilterContents::GetCoverage(const Entity& entity) const { std::optional FilterContents::GetFilterCoverage( const FilterInput::Vector& inputs, - const Entity& entity) const { + const Entity& entity, + const Matrix& effect_transform) const { // The default coverage of FilterContents is just the union of its inputs' // coverage. FilterContents implementations may choose to adjust this // coverage depending on the use case. @@ -206,20 +250,8 @@ std::optional FilterContents::RenderToSnapshot( return std::nullopt; } - // Render the filter into a new texture. - auto texture = renderer.MakeSubpass( - ISize(coverage->size), - [=](const ContentContext& renderer, RenderPass& pass) -> bool { - return RenderFilter(inputs_, renderer, entity_with_local_transform, - pass, coverage.value()); - }); - - if (!texture) { - return std::nullopt; - } - - return Snapshot{.texture = texture, - .transform = Matrix::MakeTranslation(coverage->origin)}; + return RenderFilter(inputs_, renderer, entity_with_local_transform, + effect_transform_, coverage.value()); } Matrix FilterContents::GetLocalTransform() const { diff --git a/impeller/entity/contents/filters/filter_contents.h b/impeller/entity/contents/filters/filter_contents.h index 43d2af2512b7f..acffb71e8e540 100644 --- a/impeller/entity/contents/filters/filter_contents.h +++ b/impeller/entity/contents/filters/filter_contents.h @@ -11,6 +11,7 @@ #include "impeller/entity/contents/filters/inputs/filter_input.h" #include "impeller/entity/entity.h" +#include "impeller/geometry/sigma.h" #include "impeller/renderer/formats.h" namespace impeller { @@ -30,55 +31,9 @@ class FilterContents : public Contents { kInner, }; - /// For filters that use a Gaussian distribution, this is the `Radius` size to - /// use per `Sigma` (standard deviation). - /// - /// This cutoff (sqrt(3)) is taken from Flutter and Skia (where the - /// multiplicative inverse of this constant is used (1 / sqrt(3)): - /// https://api.flutter.dev/flutter/dart-ui/Shadow/convertRadiusToSigma.html - /// - /// In practice, this value is somewhat arbitrary, and can be changed to a - /// higher number to integrate more of the Gaussian function and render higher - /// quality blurs (with exponentially diminishing returns for the same sigma - /// input). Making this value any lower results in a noticable loss of - /// quality in the blur. - constexpr static float kKernelRadiusPerSigma = 1.73205080757; - - struct Radius; - - /// @brief In filters that use Gaussian distributions, "sigma" is a size of - /// one standard deviation in terms of the local space pixel grid of - /// the filter input. In other words, this determines how wide the - /// distribution stretches. - struct Sigma { - Scalar sigma = 0.0; - - constexpr Sigma() = default; - - explicit constexpr Sigma(Scalar p_sigma) : sigma(p_sigma) {} - - constexpr operator Radius() const { - return Radius{sigma > 0.5f ? (sigma - 0.5f) * kKernelRadiusPerSigma - : 0.0f}; - }; - }; - - /// @brief For convolution filters, the "radius" is the size of the - /// convolution kernel to use on the local space pixel grid of the - /// filter input. - /// For Gaussian blur kernels, this unit has a linear - /// relationship with `Sigma`. See `kKernelRadiusPerSigma` for - /// details on how this relationship works. - struct Radius { - Scalar radius = 0.0; - - constexpr Radius() = default; - - explicit constexpr Radius(Scalar p_radius) : radius(p_radius) {} - - constexpr operator Sigma() const { - return Sigma{radius > 0 ? radius / kKernelRadiusPerSigma + 0.5f : 0.0f}; - }; + // Domain is kRGBA, we may decide to support more color modes later. + struct ColorMatrix { + float array[20]; }; static std::shared_ptr MakeBlend( @@ -91,19 +46,35 @@ class FilterContents : public Contents { Sigma sigma, Vector2 direction, BlurStyle blur_style = BlurStyle::kNormal, - FilterInput::Ref alpha_mask = nullptr); + Entity::TileMode tile_mode = Entity::TileMode::kDecal, + FilterInput::Ref alpha_mask = nullptr, + Sigma secondary_sigma = {}, + const Matrix& effect_transform = Matrix()); static std::shared_ptr MakeGaussianBlur( FilterInput::Ref input, Sigma sigma_x, Sigma sigma_y, - BlurStyle blur_style = BlurStyle::kNormal); + BlurStyle blur_style = BlurStyle::kNormal, + Entity::TileMode tile_mode = Entity::TileMode::kDecal, + const Matrix& effect_transform = Matrix()); static std::shared_ptr MakeBorderMaskBlur( FilterInput::Ref input, Sigma sigma_x, Sigma sigma_y, - BlurStyle blur_style = BlurStyle::kNormal); + BlurStyle blur_style = BlurStyle::kNormal, + const Matrix& effect_transform = Matrix()); + + static std::shared_ptr MakeColorMatrix( + FilterInput::Ref input, + const ColorMatrix& matrix); + + static std::shared_ptr MakeLinearToSrgbFilter( + FilterInput::Ref input); + + static std::shared_ptr MakeSrgbToLinearFilter( + FilterInput::Ref input); FilterContents(); @@ -119,6 +90,10 @@ class FilterContents : public Contents { /// @brief Screen space bounds to use for cropping the filter output. void SetCoverageCrop(std::optional coverage_crop); + /// @brief Sets the transform which gets appended to the effect of this + /// filter. Note that this is in addition to the entity's transform. + void SetEffectTransform(Matrix effect_transform); + // |Contents| bool Render(const ContentContext& renderer, const Entity& entity, @@ -138,20 +113,22 @@ class FilterContents : public Contents { private: virtual std::optional GetFilterCoverage( const FilterInput::Vector& inputs, - const Entity& entity) const; + const Entity& entity, + const Matrix& effect_transform) const; - /// @brief Takes a set of zero or more input textures and writes to an output - /// texture. - virtual bool RenderFilter(const FilterInput::Vector& inputs, - const ContentContext& renderer, - const Entity& entity, - RenderPass& pass, - const Rect& coverage) const = 0; + /// @brief Converts zero or more filter inputs into a new texture. + virtual std::optional RenderFilter( + const FilterInput::Vector& inputs, + const ContentContext& renderer, + const Entity& entity, + const Matrix& effect_transform, + const Rect& coverage) const = 0; std::optional GetLocalCoverage(const Entity& local_entity) const; FilterInput::Vector inputs_; std::optional coverage_crop_; + Matrix effect_transform_; FML_DISALLOW_COPY_AND_ASSIGN(FilterContents); }; diff --git a/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc b/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc index 1be110668a569..47dadff5c895a 100644 --- a/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc +++ b/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc @@ -4,15 +4,19 @@ #include "impeller/entity/contents/filters/gaussian_blur_filter_contents.h" +#include #include +#include "impeller/base/strings.h" #include "impeller/base/validation.h" #include "impeller/entity/contents/content_context.h" #include "impeller/entity/contents/filters/filter_contents.h" #include "impeller/geometry/rect.h" #include "impeller/geometry/scalar.h" +#include "impeller/renderer/command_buffer.h" #include "impeller/renderer/formats.h" #include "impeller/renderer/render_pass.h" +#include "impeller/renderer/render_target.h" #include "impeller/renderer/sampler_descriptor.h" #include "impeller/renderer/sampler_library.h" @@ -25,17 +29,13 @@ DirectionalGaussianBlurFilterContents:: ~DirectionalGaussianBlurFilterContents() = default; void DirectionalGaussianBlurFilterContents::SetSigma(Sigma sigma) { - if (sigma.sigma < kEhCloseEnough) { - // This cutoff is an implementation detail of the blur that's tied to the - // fragment shader. When the blur is set to 0, having a value slightly above - // zero makes the shader do 1 finite sample to pass the image through with - // no blur (while retaining correct alpha mask behavior). - blur_sigma_ = Sigma{kEhCloseEnough}; - return; - } blur_sigma_ = sigma; } +void DirectionalGaussianBlurFilterContents::SetSecondarySigma(Sigma sigma) { + secondary_blur_sigma_ = sigma; +} + void DirectionalGaussianBlurFilterContents::SetDirection(Vector2 direction) { blur_direction_ = direction.Normalize(); if (blur_direction_.IsZero()) { @@ -70,102 +70,199 @@ void DirectionalGaussianBlurFilterContents::SetBlurStyle(BlurStyle blur_style) { } } +void DirectionalGaussianBlurFilterContents::SetTileMode( + Entity::TileMode tile_mode) { + tile_mode_ = tile_mode; +} + void DirectionalGaussianBlurFilterContents::SetSourceOverride( FilterInput::Ref source_override) { source_override_ = source_override; } -bool DirectionalGaussianBlurFilterContents::RenderFilter( +std::optional DirectionalGaussianBlurFilterContents::RenderFilter( const FilterInput::Vector& inputs, const ContentContext& renderer, const Entity& entity, - RenderPass& pass, + const Matrix& effect_transform, const Rect& coverage) const { - if (inputs.empty()) { - return true; - } - using VS = GaussianBlurPipeline::VertexShader; using FS = GaussianBlurPipeline::FragmentShader; - auto& host_buffer = pass.GetTransientsBuffer(); + //---------------------------------------------------------------------------- + /// Handle inputs. + /// + + if (inputs.empty()) { + return std::nullopt; + } - // Input 0 snapshot and UV mapping. + // Input 0 snapshot. auto input_snapshot = inputs[0]->GetSnapshot(renderer, entity); if (!input_snapshot.has_value()) { - return true; + return std::nullopt; } - auto maybe_input_uvs = input_snapshot->GetCoverageUVs(coverage); - if (!maybe_input_uvs.has_value()) { - return true; + + if (blur_sigma_.sigma < kEhCloseEnough) { + return input_snapshot.value(); // No blur to render. + } + + auto radius = Radius{blur_sigma_}.radius; + + auto transform = entity.GetTransformation() * effect_transform; + auto transformed_blur_radius = + transform.TransformDirection(blur_direction_ * radius); + + auto transformed_blur_radius_length = transformed_blur_radius.GetLength(); + + // If the radius length is < .5, the shader will take at most 1 sample, + // resulting in no blur. + if (transformed_blur_radius_length < .5) { + return input_snapshot.value(); // No blur to render. } - auto input_uvs = maybe_input_uvs.value(); - // Source override snapshot and UV mapping. + // A matrix that rotates the snapshot space such that the blur direction is + // +X. + auto texture_rotate = Matrix::MakeRotationZ( + transformed_blur_radius.Normalize().AngleTo({1, 0})); + + // Converts local pass space to screen space. This is just the snapshot space + // rotated such that the blur direction is +X. + auto pass_transform = texture_rotate * input_snapshot->transform; + + // The pass texture coverage, but rotated such that the blur is in the +X + // direction, and expanded to include the blur radius. This is used for UV + // projection and as a source for the pass size. Note that it doesn't matter + // which direction the space is rotated in when grabbing the pass size. + auto pass_texture_rect = Rect::MakeSize(input_snapshot->texture->GetSize()) + .TransformBounds(pass_transform); + pass_texture_rect.origin.x -= transformed_blur_radius_length; + pass_texture_rect.size.width += transformed_blur_radius_length * 2; + + // Source override snapshot. auto source = source_override_ ? source_override_ : inputs[0]; auto source_snapshot = source->GetSnapshot(renderer, entity); if (!source_snapshot.has_value()) { - return true; + return std::nullopt; } - auto maybe_source_uvs = source_snapshot->GetCoverageUVs(coverage); - if (!maybe_source_uvs.has_value()) { - return true; + + // UV mapping. + + auto pass_uv_project = [&texture_rotate, + &pass_texture_rect](Snapshot& input) { + auto uv_matrix = Matrix::MakeScale(1 / Vector2(input.texture->GetSize())) * + (texture_rotate * input.transform).Invert(); + return pass_texture_rect.GetTransformedPoints(uv_matrix); + }; + + auto input_uvs = pass_uv_project(input_snapshot.value()); + + auto source_uvs = pass_uv_project(source_snapshot.value()); + + //---------------------------------------------------------------------------- + /// Render to texture. + /// + + ContentContext::SubpassCallback callback = [&](const ContentContext& renderer, + RenderPass& pass) { + auto& host_buffer = pass.GetTransientsBuffer(); + + VertexBufferBuilder vtx_builder; + vtx_builder.AddVertices({ + {Point(0, 0), input_uvs[0], source_uvs[0]}, + {Point(1, 0), input_uvs[1], source_uvs[1]}, + {Point(1, 1), input_uvs[3], source_uvs[3]}, + {Point(0, 0), input_uvs[0], source_uvs[0]}, + {Point(1, 1), input_uvs[3], source_uvs[3]}, + {Point(0, 1), input_uvs[2], source_uvs[2]}, + }); + auto vtx_buffer = vtx_builder.CreateVertexBuffer(host_buffer); + + VS::FrameInfo frame_info; + frame_info.mvp = Matrix::MakeOrthographic(ISize(1, 1)); + + FS::FragInfo frag_info; + frag_info.texture_sampler_y_coord_scale = + input_snapshot->texture->GetYCoordScale(); + frag_info.alpha_mask_sampler_y_coord_scale = + source_snapshot->texture->GetYCoordScale(); + + auto r = Radius{transformed_blur_radius_length}; + frag_info.blur_sigma = Sigma{r}.sigma; + frag_info.blur_radius = r.radius; + + // The blur direction is in input UV space. + frag_info.blur_direction = + pass_transform.Invert().TransformDirection(Vector2(1, 0)).Normalize(); + + frag_info.tile_mode = static_cast(tile_mode_); + frag_info.src_factor = src_color_factor_; + frag_info.inner_blur_factor = inner_blur_factor_; + frag_info.outer_blur_factor = outer_blur_factor_; + frag_info.texture_size = Point(input_snapshot->GetCoverage().value().size); + + Command cmd; + cmd.label = SPrintF("Gaussian Blur Filter (Radius=%.2f)", + transformed_blur_radius_length); + auto options = OptionsFromPass(pass); + options.blend_mode = Entity::BlendMode::kSource; + cmd.pipeline = renderer.GetGaussianBlurPipeline(options); + cmd.BindVertices(vtx_buffer); + + FS::BindTextureSampler( + cmd, input_snapshot->texture, + renderer.GetContext()->GetSamplerLibrary()->GetSampler( + input_snapshot->sampler_descriptor)); + FS::BindAlphaMaskSampler( + cmd, source_snapshot->texture, + renderer.GetContext()->GetSamplerLibrary()->GetSampler( + source_snapshot->sampler_descriptor)); + VS::BindFrameInfo(cmd, host_buffer.EmplaceUniform(frame_info)); + FS::BindFragInfo(cmd, host_buffer.EmplaceUniform(frag_info)); + + return pass.AddCommand(cmd); + }; + + Vector2 scale; + { + scale.x = + 1.0 / + std::ceil(std::log2(std::max(2.0f, transformed_blur_radius_length))); + + Scalar y_radius = std::abs(pass_transform.GetDirectionScale(Vector2( + 0, source_override_ ? Radius{secondary_blur_sigma_}.radius : 1))); + scale.y = 1.0 / std::ceil(std::log2(std::max(2.0f, y_radius))); } - auto source_uvs = maybe_source_uvs.value(); - - VertexBufferBuilder vtx_builder; - vtx_builder.AddVertices({ - {Point(0, 0), input_uvs[0], source_uvs[0]}, - {Point(1, 0), input_uvs[1], source_uvs[1]}, - {Point(1, 1), input_uvs[3], source_uvs[3]}, - {Point(0, 0), input_uvs[0], source_uvs[0]}, - {Point(1, 1), input_uvs[3], source_uvs[3]}, - {Point(0, 1), input_uvs[2], source_uvs[2]}, - }); - auto vtx_buffer = vtx_builder.CreateVertexBuffer(host_buffer); - - auto transformed_blur = entity.GetTransformation().TransformDirection( - blur_direction_ * blur_sigma_.sigma); - - VS::FrameInfo frame_info; - frame_info.texture_size = Point(input_snapshot->GetCoverage().value().size); - frame_info.blur_sigma = transformed_blur.GetLength(); - frame_info.blur_radius = Radius{Sigma{frame_info.blur_sigma}}.radius; - frame_info.blur_direction = input_snapshot->transform.Invert() - .TransformDirection(transformed_blur) - .Normalize(); - frame_info.src_factor = src_color_factor_; - frame_info.inner_blur_factor = inner_blur_factor_; - frame_info.outer_blur_factor = outer_blur_factor_; - SamplerDescriptor sampler_desc; - sampler_desc.min_filter = MinMagFilter::kLinear; - sampler_desc.mag_filter = MinMagFilter::kLinear; - auto sampler = - renderer.GetContext()->GetSamplerLibrary()->GetSampler(sampler_desc); + Vector2 scaled_size = pass_texture_rect.size * scale; + ISize floored_size = ISize(scaled_size.x, scaled_size.y); - Command cmd; - cmd.label = "Gaussian Blur Filter"; - auto options = OptionsFromPass(pass); - options.blend_mode = Entity::BlendMode::kSource; - cmd.pipeline = renderer.GetGaussianBlurPipeline(options); - cmd.BindVertices(vtx_buffer); + auto out_texture = renderer.MakeSubpass(floored_size, callback); - FS::BindTextureSampler(cmd, input_snapshot->texture, sampler); - FS::BindAlphaMaskSampler(cmd, source_snapshot->texture, sampler); + if (!out_texture) { + return std::nullopt; + } + out_texture->SetLabel("DirectionalGaussianBlurFilter Texture"); - frame_info.mvp = Matrix::MakeOrthographic(ISize(1, 1)); - auto uniform_view = host_buffer.EmplaceUniform(frame_info); - VS::BindFrameInfo(cmd, uniform_view); + SamplerDescriptor sampler_desc; + sampler_desc.min_filter = MinMagFilter::kLinear; + sampler_desc.mag_filter = MinMagFilter::kLinear; - return pass.AddCommand(cmd); + return Snapshot{ + .texture = out_texture, + .transform = + texture_rotate.Invert() * + Matrix::MakeTranslation(pass_texture_rect.origin) * + Matrix::MakeScale((1 / scale) * (scaled_size / floored_size)), + .sampler_descriptor = sampler_desc}; } std::optional DirectionalGaussianBlurFilterContents::GetFilterCoverage( const FilterInput::Vector& inputs, - const Entity& entity) const { + const Entity& entity, + const Matrix& effect_transform) const { if (inputs.empty()) { return std::nullopt; } @@ -175,10 +272,9 @@ std::optional DirectionalGaussianBlurFilterContents::GetFilterCoverage( return std::nullopt; } + auto transform = inputs[0]->GetTransform(entity) * effect_transform; auto transformed_blur_vector = - inputs[0] - ->GetTransform(entity) - .TransformDirection(blur_direction_ * Radius{blur_sigma_}.radius) + transform.TransformDirection(blur_direction_ * Radius{blur_sigma_}.radius) .Abs(); auto extent = coverage->size + transformed_blur_vector * 2; return Rect(coverage->origin - transformed_blur_vector, diff --git a/impeller/entity/contents/filters/gaussian_blur_filter_contents.h b/impeller/entity/contents/filters/gaussian_blur_filter_contents.h index e85ad20dc5f0c..7c4e44d68ece5 100644 --- a/impeller/entity/contents/filters/gaussian_blur_filter_contents.h +++ b/impeller/entity/contents/filters/gaussian_blur_filter_contents.h @@ -19,26 +19,35 @@ class DirectionalGaussianBlurFilterContents final : public FilterContents { void SetSigma(Sigma sigma); + void SetSecondarySigma(Sigma sigma); + void SetDirection(Vector2 direction); void SetBlurStyle(BlurStyle blur_style); + void SetTileMode(Entity::TileMode tile_mode); + void SetSourceOverride(FilterInput::Ref alpha_mask); // |FilterContents| - std::optional GetFilterCoverage(const FilterInput::Vector& inputs, - const Entity& entity) const override; + std::optional GetFilterCoverage( + const FilterInput::Vector& inputs, + const Entity& entity, + const Matrix& effect_transform) const override; private: // |FilterContents| - bool RenderFilter(const FilterInput::Vector& input_textures, - const ContentContext& renderer, - const Entity& entity, - RenderPass& pass, - const Rect& coverage) const override; + std::optional RenderFilter( + const FilterInput::Vector& input_textures, + const ContentContext& renderer, + const Entity& entity, + const Matrix& effect_transform, + const Rect& coverage) const override; Sigma blur_sigma_; + Sigma secondary_blur_sigma_; Vector2 blur_direction_; BlurStyle blur_style_ = BlurStyle::kNormal; + Entity::TileMode tile_mode_ = Entity::TileMode::kDecal; bool src_color_factor_ = false; bool inner_blur_factor_ = true; bool outer_blur_factor_ = true; diff --git a/impeller/entity/contents/filters/linear_to_srgb_filter_contents.cc b/impeller/entity/contents/filters/linear_to_srgb_filter_contents.cc new file mode 100644 index 0000000000000..3d7ffdae9cf83 --- /dev/null +++ b/impeller/entity/contents/filters/linear_to_srgb_filter_contents.cc @@ -0,0 +1,84 @@ +// 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 "impeller/entity/contents/filters/linear_to_srgb_filter_contents.h" + +#include "impeller/entity/contents/content_context.h" +#include "impeller/entity/contents/contents.h" +#include "impeller/geometry/point.h" +#include "impeller/geometry/vector.h" +#include "impeller/renderer/render_pass.h" +#include "impeller/renderer/sampler_library.h" + +namespace impeller { + +LinearToSrgbFilterContents::LinearToSrgbFilterContents() = default; + +LinearToSrgbFilterContents::~LinearToSrgbFilterContents() = default; + +std::optional LinearToSrgbFilterContents::RenderFilter( + const FilterInput::Vector& inputs, + const ContentContext& renderer, + const Entity& entity, + const Matrix& effect_transform, + const Rect& coverage) const { + if (inputs.empty()) { + return std::nullopt; + } + + using VS = LinearToSrgbFilterPipeline::VertexShader; + using FS = LinearToSrgbFilterPipeline::FragmentShader; + + auto input_snapshot = inputs[0]->GetSnapshot(renderer, entity); + if (!input_snapshot.has_value()) { + return std::nullopt; + } + + ContentContext::SubpassCallback callback = [&](const ContentContext& renderer, + RenderPass& pass) { + Command cmd; + cmd.label = "Linear to sRGB Filter"; + + auto options = OptionsFromPass(pass); + options.blend_mode = Entity::BlendMode::kSource; + cmd.pipeline = renderer.GetLinearToSrgbFilterPipeline(options); + + VertexBufferBuilder vtx_builder; + vtx_builder.AddVertices({ + {Point(0, 0)}, + {Point(1, 0)}, + {Point(1, 1)}, + {Point(0, 0)}, + {Point(1, 1)}, + {Point(0, 1)}, + }); + + auto& host_buffer = pass.GetTransientsBuffer(); + auto vtx_buffer = vtx_builder.CreateVertexBuffer(host_buffer); + cmd.BindVertices(vtx_buffer); + + VS::FrameInfo frame_info; + frame_info.mvp = Matrix::MakeOrthographic(ISize(1, 1)); + + auto sampler = renderer.GetContext()->GetSamplerLibrary()->GetSampler({}); + FS::BindInputTexture(cmd, input_snapshot->texture, sampler); + + VS::BindFrameInfo(cmd, host_buffer.EmplaceUniform(frame_info)); + + return pass.AddCommand(std::move(cmd)); + }; + + auto out_texture = + renderer.MakeSubpass(input_snapshot->texture->GetSize(), callback); + if (!out_texture) { + return std::nullopt; + } + out_texture->SetLabel("LinearToSrgb Texture"); + + return Snapshot{.texture = out_texture, + .transform = input_snapshot->transform, + .sampler_descriptor = input_snapshot->sampler_descriptor}; +} + +} // namespace impeller diff --git a/impeller/entity/contents/filters/linear_to_srgb_filter_contents.h b/impeller/entity/contents/filters/linear_to_srgb_filter_contents.h new file mode 100644 index 0000000000000..271ac250ca711 --- /dev/null +++ b/impeller/entity/contents/filters/linear_to_srgb_filter_contents.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. + +#pragma once + +#include "impeller/entity/contents/filters/filter_contents.h" +#include "impeller/entity/contents/filters/inputs/filter_input.h" + +namespace impeller { + +class LinearToSrgbFilterContents final : public FilterContents { + public: + LinearToSrgbFilterContents(); + + ~LinearToSrgbFilterContents() override; + + private: + // |FilterContents| + std::optional RenderFilter( + const FilterInput::Vector& input_textures, + const ContentContext& renderer, + const Entity& entity, + const Matrix& effect_transform, + const Rect& coverage) const override; + + FML_DISALLOW_COPY_AND_ASSIGN(LinearToSrgbFilterContents); +}; + +} // namespace impeller diff --git a/impeller/entity/contents/filters/srgb_to_linear_filter_contents.cc b/impeller/entity/contents/filters/srgb_to_linear_filter_contents.cc new file mode 100644 index 0000000000000..3c3a5fa1befbb --- /dev/null +++ b/impeller/entity/contents/filters/srgb_to_linear_filter_contents.cc @@ -0,0 +1,84 @@ +// 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 "impeller/entity/contents/filters/srgb_to_linear_filter_contents.h" + +#include "impeller/entity/contents/content_context.h" +#include "impeller/entity/contents/contents.h" +#include "impeller/geometry/point.h" +#include "impeller/geometry/vector.h" +#include "impeller/renderer/render_pass.h" +#include "impeller/renderer/sampler_library.h" + +namespace impeller { + +SrgbToLinearFilterContents::SrgbToLinearFilterContents() = default; + +SrgbToLinearFilterContents::~SrgbToLinearFilterContents() = default; + +std::optional SrgbToLinearFilterContents::RenderFilter( + const FilterInput::Vector& inputs, + const ContentContext& renderer, + const Entity& entity, + const Matrix& effect_transform, + const Rect& coverage) const { + if (inputs.empty()) { + return std::nullopt; + } + + using VS = SrgbToLinearFilterPipeline::VertexShader; + using FS = SrgbToLinearFilterPipeline::FragmentShader; + + auto input_snapshot = inputs[0]->GetSnapshot(renderer, entity); + if (!input_snapshot.has_value()) { + return std::nullopt; + } + + ContentContext::SubpassCallback callback = [&](const ContentContext& renderer, + RenderPass& pass) { + Command cmd; + cmd.label = "sRGB to Linear Filter"; + + auto options = OptionsFromPass(pass); + options.blend_mode = Entity::BlendMode::kSource; + cmd.pipeline = renderer.GetSrgbToLinearFilterPipeline(options); + + VertexBufferBuilder vtx_builder; + vtx_builder.AddVertices({ + {Point(0, 0)}, + {Point(1, 0)}, + {Point(1, 1)}, + {Point(0, 0)}, + {Point(1, 1)}, + {Point(0, 1)}, + }); + + auto& host_buffer = pass.GetTransientsBuffer(); + auto vtx_buffer = vtx_builder.CreateVertexBuffer(host_buffer); + cmd.BindVertices(vtx_buffer); + + VS::FrameInfo frame_info; + frame_info.mvp = Matrix::MakeOrthographic(ISize(1, 1)); + + auto sampler = renderer.GetContext()->GetSamplerLibrary()->GetSampler({}); + FS::BindInputTexture(cmd, input_snapshot->texture, sampler); + + VS::BindFrameInfo(cmd, host_buffer.EmplaceUniform(frame_info)); + + return pass.AddCommand(std::move(cmd)); + }; + + auto out_texture = + renderer.MakeSubpass(input_snapshot->texture->GetSize(), callback); + if (!out_texture) { + return std::nullopt; + } + out_texture->SetLabel("SrgbToLinear Texture"); + + return Snapshot{.texture = out_texture, + .transform = input_snapshot->transform, + .sampler_descriptor = input_snapshot->sampler_descriptor}; +} + +} // namespace impeller diff --git a/impeller/entity/contents/filters/srgb_to_linear_filter_contents.h b/impeller/entity/contents/filters/srgb_to_linear_filter_contents.h new file mode 100644 index 0000000000000..11d0dc762cffa --- /dev/null +++ b/impeller/entity/contents/filters/srgb_to_linear_filter_contents.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. + +#pragma once + +#include "impeller/entity/contents/filters/filter_contents.h" +#include "impeller/entity/contents/filters/inputs/filter_input.h" + +namespace impeller { + +class SrgbToLinearFilterContents final : public FilterContents { + public: + SrgbToLinearFilterContents(); + + ~SrgbToLinearFilterContents() override; + + private: + // |FilterContents| + std::optional RenderFilter( + const FilterInput::Vector& input_textures, + const ContentContext& renderer, + const Entity& entity, + const Matrix& effect_transform, + const Rect& coverage) const override; + + FML_DISALLOW_COPY_AND_ASSIGN(SrgbToLinearFilterContents); +}; + +} // namespace impeller diff --git a/impeller/entity/contents/linear_gradient_contents.cc b/impeller/entity/contents/linear_gradient_contents.cc index d4188efbecad4..7eb09622eb663 100644 --- a/impeller/entity/contents/linear_gradient_contents.cc +++ b/impeller/entity/contents/linear_gradient_contents.cc @@ -16,10 +16,6 @@ LinearGradientContents::LinearGradientContents() = default; LinearGradientContents::~LinearGradientContents() = default; -void LinearGradientContents::SetPath(Path path) { - path_ = std::move(path); -} - void LinearGradientContents::SetEndPoints(Point start_point, Point end_point) { start_point_ = start_point; end_point_ = end_point; @@ -35,30 +31,29 @@ void LinearGradientContents::SetColors(std::vector colors) { } } +void LinearGradientContents::SetTileMode(Entity::TileMode tile_mode) { + tile_mode_ = tile_mode; +} + const std::vector& LinearGradientContents::GetColors() const { return colors_; } -std::optional LinearGradientContents::GetCoverage( - const Entity& entity) const { - return path_.GetTransformedBoundingBox(entity.GetTransformation()); -}; - bool LinearGradientContents::Render(const ContentContext& renderer, const Entity& entity, RenderPass& pass) const { - using VS = GradientFillPipeline::VertexShader; - using FS = GradientFillPipeline::FragmentShader; + using VS = LinearGradientFillPipeline::VertexShader; + using FS = LinearGradientFillPipeline::FragmentShader; auto vertices_builder = VertexBufferBuilder(); { - auto result = - Tessellator{}.Tessellate(path_.GetFillType(), path_.CreatePolyline(), - [&vertices_builder](Point point) { - VS::PerVertexData vtx; - vtx.vertices = point; - vertices_builder.AppendVertex(vtx); - }); + auto result = Tessellator{}.Tessellate(GetPath().GetFillType(), + GetPath().CreatePolyline(), + [&vertices_builder](Point point) { + VS::PerVertexData vtx; + vtx.position = point; + vertices_builder.AppendVertex(vtx); + }); if (result == Tessellator::Result::kInputError) { return true; @@ -71,17 +66,19 @@ bool LinearGradientContents::Render(const ContentContext& renderer, VS::FrameInfo frame_info; frame_info.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * entity.GetTransformation(); + frame_info.matrix = GetInverseMatrix(); FS::GradientInfo gradient_info; gradient_info.start_point = start_point_; gradient_info.end_point = end_point_; gradient_info.start_color = colors_[0].Premultiply(); gradient_info.end_color = colors_[1].Premultiply(); + gradient_info.tile_mode = static_cast(tile_mode_); Command cmd; cmd.label = "LinearGradientFill"; - cmd.pipeline = - renderer.GetGradientFillPipeline(OptionsFromPassAndEntity(pass, entity)); + cmd.pipeline = renderer.GetLinearGradientFillPipeline( + OptionsFromPassAndEntity(pass, entity)); cmd.stencil_reference = entity.GetStencilDepth(); cmd.BindVertices( vertices_builder.CreateVertexBuffer(pass.GetTransientsBuffer())); diff --git a/impeller/entity/contents/linear_gradient_contents.h b/impeller/entity/contents/linear_gradient_contents.h index e534a9423e39c..4ba2c82afcebe 100644 --- a/impeller/entity/contents/linear_gradient_contents.h +++ b/impeller/entity/contents/linear_gradient_contents.h @@ -9,24 +9,20 @@ #include #include "flutter/fml/macros.h" -#include "impeller/entity/contents/path_contents.h" +#include "impeller/entity/contents/color_source_contents.h" +#include "impeller/entity/entity.h" #include "impeller/geometry/color.h" #include "impeller/geometry/path.h" #include "impeller/geometry/point.h" namespace impeller { -class LinearGradientContents final : public PathContents { +class LinearGradientContents final : public ColorSourceContents { public: LinearGradientContents(); ~LinearGradientContents() override; - void SetPath(Path path) override; - - // |Contents| - std::optional GetCoverage(const Entity& entity) const override; - // |Contents| bool Render(const ContentContext& renderer, const Entity& entity, @@ -36,13 +32,15 @@ class LinearGradientContents final : public PathContents { void SetColors(std::vector colors); + void SetTileMode(Entity::TileMode tile_mode); + const std::vector& GetColors() const; private: - Path path_; Point start_point_; Point end_point_; std::vector colors_; + Entity::TileMode tile_mode_; FML_DISALLOW_COPY_AND_ASSIGN(LinearGradientContents); }; diff --git a/impeller/entity/contents/radial_gradient_contents.cc b/impeller/entity/contents/radial_gradient_contents.cc index f94fca51164f5..605a5fe81d5ef 100644 --- a/impeller/entity/contents/radial_gradient_contents.cc +++ b/impeller/entity/contents/radial_gradient_contents.cc @@ -16,12 +16,8 @@ RadialGradientContents::RadialGradientContents() = default; RadialGradientContents::~RadialGradientContents() = default; -void RadialGradientContents::SetPath(Path path) { - path_ = std::move(path); -} - -void RadialGradientContents::SetCenterAndRadius(Point centre, Scalar radius) { - center_ = centre; +void RadialGradientContents::SetCenterAndRadius(Point center, Scalar radius) { + center_ = center; radius_ = radius; } @@ -35,15 +31,14 @@ void RadialGradientContents::SetColors(std::vector colors) { } } +void RadialGradientContents::SetTileMode(Entity::TileMode tile_mode) { + tile_mode_ = tile_mode; +} + const std::vector& RadialGradientContents::GetColors() const { return colors_; } -std::optional RadialGradientContents::GetCoverage( - const Entity& entity) const { - return path_.GetTransformedBoundingBox(entity.GetTransformation()); -}; - bool RadialGradientContents::Render(const ContentContext& renderer, const Entity& entity, RenderPass& pass) const { @@ -52,13 +47,13 @@ bool RadialGradientContents::Render(const ContentContext& renderer, auto vertices_builder = VertexBufferBuilder(); { - auto result = - Tessellator{}.Tessellate(path_.GetFillType(), path_.CreatePolyline(), - [&vertices_builder](Point point) { - VS::PerVertexData vtx; - vtx.vertices = point; - vertices_builder.AppendVertex(vtx); - }); + auto result = Tessellator{}.Tessellate(GetPath().GetFillType(), + GetPath().CreatePolyline(), + [&vertices_builder](Point point) { + VS::PerVertexData vtx; + vtx.position = point; + vertices_builder.AppendVertex(vtx); + }); if (result == Tessellator::Result::kInputError) { return true; @@ -71,12 +66,14 @@ bool RadialGradientContents::Render(const ContentContext& renderer, VS::FrameInfo frame_info; frame_info.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * entity.GetTransformation(); + frame_info.matrix = GetInverseMatrix(); FS::GradientInfo gradient_info; gradient_info.center = center_; gradient_info.radius = radius_; gradient_info.center_color = colors_[0].Premultiply(); gradient_info.edge_color = colors_[1].Premultiply(); + gradient_info.tile_mode = static_cast(tile_mode_); Command cmd; cmd.label = "RadialGradientFill"; diff --git a/impeller/entity/contents/radial_gradient_contents.h b/impeller/entity/contents/radial_gradient_contents.h index 83e096841dddd..226e9d8b7b513 100644 --- a/impeller/entity/contents/radial_gradient_contents.h +++ b/impeller/entity/contents/radial_gradient_contents.h @@ -9,40 +9,38 @@ #include #include "flutter/fml/macros.h" -#include "impeller/entity/contents/path_contents.h" +#include "impeller/entity/contents/color_source_contents.h" +#include "impeller/entity/entity.h" #include "impeller/geometry/color.h" #include "impeller/geometry/path.h" #include "impeller/geometry/point.h" namespace impeller { -class RadialGradientContents final : public PathContents { +class RadialGradientContents final : public ColorSourceContents { public: RadialGradientContents(); ~RadialGradientContents() override; - void SetPath(Path path) override; - - // |Contents| - std::optional GetCoverage(const Entity& entity) const override; - // |Contents| bool Render(const ContentContext& renderer, const Entity& entity, RenderPass& pass) const override; - void SetCenterAndRadius(Point centre, Scalar radius); + void SetCenterAndRadius(Point center, Scalar radius); void SetColors(std::vector colors); + void SetTileMode(Entity::TileMode tile_mode); + const std::vector& GetColors() const; private: - Path path_; Point center_; Scalar radius_; std::vector colors_; + Entity::TileMode tile_mode_; FML_DISALLOW_COPY_AND_ASSIGN(RadialGradientContents); }; diff --git a/impeller/entity/contents/rrect_shadow_contents.cc b/impeller/entity/contents/rrect_shadow_contents.cc new file mode 100644 index 0000000000000..b6371f043e1dc --- /dev/null +++ b/impeller/entity/contents/rrect_shadow_contents.cc @@ -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. + +#include "impeller/entity/contents/rrect_shadow_contents.h" +#include + +#include "impeller/entity/contents/content_context.h" +#include "impeller/entity/entity.h" +#include "impeller/geometry/path.h" +#include "impeller/geometry/path_builder.h" +#include "impeller/renderer/render_pass.h" +#include "impeller/renderer/vertex_buffer_builder.h" +#include "impeller/tessellator/tessellator.h" + +namespace impeller { + +RRectShadowContents::RRectShadowContents() = default; + +RRectShadowContents::~RRectShadowContents() = default; + +void RRectShadowContents::SetRRect(std::optional rect, + Scalar corner_radius) { + rect_ = rect; + corner_radius_ = corner_radius; +} + +void RRectShadowContents::SetSigma(Sigma sigma) { + sigma_ = sigma; +} + +void RRectShadowContents::SetColor(Color color) { + color_ = color.Premultiply(); +} + +std::optional RRectShadowContents::GetCoverage( + const Entity& entity) const { + if (!rect_.has_value()) { + return std::nullopt; + } + + Scalar radius = Radius{sigma_}.radius; + + auto ltrb = rect_->GetLTRB(); + Rect bounds = Rect::MakeLTRB(ltrb[0] - radius, ltrb[1] - radius, + ltrb[2] + radius, ltrb[3] + radius); + return bounds.TransformBounds(entity.GetTransformation()); +}; + +bool RRectShadowContents::Render(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const { + if (!rect_.has_value()) { + return true; + } + + using VS = RRectBlurPipeline::VertexShader; + using FS = RRectBlurPipeline::FragmentShader; + + VertexBufferBuilder vtx_builder; + + auto blur_radius = Radius{sigma_}.radius; + auto positive_rect = rect_->GetPositive(); + { + auto left = -blur_radius; + auto top = -blur_radius; + auto right = positive_rect.size.width + blur_radius; + auto bottom = positive_rect.size.height + blur_radius; + + vtx_builder.AddVertices({ + {Point(left, top)}, + {Point(right, top)}, + {Point(left, bottom)}, + {Point(left, bottom)}, + {Point(right, top)}, + {Point(right, bottom)}, + }); + } + + Command cmd; + cmd.label = "RRect Shadow"; + cmd.pipeline = + renderer.GetRRectBlurPipeline(OptionsFromPassAndEntity(pass, entity)); + cmd.stencil_reference = entity.GetStencilDepth(); + + cmd.primitive_type = PrimitiveType::kTriangle; + cmd.BindVertices(vtx_builder.CreateVertexBuffer(pass.GetTransientsBuffer())); + + VS::VertInfo vert_info; + vert_info.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * + entity.GetTransformation() * + Matrix::MakeTranslation({positive_rect.origin}); + VS::BindVertInfo(cmd, pass.GetTransientsBuffer().EmplaceUniform(vert_info)); + + FS::FragInfo frag_info; + frag_info.color = color_; + frag_info.blur_radius = blur_radius; + frag_info.rect_size = Point(positive_rect.size); + frag_info.corner_radius = + std::min(corner_radius_, std::min(positive_rect.size.width / 2.0f, + positive_rect.size.height / 2.0f)); + FS::BindFragInfo(cmd, pass.GetTransientsBuffer().EmplaceUniform(frag_info)); + + if (!pass.AddCommand(std::move(cmd))) { + return false; + } + + return true; +} + +} // namespace impeller diff --git a/impeller/entity/contents/rrect_shadow_contents.h b/impeller/entity/contents/rrect_shadow_contents.h new file mode 100644 index 0000000000000..d6d46b7f5223a --- /dev/null +++ b/impeller/entity/contents/rrect_shadow_contents.h @@ -0,0 +1,51 @@ +// 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. + +#pragma once + +#include +#include +#include + +#include "impeller/entity/contents/contents.h" +#include "impeller/entity/contents/filters/filter_contents.h" +#include "impeller/geometry/color.h" + +namespace impeller { + +class Path; +class HostBuffer; +struct VertexBuffer; + +class RRectShadowContents final : public Contents { + public: + RRectShadowContents(); + + ~RRectShadowContents() override; + + void SetRRect(std::optional rect, Scalar corner_radius = 0); + + void SetSigma(Sigma sigma); + + void SetColor(Color color); + + // |Contents| + std::optional GetCoverage(const Entity& entity) const override; + + // |Contents| + bool Render(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const override; + + private: + std::optional rect_; + Scalar corner_radius_; + Sigma sigma_; + + Color color_; + + FML_DISALLOW_COPY_AND_ASSIGN(RRectShadowContents); +}; + +} // namespace impeller diff --git a/impeller/entity/contents/solid_stroke_contents.cc b/impeller/entity/contents/solid_stroke_contents.cc index c332b966c5b51..357498ad18cc3 100644 --- a/impeller/entity/contents/solid_stroke_contents.cc +++ b/impeller/entity/contents/solid_stroke_contents.cc @@ -52,8 +52,13 @@ std::optional SolidStrokeContents::GetCoverage( if (join_ == Join::kMiter) { max_radius = std::max(max_radius, miter_limit_ * 0.5f); } + Scalar determinant = entity.GetTransformation().GetDeterminant(); + if (determinant == 0) { + return std::nullopt; + } + Scalar min_size = 1.0f / sqrt(std::abs(determinant)); Vector2 max_radius_xy = entity.GetTransformation().TransformDirection( - Vector2(max_radius, max_radius) * stroke_size_); + Vector2(max_radius, max_radius) * std::max(stroke_size_, min_size)); return Rect(path_coverage.origin - max_radius_xy, Size(path_coverage.size.width + max_radius_xy.x * 2, @@ -72,10 +77,6 @@ static VertexBuffer CreateSolidStrokeVertices( VertexBufferBuilder vtx_builder; auto polyline = path.CreatePolyline(); - if (polyline.points.size() < 2) { - return {}; // Nothing to render. - } - VS::PerVertexData vtx; // Normal state. @@ -95,8 +96,17 @@ static VertexBuffer CreateSolidStrokeVertices( std::tie(contour_start_point_i, contour_end_point_i) = polyline.GetContourPointBounds(contour_i); - if (contour_end_point_i - contour_start_point_i < 2) { - continue; // This contour has no renderable content. + switch (contour_end_point_i - contour_start_point_i) { + case 1: { + Point p = polyline.points[contour_start_point_i]; + cap_proc(vtx_builder, p, {-1, 0}, smoothing); + cap_proc(vtx_builder, p, {1, 0}, smoothing); + continue; + } + case 0: + continue; // This contour has no renderable content. + default: + break; } // The first point's normal is always the same as @@ -177,7 +187,7 @@ static VertexBuffer CreateSolidStrokeVertices( bool SolidStrokeContents::Render(const ContentContext& renderer, const Entity& entity, RenderPass& pass) const { - if (stroke_size_ <= 0.0) { + if (stroke_size_ < 0.0) { return true; } @@ -187,7 +197,12 @@ bool SolidStrokeContents::Render(const ContentContext& renderer, VS::VertInfo vert_info; vert_info.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * entity.GetTransformation(); - vert_info.size = stroke_size_; + Scalar determinant = entity.GetTransformation().GetDeterminant(); + if (determinant == 0) { + return true; + } + Scalar min_size = 1.0f / sqrt(std::abs(determinant)); + vert_info.size = std::max(stroke_size_, min_size); FS::FragInfo frag_info; frag_info.color = color_.Premultiply(); diff --git a/impeller/entity/contents/sweep_gradient_contents.cc b/impeller/entity/contents/sweep_gradient_contents.cc new file mode 100644 index 0000000000000..a5b332d239e68 --- /dev/null +++ b/impeller/entity/contents/sweep_gradient_contents.cc @@ -0,0 +1,99 @@ +// 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 "sweep_gradient_contents.h" + +#include "flutter/fml/logging.h" +#include "impeller/entity/contents/content_context.h" +#include "impeller/entity/entity.h" +#include "impeller/renderer/render_pass.h" +#include "impeller/tessellator/tessellator.h" + +namespace impeller { + +SweepGradientContents::SweepGradientContents() = default; + +SweepGradientContents::~SweepGradientContents() = default; + +void SweepGradientContents::SetCenterAndAngles(Point center, + Degrees start_angle, + Degrees end_angle) { + center_ = center; + Scalar t0 = start_angle.degrees / 360; + Scalar t1 = end_angle.degrees / 360; + FML_DCHECK(t0 < t1); + bias_ = -t0; + scale_ = 1 / (t1 - t0); +} + +void SweepGradientContents::SetColors(std::vector colors) { + colors_ = std::move(colors); + if (colors_.empty()) { + colors_.push_back(Color::Black()); + colors_.push_back(Color::Black()); + } else if (colors_.size() < 2u) { + colors_.push_back(colors_.back()); + } +} + +void SweepGradientContents::SetTileMode(Entity::TileMode tile_mode) { + tile_mode_ = tile_mode; +} + +const std::vector& SweepGradientContents::GetColors() const { + return colors_; +} + +bool SweepGradientContents::Render(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const { + using VS = SweepGradientFillPipeline::VertexShader; + using FS = SweepGradientFillPipeline::FragmentShader; + + auto vertices_builder = VertexBufferBuilder(); + { + auto result = Tessellator{}.Tessellate(GetPath().GetFillType(), + GetPath().CreatePolyline(), + [&vertices_builder](Point point) { + VS::PerVertexData vtx; + vtx.position = point; + vertices_builder.AppendVertex(vtx); + }); + + if (result == Tessellator::Result::kInputError) { + return true; + } + if (result == Tessellator::Result::kTessellationError) { + return false; + } + } + + VS::FrameInfo frame_info; + frame_info.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * + entity.GetTransformation(); + frame_info.matrix = GetInverseMatrix(); + + FS::GradientInfo gradient_info; + gradient_info.center = center_; + gradient_info.bias = bias_; + gradient_info.scale = scale_; + gradient_info.start_color = colors_[0].Premultiply(); + gradient_info.end_color = colors_[1].Premultiply(); + gradient_info.tile_mode = static_cast(tile_mode_); + + Command cmd; + cmd.label = "SweepGradientFill"; + cmd.pipeline = renderer.GetSweepGradientFillPipeline( + OptionsFromPassAndEntity(pass, entity)); + cmd.stencil_reference = entity.GetStencilDepth(); + cmd.BindVertices( + vertices_builder.CreateVertexBuffer(pass.GetTransientsBuffer())); + cmd.primitive_type = PrimitiveType::kTriangle; + FS::BindGradientInfo( + cmd, pass.GetTransientsBuffer().EmplaceUniform(gradient_info)); + VS::BindFrameInfo(cmd, pass.GetTransientsBuffer().EmplaceUniform(frame_info)); + return pass.AddCommand(std::move(cmd)); +} + +} // namespace impeller diff --git a/impeller/entity/contents/sweep_gradient_contents.h b/impeller/entity/contents/sweep_gradient_contents.h new file mode 100644 index 0000000000000..2a2b345f740a0 --- /dev/null +++ b/impeller/entity/contents/sweep_gradient_contents.h @@ -0,0 +1,50 @@ +// 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. + +#pragma once + +#include +#include +#include + +#include "flutter/fml/macros.h" +#include "impeller/entity/contents/color_source_contents.h" +#include "impeller/entity/entity.h" +#include "impeller/geometry/color.h" +#include "impeller/geometry/path.h" +#include "impeller/geometry/point.h" +#include "impeller/geometry/scalar.h" + +namespace impeller { + +class SweepGradientContents final : public ColorSourceContents { + public: + SweepGradientContents(); + + ~SweepGradientContents() override; + + // |Contents| + bool Render(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const override; + + void SetCenterAndAngles(Point center, Degrees start_angle, Degrees end_angle); + + void SetColors(std::vector colors); + + void SetTileMode(Entity::TileMode tile_mode); + + const std::vector& GetColors() const; + + private: + Point center_; + Scalar bias_; + Scalar scale_; + std::vector colors_; + Entity::TileMode tile_mode_; + + FML_DISALLOW_COPY_AND_ASSIGN(SweepGradientContents); +}; + +} // namespace impeller diff --git a/impeller/entity/contents/text_contents.cc b/impeller/entity/contents/text_contents.cc index 17b5f9948ac6a..c082aabb4a1b3 100644 --- a/impeller/entity/contents/text_contents.cc +++ b/impeller/entity/contents/text_contents.cc @@ -9,7 +9,9 @@ #include "impeller/entity/contents/content_context.h" #include "impeller/entity/entity.h" #include "impeller/geometry/path_builder.h" +#include "impeller/renderer/formats.h" #include "impeller/renderer/render_pass.h" +#include "impeller/renderer/sampler_descriptor.h" #include "impeller/renderer/sampler_library.h" #include "impeller/tessellator/tessellator.h" #include "impeller/typographer/glyph_atlas.h" @@ -93,11 +95,16 @@ bool TextContents::Render(const ContentContext& renderer, frame_info.text_color = ToVector(color_.Premultiply()); VS::BindFrameInfo(cmd, pass.GetTransientsBuffer().EmplaceUniform(frame_info)); + SamplerDescriptor sampler_desc; + sampler_desc.min_filter = MinMagFilter::kLinear; + sampler_desc.mag_filter = MinMagFilter::kLinear; + // Common fragment uniforms for all glyphs. FS::BindGlyphAtlasSampler( - cmd, // command - atlas->GetTexture(), // texture - renderer.GetContext()->GetSamplerLibrary()->GetSampler({}) // sampler + cmd, // command + atlas->GetTexture(), // texture + renderer.GetContext()->GetSamplerLibrary()->GetSampler( + sampler_desc) // sampler ); // Common vertex information for all glyphs. diff --git a/impeller/entity/contents/texture_contents.cc b/impeller/entity/contents/texture_contents.cc index 10a72daab7073..758b31e16d8db 100644 --- a/impeller/entity/contents/texture_contents.cc +++ b/impeller/entity/contents/texture_contents.cc @@ -4,12 +4,14 @@ #include "texture_contents.h" +#include #include #include "impeller/entity/contents/content_context.h" #include "impeller/entity/entity.h" #include "impeller/entity/texture_fill.frag.h" #include "impeller/entity/texture_fill.vert.h" +#include "impeller/geometry/path_builder.h" #include "impeller/renderer/render_pass.h" #include "impeller/renderer/sampler_library.h" #include "impeller/tessellator/tessellator.h" @@ -20,8 +22,16 @@ TextureContents::TextureContents() = default; TextureContents::~TextureContents() = default; +std::shared_ptr TextureContents::MakeRect(Rect destination) { + auto contents = std::make_shared(); + contents->path_ = PathBuilder{}.AddRect(destination).TakePath(); + contents->is_rect_ = true; + return contents; +} + void TextureContents::SetPath(Path path) { path_ = std::move(path); + is_rect_ = false; } void TextureContents::SetTexture(std::shared_ptr texture) { @@ -43,6 +53,27 @@ std::optional TextureContents::GetCoverage(const Entity& entity) const { return path_.GetTransformedBoundingBox(entity.GetTransformation()); }; +std::optional TextureContents::RenderToSnapshot( + const ContentContext& renderer, + const Entity& entity) const { + auto bounds = path_.GetBoundingBox(); + if (!bounds.has_value()) { + return std::nullopt; + } + + // Passthrough textures that have simple rectangle paths and complete source + // rects. + if (is_rect_ && source_rect_ == Rect::MakeSize(texture_->GetSize())) { + auto scale = Vector2(bounds->size / Size(texture_->GetSize())); + return Snapshot{.texture = texture_, + .transform = entity.GetTransformation() * + Matrix::MakeTranslation(bounds->origin) * + Matrix::MakeScale(scale), + .sampler_descriptor = sampler_descriptor_}; + } + return Contents::RenderToSnapshot(renderer, entity); +} + bool TextureContents::Render(const ContentContext& renderer, const Entity& entity, RenderPass& pass) const { diff --git a/impeller/entity/contents/texture_contents.h b/impeller/entity/contents/texture_contents.h index 4b02d88c0b69e..b218a08503ab9 100644 --- a/impeller/entity/contents/texture_contents.h +++ b/impeller/entity/contents/texture_contents.h @@ -23,6 +23,11 @@ class TextureContents final : public Contents { ~TextureContents() override; + /// @brief A common case factory that marks the texture contents as having a + /// destination rectangle. In this situation, a subpass can be avoided + /// when image filters are applied. + static std::shared_ptr MakeRect(Rect destination); + void SetPath(Path path); void SetTexture(std::shared_ptr texture); @@ -42,13 +47,18 @@ class TextureContents final : public Contents { // |Contents| std::optional GetCoverage(const Entity& entity) const override; + // |Contents| + std::optional RenderToSnapshot(const ContentContext& renderer, + const Entity& entity) const override; + // |Contents| bool Render(const ContentContext& renderer, const Entity& entity, RenderPass& pass) const override; - public: + private: Path path_; + bool is_rect_ = false; std::shared_ptr texture_; SamplerDescriptor sampler_descriptor_ = {}; diff --git a/impeller/entity/contents/tiled_texture_contents.cc b/impeller/entity/contents/tiled_texture_contents.cc new file mode 100644 index 0000000000000..d6eacd3385e64 --- /dev/null +++ b/impeller/entity/contents/tiled_texture_contents.cc @@ -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. + +#include "impeller/entity/contents/tiled_texture_contents.h" + +#include "impeller/entity/contents/content_context.h" +#include "impeller/entity/tiled_texture_fill.frag.h" +#include "impeller/entity/tiled_texture_fill.vert.h" +#include "impeller/renderer/render_pass.h" +#include "impeller/renderer/sampler_library.h" +#include "impeller/tessellator/tessellator.h" + +namespace impeller { + +TiledTextureContents::TiledTextureContents() = default; + +TiledTextureContents::~TiledTextureContents() = default; + +void TiledTextureContents::SetTexture(std::shared_ptr texture) { + texture_ = std::move(texture); +} + +void TiledTextureContents::SetTileModes(Entity::TileMode x_tile_mode, + Entity::TileMode y_tile_mode) { + x_tile_mode_ = x_tile_mode; + y_tile_mode_ = y_tile_mode; +} + +void TiledTextureContents::SetSamplerDescriptor(SamplerDescriptor desc) { + sampler_descriptor_ = std::move(desc); +} + +bool TiledTextureContents::Render(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const { + if (texture_ == nullptr) { + return true; + } + + using VS = TiledTextureFillVertexShader; + using FS = TiledTextureFillFragmentShader; + + const auto coverage_rect = GetPath().GetBoundingBox(); + + if (!coverage_rect.has_value()) { + return true; + } + + if (coverage_rect->size.IsEmpty()) { + return true; + } + + const auto texture_size = texture_->GetSize(); + if (texture_size.IsEmpty()) { + return true; + } + + VertexBufferBuilder vertex_builder; + { + const auto tess_result = Tessellator{}.Tessellate( + GetPath().GetFillType(), GetPath().CreatePolyline(), + [&vertex_builder](Point vtx) { + VS::PerVertexData data; + data.position = vtx; + vertex_builder.AppendVertex(data); + }); + + if (tess_result == Tessellator::Result::kInputError) { + return true; + } + if (tess_result == Tessellator::Result::kTessellationError) { + return false; + } + } + + if (!vertex_builder.HasVertices()) { + return true; + } + + auto& host_buffer = pass.GetTransientsBuffer(); + + VS::VertInfo vert_info; + vert_info.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * + entity.GetTransformation(); + vert_info.matrix = GetInverseMatrix(); + vert_info.texture_size = Vector2{static_cast(texture_size.width), + static_cast(texture_size.height)}; + + FS::FragInfo frag_info; + frag_info.texture_sampler_y_coord_scale = texture_->GetYCoordScale(); + frag_info.x_tile_mode = static_cast(x_tile_mode_); + frag_info.y_tile_mode = static_cast(y_tile_mode_); + + Command cmd; + cmd.label = "TiledTextureFill"; + cmd.pipeline = + renderer.GetTiledTexturePipeline(OptionsFromPassAndEntity(pass, entity)); + cmd.stencil_reference = entity.GetStencilDepth(); + cmd.BindVertices(vertex_builder.CreateVertexBuffer(host_buffer)); + VS::BindVertInfo(cmd, host_buffer.EmplaceUniform(vert_info)); + FS::BindFragInfo(cmd, host_buffer.EmplaceUniform(frag_info)); + FS::BindTextureSampler(cmd, texture_, + renderer.GetContext()->GetSamplerLibrary()->GetSampler( + sampler_descriptor_)); + pass.AddCommand(std::move(cmd)); + + return true; +} + +} // namespace impeller diff --git a/impeller/entity/contents/tiled_texture_contents.h b/impeller/entity/contents/tiled_texture_contents.h new file mode 100644 index 0000000000000..95806119c2950 --- /dev/null +++ b/impeller/entity/contents/tiled_texture_contents.h @@ -0,0 +1,45 @@ +// 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. + +#pragma once + +#include +#include +#include + +#include "flutter/fml/macros.h" +#include "impeller/entity/contents/color_source_contents.h" +#include "impeller/entity/entity.h" +#include "impeller/geometry/path.h" +#include "impeller/renderer/sampler_descriptor.h" + +namespace impeller { + +class TiledTextureContents final : public ColorSourceContents { + public: + TiledTextureContents(); + + ~TiledTextureContents() override; + + // |Contents| + bool Render(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const override; + + void SetTexture(std::shared_ptr texture); + + void SetTileModes(Entity::TileMode x_tile_mode, Entity::TileMode y_tile_mode); + + void SetSamplerDescriptor(SamplerDescriptor desc); + + private: + std::shared_ptr texture_; + SamplerDescriptor sampler_descriptor_ = {}; + Entity::TileMode x_tile_mode_; + Entity::TileMode y_tile_mode_; + + FML_DISALLOW_COPY_AND_ASSIGN(TiledTextureContents); +}; + +} // namespace impeller diff --git a/impeller/entity/contents/vertices_contents.cc b/impeller/entity/contents/vertices_contents.cc index ef5bb6d844178..8d014aab24231 100644 --- a/impeller/entity/contents/vertices_contents.cc +++ b/impeller/entity/contents/vertices_contents.cc @@ -80,7 +80,7 @@ bool VerticesContents::Render(const ContentContext& renderer, size_t total_vtx_bytes = vertex_data.size() * sizeof(VS::PerVertexData); size_t total_idx_bytes = vertices_.GetIndices().size() * sizeof(uint16_t); - auto buffer = renderer.GetContext()->GetTransientsAllocator()->CreateBuffer( + auto buffer = renderer.GetContext()->GetResourceAllocator()->CreateBuffer( StorageMode::kHostVisible, total_vtx_bytes + total_idx_bytes); if (!buffer->CopyHostBuffer(reinterpret_cast(vertex_data.data()), diff --git a/impeller/entity/entity.cc b/impeller/entity/entity.cc index e5695ef8017be..56f534a880da2 100644 --- a/impeller/entity/entity.cc +++ b/impeller/entity/entity.cc @@ -42,6 +42,9 @@ std::optional Entity::GetCoverage() const { } bool Entity::ShouldRender(const ISize& target_size) const { + if (BlendModeShouldCoverWholeScreen(blend_mode_)) { + return true; + } return contents_->ShouldRender(*this, target_size); } @@ -73,6 +76,23 @@ Entity::BlendMode Entity::GetBlendMode() const { return blend_mode_; } +bool Entity::BlendModeShouldCoverWholeScreen(BlendMode blend_mode) { + switch (blend_mode) { + case BlendMode::kClear: + case BlendMode::kSource: + case BlendMode::kSourceIn: + case BlendMode::kDestinationIn: + case BlendMode::kSourceOut: + case BlendMode::kDestinationOut: + case BlendMode::kDestinationATop: + case BlendMode::kXor: + case BlendMode::kModulate: + return true; + default: + return false; + } +} + bool Entity::Render(const ContentContext& renderer, RenderPass& parent_pass) const { if (!contents_) { diff --git a/impeller/entity/entity.h b/impeller/entity/entity.h index 7c83f0f2da726..8c787e2c77359 100644 --- a/impeller/entity/entity.h +++ b/impeller/entity/entity.h @@ -62,6 +62,28 @@ class Entity { kLastAdvancedBlendMode = kLuminosity, }; + /// An enum to define how to repeat, fold, or omit colors outside of the + /// typically defined range of the source of the colors (such as the + /// bounds of an image or the defining geometry of a gradient). + enum class TileMode { + /// Replicate the edge color if the shader draws outside of its original + /// bounds. + kClamp, + + /// Repeat the shader's image horizontally and vertically (or both along and + /// perpendicular to a gradient's geometry). + kRepeat, + + /// Repeat the shader's image horizontally and vertically, seamlessly + /// alternating mirrored images. + kMirror, + + /// Render the shader's image pixels only within its original bounds. If the + /// shader draws outside of its original bounds, transparent black is drawn + /// instead. + kDecal, + }; + enum class ClipOperation { kDifference, kIntersect, @@ -99,6 +121,8 @@ class Entity { bool Render(const ContentContext& renderer, RenderPass& parent_pass) const; + static bool BlendModeShouldCoverWholeScreen(BlendMode blend_mode); + private: Matrix transformation_; std::shared_ptr contents_; diff --git a/impeller/entity/entity_pass.cc b/impeller/entity/entity_pass.cc index 5cae1fc91b266..76bc5d8918037 100644 --- a/impeller/entity/entity_pass.cc +++ b/impeller/entity/entity_pass.cc @@ -144,25 +144,31 @@ EntityPass* EntityPass::AddSubpass(std::unique_ptr pass) { bool EntityPass::Render(ContentContext& renderer, RenderTarget render_target) const { if (reads_from_pass_texture_) { - auto offscreen_target = RenderTarget::CreateOffscreen( - *renderer.GetContext(), render_target.GetRenderTargetSize(), - "EntityPass", // - StorageMode::kDevicePrivate, LoadAction::kClear, StoreAction::kStore, - StorageMode::kDevicePrivate, LoadAction::kClear, StoreAction::kStore); + auto offscreen_target = RenderTarget::CreateOffscreenMSAA( + *renderer.GetContext(), // context + render_target.GetRenderTargetSize(), // size + "EntityPass", // label + StorageMode::kDevicePrivate, // color_storage_mode + StorageMode::kDevicePrivate, // color_resolve_storage_mode + LoadAction::kClear, // color_load_action + StoreAction::kStoreAndMultisampleResolve, // color_store_action + StorageMode::kDevicePrivate, // stencil_storage_mode + LoadAction::kClear, // stencil_load_action + StoreAction::kStore // stencil_store_action + ); if (!OnRender(renderer, offscreen_target.GetRenderTargetSize(), offscreen_target, Point(), Point(), 0)) { return false; } - auto command_buffer = renderer.GetContext()->CreateRenderCommandBuffer(); + auto command_buffer = renderer.GetContext()->CreateCommandBuffer(); command_buffer->SetLabel("EntityPass Root Command Buffer"); auto render_pass = command_buffer->CreateRenderPass(render_target); render_pass->SetLabel("EntityPass Root Render Pass"); { auto size_rect = Rect::MakeSize(offscreen_target.GetRenderTargetSize()); - auto contents = std::make_shared(); - contents->SetPath(PathBuilder{}.AddRect(size_rect).TakePath()); + auto contents = TextureContents::MakeRect(size_rect); contents->SetTexture(offscreen_target.GetRenderTargetTexture()); contents->SetSourceRect(size_rect); @@ -173,8 +179,7 @@ bool EntityPass::Render(ContentContext& renderer, entity.Render(renderer, *render_pass); } - if (!render_pass->EncodeCommands( - renderer.GetContext()->GetTransientsAllocator())) { + if (!render_pass->EncodeCommands()) { return false; } if (!command_buffer->SubmitCommands()) { @@ -242,7 +247,8 @@ EntityPass::EntityResult EntityPass::GetEntityForElement( auto texture = pass_context.GetTexture(); // Render the backdrop texture before any of the pass elements. const auto& proc = subpass->backdrop_filter_proc_.value(); - backdrop_contents = proc(FilterInput::Make(std::move(texture))); + backdrop_contents = + proc(FilterInput::Make(std::move(texture)), subpass->xformation_); // The subpass will need to read from the current pass texture when // rendering the backdrop, so if there's an active pass, end it prior to @@ -252,7 +258,10 @@ EntityPass::EntityResult EntityPass::GetEntityForElement( auto subpass_coverage = GetSubpassCoverage(*subpass, Rect::MakeSize(root_pass_size)); - + if (subpass->cover_whole_screen) { + subpass_coverage = Rect( + position, Size(pass_context.GetRenderTarget().GetRenderTargetSize())); + } if (backdrop_contents) { auto backdrop_coverage = backdrop_contents->GetCoverage(Entity{}); if (backdrop_coverage.has_value()) { @@ -283,17 +292,31 @@ EntityPass::EntityResult EntityPass::GetEntityForElement( RenderTarget subpass_target; if (subpass->reads_from_pass_texture_) { - subpass_target = RenderTarget::CreateOffscreen( - *renderer.GetContext(), ISize::Ceil(subpass_coverage->size), - "EntityPass", StorageMode::kDevicePrivate, LoadAction::kClear, - StoreAction::kStore, StorageMode::kDevicePrivate, LoadAction::kClear, - StoreAction::kStore); + subpass_target = RenderTarget::CreateOffscreenMSAA( + *renderer.GetContext(), // context + ISize::Ceil(subpass_coverage->size), // size + "EntityPass", // label + StorageMode::kDevicePrivate, // color_storage_mode + StorageMode::kDevicePrivate, // color_resolve_storage_mode + LoadAction::kClear, // color_load_action + StoreAction::kStoreAndMultisampleResolve, // color_store_action + StorageMode::kDevicePrivate, // stencil_storage_mode + LoadAction::kClear, // stencil_load_action + StoreAction::kStore // stencil_store_action + ); } else { - subpass_target = RenderTarget::CreateOffscreen( - *renderer.GetContext(), ISize::Ceil(subpass_coverage->size), - "EntityPass", StorageMode::kDevicePrivate, LoadAction::kClear, - StoreAction::kStore, StorageMode::kDeviceTransient, - LoadAction::kClear, StoreAction::kDontCare); + subpass_target = RenderTarget::CreateOffscreenMSAA( + *renderer.GetContext(), // context + ISize::Ceil(subpass_coverage->size), // size + "EntityPass", // label + StorageMode::kDeviceTransient, // color_storage_mode + StorageMode::kDevicePrivate, // color_resolve_storage_mode + LoadAction::kClear, // color_load_action + StoreAction::kMultisampleResolve, // color_store_action + StorageMode::kDeviceTransient, // stencil_storage_mode + LoadAction::kClear, // stencil_load_action + StoreAction::kDontCare // stencil_store_action + ); } auto subpass_texture = subpass_target.GetRenderTargetTexture(); @@ -303,7 +326,8 @@ EntityPass::EntityResult EntityPass::GetEntityForElement( } auto offscreen_texture_contents = - subpass->delegate_->CreateContentsForSubpassTarget(subpass_texture); + subpass->delegate_->CreateContentsForSubpassTarget( + subpass_texture, subpass->xformation_); if (!offscreen_texture_contents) { // This is an error because the subpass delegate said the pass couldn't @@ -328,10 +352,6 @@ EntityPass::EntityResult EntityPass::GetEntityForElement( element_entity.SetContents(std::move(offscreen_texture_contents)); element_entity.SetStencilDepth(subpass->stencil_depth_); element_entity.SetBlendMode(subpass->blend_mode_); - // Once we have filters being applied for SaveLayer, some special sauce - // may be needed here (or in PaintPassDelegate) to ensure the filter - // parameters are transformed by the `xformation_` matrix, while - // continuing to apply only the subpass offset to the offscreen texture. element_entity.SetTransformation( Matrix::MakeTranslation(Vector3(subpass_coverage->origin - position))); } else { @@ -363,6 +383,11 @@ bool EntityPass::OnRender(ContentContext& renderer, stencil_depth_floor); auto pass = pass_context.GetRenderPass(pass_depth); + + if (!pass) { + return false; + } + if (!element_entity.ShouldRender(pass->GetRenderTargetSize())) { return true; // Nothing to render. } @@ -501,11 +526,12 @@ void EntityPass::SetStencilDepth(size_t stencil_depth) { void EntityPass::SetBlendMode(Entity::BlendMode blend_mode) { blend_mode_ = blend_mode; + cover_whole_screen = Entity::BlendModeShouldCoverWholeScreen(blend_mode); } void EntityPass::SetBackdropFilter(std::optional proc) { backdrop_filter_proc_ = proc; - if (superpass_) { + if (proc.has_value() && superpass_) { superpass_->reads_from_pass_texture_ = true; } } diff --git a/impeller/entity/entity_pass.h b/impeller/entity/entity_pass.h index 87c7bddd4cd33..16f6a25842f57 100644 --- a/impeller/entity/entity_pass.h +++ b/impeller/entity/entity_pass.h @@ -25,8 +25,9 @@ class ContentContext; class EntityPass { public: using Element = std::variant>; - using BackdropFilterProc = - std::function(FilterInput::Ref)>; + using BackdropFilterProc = std::function( + FilterInput::Ref, + const Matrix& effect_transform)>; EntityPass(); @@ -114,7 +115,7 @@ class EntityPass { Matrix xformation_; size_t stencil_depth_ = 0u; Entity::BlendMode blend_mode_ = Entity::BlendMode::kSourceOver; - + bool cover_whole_screen = false; /// This flag is set to `true` whenever an entity is added to the pass that /// requires reading the pass texture during rendering. This can happen in the /// following scenarios: diff --git a/impeller/entity/entity_pass_delegate.cc b/impeller/entity/entity_pass_delegate.cc index 00963048a6e9e..529c8698f68bd 100644 --- a/impeller/entity/entity_pass_delegate.cc +++ b/impeller/entity/entity_pass_delegate.cc @@ -28,7 +28,8 @@ class DefaultEntityPassDelegate final : public EntityPassDelegate { // |EntityPassDelegate| std::shared_ptr CreateContentsForSubpassTarget( - std::shared_ptr target) override { + std::shared_ptr target, + const Matrix& effect_transform) override { // Not possible since this pass always collapses into its parent. FML_UNREACHABLE(); } diff --git a/impeller/entity/entity_pass_delegate.h b/impeller/entity/entity_pass_delegate.h index 19dd4f01f8fdc..28f485c1237f7 100644 --- a/impeller/entity/entity_pass_delegate.h +++ b/impeller/entity/entity_pass_delegate.h @@ -27,7 +27,8 @@ class EntityPassDelegate { virtual bool CanCollapseIntoParentPass() = 0; virtual std::shared_ptr CreateContentsForSubpassTarget( - std::shared_ptr target) = 0; + std::shared_ptr target, + const Matrix& effect_transform) = 0; private: FML_DISALLOW_COPY_AND_ASSIGN(EntityPassDelegate); diff --git a/impeller/entity/entity_unittests.cc b/impeller/entity/entity_unittests.cc index dca12868062f0..4c79890f77f57 100644 --- a/impeller/entity/entity_unittests.cc +++ b/impeller/entity/entity_unittests.cc @@ -6,10 +6,13 @@ #include #include "flutter/testing/testing.h" +#include "impeller/entity/contents/atlas_contents.h" #include "impeller/entity/contents/clip_contents.h" +#include "impeller/entity/contents/contents.h" #include "impeller/entity/contents/filters/blend_filter_contents.h" #include "impeller/entity/contents/filters/filter_contents.h" #include "impeller/entity/contents/filters/inputs/filter_input.h" +#include "impeller/entity/contents/rrect_shadow_contents.h" #include "impeller/entity/contents/solid_color_contents.h" #include "impeller/entity/contents/solid_stroke_contents.h" #include "impeller/entity/contents/texture_contents.h" @@ -20,6 +23,7 @@ #include "impeller/entity/entity_playground.h" #include "impeller/geometry/geometry_unittests.h" #include "impeller/geometry/path_builder.h" +#include "impeller/geometry/sigma.h" #include "impeller/playground/playground.h" #include "impeller/playground/widgets.h" #include "impeller/renderer/render_pass.h" @@ -57,7 +61,8 @@ class TestPassDelegate final : public EntityPassDelegate { // |EntityPassDelgate| std::shared_ptr CreateContentsForSubpassTarget( - std::shared_ptr target) override { + std::shared_ptr target, + const Matrix& transform) override { return nullptr; } @@ -839,31 +844,40 @@ TEST_P(EntityTest, GaussianBlurFilter) { auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { if (first_frame) { first_frame = false; - ImGui::SetNextWindowSize({500, 290}); - ImGui::SetNextWindowPos({300, 480}); + ImGui::SetNextWindowPos({10, 10}); } const char* input_type_names[] = {"Texture", "Solid Color"}; const char* blur_type_names[] = {"Image blur", "Mask blur"}; + const char* pass_variation_names[] = {"Two pass", "Directional"}; const char* blur_style_names[] = {"Normal", "Solid", "Outer", "Inner"}; + const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"}; const FilterContents::BlurStyle blur_styles[] = { FilterContents::BlurStyle::kNormal, FilterContents::BlurStyle::kSolid, FilterContents::BlurStyle::kOuter, FilterContents::BlurStyle::kInner}; + const Entity::TileMode tile_modes[] = { + Entity::TileMode::kClamp, Entity::TileMode::kRepeat, + Entity::TileMode::kMirror, Entity::TileMode::kDecal}; // UI state. static int selected_input_type = 0; static Color input_color = Color::Black(); static int selected_blur_type = 0; - static float blur_amount[2] = {20, 20}; + static int selected_pass_variation = 0; + static float blur_amount[2] = {10, 10}; static int selected_blur_style = 0; + static int selected_tile_mode = 3; static Color cover_color(1, 0, 0, 0.2); static Color bounds_color(0, 1, 0, 0.1); static float offset[2] = {500, 400}; static float rotation = 0; - static float scale[2] = {0.75, 0.75}; + static float scale[2] = {0.65, 0.65}; static float skew[2] = {0, 0}; + static float path_rect[4] = {0, 0, + static_cast(boston->GetSize().width), + static_cast(boston->GetSize().height)}; - ImGui::Begin("Controls"); + ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); { ImGui::Combo("Input type", &selected_input_type, input_type_names, sizeof(input_type_names) / sizeof(char*)); @@ -875,27 +889,36 @@ TEST_P(EntityTest, GaussianBlurFilter) { } ImGui::Combo("Blur type", &selected_blur_type, blur_type_names, sizeof(blur_type_names) / sizeof(char*)); - ImGui::SliderFloat2("Blur", &blur_amount[0], 0, 200); + if (selected_blur_type == 0) { + ImGui::Combo("Pass variation", &selected_pass_variation, + pass_variation_names, + sizeof(pass_variation_names) / sizeof(char*)); + } + ImGui::SliderFloat2("Sigma", blur_amount, 0, 10); ImGui::Combo("Blur style", &selected_blur_style, blur_style_names, sizeof(blur_style_names) / sizeof(char*)); + ImGui::Combo("Tile mode", &selected_tile_mode, tile_mode_names, + sizeof(tile_mode_names) / sizeof(char*)); ImGui::ColorEdit4("Cover color", reinterpret_cast(&cover_color)); ImGui::ColorEdit4("Bounds color", reinterpret_cast(&bounds_color)); - ImGui::SliderFloat2("Translation", &offset[0], 0, + ImGui::SliderFloat2("Translation", offset, 0, pass.GetRenderTargetSize().width); ImGui::SliderFloat("Rotation", &rotation, 0, kPi * 2); - ImGui::SliderFloat2("Scale", &scale[0], 0, 3); - ImGui::SliderFloat2("Skew", &skew[0], -3, 3); + ImGui::SliderFloat2("Scale", scale, 0, 3); + ImGui::SliderFloat2("Skew", skew, -3, 3); + ImGui::SliderFloat4("Path XYWH", path_rect, -1000, 1000); } ImGui::End(); std::shared_ptr input; Size input_size; + auto input_rect = + Rect::MakeXYWH(path_rect[0], path_rect[1], path_rect[2], path_rect[3]); if (selected_input_type == 0) { auto texture = std::make_shared(); - auto input_rect = Rect::MakeSize(boston->GetSize()); - texture->SetSourceRect(input_rect); + texture->SetSourceRect(Rect::MakeSize(boston->GetSize())); texture->SetPath(PathBuilder{}.AddRect(input_rect).TakePath()); texture->SetTexture(boston); texture->SetOpacity(input_color.alpha); @@ -904,7 +927,6 @@ TEST_P(EntityTest, GaussianBlurFilter) { input_size = input_rect.size; } else { auto fill = std::make_shared(); - auto input_rect = Rect::MakeSize(boston->GetSize()); fill->SetColor(input_color); fill->SetPath(PathBuilder{}.AddRect(input_rect).TakePath()); @@ -912,14 +934,21 @@ TEST_P(EntityTest, GaussianBlurFilter) { input_size = input_rect.size; } - auto blur = FilterContents::MakeGaussianBlur( - FilterInput::Make(input), FilterContents::Sigma{blur_amount[0]}, - FilterContents::Sigma{blur_amount[1]}, - blur_styles[selected_blur_style]); + std::shared_ptr blur; + if (selected_pass_variation == 0) { + blur = FilterContents::MakeGaussianBlur( + FilterInput::Make(input), Sigma{blur_amount[0]}, + Sigma{blur_amount[1]}, blur_styles[selected_blur_style], + tile_modes[selected_tile_mode]); + } else { + Vector2 blur_vector(blur_amount[0], blur_amount[1]); + blur = FilterContents::MakeDirectionalGaussianBlur( + FilterInput::Make(input), Sigma{blur_vector.GetLength()}, + blur_vector.Normalize()); + } auto mask_blur = FilterContents::MakeBorderMaskBlur( - FilterInput::Make(input), FilterContents::Sigma{blur_amount[0]}, - FilterContents::Sigma{blur_amount[1]}, + FilterInput::Make(input), Sigma{blur_amount[0]}, Sigma{blur_amount[1]}, blur_styles[selected_blur_style]); auto ctm = Matrix::MakeScale(GetContentScale()) * @@ -941,8 +970,7 @@ TEST_P(EntityTest, GaussianBlurFilter) { // unfiltered input. Entity cover_entity; cover_entity.SetContents(SolidColorContents::Make( - PathBuilder{}.AddRect(Rect::MakeSize(input_size)).TakePath(), - cover_color)); + PathBuilder{}.AddRect(input_rect).TakePath(), cover_color)); cover_entity.SetTransformation(ctm); cover_entity.Render(context, pass); @@ -1033,8 +1061,7 @@ TEST_P(EntityTest, BorderMaskBlurCoverageIsCorrect) { PathBuilder{}.AddRect(Rect::MakeXYWH(0, 0, 300, 400)).TakePath()); fill->SetColor(Color::CornflowerBlue()); auto border_mask_blur = FilterContents::MakeBorderMaskBlur( - FilterInput::Make(fill), FilterContents::Radius{3}, - FilterContents::Radius{4}); + FilterInput::Make(fill), Radius{3}, Radius{4}); { Entity e; @@ -1091,6 +1118,167 @@ TEST_P(EntityTest, DrawVerticesSolidColorTrianglesWithIndices) { ASSERT_TRUE(OpenPlaygroundHere(e)); } +TEST_P(EntityTest, DrawAtlasNoColor) { + // Draws the image as four squares stiched together. + auto atlas = CreateTextureForFixture("bay_bridge.jpg"); + auto size = atlas->GetSize(); + // Divide image into four quadrants. + Scalar half_width = size.width / 2; + Scalar half_height = size.height / 2; + std::vector texture_coordinates = { + Rect::MakeLTRB(0, 0, half_width, half_height), + Rect::MakeLTRB(half_width, 0, size.width, half_height), + Rect::MakeLTRB(0, half_height, half_width, size.height), + Rect::MakeLTRB(half_width, half_height, size.width, size.height)}; + // Position quadrants adjacent to eachother. + std::vector transforms = { + Matrix::MakeTranslation({0, 0, 0}), + Matrix::MakeTranslation({half_width, 0, 0}), + Matrix::MakeTranslation({0, half_height, 0}), + Matrix::MakeTranslation({half_width, half_height, 0})}; + std::shared_ptr contents = std::make_shared(); + + contents->SetTransforms(std::move(transforms)); + contents->SetTextureCoordinates(std::move(texture_coordinates)); + contents->SetTexture(atlas); + contents->SetBlendMode(Entity::BlendMode::kSource); + + Entity e; + e.SetTransformation(Matrix::MakeScale(GetContentScale())); + e.SetContents(contents); + + ASSERT_TRUE(OpenPlaygroundHere(e)); +} + +TEST_P(EntityTest, DrawAtlasWithColor) { + // Draws the image as four squares stiched together. Because blend modes + // aren't implented this ends up as four solid color blocks. + auto atlas = CreateTextureForFixture("bay_bridge.jpg"); + auto size = atlas->GetSize(); + // Divide image into four quadrants. + Scalar half_width = size.width / 2; + Scalar half_height = size.height / 2; + std::vector texture_coordinates = { + Rect::MakeLTRB(0, 0, half_width, half_height), + Rect::MakeLTRB(half_width, 0, size.width, half_height), + Rect::MakeLTRB(0, half_height, half_width, size.height), + Rect::MakeLTRB(half_width, half_height, size.width, size.height)}; + // Position quadrants adjacent to eachother. + std::vector transforms = { + Matrix::MakeTranslation({0, 0, 0}), + Matrix::MakeTranslation({half_width, 0, 0}), + Matrix::MakeTranslation({0, half_height, 0}), + Matrix::MakeTranslation({half_width, half_height, 0})}; + std::vector colors = {Color::Red(), Color::Green(), Color::Blue(), + Color::Yellow()}; + std::shared_ptr contents = std::make_shared(); + + contents->SetTransforms(std::move(transforms)); + contents->SetTextureCoordinates(std::move(texture_coordinates)); + contents->SetTexture(atlas); + contents->SetColors(colors); + contents->SetBlendMode(Entity::BlendMode::kSource); + + Entity e; + e.SetTransformation(Matrix::MakeScale(GetContentScale())); + e.SetContents(contents); + + ASSERT_TRUE(OpenPlaygroundHere(e)); +} + +TEST_P(EntityTest, DrawAtlasUsesProvidedCullRectForCoverage) { + auto atlas = CreateTextureForFixture("bay_bridge.jpg"); + auto size = atlas->GetSize(); + + Scalar half_width = size.width / 2; + Scalar half_height = size.height / 2; + std::vector texture_coordinates = { + Rect::MakeLTRB(0, 0, half_width, half_height), + Rect::MakeLTRB(half_width, 0, size.width, half_height), + Rect::MakeLTRB(0, half_height, half_width, size.height), + Rect::MakeLTRB(half_width, half_height, size.width, size.height)}; + std::vector transforms = { + Matrix::MakeTranslation({0, 0, 0}), + Matrix::MakeTranslation({half_width, 0, 0}), + Matrix::MakeTranslation({0, half_height, 0}), + Matrix::MakeTranslation({half_width, half_height, 0})}; + + std::shared_ptr contents = std::make_shared(); + + contents->SetTransforms(std::move(transforms)); + contents->SetTextureCoordinates(std::move(texture_coordinates)); + contents->SetTexture(atlas); + contents->SetBlendMode(Entity::BlendMode::kSource); + + auto transform = Matrix::MakeScale(GetContentScale()); + Entity e; + e.SetTransformation(transform); + e.SetContents(contents); + + ASSERT_EQ(contents->GetCoverage(e).value(), + Rect::MakeSize(size).TransformBounds(transform)); + + contents->SetCullRect(Rect::MakeLTRB(0, 0, 10, 10)); + + ASSERT_EQ(contents->GetCoverage(e).value(), + Rect::MakeLTRB(0, 0, 10, 10).TransformBounds(transform)); +} + +TEST_P(EntityTest, DrawAtlasWithOpacity) { + // Draws the image as four squares stiched together slightly + // opaque + auto atlas = CreateTextureForFixture("bay_bridge.jpg"); + auto size = atlas->GetSize(); + // Divide image into four quadrants. + Scalar half_width = size.width / 2; + Scalar half_height = size.height / 2; + std::vector texture_coordinates = { + Rect::MakeLTRB(0, 0, half_width, half_height), + Rect::MakeLTRB(half_width, 0, size.width, half_height), + Rect::MakeLTRB(0, half_height, half_width, size.height), + Rect::MakeLTRB(half_width, half_height, size.width, size.height)}; + // Position quadrants adjacent to eachother. + std::vector transforms = { + Matrix::MakeTranslation({0, 0, 0}), + Matrix::MakeTranslation({half_width, 0, 0}), + Matrix::MakeTranslation({0, half_height, 0}), + Matrix::MakeTranslation({half_width, half_height, 0})}; + + std::shared_ptr contents = std::make_shared(); + + contents->SetTransforms(std::move(transforms)); + contents->SetTextureCoordinates(std::move(texture_coordinates)); + contents->SetTexture(atlas); + contents->SetBlendMode(Entity::BlendMode::kSource); + contents->SetAlpha(0.5); + + Entity e; + e.SetTransformation(Matrix::MakeScale(GetContentScale())); + e.SetContents(contents); + + ASSERT_TRUE(OpenPlaygroundHere(e)); +} + +TEST_P(EntityTest, DrawAtlasNoColorFullSize) { + auto atlas = CreateTextureForFixture("bay_bridge.jpg"); + auto size = atlas->GetSize(); + std::vector texture_coordinates = { + Rect::MakeLTRB(0, 0, size.width, size.height)}; + std::vector transforms = {Matrix::MakeTranslation({0, 0, 0})}; + std::shared_ptr contents = std::make_shared(); + + contents->SetTransforms(std::move(transforms)); + contents->SetTextureCoordinates(std::move(texture_coordinates)); + contents->SetTexture(atlas); + contents->SetBlendMode(Entity::BlendMode::kSource); + + Entity e; + e.SetTransformation(Matrix::MakeScale(GetContentScale())); + e.SetContents(contents); + + ASSERT_TRUE(OpenPlaygroundHere(e)); +} + TEST_P(EntityTest, SolidFillCoverageIsCorrect) { // No transform { @@ -1176,5 +1364,287 @@ TEST_P(EntityTest, ClipContentsShouldRenderIsCorrect) { } } +TEST_P(EntityTest, RRectShadowTest) { + bool first_frame = true; + auto callback = [&](ContentContext& context, RenderPass& pass) { + if (first_frame) { + first_frame = false; + ImGui::SetNextWindowPos({10, 10}); + } + + static Color color = Color::Red(); + static float corner_radius = 100; + static float blur_radius = 100; + static bool show_coverage = false; + static Color coverage_color = Color::Green().WithAlpha(0.2); + + ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_AlwaysAutoResize); + ImGui::SliderFloat("Corner radius", &corner_radius, 0, 300); + ImGui::SliderFloat("Blur radius", &blur_radius, 0, 300); + ImGui::ColorEdit4("Color", reinterpret_cast(&color)); + ImGui::Checkbox("Show coverage", &show_coverage); + if (show_coverage) { + ImGui::ColorEdit4("Coverage color", + reinterpret_cast(&coverage_color)); + } + ImGui::End(); + + auto [top_left, bottom_right] = IMPELLER_PLAYGROUND_LINE( + Point(200, 200), Point(600, 400), 30, Color::White(), Color::White()); + auto rect = + Rect::MakeLTRB(top_left.x, top_left.y, bottom_right.x, bottom_right.y); + + auto contents = std::make_unique(); + contents->SetRRect(rect, corner_radius); + contents->SetColor(color); + contents->SetSigma(Radius(blur_radius)); + + Entity entity; + entity.SetTransformation(Matrix::MakeScale(GetContentScale())); + entity.SetContents(std::move(contents)); + entity.Render(context, pass); + + auto coverage = entity.GetCoverage(); + if (show_coverage && coverage.has_value()) { + auto bounds_contents = std::make_unique(); + bounds_contents->SetPath( + PathBuilder{}.AddRect(entity.GetCoverage().value()).TakePath()); + bounds_contents->SetColor(coverage_color.Premultiply()); + Entity bounds_entity; + bounds_entity.SetContents(std::move(bounds_contents)); + bounds_entity.Render(context, pass); + } + + return true; + }; + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + +TEST_P(EntityTest, ColorMatrixFilterCoverageIsCorrect) { + // Set up a simple color background. + auto fill = std::make_shared(); + fill->SetPath( + PathBuilder{}.AddRect(Rect::MakeXYWH(0, 0, 300, 400)).TakePath()); + fill->SetColor(Color::Coral()); + + // Set the color matrix filter. + FilterContents::ColorMatrix matrix = { + 1, 1, 1, 1, 1, // + 1, 1, 1, 1, 1, // + 1, 1, 1, 1, 1, // + 1, 1, 1, 1, 1, // + }; + + auto filter = + FilterContents::MakeColorMatrix(FilterInput::Make(fill), matrix); + + Entity e; + e.SetTransformation(Matrix()); + + // Confirm that the actual filter coverage matches the expected coverage. + auto actual = filter->GetCoverage(e); + auto expected = Rect::MakeXYWH(0, 0, 300, 400); + + ASSERT_TRUE(actual.has_value()); + ASSERT_RECT_NEAR(actual.value(), expected); +} + +TEST_P(EntityTest, ColorMatrixFilter) { + auto image = CreateTextureForFixture("boston.jpg"); + ASSERT_TRUE(image); + + auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { + // Set the color matrix filter. + FilterContents::ColorMatrix matrix = { + 1, 0, 0, 0, 0, // + 0, 3, 0, 0, 0, // + 0, 0, 1, 0, 0, // + 0, 0, 0, 1, 0, // + }; + + auto filter = + FilterContents::MakeColorMatrix(FilterInput::Make(image), matrix); + + // Define the entity with the color matrix filter. + Entity entity; + entity.SetTransformation(Matrix::MakeScale(GetContentScale()) * + Matrix::MakeTranslation({500, 300}) * + Matrix::MakeScale(Vector2{0.5, 0.5})); + entity.SetContents(filter); + return entity.Render(context, pass); + }; + + // Should output the boston image with a green filter. + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + +TEST_P(EntityTest, ColorMatrixFilterEditable) { + auto bay_bridge = CreateTextureForFixture("bay_bridge.jpg"); + ASSERT_TRUE(bay_bridge); + + bool first_frame = true; + auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { + // If this is the first frame, set the ImGui's initial size and postion. + if (first_frame) { + first_frame = false; + ImGui::SetNextWindowPos({10, 10}); + } + + // UI state. + static FilterContents::ColorMatrix color_matrix = { + 1, 0, 0, 0, 0, // + 0, 3, 0, 0, 0, // + 0, 0, 1, 0, 0, // + 0, 0, 0, 1, 0, // + }; + static float offset[2] = {500, 400}; + static float rotation = 0; + static float scale[2] = {0.65, 0.65}; + static float skew[2] = {0, 0}; + + // Define the ImGui + ImGui::Begin("Color Matrix", nullptr, ImGuiWindowFlags_AlwaysAutoResize); + { + std::string label = "##1"; + label.c_str(); + for (int i = 0; i < 20; i += 5) { + ImGui::InputScalarN(label.c_str(), ImGuiDataType_Float, + &(color_matrix.array[i]), 5, nullptr, nullptr, + "%.2f", 0); + label[2]++; + } + + ImGui::SliderFloat2("Translation", &offset[0], 0, + pass.GetRenderTargetSize().width); + ImGui::SliderFloat("Rotation", &rotation, 0, kPi * 2); + ImGui::SliderFloat2("Scale", &scale[0], 0, 3); + ImGui::SliderFloat2("Skew", &skew[0], -3, 3); + } + ImGui::End(); + + // Set the color matrix filter. + auto filter = FilterContents::MakeColorMatrix(FilterInput::Make(bay_bridge), + color_matrix); + + // Define the entity with the color matrix filter. + Entity entity; + entity.SetTransformation( + Matrix::MakeScale(GetContentScale()) * + Matrix::MakeTranslation(Vector3(offset[0], offset[1])) * + Matrix::MakeRotationZ(Radians(rotation)) * + Matrix::MakeScale(Vector2(scale[0], scale[1])) * + Matrix::MakeSkew(skew[0], skew[1]) * + Matrix::MakeTranslation(-Point(bay_bridge->GetSize()) / 2)); + entity.SetContents(filter); + entity.Render(context, pass); + + return true; + }; + + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + +TEST_P(EntityTest, LinearToSrgbFilterCoverageIsCorrect) { + // Set up a simple color background. + auto fill = std::make_shared(); + fill->SetPath( + PathBuilder{}.AddRect(Rect::MakeXYWH(0, 0, 300, 400)).TakePath()); + fill->SetColor(Color::MintCream()); + + auto filter = FilterContents::MakeLinearToSrgbFilter(FilterInput::Make(fill)); + + Entity e; + e.SetTransformation(Matrix()); + + // Confirm that the actual filter coverage matches the expected coverage. + auto actual = filter->GetCoverage(e); + auto expected = Rect::MakeXYWH(0, 0, 300, 400); + + ASSERT_TRUE(actual.has_value()); + ASSERT_RECT_NEAR(actual.value(), expected); +} + +TEST_P(EntityTest, LinearToSrgbFilter) { + auto image = CreateTextureForFixture("kalimba.jpg"); + ASSERT_TRUE(image); + + auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { + auto filtered = + FilterContents::MakeLinearToSrgbFilter(FilterInput::Make(image)); + + // Define the entity that will serve as the control image as a Gaussian blur + // filter with no filter at all. + Entity entity_left; + entity_left.SetTransformation(Matrix::MakeScale(GetContentScale()) * + Matrix::MakeTranslation({100, 300}) * + Matrix::MakeScale(Vector2{0.5, 0.5})); + auto unfiltered = FilterContents::MakeGaussianBlur(FilterInput::Make(image), + Sigma{0}, Sigma{0}); + entity_left.SetContents(unfiltered); + + // Define the entity that will be filtered from linear to sRGB. + Entity entity_right; + entity_right.SetTransformation(Matrix::MakeScale(GetContentScale()) * + Matrix::MakeTranslation({500, 300}) * + Matrix::MakeScale(Vector2{0.5, 0.5})); + entity_right.SetContents(filtered); + return entity_left.Render(context, pass) && + entity_right.Render(context, pass); + }; + + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + +TEST_P(EntityTest, SrgbToLinearFilterCoverageIsCorrect) { + // Set up a simple color background. + auto fill = std::make_shared(); + fill->SetPath( + PathBuilder{}.AddRect(Rect::MakeXYWH(0, 0, 300, 400)).TakePath()); + fill->SetColor(Color::DeepPink()); + + auto filter = FilterContents::MakeSrgbToLinearFilter(FilterInput::Make(fill)); + + Entity e; + e.SetTransformation(Matrix()); + + // Confirm that the actual filter coverage matches the expected coverage. + auto actual = filter->GetCoverage(e); + auto expected = Rect::MakeXYWH(0, 0, 300, 400); + + ASSERT_TRUE(actual.has_value()); + ASSERT_RECT_NEAR(actual.value(), expected); +} + +TEST_P(EntityTest, SrgbToLinearFilter) { + auto image = CreateTextureForFixture("embarcadero.jpg"); + ASSERT_TRUE(image); + + auto callback = [&](ContentContext& context, RenderPass& pass) -> bool { + auto filtered = + FilterContents::MakeSrgbToLinearFilter(FilterInput::Make(image)); + + // Define the entity that will serve as the control image as a Gaussian blur + // filter with no filter at all. + Entity entity_left; + entity_left.SetTransformation(Matrix::MakeScale(GetContentScale()) * + Matrix::MakeTranslation({100, 300}) * + Matrix::MakeScale(Vector2{0.5, 0.5})); + auto unfiltered = FilterContents::MakeGaussianBlur(FilterInput::Make(image), + Sigma{0}, Sigma{0}); + entity_left.SetContents(unfiltered); + + // Define the entity that will be filtered from sRGB to linear. + Entity entity_right; + entity_right.SetTransformation(Matrix::MakeScale(GetContentScale()) * + Matrix::MakeTranslation({500, 300}) * + Matrix::MakeScale(Vector2{0.5, 0.5})); + entity_right.SetContents(filtered); + return entity_left.Render(context, pass) && + entity_right.Render(context, pass); + }; + + ASSERT_TRUE(OpenPlaygroundHere(callback)); +} + } // namespace testing } // namespace impeller diff --git a/impeller/entity/inline_pass_context.cc b/impeller/entity/inline_pass_context.cc index 0b41e8687e2d7..485d91d99df31 100644 --- a/impeller/entity/inline_pass_context.cc +++ b/impeller/entity/inline_pass_context.cc @@ -4,6 +4,7 @@ #include "impeller/entity/inline_pass_context.h" +#include "impeller/base/validation.h" #include "impeller/renderer/command_buffer.h" namespace impeller { @@ -28,8 +29,7 @@ std::shared_ptr InlinePassContext::GetTexture() { if (!IsValid()) { return nullptr; } - auto color0 = render_target_.GetColorAttachments().find(0)->second; - return color0.resolve_texture ? color0.resolve_texture : color0.texture; + return render_target_.GetRenderTargetTexture(); } bool InlinePassContext::EndPass() { @@ -37,7 +37,7 @@ bool InlinePassContext::EndPass() { return true; } - if (!pass_->EncodeCommands(context_->GetTransientsAllocator())) { + if (!pass_->EncodeCommands()) { return false; } @@ -59,8 +59,9 @@ std::shared_ptr InlinePassContext::GetRenderPass( uint32_t pass_depth) { // Create a new render pass if one isn't active. if (!IsActive()) { - command_buffer_ = context_->CreateRenderCommandBuffer(); + command_buffer_ = context_->CreateCommandBuffer(); if (!command_buffer_) { + VALIDATION_LOG << "Could not create command buffer."; return nullptr; } @@ -85,6 +86,7 @@ std::shared_ptr InlinePassContext::GetRenderPass( pass_ = command_buffer_->CreateRenderPass(render_target_); if (!pass_) { + VALIDATION_LOG << "Could not create render pass."; return nullptr; } diff --git a/impeller/entity/shaders/atlas_fill.frag b/impeller/entity/shaders/atlas_fill.frag new file mode 100644 index 0000000000000..e211971ca1bb9 --- /dev/null +++ b/impeller/entity/shaders/atlas_fill.frag @@ -0,0 +1,29 @@ +// 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 + +uniform sampler2D texture_sampler; + +uniform FragInfo { + float texture_sampler_y_coord_scale; + float has_vertex_color; + float alpha; +} +frag_info; + +in vec2 v_texture_coords; +in vec4 v_color; + +out vec4 frag_color; + +void main() { + vec4 sampled = IPSample(texture_sampler, v_texture_coords, + frag_info.texture_sampler_y_coord_scale); + if (frag_info.has_vertex_color == 1.0) { + frag_color = sampled.aaaa * v_color * frag_info.alpha; + } else { + frag_color = sampled * frag_info.alpha; + } +} diff --git a/impeller/entity/shaders/atlas_fill.vert b/impeller/entity/shaders/atlas_fill.vert new file mode 100644 index 0000000000000..29d3c0a5be6e7 --- /dev/null +++ b/impeller/entity/shaders/atlas_fill.vert @@ -0,0 +1,21 @@ +// 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. + +uniform VertInfo { + mat4 mvp; +} +vert_info; + +in vec2 position; +in vec2 texture_coords; +in vec4 color; + +out vec2 v_texture_coords; +out vec4 v_color; + +void main() { + gl_Position = vert_info.mvp * vec4(position, 0.0, 1.0); + v_texture_coords = texture_coords; + v_color = color; +} diff --git a/impeller/entity/shaders/blending/advanced_blend.glsl b/impeller/entity/shaders/blending/advanced_blend.glsl index 64476c3fbdae9..20d7b40a0ec11 100644 --- a/impeller/entity/shaders/blending/advanced_blend.glsl +++ b/impeller/entity/shaders/blending/advanced_blend.glsl @@ -2,11 +2,13 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include +#include #include #include uniform BlendInfo { + float dst_y_coord_scale; + float src_y_coord_scale; float color_factor; vec4 color; // This color input is expected to be unpremultiplied. } @@ -21,12 +23,20 @@ in vec2 v_src_texture_coords; out vec4 frag_color; void main() { - vec4 dst = IPUnpremultiply( - IPSampleClampToBorder(texture_sampler_dst, v_dst_texture_coords)); + vec4 dst = IPUnpremultiply(IPSampleWithTileMode( + texture_sampler_dst, // sampler + v_dst_texture_coords, // texture coordinates + blend_info.dst_y_coord_scale, // y coordinate scale + kTileModeDecal // tile mode + )); vec4 src = blend_info.color_factor > 0 ? blend_info.color - : IPUnpremultiply(IPSampleClampToBorder(texture_sampler_src, - v_src_texture_coords)); + : IPUnpremultiply(IPSampleWithTileMode( + texture_sampler_src, // sampler + v_src_texture_coords, // texture coordinates + blend_info.src_y_coord_scale, // y coordinate scale + kTileModeDecal // tile mode + )); vec3 blended = Blend(dst.rgb, src.rgb); diff --git a/impeller/entity/shaders/blending/advanced_blend_color.frag b/impeller/entity/shaders/blending/advanced_blend_color.frag index 290e20adc72e5..225bd3f098122 100644 --- a/impeller/entity/shaders/blending/advanced_blend_color.frag +++ b/impeller/entity/shaders/blending/advanced_blend_color.frag @@ -2,11 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "advanced_blend_utils.glsl" +#include vec3 Blend(vec3 dst, vec3 src) { - // https://www.w3.org/TR/compositing-1/#blendingcolor - return SetLuminosity(src, Luminosity(dst)); + return IPBlendColor(dst, src); } #include "advanced_blend.glsl" diff --git a/impeller/entity/shaders/blending/advanced_blend_colorburn.frag b/impeller/entity/shaders/blending/advanced_blend_colorburn.frag index 464728895562c..95aac01645999 100644 --- a/impeller/entity/shaders/blending/advanced_blend_colorburn.frag +++ b/impeller/entity/shaders/blending/advanced_blend_colorburn.frag @@ -2,30 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "advanced_blend_utils.glsl" +#include vec3 Blend(vec3 dst, vec3 src) { - // https://www.w3.org/TR/compositing-1/#blendingcolorburn - vec3 color = 1 - min(vec3(1), (1 - dst) / src); - if (1 - dst.r < kEhCloseEnough) { - color.r = 1; - } - if (1 - dst.g < kEhCloseEnough) { - color.g = 1; - } - if (1 - dst.b < kEhCloseEnough) { - color.b = 1; - } - if (src.r < kEhCloseEnough) { - color.r = 0; - } - if (src.g < kEhCloseEnough) { - color.g = 0; - } - if (src.b < kEhCloseEnough) { - color.b = 0; - } - return color; + return IPBlendColorBurn(dst, src); } #include "advanced_blend.glsl" diff --git a/impeller/entity/shaders/blending/advanced_blend_colordodge.frag b/impeller/entity/shaders/blending/advanced_blend_colordodge.frag index f28a4f1026359..f11fc5e68584d 100644 --- a/impeller/entity/shaders/blending/advanced_blend_colordodge.frag +++ b/impeller/entity/shaders/blending/advanced_blend_colordodge.frag @@ -2,30 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "advanced_blend_utils.glsl" +#include vec3 Blend(vec3 dst, vec3 src) { - // https://www.w3.org/TR/compositing-1/#blendingcolordodge - vec3 color = min(vec3(1), dst / (1 - src)); - if (dst.r < kEhCloseEnough) { - color.r = 0; - } - if (dst.g < kEhCloseEnough) { - color.g = 0; - } - if (dst.b < kEhCloseEnough) { - color.b = 0; - } - if (1 - src.r < kEhCloseEnough) { - color.r = 1; - } - if (1 - src.g < kEhCloseEnough) { - color.g = 1; - } - if (1 - src.b < kEhCloseEnough) { - color.b = 1; - } - return color; + return IPBlendColorDodge(dst, src); } #include "advanced_blend.glsl" diff --git a/impeller/entity/shaders/blending/advanced_blend_darken.frag b/impeller/entity/shaders/blending/advanced_blend_darken.frag index bfc8cb20e22b9..286b7ff2912e6 100644 --- a/impeller/entity/shaders/blending/advanced_blend_darken.frag +++ b/impeller/entity/shaders/blending/advanced_blend_darken.frag @@ -2,9 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include + vec3 Blend(vec3 dst, vec3 src) { - // https://www.w3.org/TR/compositing-1/#blendingdarken - return min(dst, src); + return IPBlendDarken(dst, src); } #include "advanced_blend.glsl" diff --git a/impeller/entity/shaders/blending/advanced_blend_difference.frag b/impeller/entity/shaders/blending/advanced_blend_difference.frag index 7545bc08b55cd..9d9320fdffd78 100644 --- a/impeller/entity/shaders/blending/advanced_blend_difference.frag +++ b/impeller/entity/shaders/blending/advanced_blend_difference.frag @@ -2,9 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include + vec3 Blend(vec3 dst, vec3 src) { - // https://www.w3.org/TR/compositing-1/#blendingdifference - return abs(dst - src); + return IPBlendDifference(dst, src); } #include "advanced_blend.glsl" diff --git a/impeller/entity/shaders/blending/advanced_blend_exclusion.frag b/impeller/entity/shaders/blending/advanced_blend_exclusion.frag index a12e47e3d043b..7c2f9f92996ca 100644 --- a/impeller/entity/shaders/blending/advanced_blend_exclusion.frag +++ b/impeller/entity/shaders/blending/advanced_blend_exclusion.frag @@ -2,9 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include + vec3 Blend(vec3 dst, vec3 src) { - // https://www.w3.org/TR/compositing-1/#blendingexclusion - return dst + src - 2 * dst * src; + return IPBlendExclusion(dst, src); } #include "advanced_blend.glsl" diff --git a/impeller/entity/shaders/blending/advanced_blend_hardlight.frag b/impeller/entity/shaders/blending/advanced_blend_hardlight.frag index cbd109fc660fc..aa126dfdc7cc7 100644 --- a/impeller/entity/shaders/blending/advanced_blend_hardlight.frag +++ b/impeller/entity/shaders/blending/advanced_blend_hardlight.frag @@ -2,11 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "advanced_blend_utils.glsl" +#include vec3 Blend(vec3 dst, vec3 src) { - // https://www.w3.org/TR/compositing-1/#blendinghardlight - return BlendHardLight(dst, src); + return IPBlendHardLight(dst, src); } #include "advanced_blend.glsl" diff --git a/impeller/entity/shaders/blending/advanced_blend_hue.frag b/impeller/entity/shaders/blending/advanced_blend_hue.frag index bdcf6e2daf106..c0355b4b00d34 100644 --- a/impeller/entity/shaders/blending/advanced_blend_hue.frag +++ b/impeller/entity/shaders/blending/advanced_blend_hue.frag @@ -2,11 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "advanced_blend_utils.glsl" +#include vec3 Blend(vec3 dst, vec3 src) { - // https://www.w3.org/TR/compositing-1/#blendinghue - return SetLuminosity(SetSaturation(src, Saturation(dst)), Luminosity(dst)); + return IPBlendHue(dst, src); } #include "advanced_blend.glsl" diff --git a/impeller/entity/shaders/blending/advanced_blend_lighten.frag b/impeller/entity/shaders/blending/advanced_blend_lighten.frag index 1aca8cc4b7fcd..32f2df082b4f5 100644 --- a/impeller/entity/shaders/blending/advanced_blend_lighten.frag +++ b/impeller/entity/shaders/blending/advanced_blend_lighten.frag @@ -2,9 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include + vec3 Blend(vec3 dst, vec3 src) { - // https://www.w3.org/TR/compositing-1/#blendinglighten - return max(dst, src); + return IPBlendLighten(dst, src); } #include "advanced_blend.glsl" diff --git a/impeller/entity/shaders/blending/advanced_blend_luminosity.frag b/impeller/entity/shaders/blending/advanced_blend_luminosity.frag index 5faecd9e509aa..4ceae64493947 100644 --- a/impeller/entity/shaders/blending/advanced_blend_luminosity.frag +++ b/impeller/entity/shaders/blending/advanced_blend_luminosity.frag @@ -2,11 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "advanced_blend_utils.glsl" +#include vec3 Blend(vec3 dst, vec3 src) { - // https://www.w3.org/TR/compositing-1/#blendingluminosity - return SetLuminosity(dst, Luminosity(src)); + return IPBlendLuminosity(dst, src); } #include "advanced_blend.glsl" diff --git a/impeller/entity/shaders/blending/advanced_blend_multiply.frag b/impeller/entity/shaders/blending/advanced_blend_multiply.frag index 92656d23fa0a7..a2fd42b6c7d2e 100644 --- a/impeller/entity/shaders/blending/advanced_blend_multiply.frag +++ b/impeller/entity/shaders/blending/advanced_blend_multiply.frag @@ -2,9 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include + vec3 Blend(vec3 dst, vec3 src) { - // https://www.w3.org/TR/compositing-1/#blendingmultiply - return dst * src; + return IPBlendMultiply(dst, src); } #include "advanced_blend.glsl" diff --git a/impeller/entity/shaders/blending/advanced_blend_overlay.frag b/impeller/entity/shaders/blending/advanced_blend_overlay.frag index d6197f1103147..0837b91d8bfbd 100644 --- a/impeller/entity/shaders/blending/advanced_blend_overlay.frag +++ b/impeller/entity/shaders/blending/advanced_blend_overlay.frag @@ -2,12 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "advanced_blend_utils.glsl" +#include vec3 Blend(vec3 dst, vec3 src) { - // https://www.w3.org/TR/compositing-1/#blendinghardlight - // HardLight, but with reversed parameters. - return BlendHardLight(src, dst); + return IPBlendOverlay(dst, src); } #include "advanced_blend.glsl" diff --git a/impeller/entity/shaders/blending/advanced_blend_saturation.frag b/impeller/entity/shaders/blending/advanced_blend_saturation.frag index a127446db4aad..c3fd3bebc87d7 100644 --- a/impeller/entity/shaders/blending/advanced_blend_saturation.frag +++ b/impeller/entity/shaders/blending/advanced_blend_saturation.frag @@ -2,11 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "advanced_blend_utils.glsl" +#include vec3 Blend(vec3 dst, vec3 src) { - // https://www.w3.org/TR/compositing-1/#blendingsaturation - return SetLuminosity(SetSaturation(dst, Saturation(src)), Luminosity(dst)); + return IPBlendSaturation(dst, src); } #include "advanced_blend.glsl" diff --git a/impeller/entity/shaders/blending/advanced_blend_screen.frag b/impeller/entity/shaders/blending/advanced_blend_screen.frag index 7f059766abcf7..f65ab5db1d563 100644 --- a/impeller/entity/shaders/blending/advanced_blend_screen.frag +++ b/impeller/entity/shaders/blending/advanced_blend_screen.frag @@ -2,11 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "advanced_blend_utils.glsl" +#include vec3 Blend(vec3 dst, vec3 src) { - // https://www.w3.org/TR/compositing-1/#blendingscreen - return BlendScreen(dst, src); + return IPBlendScreen(dst, src); } #include "advanced_blend.glsl" diff --git a/impeller/entity/shaders/blending/advanced_blend_softlight.frag b/impeller/entity/shaders/blending/advanced_blend_softlight.frag index 2d7427ae89166..3a504afbb99c6 100644 --- a/impeller/entity/shaders/blending/advanced_blend_softlight.frag +++ b/impeller/entity/shaders/blending/advanced_blend_softlight.frag @@ -2,19 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "advanced_blend_utils.glsl" +#include vec3 Blend(vec3 dst, vec3 src) { - // https://www.w3.org/TR/compositing-1/#blendingsoftlight - - vec3 D = IPVec3ChooseCutoff(((16 * dst - 12) * dst + 4) * dst, // - sqrt(dst), // - dst, // - 0.25); - - return IPVec3Choose(dst - (1 - 2 * src) * dst * (1 - dst), // - dst + (2 * src - 1) * (D - dst), // - src); + return IPBlendSoftLight(dst, src); } #include "advanced_blend.glsl" diff --git a/impeller/entity/shaders/blending/advanced_blend_utils.glsl b/impeller/entity/shaders/blending/advanced_blend_utils.glsl deleted file mode 100644 index 55645331bba8f..0000000000000 --- a/impeller/entity/shaders/blending/advanced_blend_utils.glsl +++ /dev/null @@ -1,55 +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. - -#include -#include - -vec3 BlendScreen(vec3 dst, vec3 src) { - return dst + src - (dst * src); -} - -vec3 BlendHardLight(vec3 dst, vec3 src) { - return IPVec3Choose(dst * (2 * src), BlendScreen(dst, 2 * src - 1), src); -} - -//------------------------------------------------------------------------------ -// HSV utilities. -// - -float Luminosity(vec3 color) { - return color.r * 0.3 + color.g * 0.59 + color.b * 0.11; -} - -/// Scales the color's luma by the amount necessary to place the color -/// components in a 1-0 range. -vec3 ClipColor(vec3 color) { - float lum = Luminosity(color); - float mn = min(min(color.r, color.g), color.b); - float mx = max(max(color.r, color.g), color.b); - // `lum - mn` and `mx - lum` will always be >= 0 in the following conditions, - // so adding a tiny value is enough to make these divisions safe. - if (mn < 0) { - color = lum + (((color - lum) * lum) / (lum - mn + kEhCloseEnough)); - } - if (mx > 1) { - color = lum + (((color - lum) * (1 - lum)) / (mx - lum + kEhCloseEnough)); - } - return color; -} - -vec3 SetLuminosity(vec3 color, float luminosity) { - float relative_lum = luminosity - Luminosity(color); - return ClipColor(color + relative_lum); -} - -float Saturation(vec3 color) { - return max(max(color.r, color.g), color.b) - - min(min(color.r, color.g), color.b); -} - -vec3 SetSaturation(vec3 color, float saturation) { - float mn = min(min(color.r, color.g), color.b); - float mx = max(max(color.r, color.g), color.b); - return (mn < mx) ? ((color - mn) * saturation) / (mx - mn) : vec3(0); -} diff --git a/impeller/entity/shaders/color_matrix_color_filter.frag b/impeller/entity/shaders/color_matrix_color_filter.frag new file mode 100644 index 0000000000000..08adb27b6647c --- /dev/null +++ b/impeller/entity/shaders/color_matrix_color_filter.frag @@ -0,0 +1,48 @@ +// 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 + +// A color filter that transforms colors through a 4x5 color matrix. +// +// This filter can be used to change the saturation of pixels, convert from YUV to RGB, etc. +// +// 4x5 matrix for transforming the color and alpha components of a Bitmap. +// The matrix can be passed as single array, and is treated as follows: +// +// [ a, b, c, d, e, +// f, g, h, i, j, +// k, l, m, n, o, +// p, q, r, s, t ] +// +// When applied to a color [R, G, B, A], the resulting color is computed as: +// +// R’ = a*R + b*G + c*B + d*A + e; +// G’ = f*R + g*G + h*B + i*A + j; +// B’ = k*R + l*G + m*B + n*A + o; +// A’ = p*R + q*G + r*B + s*A + t; +// +// That resulting color [R’, G’, B’, A’] then has each channel clamped to the 0 to 255 range. + +uniform FragInfo { + mat4 color_m; + vec4 color_v; +} frag_info; + +uniform sampler2D input_texture; + +in vec2 v_position; +out vec4 frag_color; + +void main() { + vec4 input_color = texture(input_texture, v_position); + + // unpremultiply first, as filter inputs are premultiplied. + vec4 color = IPUnpremultiply(input_color); + + color = frag_info.color_m * color + frag_info.color_v; + + // premultiply the outputs + frag_color = vec4(color.rgb * color.a, color.a); +} diff --git a/impeller/entity/shaders/radial_gradient_fill.vert b/impeller/entity/shaders/color_matrix_color_filter.vert similarity index 61% rename from impeller/entity/shaders/radial_gradient_fill.vert rename to impeller/entity/shaders/color_matrix_color_filter.vert index f9d0ccdabfc3e..b741b2744ec60 100644 --- a/impeller/entity/shaders/radial_gradient_fill.vert +++ b/impeller/entity/shaders/color_matrix_color_filter.vert @@ -6,11 +6,10 @@ uniform FrameInfo { mat4 mvp; } frame_info; -in vec2 vertices; - -out vec2 interpolated_vertices; +in vec2 position; +out vec2 v_position; void main() { - gl_Position = frame_info.mvp * vec4(vertices, 0.0, 1.0); - interpolated_vertices = vertices; + v_position = position; + gl_Position = frame_info.mvp * vec4(position, 0.0, 1.0); } diff --git a/impeller/entity/shaders/gaussian_blur.frag b/impeller/entity/shaders/gaussian_blur.frag index 76cac10343327..ec099a6965bc4 100644 --- a/impeller/entity/shaders/gaussian_blur.frag +++ b/impeller/entity/shaders/gaussian_blur.frag @@ -7,51 +7,78 @@ // Paths for future optimization: // * Remove the uv bounds multiplier in SampleColor by adding optional // support for SamplerAddressMode::ClampToBorder in the texture sampler. -// * Sample from higher mipmap levels when the blur radius is high enough. +// * Render both blur passes into a smaller texture than the source image +// (~1/radius size). +// * If doing the small texture render optimization, cache misses can be +// reduced in the first pass by sampling the source textures with a mip +// level of log2(min_radius). +#include #include uniform sampler2D texture_sampler; uniform sampler2D alpha_mask_sampler; +uniform FragInfo { + float texture_sampler_y_coord_scale; + float alpha_mask_sampler_y_coord_scale; + + vec2 texture_size; + vec2 blur_direction; + + float tile_mode; + + // The blur sigma and radius have a linear relationship which is defined + // host-side, but both are useful controls here. Sigma (pixels per standard + // deviation) is used to define the gaussian function itself, whereas the + // radius is used to limit how much of the function is integrated. + float blur_sigma; + float blur_radius; + + float src_factor; + float inner_blur_factor; + float outer_blur_factor; +} +frag_info; + in vec2 v_texture_coords; in vec2 v_src_texture_coords; -in vec2 v_texture_size; -in vec2 v_blur_direction; -in float v_blur_sigma; -in float v_blur_radius; -in float v_src_factor; -in float v_inner_blur_factor; -in float v_outer_blur_factor; out vec4 frag_color; -const float kSqrtTwoPi = 2.50662827463; - float Gaussian(float x) { - float variance = v_blur_sigma * v_blur_sigma; - return exp(-0.5 * x * x / variance) / (kSqrtTwoPi * v_blur_sigma); + float variance = frag_info.blur_sigma * frag_info.blur_sigma; + return exp(-0.5 * x * x / variance) / (kSqrtTwoPi * frag_info.blur_sigma); } void main() { vec4 total_color = vec4(0); float gaussian_integral = 0; - vec2 blur_uv_offset = v_blur_direction / v_texture_size; + vec2 blur_uv_offset = frag_info.blur_direction / frag_info.texture_size; - for (float i = -v_blur_radius; i <= v_blur_radius; i++) { + for (float i = -frag_info.blur_radius; i <= frag_info.blur_radius; i++) { float gaussian = Gaussian(i); gaussian_integral += gaussian; total_color += - gaussian * IPSampleClampToBorder(texture_sampler, - v_texture_coords + blur_uv_offset * i); + gaussian * + IPSampleWithTileMode( + texture_sampler, // sampler + v_texture_coords + blur_uv_offset * i, // texture coordinates + frag_info.texture_sampler_y_coord_scale, // y coordinate scale + frag_info.tile_mode // tile mode + ); } vec4 blur_color = total_color / gaussian_integral; - vec4 src_color = - IPSampleClampToBorder(alpha_mask_sampler, v_src_texture_coords); - float blur_factor = v_inner_blur_factor * float(src_color.a > 0) + - v_outer_blur_factor * float(src_color.a == 0); + vec4 src_color = IPSampleWithTileMode( + alpha_mask_sampler, // sampler + v_src_texture_coords, // texture coordinates + frag_info.alpha_mask_sampler_y_coord_scale, // y coordinate scale + frag_info.tile_mode // tile mode + ); + float blur_factor = frag_info.inner_blur_factor * float(src_color.a > 0) + + frag_info.outer_blur_factor * float(src_color.a == 0); - frag_color = blur_color * blur_factor + src_color * v_src_factor; + frag_color = blur_color * blur_factor + src_color * frag_info.src_factor; } diff --git a/impeller/entity/shaders/gaussian_blur.vert b/impeller/entity/shaders/gaussian_blur.vert index 1a66dc0eb5a86..0fd3595a2d267 100644 --- a/impeller/entity/shaders/gaussian_blur.vert +++ b/impeller/entity/shaders/gaussian_blur.vert @@ -4,15 +4,6 @@ uniform FrameInfo { mat4 mvp; - vec2 texture_size; - - vec2 blur_direction; - float blur_sigma; - float blur_radius; - - float src_factor; - float inner_blur_factor; - float outer_blur_factor; } frame_info; @@ -22,23 +13,9 @@ in vec2 src_texture_coords; out vec2 v_texture_coords; out vec2 v_src_texture_coords; -out vec2 v_texture_size; -out vec2 v_blur_direction; -out float v_blur_sigma; -out float v_blur_radius; -out float v_src_factor; -out float v_inner_blur_factor; -out float v_outer_blur_factor; void main() { gl_Position = frame_info.mvp * vec4(vertices, 0.0, 1.0); v_texture_coords = texture_coords; v_src_texture_coords = src_texture_coords; - v_texture_size = frame_info.texture_size; - v_blur_direction = frame_info.blur_direction; - v_blur_sigma = frame_info.blur_sigma; - v_blur_radius = frame_info.blur_radius; - v_src_factor = frame_info.src_factor; - v_inner_blur_factor = frame_info.inner_blur_factor; - v_outer_blur_factor = frame_info.outer_blur_factor; } diff --git a/impeller/entity/shaders/gradient_fill.vert b/impeller/entity/shaders/gradient_fill.vert index f9d0ccdabfc3e..4b793dbfa183d 100644 --- a/impeller/entity/shaders/gradient_fill.vert +++ b/impeller/entity/shaders/gradient_fill.vert @@ -2,15 +2,18 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include + uniform FrameInfo { mat4 mvp; + mat4 matrix; } frame_info; -in vec2 vertices; +in vec2 position; -out vec2 interpolated_vertices; +out vec2 v_position; void main() { - gl_Position = frame_info.mvp * vec4(vertices, 0.0, 1.0); - interpolated_vertices = vertices; + gl_Position = frame_info.mvp * vec4(position, 0.0, 1.0); + v_position = IPVec2TransformPosition(frame_info.matrix, position); } diff --git a/impeller/entity/shaders/gradient_fill.frag b/impeller/entity/shaders/linear_gradient_fill.frag similarity index 62% rename from impeller/entity/shaders/gradient_fill.frag rename to impeller/entity/shaders/linear_gradient_fill.frag index b70cc6b879254..5282d4d623bf7 100644 --- a/impeller/entity/shaders/gradient_fill.frag +++ b/impeller/entity/shaders/linear_gradient_fill.frag @@ -2,23 +2,32 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include + uniform GradientInfo { vec2 start_point; vec2 end_point; vec4 start_color; vec4 end_color; + float tile_mode; } gradient_info; -in vec2 interpolated_vertices; +in vec2 v_position; out vec4 frag_color; void main() { float len = length(gradient_info.end_point - gradient_info.start_point); float dot = dot( - interpolated_vertices - gradient_info.start_point, + v_position - gradient_info.start_point, gradient_info.end_point - gradient_info.start_point ); - float interp = dot / (len * len); - frag_color = mix(gradient_info.start_color, gradient_info.end_color, interp); + float t = dot / (len * len); + if ((t < 0.0 || t > 1.0) && gradient_info.tile_mode == kTileModeDecal) { + frag_color = vec4(0); + return; + } + + t = IPFloatTile(t, gradient_info.tile_mode); + frag_color = mix(gradient_info.start_color, gradient_info.end_color, t); } diff --git a/impeller/entity/shaders/linear_to_srgb_filter.frag b/impeller/entity/shaders/linear_to_srgb_filter.frag new file mode 100644 index 0000000000000..fea911c14f1e5 --- /dev/null +++ b/impeller/entity/shaders/linear_to_srgb_filter.frag @@ -0,0 +1,28 @@ +// 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 + +// A color filter that applies the sRGB gamma curve to the color. +// +// This filter is used so that the colors are suitable for display in monitors. + +uniform sampler2D input_texture; + +in vec2 v_position; +out vec4 frag_color; + +void main() { + vec4 input_color = texture(input_texture, v_position); + + for (int i = 0; i < 4; i++) { + if (input_color[i] <= 0.0031308) { + input_color[i] = input_color[i] * 12.92; + } else { + input_color[i] = 1.055 * pow(input_color[i], (1.0 / 2.4)) - 0.055; + } + } + + frag_color = input_color; +} diff --git a/impeller/entity/shaders/linear_to_srgb_filter.vert b/impeller/entity/shaders/linear_to_srgb_filter.vert new file mode 100644 index 0000000000000..b741b2744ec60 --- /dev/null +++ b/impeller/entity/shaders/linear_to_srgb_filter.vert @@ -0,0 +1,15 @@ +// 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. + +uniform FrameInfo { + mat4 mvp; +} frame_info; + +in vec2 position; +out vec2 v_position; + +void main() { + v_position = position; + gl_Position = frame_info.mvp * vec4(position, 0.0, 1.0); +} diff --git a/impeller/entity/shaders/radial_gradient_fill.frag b/impeller/entity/shaders/radial_gradient_fill.frag index 5e1dcc7380d39..3804d2f9bba35 100644 --- a/impeller/entity/shaders/radial_gradient_fill.frag +++ b/impeller/entity/shaders/radial_gradient_fill.frag @@ -2,19 +2,28 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include + uniform GradientInfo { vec2 center; float radius; vec4 center_color; vec4 edge_color; + float tile_mode; } gradient_info; -in vec2 interpolated_vertices; +in vec2 v_position; out vec4 frag_color; void main() { - float len = length(interpolated_vertices - gradient_info.center); - float t = smoothstep(0.0, gradient_info.radius, len); + float len = length(v_position - gradient_info.center); + float t = len / gradient_info.radius; + if ((t < 0.0 || t > 1.0) && gradient_info.tile_mode == kTileModeDecal) { + frag_color = vec4(0); + return; + } + + t = IPFloatTile(t, gradient_info.tile_mode); frag_color = mix(gradient_info.center_color, gradient_info.edge_color, t); } diff --git a/impeller/entity/shaders/rrect_blur.frag b/impeller/entity/shaders/rrect_blur.frag new file mode 100644 index 0000000000000..6178b1751bca5 --- /dev/null +++ b/impeller/entity/shaders/rrect_blur.frag @@ -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. + +uniform FragInfo { + vec4 color; + float blur_radius; + vec2 rect_size; + float corner_radius; +} +frag_info; + +in vec2 v_position; + +out vec4 frag_color; + +// Simple logistic sigmoid with a domain of [-1, 1] and range of [0, 1]. +float Sigmoid(float x) { + return 1.03731472073 / (1 + exp(-4 * x)) - 0.0186573603638; +} + +float RRectDistance(vec2 sample_position, vec2 rect_size, float corner_radius) { + vec2 space = abs(sample_position) - rect_size + corner_radius; + return length(max(space, 0.0)) + min(max(space.x, space.y), 0.0) - + corner_radius; +} + +void main() { + vec2 center = v_position - frag_info.rect_size / 2.0; + float dist = + RRectDistance(center, frag_info.rect_size / 2.0, frag_info.corner_radius); + float shadow_mask = Sigmoid(-dist / frag_info.blur_radius); + frag_color = frag_info.color * shadow_mask; +} diff --git a/impeller/entity/shaders/rrect_blur.vert b/impeller/entity/shaders/rrect_blur.vert new file mode 100644 index 0000000000000..36711c6ede5e0 --- /dev/null +++ b/impeller/entity/shaders/rrect_blur.vert @@ -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. + +uniform VertInfo { + mat4 mvp; +} +vert_info; + +in vec2 position; + +out vec2 v_position; + +void main() { + gl_Position = vert_info.mvp * vec4(position, 0.0, 1.0); + // The fragment stage uses local coordinates to compute the blur. + v_position = position; +} diff --git a/impeller/entity/shaders/srgb_to_linear_filter.frag b/impeller/entity/shaders/srgb_to_linear_filter.frag new file mode 100644 index 0000000000000..6c51b59a1de4c --- /dev/null +++ b/impeller/entity/shaders/srgb_to_linear_filter.frag @@ -0,0 +1,27 @@ +// 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 + +// Creates a color filter that applies the inverse of the sRGB gamma curve +// to the RGB channels. + +uniform sampler2D input_texture; + +in vec2 v_position; +out vec4 frag_color; + +void main() { + vec4 input_color = texture(input_texture, v_position); + + for (int i = 0; i < 4; i++) { + if (input_color[i] <= 0.04045) { + input_color[i] = input_color[i] / 12.92; + } else { + input_color[i] = pow((input_color[i] + 0.055) / 1.055, 2.4); + } + } + + frag_color = input_color; +} diff --git a/impeller/entity/shaders/srgb_to_linear_filter.vert b/impeller/entity/shaders/srgb_to_linear_filter.vert new file mode 100644 index 0000000000000..b741b2744ec60 --- /dev/null +++ b/impeller/entity/shaders/srgb_to_linear_filter.vert @@ -0,0 +1,15 @@ +// 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. + +uniform FrameInfo { + mat4 mvp; +} frame_info; + +in vec2 position; +out vec2 v_position; + +void main() { + v_position = position; + gl_Position = frame_info.mvp * vec4(position, 0.0, 1.0); +} diff --git a/impeller/entity/shaders/sweep_gradient_fill.frag b/impeller/entity/shaders/sweep_gradient_fill.frag new file mode 100644 index 0000000000000..f7ab313319cbf --- /dev/null +++ b/impeller/entity/shaders/sweep_gradient_fill.frag @@ -0,0 +1,33 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include + +uniform GradientInfo { + vec2 center; + float bias; + float scale; + vec4 start_color; + vec4 end_color; + float tile_mode; +} gradient_info; + +in vec2 v_position; + +out vec4 frag_color; + +void main() { + vec2 coord = v_position - gradient_info.center; + float angle = atan(-coord.y, -coord.x); + + float t = (angle * k1Over2Pi + 0.5 + gradient_info.bias) * gradient_info.scale; + if ((t < 0.0 || t > 1.0) && gradient_info.tile_mode == kTileModeDecal) { + frag_color = vec4(0); + return; + } + + t = IPFloatTile(t, gradient_info.tile_mode); + frag_color = mix(gradient_info.start_color, gradient_info.end_color, t); +} diff --git a/impeller/entity/shaders/tiled_texture_fill.frag b/impeller/entity/shaders/tiled_texture_fill.frag new file mode 100644 index 0000000000000..1c0c0411414b0 --- /dev/null +++ b/impeller/entity/shaders/tiled_texture_fill.frag @@ -0,0 +1,29 @@ +// 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 + +uniform sampler2D texture_sampler; + +uniform FragInfo { + float texture_sampler_y_coord_scale; + float x_tile_mode; + float y_tile_mode; +} +frag_info; + +in vec2 v_texture_coords; + +out vec4 frag_color; + +void main() { + frag_color = + IPSampleWithTileMode( + texture_sampler, // sampler + v_texture_coords, // texture coordinates + frag_info.texture_sampler_y_coord_scale, // y coordinate scale + frag_info.x_tile_mode, // x tile mode + frag_info.y_tile_mode // y tile mode + ); +} diff --git a/impeller/entity/shaders/tiled_texture_fill.vert b/impeller/entity/shaders/tiled_texture_fill.vert new file mode 100644 index 0000000000000..db73769940067 --- /dev/null +++ b/impeller/entity/shaders/tiled_texture_fill.vert @@ -0,0 +1,22 @@ +// 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 + +uniform VertInfo { + mat4 mvp; + mat4 matrix; + vec2 texture_size; +} +vert_info; + +in vec2 position; + +out vec2 v_texture_coords; + +void main() { + gl_Position = vert_info.mvp * vec4(position, 0.0, 1.0); + vec2 transformed = IPVec2TransformPosition(vert_info.matrix, position); + v_texture_coords = transformed / vert_info.texture_size; +} diff --git a/impeller/fixtures/BUILD.gn b/impeller/fixtures/BUILD.gn index d955a90bfa9df..0c6a3ae73c04d 100644 --- a/impeller/fixtures/BUILD.gn +++ b/impeller/fixtures/BUILD.gn @@ -42,10 +42,13 @@ test_fixtures("file_fixtures") { "boston.jpg", "embarcadero.jpg", "kalimba.jpg", + "resources_limit.vert", "sample.comp", + "sample.frag", "sample.tesc", "sample.tese", "sample.vert", + "sa%m#ple.vert", "struct_def_bug.vert", "table_mountain_nx.png", "table_mountain_ny.png", diff --git a/impeller/fixtures/ink_sparkle.frag b/impeller/fixtures/ink_sparkle.frag index adc410c776c1b..1b3a43ca45d35 100644 --- a/impeller/fixtures/ink_sparkle.frag +++ b/impeller/fixtures/ink_sparkle.frag @@ -32,10 +32,6 @@ const float PI_ROTATE_LEFT = PI * -0.0078125; const float ONE_THIRD = 1./3.; const vec2 TURBULENCE_SCALE = vec2(0.8); -float saturate(float x) { - return clamp(x, 0.0, 1.0); -} - float triangle_noise(highp vec2 n) { n = fract(n * vec2(5.3987, 5.4421)); n += dot(n.yx, n.xy + vec2(21.5351, 14.3137)); @@ -60,7 +56,7 @@ float soft_circle(vec2 uv, vec2 xy, float radius, float blur) { float soft_ring(vec2 uv, vec2 xy, float radius, float thickness, float blur) { float circle_outer = soft_circle(uv, xy, radius + thickness, blur); float circle_inner = soft_circle(uv, xy, max(radius - thickness, 0.0), blur); - return saturate(circle_outer - circle_inner); + return clamp(circle_outer - circle_inner, 0.0, 1.0); } float circle_grid(vec2 resolution, vec2 p, vec2 xy, vec2 rotation, float cell_diameter) { @@ -77,7 +73,7 @@ float sparkle(vec2 uv, float t) { s += threshold(n + sin(PI * (t + 0.35)), 0.1, 0.15); s += threshold(n + sin(PI * (t + 0.7)), 0.2, 0.25); s += threshold(n + sin(PI * (t + 1.05)), 0.3, 0.35); - return saturate(s) * 0.55; + return clamp(s, 0.0, 1.0) * 0.55; } float turbulence(vec2 uv) { @@ -86,7 +82,7 @@ float turbulence(vec2 uv) { float g2 = circle_grid(TURBULENCE_SCALE, uv_scale, u_circle2, u_rotation2, 0.2); float g3 = circle_grid(TURBULENCE_SCALE, uv_scale, u_circle3, u_rotation3, 0.275); float v = (g1 * g1 + g2 - g3) * 0.5; - return saturate(0.45 + 0.8 * v); + return clamp(0.45 + 0.8 * v, 0.0, 1.0); } void main() { diff --git a/impeller/fixtures/resources_limit.vert b/impeller/fixtures/resources_limit.vert new file mode 100644 index 0000000000000..95c84ee8bfc76 --- /dev/null +++ b/impeller/fixtures/resources_limit.vert @@ -0,0 +1,9 @@ +// 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. + +uniform sampler1D tex; +void main() +{ + vec4 x = textureOffset(tex, 1.0, -10); +} diff --git a/impeller/fixtures/sa%m#ple.vert b/impeller/fixtures/sa%m#ple.vert new file mode 100644 index 0000000000000..fc7e9d1122000 --- /dev/null +++ b/impeller/fixtures/sa%m#ple.vert @@ -0,0 +1,23 @@ +// 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 "types.h" + +uniform UniformBufferObject { + Uniforms uniforms; +} ubo; + +uniform sampler2D world; + +in vec2 inPosition; +in vec3 inPosition22; +in vec4 inAnotherPosition; +in float stuff; + +out vec4 outStuff; + +void main() { + gl_Position = ubo.uniforms.projection * ubo.uniforms.view * ubo.uniforms.model * vec4(inPosition22, 1.0) * inAnotherPosition; + outStuff = texture(world, inPosition); +} diff --git a/impeller/fixtures/sample.frag b/impeller/fixtures/sample.frag new file mode 100644 index 0000000000000..5d9c83604dd68 --- /dev/null +++ b/impeller/fixtures/sample.frag @@ -0,0 +1,14 @@ +// 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. + +uniform FragInfo { + vec4 color; +} +frag_info; + +out vec4 frag_color; + +void main() { + frag_color = frag_info.color; +} diff --git a/impeller/geometry/BUILD.gn b/impeller/geometry/BUILD.gn index 65b99c1290a82..0afe7a6dbd5d0 100644 --- a/impeller/geometry/BUILD.gn +++ b/impeller/geometry/BUILD.gn @@ -29,6 +29,8 @@ impeller_component("geometry") { "scalar.h", "shear.cc", "shear.h", + "sigma.cc", + "sigma.h", "size.cc", "size.h", "type_traits.cc", diff --git a/impeller/geometry/constants.h b/impeller/geometry/constants.h index 95f56a546f2a0..5e775b5385194 100644 --- a/impeller/geometry/constants.h +++ b/impeller/geometry/constants.h @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#pragma once + namespace impeller { // e diff --git a/impeller/geometry/geometry_unittests.cc b/impeller/geometry/geometry_unittests.cc index 276b6751ccb06..0957acf76b23a 100644 --- a/impeller/geometry/geometry_unittests.cc +++ b/impeller/geometry/geometry_unittests.cc @@ -5,8 +5,10 @@ #include "impeller/geometry/geometry_unittests.h" #include +#include #include "flutter/testing/testing.h" +#include "impeller/geometry/constants.h" #include "impeller/geometry/path.h" #include "impeller/geometry/path_builder.h" #include "impeller/geometry/path_component.h" @@ -28,7 +30,7 @@ TEST(GeometryTest, ScalarNearlyEqual) { } TEST(GeometryTest, RotationMatrix) { - auto rotation = Matrix::MakeRotationZ(Radians{M_PI_4}); + auto rotation = Matrix::MakeRotationZ(Radians{kPiOver4}); auto expect = Matrix{0.707, 0.707, 0, 0, // -0.707, 0.707, 0, 0, // 0, 0, 1, 0, // @@ -38,7 +40,7 @@ TEST(GeometryTest, RotationMatrix) { TEST(GeometryTest, InvertMultMatrix) { { - auto rotation = Matrix::MakeRotationZ(Radians{M_PI_4}); + auto rotation = Matrix::MakeRotationZ(Radians{kPiOver4}); auto invert = rotation.Invert(); auto expect = Matrix{0.707, -0.707, 0, 0, // 0.707, 0.707, 0, 0, // @@ -71,7 +73,7 @@ TEST(GeometryTest, MatrixBasis) { } TEST(GeometryTest, MutliplicationMatrix) { - auto rotation = Matrix::MakeRotationZ(Radians{M_PI_4}); + auto rotation = Matrix::MakeRotationZ(Radians{kPiOver4}); auto invert = rotation.Invert(); ASSERT_MATRIX_NEAR(rotation * invert, Matrix{}); } @@ -98,7 +100,7 @@ TEST(GeometryTest, InvertMatrix) { } TEST(GeometryTest, TestDecomposition) { - auto rotated = Matrix::MakeRotationZ(Radians{M_PI_4}); + auto rotated = Matrix::MakeRotationZ(Radians{kPiOver4}); auto result = rotated.Decompose(); @@ -106,12 +108,12 @@ TEST(GeometryTest, TestDecomposition) { MatrixDecomposition res = result.value(); - auto quaternion = Quaternion{{0.0, 0.0, 1.0}, M_PI_4}; + auto quaternion = Quaternion{{0.0, 0.0, 1.0}, kPiOver4}; ASSERT_QUATERNION_NEAR(res.rotation, quaternion); } TEST(GeometryTest, TestDecomposition2) { - auto rotated = Matrix::MakeRotationZ(Radians{M_PI_4}); + auto rotated = Matrix::MakeRotationZ(Radians{kPiOver4}); auto scaled = Matrix::MakeScale({2.0, 3.0, 1.0}); auto translated = Matrix::MakeTranslation({-200, 750, 20}); @@ -121,7 +123,7 @@ TEST(GeometryTest, TestDecomposition2) { MatrixDecomposition res = result.value(); - auto quaternion = Quaternion{{0.0, 0.0, 1.0}, M_PI_4}; + auto quaternion = Quaternion{{0.0, 0.0, 1.0}, kPiOver4}; ASSERT_QUATERNION_NEAR(res.rotation, quaternion); @@ -138,7 +140,7 @@ TEST(GeometryTest, TestRecomposition) { /* * Decomposition. */ - auto rotated = Matrix::MakeRotationZ(Radians{M_PI_4}); + auto rotated = Matrix::MakeRotationZ(Radians{kPiOver4}); auto result = rotated.Decompose(); @@ -146,7 +148,7 @@ TEST(GeometryTest, TestRecomposition) { MatrixDecomposition res = result.value(); - auto quaternion = Quaternion{{0.0, 0.0, 1.0}, M_PI_4}; + auto quaternion = Quaternion{{0.0, 0.0, 1.0}, kPiOver4}; ASSERT_QUATERNION_NEAR(res.rotation, quaternion); @@ -158,7 +160,7 @@ TEST(GeometryTest, TestRecomposition) { TEST(GeometryTest, TestRecomposition2) { auto matrix = Matrix::MakeTranslation({100, 100, 100}) * - Matrix::MakeRotationZ(Radians{M_PI_4}) * + Matrix::MakeRotationZ(Radians{kPiOver4}) * Matrix::MakeScale({2.0, 2.0, 2.0}); auto result = matrix.Decompose(); @@ -171,7 +173,7 @@ TEST(GeometryTest, TestRecomposition2) { TEST(GeometryTest, MatrixVectorMultiplication) { { auto matrix = Matrix::MakeTranslation({100, 100, 100}) * - Matrix::MakeRotationZ(Radians{M_PI_2}) * + Matrix::MakeRotationZ(Radians{kPiOver2}) * Matrix::MakeScale({2.0, 2.0, 2.0}); auto vector = Vector4(10, 20, 30, 2); @@ -182,7 +184,7 @@ TEST(GeometryTest, MatrixVectorMultiplication) { { auto matrix = Matrix::MakeTranslation({100, 100, 100}) * - Matrix::MakeRotationZ(Radians{M_PI_2}) * + Matrix::MakeRotationZ(Radians{kPiOver2}) * Matrix::MakeScale({2.0, 2.0, 2.0}); auto vector = Vector3(10, 20, 30); @@ -193,7 +195,7 @@ TEST(GeometryTest, MatrixVectorMultiplication) { { auto matrix = Matrix::MakeTranslation({100, 100, 100}) * - Matrix::MakeRotationZ(Radians{M_PI_2}) * + Matrix::MakeRotationZ(Radians{kPiOver2}) * Matrix::MakeScale({2.0, 2.0, 2.0}); auto vector = Point(10, 20); @@ -206,7 +208,7 @@ TEST(GeometryTest, MatrixVectorMultiplication) { TEST(GeometryTest, MatrixTransformDirection) { { auto matrix = Matrix::MakeTranslation({100, 100, 100}) * - Matrix::MakeRotationZ(Radians{M_PI_2}) * + Matrix::MakeRotationZ(Radians{kPiOver2}) * Matrix::MakeScale({2.0, 2.0, 2.0}); auto vector = Vector4(10, 20, 30, 2); @@ -217,7 +219,7 @@ TEST(GeometryTest, MatrixTransformDirection) { { auto matrix = Matrix::MakeTranslation({100, 100, 100}) * - Matrix::MakeRotationZ(Radians{M_PI_2}) * + Matrix::MakeRotationZ(Radians{kPiOver2}) * Matrix::MakeScale({2.0, 2.0, 2.0}); auto vector = Vector3(10, 20, 30); @@ -228,7 +230,7 @@ TEST(GeometryTest, MatrixTransformDirection) { { auto matrix = Matrix::MakeTranslation({100, 100, 100}) * - Matrix::MakeRotationZ(Radians{M_PI_2}) * + Matrix::MakeRotationZ(Radians{kPiOver2}) * Matrix::MakeScale({2.0, 2.0, 2.0}); auto vector = Point(10, 20); @@ -283,8 +285,8 @@ TEST(GeometryTest, MatrixMakePerspective) { auto expect = Matrix{ 3.4641, 0, 0, 0, // 0, 1.73205, 0, 0, // - 0, 0, -1.22222, -1, // - 0, 0, -2.22222, 0, // + 0, 0, -1.11111, -1, // + 0, 0, -1.11111, 0, // }; ASSERT_MATRIX_NEAR(m, expect); } @@ -294,20 +296,67 @@ TEST(GeometryTest, MatrixMakePerspective) { auto expect = Matrix{ 0.915244, 0, 0, 0, // 0, 1.83049, 0, 0, // - 0, 0, -3, -1, // - 0, 0, -40, 0, // + 0, 0, -2, -1, // + 0, 0, -20, 0, // }; ASSERT_MATRIX_NEAR(m, expect); } } +TEST(GeometryTest, MatrixGetBasisVectors) { + { + auto m = Matrix(); + Vector3 x = m.GetBasisX(); + Vector3 y = m.GetBasisY(); + Vector3 z = m.GetBasisZ(); + ASSERT_VECTOR3_NEAR(x, Vector3(1, 0, 0)); + ASSERT_VECTOR3_NEAR(y, Vector3(0, 1, 0)); + ASSERT_VECTOR3_NEAR(z, Vector3(0, 0, 1)); + } + + { + auto m = Matrix::MakeRotationZ(Radians{kPiOver2}) * + Matrix::MakeRotationX(Radians{kPiOver2}) * + Matrix::MakeScale(Vector3(2, 3, 4)); + Vector3 x = m.GetBasisX(); + Vector3 y = m.GetBasisY(); + Vector3 z = m.GetBasisZ(); + ASSERT_VECTOR3_NEAR(x, Vector3(0, 2, 0)); + ASSERT_VECTOR3_NEAR(y, Vector3(0, 0, 3)); + ASSERT_VECTOR3_NEAR(z, Vector3(4, 0, 0)); + } +} + +TEST(GeometryTest, MatrixGetDirectionScale) { + { + auto m = Matrix(); + Scalar result = m.GetDirectionScale(Vector3{1, 0, 0}); + ASSERT_FLOAT_EQ(result, 1); + } + + { + auto m = Matrix::MakeRotationX(Degrees{10}) * + Matrix::MakeRotationY(Degrees{83}) * + Matrix::MakeRotationZ(Degrees{172}); + Scalar result = m.GetDirectionScale(Vector3{0, 1, 0}); + ASSERT_FLOAT_EQ(result, 1); + } + + { + auto m = Matrix::MakeRotationZ(Radians{kPiOver2}) * + Matrix::MakeScale(Vector3(3, 4, 5)); + Scalar result = m.GetDirectionScale(Vector3{2, 0, 0}); + ASSERT_FLOAT_EQ(result, 8); + } +} + TEST(GeometryTest, QuaternionLerp) { auto q1 = Quaternion{{0.0, 0.0, 1.0}, 0.0}; - auto q2 = Quaternion{{0.0, 0.0, 1.0}, M_PI_4}; + auto q2 = Quaternion{{0.0, 0.0, 1.0}, kPiOver4}; auto q3 = q1.Slerp(q2, 0.5); - auto expected = Quaternion{{0.0, 0.0, 1.0}, M_PI_4 / 2.0}; + auto expected = Quaternion{{0.0, 0.0, 1.0}, kPiOver4 / 2.0}; ASSERT_QUATERNION_NEAR(q3, expected); } @@ -772,6 +821,35 @@ TEST(GeometryTest, PointAbs) { ASSERT_POINT_NEAR(a_abs, expected); } +TEST(GeometryTest, PointAngleTo) { + // Negative result in the CCW (with up = -Y) direction. + { + Point a(1, 1); + Point b(1, -1); + Radians actual = a.AngleTo(b); + Radians expected = Radians{-kPi / 2}; + ASSERT_FLOAT_EQ(actual.radians, expected.radians); + } + + // Check the other direction to ensure the result is signed correctly. + { + Point a(1, -1); + Point b(1, 1); + Radians actual = a.AngleTo(b); + Radians expected = Radians{kPi / 2}; + ASSERT_FLOAT_EQ(actual.radians, expected.radians); + } + + // Differences in magnitude should have no impact on the result. + { + Point a(100, -100); + Point b(0.01, 0.01); + Radians actual = a.AngleTo(b); + Radians expected = Radians{kPi / 2}; + ASSERT_FLOAT_EQ(actual.radians, expected.radians); + } +} + TEST(GeometryTest, CanUseVector3AssignmentOperators) { { Vector3 p(1, 2, 4); @@ -1048,6 +1126,20 @@ TEST(GeometryTest, RectMakePointBounds) { } } +TEST(GeometryTest, RectGetPositive) { + { + Rect r{100, 200, 300, 400}; + auto actual = r.GetPositive(); + ASSERT_RECT_NEAR(r, actual); + } + { + Rect r{100, 200, -100, -100}; + auto actual = r.GetPositive(); + Rect expected(0, 100, 100, 100); + ASSERT_RECT_NEAR(expected, actual); + } +} + TEST(GeometryTest, CubicPathComponentPolylineDoesNotIncludePointOne) { CubicPathComponent component({10, 10}, {20, 35}, {35, 20}, {40, 40}); SmoothingApproximation approximation; @@ -1239,5 +1331,34 @@ TEST(GeometryTest, VerticesConstructorAndGetters) { ASSERT_EQ(vertices.GetMode(), VertexMode::kTriangle); } +TEST(GeometryTest, MatrixPrinting) { + std::stringstream stream; + + Matrix m; + + stream << m; + + ASSERT_EQ(stream.str(), R"(( + 1.000000, 0.000000, 0.000000, 0.000000, + 0.000000, 1.000000, 0.000000, 0.000000, + 0.000000, 0.000000, 1.000000, 0.000000, + 0.000000, 0.000000, 0.000000, 1.000000, +))"); + + stream.str(""); + stream.clear(); + + m = Matrix::MakeTranslation(Vector3(10, 20, 30)); + + stream << m; + + ASSERT_EQ(stream.str(), R"(( + 1.000000, 0.000000, 0.000000, 10.000000, + 0.000000, 1.000000, 0.000000, 20.000000, + 0.000000, 0.000000, 1.000000, 30.000000, + 0.000000, 0.000000, 0.000000, 1.000000, +))"); +} + } // namespace testing } // namespace impeller diff --git a/impeller/geometry/matrix.h b/impeller/geometry/matrix.h index 2a622a4ed2f63..29bbb949b3918 100644 --- a/impeller/geometry/matrix.h +++ b/impeller/geometry/matrix.h @@ -5,6 +5,7 @@ #pragma once #include +#include #include #include #include @@ -236,10 +237,20 @@ struct Matrix { Scalar GetMaxBasisLength() const; + constexpr Vector3 GetBasisX() const { return Vector3(m[0], m[1], m[2]); } + + constexpr Vector3 GetBasisY() const { return Vector3(m[4], m[5], m[6]); } + + constexpr Vector3 GetBasisZ() const { return Vector3(m[8], m[9], m[10]); } + constexpr Vector3 GetScale() const { - return Vector3(Vector3(m[0], m[1], m[2]).Length(), - Vector3(m[4], m[5], m[6]).Length(), - Vector3(m[8], m[9], m[10]).Length()); + return Vector3(GetBasisX().Length(), GetBasisY().Length(), + GetBasisZ().Length()); + } + + constexpr Scalar GetDirectionScale(Vector3 direction) const { + return 1.0 / (this->Invert() * direction.Normalize()).Length() * + direction.Length(); } constexpr bool IsAffine() const { @@ -336,14 +347,13 @@ struct Matrix { Scalar z_far) { Scalar height = std::tan(fov_y.radians * 0.5); Scalar width = height * aspect_ratio; - Scalar z_diff = z_near - z_far; // clang-format off return { - 1.0f / width, 0.0f, 0.0f, 0.0f, - 0.0f, 1.0f / height, 0.0f, 0.0f, - 0.0f, 0.0f, (z_near + z_far) / z_diff, -1.0f, - 0.0f, 0.0f, (2.0f * z_near * z_far) / z_diff, 0.0f + 1.0f / width, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f / height, 0.0f, 0.0f, + 0.0f, 0.0f, z_far / (z_near - z_far), -1.0f, + 0.0f, 0.0f, -(z_far * z_near) / (z_far - z_near), 0.0f, }; // clang-format on } @@ -365,10 +375,10 @@ static_assert(sizeof(struct Matrix) == sizeof(Scalar) * 16, namespace std { inline std::ostream& operator<<(std::ostream& out, const impeller::Matrix& m) { - out << "("; + out << "(" << std::endl << std::fixed; for (size_t i = 0; i < 4u; i++) { for (size_t j = 0; j < 4u; j++) { - out << m.e[i][j] << ","; + out << std::setw(15) << m.e[j][i] << ","; } out << std::endl; } diff --git a/impeller/geometry/path.cc b/impeller/geometry/path.cc index 69b1cd46a4927..72e9488e4f057 100644 --- a/impeller/geometry/path.cc +++ b/impeller/geometry/path.cc @@ -237,7 +237,7 @@ Path::Polyline Path::CreatePolyline( for (const auto& point : collection) { if (previous_contour_point.has_value() && previous_contour_point.value() == point) { - // Slip over duplicate points in the same contour. + // Skip over duplicate points in the same contour. continue; } previous_contour_point = point; diff --git a/impeller/geometry/point.h b/impeller/geometry/point.h index 7b511ed9d64a6..04b5461dbe036 100644 --- a/impeller/geometry/point.h +++ b/impeller/geometry/point.h @@ -189,7 +189,7 @@ struct TPoint { constexpr TPoint Normalize() const { const auto length = GetLength(); if (length == 0) { - return {}; + return {1, 0}; } return {x / length, y / length}; } @@ -204,6 +204,10 @@ struct TPoint { return *this - axis * this->Dot(axis) * 2; } + constexpr Radians AngleTo(const TPoint& p) const { + return Radians{std::atan2(this->Cross(p), this->Dot(p))}; + } + constexpr bool IsZero() const { return x == 0 && y == 0; } }; diff --git a/impeller/geometry/rect.h b/impeller/geometry/rect.h index 3165681f898b4..8b0a169d02ff8 100644 --- a/impeller/geometry/rect.h +++ b/impeller/geometry/rect.h @@ -139,6 +139,14 @@ struct TRect { return {left, top, right, bottom}; } + /// @brief Get a version of this rectangle that has a non-negative size. + constexpr TRect GetPositive() const { + auto ltrb = GetLTRB(); + return MakeLTRB(ltrb[0], ltrb[1], ltrb[2], ltrb[3]); + } + + /// @brief Get the points that represent the 4 corners of this rectangle. The + /// order is: Top left, top right, bottom left, bottom right. constexpr std::array, 4> GetPoints() const { auto [left, top, right, bottom] = GetLTRB(); return {TPoint(left, top), TPoint(right, top), TPoint(left, bottom), diff --git a/impeller/geometry/sigma.cc b/impeller/geometry/sigma.cc new file mode 100644 index 0000000000000..a73bcd7cea8bd --- /dev/null +++ b/impeller/geometry/sigma.cc @@ -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. + +#include "impeller/geometry/sigma.h" + +#include + +namespace impeller { + +Sigma::operator Radius() const { + return Radius{sigma > 0.5f ? (sigma - 0.5f) * kKernelRadiusPerSigma : 0.0f}; +} + +Radius::operator Sigma() const { + return Sigma{radius > 0 ? radius / kKernelRadiusPerSigma + 0.5f : 0.0f}; +} + +} // namespace impeller diff --git a/impeller/geometry/sigma.h b/impeller/geometry/sigma.h new file mode 100644 index 0000000000000..c909128be4baa --- /dev/null +++ b/impeller/geometry/sigma.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. + +#pragma once + +#include "impeller/geometry/scalar.h" + +namespace impeller { + +/// For filters that use a Gaussian distribution, this is the `Radius` size to +/// use per `Sigma` (standard deviation). +/// +/// This cutoff (sqrt(3)) is taken from Flutter and Skia (where the +/// multiplicative inverse of this constant is used (1 / sqrt(3)): +/// https://api.flutter.dev/flutter/dart-ui/Shadow/convertRadiusToSigma.html +/// +/// In practice, this value is somewhat arbitrary, and can be changed to a +/// higher number to integrate more of the Gaussian function and render higher +/// quality blurs (with exponentially diminishing returns for the same sigma +/// input). Making this value any lower results in a noticable loss of +/// quality in the blur. +constexpr static float kKernelRadiusPerSigma = 1.73205080757; + +struct Radius; + +/// @brief In filters that use Gaussian distributions, "sigma" is a size of +/// one standard deviation in terms of the local space pixel grid of +/// the filter input. In other words, this determines how wide the +/// distribution stretches. +struct Sigma { + Scalar sigma = 0.0; + + constexpr Sigma() = default; + + explicit constexpr Sigma(Scalar p_sigma) : sigma(p_sigma) {} + + operator Radius() const; // NOLINT(google-explicit-constructor) +}; + +/// @brief For convolution filters, the "radius" is the size of the +/// convolution kernel to use on the local space pixel grid of the +/// filter input. +/// For Gaussian blur kernels, this unit has a linear +/// relationship with `Sigma`. See `kKernelRadiusPerSigma` for +/// details on how this relationship works. +struct Radius { + Scalar radius = 0.0; + + constexpr Radius() = default; + + explicit constexpr Radius(Scalar p_radius) : radius(p_radius) {} + + operator Sigma() const; // NOLINT(google-explicit-constructor) +}; + +} // namespace impeller diff --git a/impeller/geometry/vector.h b/impeller/geometry/vector.h index ad59f91d89edc..b1621f0147a1b 100644 --- a/impeller/geometry/vector.h +++ b/impeller/geometry/vector.h @@ -41,7 +41,7 @@ struct Vector3 { * * @return the calculated length. */ - Scalar Length() const { return sqrt(x * x + y * y + z * z); } + constexpr Scalar Length() const { return sqrt(x * x + y * y + z * z); } constexpr Vector3 Normalize() const { const auto len = Length(); @@ -130,6 +130,18 @@ struct Vector3 { std::string ToString() const; }; +// RHS algebraic operations with arithmetic types. + +template >> +constexpr Vector3 operator*(U s, const Vector3& p) { + return p * s; +} + +template >> +constexpr Vector3 operator/(U s, const Vector3& p) { + return {static_cast(s) / p.x, static_cast(s) / p.y}; +} + struct Vector4 { union { struct { diff --git a/impeller/playground/backend/vulkan/playground_impl_vk.cc b/impeller/playground/backend/vulkan/playground_impl_vk.cc index ee3d7b477a6f8..4334149260e51 100644 --- a/impeller/playground/backend/vulkan/playground_impl_vk.cc +++ b/impeller/playground/backend/vulkan/playground_impl_vk.cc @@ -6,6 +6,7 @@ #include "impeller/renderer/backend/vulkan/vk.h" +#define GLFW_INCLUDE_VULKAN #include #include "flutter/fml/logging.h" @@ -34,13 +35,35 @@ ShaderLibraryMappingsForPlayground() { }; } +void PlaygroundImplVK::DestroyWindowHandle(WindowHandle handle) { + if (!handle) { + return; + } + ::glfwDestroyWindow(reinterpret_cast(handle)); +} + PlaygroundImplVK::PlaygroundImplVK() - : concurrent_loop_(fml::ConcurrentMessageLoop::Create()) { + : concurrent_loop_(fml::ConcurrentMessageLoop::Create()), + handle_(nullptr, &DestroyWindowHandle) { if (!::glfwVulkanSupported()) { VALIDATION_LOG << "Attempted to initialize a Vulkan playground on a system " "that does not support Vulkan."; return; } + + ::glfwDefaultWindowHints(); + ::glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + ::glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + auto window = + ::glfwCreateWindow(800, 600, "Test Vulkan Window", nullptr, nullptr); + if (!window) { + VALIDATION_LOG << "Unable to create glfw window"; + return; + } + + handle_.reset(window); + auto context = ContextVK::Create(reinterpret_cast( &::glfwGetInstanceProcAddress), // ShaderLibraryMappingsForPlayground(), // @@ -55,6 +78,18 @@ PlaygroundImplVK::PlaygroundImplVK() } context_ = std::move(context); + + SetupSwapchain(); +} + +void PlaygroundImplVK::SetupSwapchain() { + ContextVK* context_vk = reinterpret_cast(context_.get()); + auto window = reinterpret_cast(handle_.get()); + vk::Instance instance = context_vk->GetInstance(); + VkSurfaceKHR surface_tmp; + ::glfwCreateWindowSurface(instance, window, nullptr, &surface_tmp); + vk::UniqueSurfaceKHR surface{surface_tmp, instance}; + context_vk->SetupSwapchain(std::move(surface)); } PlaygroundImplVK::~PlaygroundImplVK() = default; @@ -66,13 +101,14 @@ std::shared_ptr PlaygroundImplVK::GetContext() const { // |PlaygroundImpl| PlaygroundImpl::WindowHandle PlaygroundImplVK::GetWindowHandle() const { - FML_UNREACHABLE(); + return handle_.get(); } // |PlaygroundImpl| std::unique_ptr PlaygroundImplVK::AcquireSurfaceFrame( std::shared_ptr context) { - FML_UNREACHABLE(); + ContextVK* context_vk = reinterpret_cast(context_.get()); + return context_vk->AcquireSurface(); } } // namespace impeller diff --git a/impeller/playground/backend/vulkan/playground_impl_vk.h b/impeller/playground/backend/vulkan/playground_impl_vk.h index 77747e72da07d..ec5d6c6421d53 100644 --- a/impeller/playground/backend/vulkan/playground_impl_vk.h +++ b/impeller/playground/backend/vulkan/playground_impl_vk.h @@ -7,6 +7,8 @@ #include "flutter/fml/concurrent_message_loop.h" #include "flutter/fml/macros.h" #include "impeller/playground/playground_impl.h" +#include "impeller/renderer/backend/vulkan/swapchain_vk.h" +#include "impeller/renderer/backend/vulkan/vk.h" namespace impeller { @@ -20,6 +22,11 @@ class PlaygroundImplVK final : public PlaygroundImpl { std::shared_ptr concurrent_loop_; std::shared_ptr context_; + // windows + static void DestroyWindowHandle(WindowHandle handle); + using UniqueHandle = std::unique_ptr; + UniqueHandle handle_; + // |PlaygroundImpl| std::shared_ptr GetContext() const override; @@ -30,6 +37,8 @@ class PlaygroundImplVK final : public PlaygroundImpl { std::unique_ptr AcquireSurfaceFrame( std::shared_ptr context) override; + void SetupSwapchain(); + FML_DISALLOW_COPY_AND_ASSIGN(PlaygroundImplVK); }; diff --git a/impeller/playground/imgui/imgui_impl_impeller.cc b/impeller/playground/imgui/imgui_impl_impeller.cc index 619db79b4c5bb..eb3e09234c02a 100644 --- a/impeller/playground/imgui/imgui_impl_impeller.cc +++ b/impeller/playground/imgui/imgui_impl_impeller.cc @@ -74,7 +74,7 @@ bool ImGui_ImplImpeller_Init(std::shared_ptr context) { texture_descriptor.size = {width, height}; texture_descriptor.mip_count = 1u; - bd->font_texture = context->GetPermanentsAllocator()->CreateTexture( + bd->font_texture = context->GetResourceAllocator()->CreateTexture( impeller::StorageMode::kHostVisible, texture_descriptor); IM_ASSERT(bd->font_texture != nullptr && "Could not allocate ImGui font texture."); @@ -136,7 +136,7 @@ void ImGui_ImplImpeller_RenderDrawData(ImDrawData* draw_data, } // Allocate buffer for vertices + indices. - auto buffer = bd->context->GetTransientsAllocator()->CreateBuffer( + auto buffer = bd->context->GetResourceAllocator()->CreateBuffer( impeller::StorageMode::kHostVisible, total_vtx_bytes + total_idx_bytes); buffer->SetLabel(impeller::SPrintF("ImGui vertex+index buffer")); diff --git a/impeller/playground/playground.cc b/impeller/playground/playground.cc index 87a08954e2bbb..f09fa9638d3f4 100644 --- a/impeller/playground/playground.cc +++ b/impeller/playground/playground.cc @@ -126,12 +126,21 @@ void Playground::TeardownWindow() { impl_.reset(); } +static std::atomic_bool gShouldOpenNewPlaygrounds = true; + +bool Playground::ShouldOpenNewPlaygrounds() { + return gShouldOpenNewPlaygrounds; +} + static void PlaygroundKeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) { if ((key == GLFW_KEY_ESCAPE || key == GLFW_KEY_Q) && action == GLFW_RELEASE) { + if (mods & (GLFW_MOD_CONTROL | GLFW_MOD_SUPER | GLFW_MOD_SHIFT)) { + gShouldOpenNewPlaygrounds = false; + } ::glfwSetWindowShouldClose(window, GLFW_TRUE); } } @@ -224,7 +233,7 @@ bool Playground::OpenPlaygroundHere(Renderer::RenderCallback render_callback) { // Render ImGui overlay. { - auto buffer = renderer->GetContext()->CreateRenderCommandBuffer(); + auto buffer = renderer->GetContext()->CreateCommandBuffer(); if (!buffer) { return false; } @@ -244,7 +253,7 @@ bool Playground::OpenPlaygroundHere(Renderer::RenderCallback render_callback) { ImGui_ImplImpeller_RenderDrawData(ImGui::GetDrawData(), *pass); - pass->EncodeCommands(renderer->GetContext()->GetTransientsAllocator()); + pass->EncodeCommands(); if (!buffer->SubmitCommands()) { return false; } @@ -268,7 +277,7 @@ bool Playground::OpenPlaygroundHere(Renderer::RenderCallback render_callback) { bool Playground::OpenPlaygroundHere(SinglePassCallback pass_callback) { return OpenPlaygroundHere( [context = GetContext(), &pass_callback](RenderTarget& render_target) { - auto buffer = context->CreateRenderCommandBuffer(); + auto buffer = context->CreateCommandBuffer(); if (!buffer) { return false; } @@ -284,7 +293,7 @@ bool Playground::OpenPlaygroundHere(SinglePassCallback pass_callback) { return false; } - pass->EncodeCommands(context->GetTransientsAllocator()); + pass->EncodeCommands(); if (!buffer->SubmitCommands()) { return false; } @@ -332,9 +341,8 @@ std::shared_ptr Playground::CreateTextureForFixture( texture_descriptor.mip_count = enable_mipmapping ? image->GetSize().MipCount() : 1u; - auto texture = - renderer_->GetContext()->GetPermanentsAllocator()->CreateTexture( - StorageMode::kHostVisible, texture_descriptor); + auto texture = renderer_->GetContext()->GetResourceAllocator()->CreateTexture( + StorageMode::kHostVisible, texture_descriptor); if (!texture) { VALIDATION_LOG << "Could not allocate texture for fixture " << fixture_name; return nullptr; @@ -367,9 +375,8 @@ std::shared_ptr Playground::CreateTextureCubeForFixture( texture_descriptor.size = images[0].GetSize(); texture_descriptor.mip_count = 1u; - auto texture = - renderer_->GetContext()->GetPermanentsAllocator()->CreateTexture( - StorageMode::kHostVisible, texture_descriptor); + auto texture = renderer_->GetContext()->GetResourceAllocator()->CreateTexture( + StorageMode::kHostVisible, texture_descriptor); if (!texture) { VALIDATION_LOG << "Could not allocate texture cube."; return nullptr; diff --git a/impeller/playground/playground.h b/impeller/playground/playground.h index cbb38bd423b19..8b65b6f68ce3c 100644 --- a/impeller/playground/playground.h +++ b/impeller/playground/playground.h @@ -34,6 +34,8 @@ class Playground { static constexpr bool is_enabled() { return is_enabled_; } + static bool ShouldOpenNewPlaygrounds(); + void SetupWindow(PlaygroundBackend backend); void TeardownWindow(); diff --git a/impeller/playground/playground_test.cc b/impeller/playground/playground_test.cc index 3cb7c946a8868..1627cf9a711f5 100644 --- a/impeller/playground/playground_test.cc +++ b/impeller/playground/playground_test.cc @@ -16,6 +16,11 @@ void PlaygroundTest::SetUp() { return; } + if (!Playground::ShouldOpenNewPlaygrounds()) { + GTEST_SKIP_("Skipping due to user action."); + return; + } + SetupWindow(GetParam()); } diff --git a/impeller/renderer/BUILD.gn b/impeller/renderer/BUILD.gn index 9223f4646fb5f..f593a42fd144d 100644 --- a/impeller/renderer/BUILD.gn +++ b/impeller/renderer/BUILD.gn @@ -22,6 +22,7 @@ impeller_component("renderer") { "command_buffer.h", "context.cc", "context.h", + "descriptor_set_layout.h", "device_buffer.cc", "device_buffer.h", "formats.cc", @@ -80,6 +81,7 @@ impeller_component("renderer") { "../base", "../geometry", "../image", + "../runtime_stage", "../tessellator", ] diff --git a/impeller/renderer/allocator.cc b/impeller/renderer/allocator.cc index 8151eec3492fb..49e69483ac393 100644 --- a/impeller/renderer/allocator.cc +++ b/impeller/renderer/allocator.cc @@ -13,20 +13,6 @@ Allocator::Allocator() = default; Allocator::~Allocator() = default; -bool Allocator::RequiresExplicitHostSynchronization(StorageMode mode) { - if (mode != StorageMode::kHostVisible) { - return false; - } - -#if FML_OS_IOS - // StorageMode::kHostVisible is MTLStorageModeShared already. - return false; -#else // FML_OS_IOS - // StorageMode::kHostVisible is MTLResourceStorageModeManaged. - return true; -#endif // FML_OS_IOS -} - std::shared_ptr Allocator::CreateBufferWithCopy( const uint8_t* buffer, size_t length) { diff --git a/impeller/renderer/allocator.h b/impeller/renderer/allocator.h index 10e1eb3f0aaeb..e4f189f02f6ed 100644 --- a/impeller/renderer/allocator.h +++ b/impeller/renderer/allocator.h @@ -69,7 +69,7 @@ class Allocator { std::shared_ptr CreateBufferWithCopy( const fml::Mapping& mapping); - static bool RequiresExplicitHostSynchronization(StorageMode mode); + virtual ISize GetMaxTextureSizeSupported() const = 0; protected: Allocator(); diff --git a/impeller/renderer/backend/gles/allocator_gles.cc b/impeller/renderer/backend/gles/allocator_gles.cc index 5f08256ab0031..8873272c315c8 100644 --- a/impeller/renderer/backend/gles/allocator_gles.cc +++ b/impeller/renderer/backend/gles/allocator_gles.cc @@ -42,4 +42,9 @@ std::shared_ptr AllocatorGLES::CreateTexture( return std::make_shared(reactor_, std::move(desc)); } +// |Allocator| +ISize AllocatorGLES::GetMaxTextureSizeSupported() const { + return reactor_->GetProcTable().GetCapabilities()->max_texture_size; +} + } // namespace impeller diff --git a/impeller/renderer/backend/gles/allocator_gles.h b/impeller/renderer/backend/gles/allocator_gles.h index 7b322938d351b..1336b19ecb8cd 100644 --- a/impeller/renderer/backend/gles/allocator_gles.h +++ b/impeller/renderer/backend/gles/allocator_gles.h @@ -35,6 +35,9 @@ class AllocatorGLES final : public Allocator { StorageMode mode, const TextureDescriptor& desc) override; + // |Allocator| + ISize GetMaxTextureSizeSupported() const override; + FML_DISALLOW_COPY_AND_ASSIGN(AllocatorGLES); }; diff --git a/impeller/renderer/backend/gles/buffer_bindings_gles.cc b/impeller/renderer/backend/gles/buffer_bindings_gles.cc index 32896f631f34c..5119b5cc09df6 100644 --- a/impeller/renderer/backend/gles/buffer_bindings_gles.cc +++ b/impeller/renderer/backend/gles/buffer_bindings_gles.cc @@ -234,6 +234,13 @@ bool BufferBindingsGLES::BindUniformBuffer(const ProcTableGLES& gl, buffer_ptr + member.offset) // data ); continue; + case sizeof(Vector3): + gl.Uniform3fv(location->second, // location + 1u, // count + reinterpret_cast( + buffer_ptr + member.offset) // data + ); + continue; case sizeof(Vector2): gl.Uniform2fv(location->second, // location 1u, // count diff --git a/impeller/renderer/backend/gles/command_buffer_gles.cc b/impeller/renderer/backend/gles/command_buffer_gles.cc index 7bb99354c079c..f6452528995c5 100644 --- a/impeller/renderer/backend/gles/command_buffer_gles.cc +++ b/impeller/renderer/backend/gles/command_buffer_gles.cc @@ -10,8 +10,10 @@ namespace impeller { -CommandBufferGLES::CommandBufferGLES(ReactorGLES::Ref reactor) - : reactor_(std::move(reactor)), +CommandBufferGLES::CommandBufferGLES(std::weak_ptr context, + ReactorGLES::Ref reactor) + : CommandBuffer(std::move(context)), + reactor_(std::move(reactor)), is_valid_(reactor_ && reactor_->IsValid()) {} CommandBufferGLES::~CommandBufferGLES() = default; @@ -27,13 +29,8 @@ bool CommandBufferGLES::IsValid() const { } // |CommandBuffer| -bool CommandBufferGLES::SubmitCommands(CompletionCallback callback) { - if (!IsValid()) { - return false; - } - +bool CommandBufferGLES::OnSubmitCommands(CompletionCallback callback) { const auto result = reactor_->React(); - if (callback) { callback(result ? CommandBuffer::Status::kCompleted : CommandBuffer::Status::kError); @@ -48,7 +45,7 @@ std::shared_ptr CommandBufferGLES::OnCreateRenderPass( return nullptr; } auto pass = std::shared_ptr( - new RenderPassGLES(std::move(target), reactor_)); + new RenderPassGLES(context_, std::move(target), reactor_)); if (!pass->IsValid()) { return nullptr; } diff --git a/impeller/renderer/backend/gles/command_buffer_gles.h b/impeller/renderer/backend/gles/command_buffer_gles.h index 22b26464379d7..a83bab052c03d 100644 --- a/impeller/renderer/backend/gles/command_buffer_gles.h +++ b/impeller/renderer/backend/gles/command_buffer_gles.h @@ -22,7 +22,8 @@ class CommandBufferGLES final : public CommandBuffer { ReactorGLES::Ref reactor_; bool is_valid_ = false; - CommandBufferGLES(ReactorGLES::Ref reactor); + CommandBufferGLES(std::weak_ptr context, + ReactorGLES::Ref reactor); // |CommandBuffer| void SetLabel(const std::string& label) const override; @@ -31,7 +32,7 @@ class CommandBufferGLES final : public CommandBuffer { bool IsValid() const override; // |CommandBuffer| - bool SubmitCommands(CompletionCallback callback) override; + bool OnSubmitCommands(CompletionCallback callback) override; // |CommandBuffer| std::shared_ptr OnCreateRenderPass( diff --git a/impeller/renderer/backend/gles/context_gles.cc b/impeller/renderer/backend/gles/context_gles.cc index f626504d62821..49f454f542cb3 100644 --- a/impeller/renderer/backend/gles/context_gles.cc +++ b/impeller/renderer/backend/gles/context_gles.cc @@ -6,6 +6,7 @@ #include "impeller/base/config.h" #include "impeller/base/validation.h" +#include "impeller/base/work_queue_common.h" namespace impeller { @@ -42,29 +43,31 @@ ContextGLES::ContextGLES( std::shared_ptr(new PipelineLibraryGLES(reactor_)); } - // Create all allocators. + // Create allocators. { - permanents_allocator_ = + resource_allocator_ = std::shared_ptr(new AllocatorGLES(reactor_)); - if (!permanents_allocator_->IsValid()) { - VALIDATION_LOG << "Could not create permanents allocator."; - return; - } - - transients_allocator_ = - std::shared_ptr(new AllocatorGLES(reactor_)); - if (!transients_allocator_->IsValid()) { - VALIDATION_LOG << "Could not create transients allocator."; + if (!resource_allocator_->IsValid()) { + VALIDATION_LOG << "Could not create a resource allocator."; return; } } - // Create the sampler library + // Create the sampler library. { sampler_library_ = std::shared_ptr(new SamplerLibraryGLES()); } + // Create the work queue. + { + work_queue_ = WorkQueueCommon::Create(); + if (!work_queue_) { + VALIDATION_LOG << "Could not create work queue."; + return; + } + } + is_valid_ = true; } @@ -93,34 +96,35 @@ bool ContextGLES::IsValid() const { return is_valid_; } -std::shared_ptr ContextGLES::GetPermanentsAllocator() const { - return permanents_allocator_; -} - -std::shared_ptr ContextGLES::GetTransientsAllocator() const { - return transients_allocator_; +// |Context| +std::shared_ptr ContextGLES::GetResourceAllocator() const { + return resource_allocator_; } +// |Context| std::shared_ptr ContextGLES::GetShaderLibrary() const { return shader_library_; } +// |Context| std::shared_ptr ContextGLES::GetSamplerLibrary() const { return sampler_library_; } +// |Context| std::shared_ptr ContextGLES::GetPipelineLibrary() const { return pipeline_library_; } -std::shared_ptr ContextGLES::CreateRenderCommandBuffer() const { - return std::shared_ptr(new CommandBufferGLES(reactor_)); +// |Context| +std::shared_ptr ContextGLES::CreateCommandBuffer() const { + return std::shared_ptr( + new CommandBufferGLES(weak_from_this(), reactor_)); } -std::shared_ptr ContextGLES::CreateTransferCommandBuffer() - const { - // There is no such concept. Just use a render command buffer. - return CreateRenderCommandBuffer(); +// |Context| +std::shared_ptr ContextGLES::GetWorkQueue() const { + return work_queue_; } // |Context| @@ -128,4 +132,9 @@ bool ContextGLES::HasThreadingRestrictions() const { return true; } +// |Context| +bool ContextGLES::SupportsOffscreenMSAA() const { + return false; +} + } // namespace impeller diff --git a/impeller/renderer/backend/gles/context_gles.h b/impeller/renderer/backend/gles/context_gles.h index 6f516e69c7197..405f31d1b168b 100644 --- a/impeller/renderer/backend/gles/context_gles.h +++ b/impeller/renderer/backend/gles/context_gles.h @@ -38,8 +38,8 @@ class ContextGLES final : public Context, std::shared_ptr shader_library_; std::shared_ptr pipeline_library_; std::shared_ptr sampler_library_; - std::shared_ptr permanents_allocator_; - std::shared_ptr transients_allocator_; + std::shared_ptr work_queue_; + std::shared_ptr resource_allocator_; bool is_valid_ = false; ContextGLES(std::unique_ptr gl, @@ -49,10 +49,7 @@ class ContextGLES final : public Context, bool IsValid() const override; // |Context| - std::shared_ptr GetPermanentsAllocator() const override; - - // |Context| - std::shared_ptr GetTransientsAllocator() const override; + std::shared_ptr GetResourceAllocator() const override; // |Context| std::shared_ptr GetShaderLibrary() const override; @@ -64,14 +61,17 @@ class ContextGLES final : public Context, std::shared_ptr GetPipelineLibrary() const override; // |Context| - std::shared_ptr CreateRenderCommandBuffer() const override; + std::shared_ptr CreateCommandBuffer() const override; // |Context| - std::shared_ptr CreateTransferCommandBuffer() const override; + std::shared_ptr GetWorkQueue() const override; // |Context| bool HasThreadingRestrictions() const override; + // |Context| + bool SupportsOffscreenMSAA() const override; + FML_DISALLOW_COPY_AND_ASSIGN(ContextGLES); }; diff --git a/impeller/renderer/backend/gles/proc_table_gles.h b/impeller/renderer/backend/gles/proc_table_gles.h index ee98d44e3cf09..fc113e3697af0 100644 --- a/impeller/renderer/backend/gles/proc_table_gles.h +++ b/impeller/renderer/backend/gles/proc_table_gles.h @@ -157,6 +157,7 @@ struct GLProc { PROC(Uniform1fv); \ PROC(Uniform1i); \ PROC(Uniform2fv); \ + PROC(Uniform3fv); \ PROC(Uniform4fv); \ PROC(UniformMatrix4fv); \ PROC(UseProgram); \ diff --git a/impeller/renderer/backend/gles/render_pass_gles.cc b/impeller/renderer/backend/gles/render_pass_gles.cc index 3dae613f4ad31..3e96e82c054b1 100644 --- a/impeller/renderer/backend/gles/render_pass_gles.cc +++ b/impeller/renderer/backend/gles/render_pass_gles.cc @@ -16,8 +16,10 @@ namespace impeller { -RenderPassGLES::RenderPassGLES(RenderTarget target, ReactorGLES::Ref reactor) - : RenderPass(std::move(target)), +RenderPassGLES::RenderPassGLES(std::weak_ptr context, + RenderTarget target, + ReactorGLES::Ref reactor) + : RenderPass(std::move(context), std::move(target)), reactor_(std::move(reactor)), is_valid_(reactor_ && reactor_->IsValid()) {} @@ -455,8 +457,7 @@ struct RenderPassData { } // |RenderPass| -bool RenderPassGLES::EncodeCommands( - const std::shared_ptr& transients_allocator) const { +bool RenderPassGLES::OnEncodeCommands(const Context& context) const { if (!IsValid()) { return false; } @@ -507,10 +508,11 @@ bool RenderPassGLES::EncodeCommands( CanDiscardAttachmentWhenDone(stencil0->store_action); } - return reactor_->AddOperation([pass_data, transients_allocator, + return reactor_->AddOperation([pass_data, + allocator = context.GetResourceAllocator(), commands = commands_](const auto& reactor) { - auto result = EncodeCommandsInReactor(*pass_data, transients_allocator, - reactor, commands); + auto result = + EncodeCommandsInReactor(*pass_data, allocator, reactor, commands); FML_CHECK(result) << "Must be able to encode GL commands without error."; }); } diff --git a/impeller/renderer/backend/gles/render_pass_gles.h b/impeller/renderer/backend/gles/render_pass_gles.h index 7f66f036dc77a..7ffc5202a2cad 100644 --- a/impeller/renderer/backend/gles/render_pass_gles.h +++ b/impeller/renderer/backend/gles/render_pass_gles.h @@ -22,7 +22,9 @@ class RenderPassGLES final : public RenderPass { std::string label_; bool is_valid_ = false; - RenderPassGLES(RenderTarget target, ReactorGLES::Ref reactor); + RenderPassGLES(std::weak_ptr context, + RenderTarget target, + ReactorGLES::Ref reactor); // |RenderPass| bool IsValid() const override; @@ -31,8 +33,7 @@ class RenderPassGLES final : public RenderPass { void OnSetLabel(std::string label) override; // |RenderPass| - bool EncodeCommands( - const std::shared_ptr& transients_allocator) const override; + bool OnEncodeCommands(const Context& context) const override; FML_DISALLOW_COPY_AND_ASSIGN(RenderPassGLES); }; diff --git a/impeller/renderer/backend/gles/shader_library_gles.cc b/impeller/renderer/backend/gles/shader_library_gles.cc index bfe199aa06017..b20e46d160f90 100644 --- a/impeller/renderer/backend/gles/shader_library_gles.cc +++ b/impeller/renderer/backend/gles/shader_library_gles.cc @@ -14,11 +14,11 @@ namespace impeller { -static ShaderStage ToShaderStage(Blob::ShaderType type) { +static ShaderStage ToShaderStage(BlobShaderType type) { switch (type) { - case Blob::ShaderType::kVertex: + case BlobShaderType::kVertex: return ShaderStage::kVertex; - case Blob::ShaderType::kFragment: + case BlobShaderType::kFragment: return ShaderStage::kFragment; } FML_UNREACHABLE(); diff --git a/impeller/renderer/backend/metal/allocator_mtl.h b/impeller/renderer/backend/metal/allocator_mtl.h index 256ed3a6fc6db..62cb0eee4a0c6 100644 --- a/impeller/renderer/backend/metal/allocator_mtl.h +++ b/impeller/renderer/backend/metal/allocator_mtl.h @@ -21,12 +21,12 @@ class AllocatorMTL final : public Allocator { private: friend class ContextMTL; - // In the prototype, we are going to be allocating resources directly with the - // MTLDevice APIs. But, in the future, this could be backed by named heaps - // with specific limits. id device_; std::string allocator_label_; + bool supports_memoryless_targets_ = false; + bool supports_uma_ = false; bool is_valid_ = false; + ISize max_texture_supported_; AllocatorMTL(id device, std::string label); @@ -42,6 +42,9 @@ class AllocatorMTL final : public Allocator { StorageMode mode, const TextureDescriptor& desc) override; + // |Allocator| + ISize GetMaxTextureSizeSupported() const override; + FML_DISALLOW_COPY_AND_ASSIGN(AllocatorMTL); }; diff --git a/impeller/renderer/backend/metal/allocator_mtl.mm b/impeller/renderer/backend/metal/allocator_mtl.mm index 7b0120503d630..fad4803ed077c 100644 --- a/impeller/renderer/backend/metal/allocator_mtl.mm +++ b/impeller/renderer/backend/metal/allocator_mtl.mm @@ -14,12 +14,80 @@ namespace impeller { +static bool DeviceSupportsMemorylessTargets(id device) { + // Refer to the "Memoryless render targets" feature in the table below: + // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf + if (@available(ios 13.0, tvos 13.0, macos 10.15, *)) { + return [device supportsFamily:MTLGPUFamilyApple2]; + } else { +#if FML_OS_IOS + // This is perhaps redundant. But, just in case we somehow get into a case + // where Impeller runs on iOS versions less than 8.0 and/or without A8 + // GPUs, we explicitly check feature set support. + return [device supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily2_v1]; +#else + // MacOS devices with Apple GPUs are only available with macos 10.15 and + // above. So, if we are here, it is safe to assume that memory-less targets + // are not supported. + return false; +#endif + } + FML_UNREACHABLE(); +} + +static bool DeviceHasUnifiedMemoryArchitecture(id device) { + if (@available(ios 13.0, tvos 13.0, macOS 10.15, *)) { + return [device hasUnifiedMemory]; + } else { +#if FML_OS_IOS + // iOS devices where the availability check can fail always have had UMA. + return true; +#else + // Mac devices where the availability check can fail have never had UMA. + return false; +#endif + } + FML_UNREACHABLE(); +} + +static ISize DeviceMaxTextureSizeSupported(id device) { + // Since Apple didn't expose API for us to get the max texture size, we have + // to use hardcoded data from + // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf + // According to the feature set table, there are two supported max sizes : + // 16384 and 8192 for devices flutter support. The former is used on macs and + // latest ios devices. The latter is used on old ios devices. + if (@available(macOS 10.15, iOS 13, tvOS 13, *)) { + if ([device supportsFamily:MTLGPUFamilyApple3] || + [device supportsFamily:MTLGPUFamilyMacCatalyst1] || + [device supportsFamily:MTLGPUFamilyMac1]) { + return {16384, 16384}; + } + return {8192, 8192}; + } else { +#if FML_OS_IOS + if ([device supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily4_v1] || + [device supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily3_v1]) { + return {16384, 16384}; + } +#endif +#if FML_OS_MACOSX + return {16384, 16384}; +#endif + return {8192, 8192}; + } +} + AllocatorMTL::AllocatorMTL(id device, std::string label) : device_(device), allocator_label_(std::move(label)) { if (!device_) { return; } + supports_memoryless_targets_ = DeviceSupportsMemorylessTargets(device_); + supports_uma_ = DeviceHasUnifiedMemoryArchitecture(device_); + max_texture_supported_ = DeviceMaxTextureSizeSupported(device_); + is_valid_ = true; } @@ -29,56 +97,86 @@ return is_valid_; } -static MTLResourceOptions ToMTLResourceOptions(StorageMode type) { +static MTLResourceOptions ToMTLResourceOptions(StorageMode type, + bool supports_memoryless_targets, + bool supports_uma) { switch (type) { case StorageMode::kHostVisible: #if FML_OS_IOS return MTLResourceStorageModeShared; #else - return MTLResourceStorageModeManaged; + if (supports_uma) { + return MTLResourceStorageModeShared; + } else { + return MTLResourceStorageModeManaged; + } #endif case StorageMode::kDevicePrivate: return MTLResourceStorageModePrivate; case StorageMode::kDeviceTransient: -#if FML_OS_IOS - return MTLResourceStorageModeMemoryless; -#else - return MTLResourceStorageModePrivate; -#endif + if (supports_memoryless_targets) { + // Device may support but the OS has not been updated. + if (@available(macOS 11.0, *)) { + return MTLResourceStorageModeMemoryless; + } else { + return MTLResourceStorageModePrivate; + } + } else { + return MTLResourceStorageModePrivate; + } + FML_UNREACHABLE(); } - - return MTLResourceStorageModePrivate; + FML_UNREACHABLE(); } -static MTLStorageMode ToMTLStorageMode(StorageMode mode) { +static MTLStorageMode ToMTLStorageMode(StorageMode mode, + bool supports_memoryless_targets, + bool supports_uma) { switch (mode) { case StorageMode::kHostVisible: #if FML_OS_IOS return MTLStorageModeShared; #else - return MTLStorageModeManaged; + if (supports_uma) { + return MTLStorageModeShared; + } else { + return MTLStorageModeManaged; + } #endif case StorageMode::kDevicePrivate: return MTLStorageModePrivate; case StorageMode::kDeviceTransient: -#if FML_OS_IOS - return MTLStorageModeMemoryless; -#else - return MTLStorageModePrivate; -#endif + if (supports_memoryless_targets) { + // Device may support but the OS has not been updated. + if (@available(macOS 11.0, *)) { + return MTLStorageModeMemoryless; + } else { + return MTLStorageModePrivate; + } + } else { + return MTLStorageModePrivate; + } + FML_UNREACHABLE(); } - return MTLStorageModeShared; + FML_UNREACHABLE(); } std::shared_ptr AllocatorMTL::CreateBuffer(StorageMode mode, size_t length) { - auto buffer = [device_ newBufferWithLength:length - options:ToMTLResourceOptions(mode)]; + const auto resource_options = + ToMTLResourceOptions(mode, supports_memoryless_targets_, supports_uma_); + const auto storage_mode = + ToMTLStorageMode(mode, supports_memoryless_targets_, supports_uma_); + + auto buffer = [device_ newBufferWithLength:length options:resource_options]; if (!buffer) { return nullptr; } - return std::shared_ptr( - new DeviceBufferMTL(buffer, length, mode)); + return std::shared_ptr(new DeviceBufferMTL(buffer, // + length, // + mode, // + storage_mode // + )); } std::shared_ptr AllocatorMTL::CreateTexture( @@ -95,7 +193,8 @@ static MTLStorageMode ToMTLStorageMode(StorageMode mode) { return nullptr; } - mtl_texture_desc.storageMode = ToMTLStorageMode(mode); + mtl_texture_desc.storageMode = + ToMTLStorageMode(mode, supports_memoryless_targets_, supports_uma_); auto texture = [device_ newTextureWithDescriptor:mtl_texture_desc]; if (!texture) { return nullptr; @@ -103,4 +202,8 @@ static MTLStorageMode ToMTLStorageMode(StorageMode mode) { return std::make_shared(desc, texture); } +ISize AllocatorMTL::GetMaxTextureSizeSupported() const { + return max_texture_supported_; +} + } // namespace impeller diff --git a/impeller/renderer/backend/metal/command_buffer_mtl.h b/impeller/renderer/backend/metal/command_buffer_mtl.h index 6c038d675376a..e4e09609657a8 100644 --- a/impeller/renderer/backend/metal/command_buffer_mtl.h +++ b/impeller/renderer/backend/metal/command_buffer_mtl.h @@ -13,8 +13,6 @@ namespace impeller { class CommandBufferMTL final : public CommandBuffer { public: - CommandBufferMTL(); - // |CommandBuffer| ~CommandBufferMTL() override; @@ -23,7 +21,8 @@ class CommandBufferMTL final : public CommandBuffer { id buffer_ = nullptr; - CommandBufferMTL(id queue); + CommandBufferMTL(const std::weak_ptr context, + id queue); // |CommandBuffer| void SetLabel(const std::string& label) const override; @@ -32,7 +31,7 @@ class CommandBufferMTL final : public CommandBuffer { bool IsValid() const override; // |CommandBuffer| - bool SubmitCommands(CompletionCallback callback) override; + bool OnSubmitCommands(CompletionCallback callback) override; // |CommandBuffer| std::shared_ptr OnCreateRenderPass( diff --git a/impeller/renderer/backend/metal/command_buffer_mtl.mm b/impeller/renderer/backend/metal/command_buffer_mtl.mm index 90bcbf1cf9256..babafbdf0c75f 100644 --- a/impeller/renderer/backend/metal/command_buffer_mtl.mm +++ b/impeller/renderer/backend/metal/command_buffer_mtl.mm @@ -137,8 +137,9 @@ static void LogMTLCommandBufferErrorIfPresent(id buffer) { return [queue commandBuffer]; } -CommandBufferMTL::CommandBufferMTL(id queue) - : buffer_(CreateCommandBuffer(queue)) {} +CommandBufferMTL::CommandBufferMTL(const std::weak_ptr context, + id queue) + : CommandBuffer(std::move(context)), buffer_(CreateCommandBuffer(queue)) {} CommandBufferMTL::~CommandBufferMTL() = default; @@ -166,15 +167,7 @@ static void LogMTLCommandBufferErrorIfPresent(id buffer) { return CommandBufferMTL::Status::kError; } -bool CommandBufferMTL::SubmitCommands(CompletionCallback callback) { - if (!IsValid()) { - // Already committed or was never valid. Either way, this is caller error. - if (callback) { - callback(Status::kError); - } - return false; - } - +bool CommandBufferMTL::OnSubmitCommands(CompletionCallback callback) { if (callback) { [buffer_ addCompletedHandler:^(id buffer) { LogMTLCommandBufferErrorIfPresent(buffer); @@ -195,7 +188,7 @@ static void LogMTLCommandBufferErrorIfPresent(id buffer) { } auto pass = std::shared_ptr( - new RenderPassMTL(buffer_, std::move(target))); + new RenderPassMTL(context_, std::move(target), buffer_)); if (!pass->IsValid()) { return nullptr; } diff --git a/impeller/renderer/backend/metal/context_mtl.h b/impeller/renderer/backend/metal/context_mtl.h index 705978601b8f6..98f6c9139a118 100644 --- a/impeller/renderer/backend/metal/context_mtl.h +++ b/impeller/renderer/backend/metal/context_mtl.h @@ -37,13 +37,12 @@ class ContextMTL final : public Context, private: id device_ = nullptr; - id render_queue_ = nullptr; - id transfer_queue_ = nullptr; + id command_queue_ = nullptr; std::shared_ptr shader_library_; std::shared_ptr pipeline_library_; std::shared_ptr sampler_library_; - std::shared_ptr permanents_allocator_; - std::shared_ptr transients_allocator_; + std::shared_ptr resource_allocator_; + std::shared_ptr work_queue_; bool is_valid_ = false; ContextMTL(id device, NSArray>* shader_libraries); @@ -52,10 +51,7 @@ class ContextMTL final : public Context, bool IsValid() const override; // |Context| - std::shared_ptr GetPermanentsAllocator() const override; - - // |Context| - std::shared_ptr GetTransientsAllocator() const override; + std::shared_ptr GetResourceAllocator() const override; // |Context| std::shared_ptr GetShaderLibrary() const override; @@ -67,10 +63,13 @@ class ContextMTL final : public Context, std::shared_ptr GetPipelineLibrary() const override; // |Context| - std::shared_ptr CreateRenderCommandBuffer() const override; + std::shared_ptr CreateCommandBuffer() const override; + + // |Context| + std::shared_ptr GetWorkQueue() const override; // |Context| - std::shared_ptr CreateTransferCommandBuffer() const override; + bool SupportsOffscreenMSAA() const override; std::shared_ptr CreateCommandBufferInQueue( id queue) const; diff --git a/impeller/renderer/backend/metal/context_mtl.mm b/impeller/renderer/backend/metal/context_mtl.mm index af3ec0de7640b..8cc908a8d06f8 100644 --- a/impeller/renderer/backend/metal/context_mtl.mm +++ b/impeller/renderer/backend/metal/context_mtl.mm @@ -9,6 +9,7 @@ #include "flutter/fml/file.h" #include "flutter/fml/logging.h" #include "flutter/fml/paths.h" +#include "impeller/base/platform/darwin/work_queue_darwin.h" #include "impeller/renderer/backend/metal/sampler_library_mtl.h" #include "impeller/renderer/sampler_descriptor.h" @@ -40,39 +41,43 @@ shader_library_ = std::move(library); } - // Setup command queues. - render_queue_ = device_.newCommandQueue; - transfer_queue_ = device_.newCommandQueue; - - if (!render_queue_ || !transfer_queue_) { - return; + // Setup command queue. + { + command_queue_ = device_.newCommandQueue; + if (!command_queue_) { + VALIDATION_LOG << "Could not setup the command queue."; + return; + } + command_queue_.label = @"Impeller Command Queue"; } - render_queue_.label = @"Impeller Render Queue"; - transfer_queue_.label = @"Impeller Transfer Queue"; - // Setup the pipeline library. - { // + { pipeline_library_ = std::shared_ptr(new PipelineLibraryMTL(device_)); } // Setup the sampler library. - { // + { sampler_library_ = std::shared_ptr(new SamplerLibraryMTL(device_)); } + // Setup the resource allocator. { - transients_allocator_ = std::shared_ptr( - new AllocatorMTL(device_, "Impeller Transients Allocator")); - if (!transients_allocator_) { + resource_allocator_ = std::shared_ptr( + new AllocatorMTL(device_, "Impeller Permanents Allocator")); + if (!resource_allocator_) { + VALIDATION_LOG << "Could not setup the resource allocator."; return; } + } - permanents_allocator_ = std::shared_ptr( - new AllocatorMTL(device_, "Impeller Permanents Allocator")); - if (!permanents_allocator_) { + // Setup the work queue. + { + work_queue_ = WorkQueueDarwin::Create(); + if (!work_queue_) { + VALIDATION_LOG << "Could not setup the work queue."; return; } } @@ -175,28 +180,34 @@ ContextMTL::~ContextMTL() = default; +// |Context| bool ContextMTL::IsValid() const { return is_valid_; } +// |Context| std::shared_ptr ContextMTL::GetShaderLibrary() const { return shader_library_; } +// |Context| std::shared_ptr ContextMTL::GetPipelineLibrary() const { return pipeline_library_; } +// |Context| std::shared_ptr ContextMTL::GetSamplerLibrary() const { return sampler_library_; } -std::shared_ptr ContextMTL::CreateRenderCommandBuffer() const { - return CreateCommandBufferInQueue(render_queue_); +// |Context| +std::shared_ptr ContextMTL::CreateCommandBuffer() const { + return CreateCommandBufferInQueue(command_queue_); } -std::shared_ptr ContextMTL::CreateTransferCommandBuffer() const { - return CreateCommandBufferInQueue(transfer_queue_); +// |Context| +std::shared_ptr ContextMTL::GetWorkQueue() const { + return work_queue_; } std::shared_ptr ContextMTL::CreateCommandBufferInQueue( @@ -205,23 +216,25 @@ return nullptr; } - auto buffer = std::shared_ptr(new CommandBufferMTL(queue)); + auto buffer = std::shared_ptr( + new CommandBufferMTL(weak_from_this(), queue)); if (!buffer->IsValid()) { return nullptr; } return buffer; } -std::shared_ptr ContextMTL::GetPermanentsAllocator() const { - return permanents_allocator_; -} - -std::shared_ptr ContextMTL::GetTransientsAllocator() const { - return transients_allocator_; +std::shared_ptr ContextMTL::GetResourceAllocator() const { + return resource_allocator_; } id ContextMTL::GetMTLDevice() const { return device_; } +// |Context| +bool ContextMTL::SupportsOffscreenMSAA() const { + return true; +} + } // namespace impeller diff --git a/impeller/renderer/backend/metal/device_buffer_mtl.h b/impeller/renderer/backend/metal/device_buffer_mtl.h index e167dd86e66de..11207794b9dec 100644 --- a/impeller/renderer/backend/metal/device_buffer_mtl.h +++ b/impeller/renderer/backend/metal/device_buffer_mtl.h @@ -27,8 +27,12 @@ class DeviceBufferMTL final friend class AllocatorMTL; const id buffer_; + const MTLStorageMode storage_mode_; - DeviceBufferMTL(id buffer, size_t size, StorageMode mode); + DeviceBufferMTL(id buffer, + size_t size, + StorageMode mode, + MTLStorageMode storage_mode); // |DeviceBuffer| bool CopyHostBuffer(const uint8_t* source, diff --git a/impeller/renderer/backend/metal/device_buffer_mtl.mm b/impeller/renderer/backend/metal/device_buffer_mtl.mm index 32e7c551439c9..e784bcf1174c8 100644 --- a/impeller/renderer/backend/metal/device_buffer_mtl.mm +++ b/impeller/renderer/backend/metal/device_buffer_mtl.mm @@ -13,8 +13,9 @@ DeviceBufferMTL::DeviceBufferMTL(id buffer, size_t size, - StorageMode mode) - : DeviceBuffer(size, mode), buffer_(buffer) {} + StorageMode mode, + MTLStorageMode storage_mode) + : DeviceBuffer(size, mode), buffer_(buffer), storage_mode_(storage_mode) {} DeviceBufferMTL::~DeviceBufferMTL() = default; @@ -45,15 +46,11 @@ ::memmove(dest + offset, source + source_range.offset, source_range.length); } -// |RequiresExplicitHostSynchronization| always returns false on iOS. But the -// compiler is mad that `didModifyRange:` appears in a TU meant for iOS. So, +// MTLStorageModeManaged is never present on always returns false on iOS. But +// the compiler is mad that `didModifyRange:` appears in a TU meant for iOS. So, // just compile it away. -// -// Making this call is never necessary on iOS because there is no -// MTLResourceStorageModeManaged mode. Only the MTLStorageModeShared mode is -// available. #if !FML_OS_IOS - if (Allocator::RequiresExplicitHostSynchronization(mode_)) { + if (storage_mode_ == MTLStorageModeManaged) { [buffer_ didModifyRange:NSMakeRange(offset, source_range.length)]; } #endif @@ -76,7 +73,6 @@ [buffer_ addDebugMarker:@(label.c_str()) range:NSMakeRange(range.offset, range.length)]; return true; - FML_UNREACHABLE(); } } // namespace impeller diff --git a/impeller/renderer/backend/metal/formats_mtl.h b/impeller/renderer/backend/metal/formats_mtl.h index ef9bdc31fe0fc..c9857f9a3b108 100644 --- a/impeller/renderer/backend/metal/formats_mtl.h +++ b/impeller/renderer/backend/metal/formats_mtl.h @@ -247,6 +247,8 @@ constexpr MTLStoreAction ToMTLStoreAction(StoreAction action) { return MTLStoreActionStore; case StoreAction::kMultisampleResolve: return MTLStoreActionMultisampleResolve; + case StoreAction::kStoreAndMultisampleResolve: + return MTLStoreActionStoreAndMultisampleResolve; } return MTLStoreActionDontCare; } @@ -259,6 +261,8 @@ constexpr StoreAction FromMTLStoreAction(MTLStoreAction action) { return StoreAction::kStore; case MTLStoreActionMultisampleResolve: return StoreAction::kMultisampleResolve; + case MTLStoreActionStoreAndMultisampleResolve: + return StoreAction::kStoreAndMultisampleResolve; default: break; } diff --git a/impeller/renderer/backend/metal/render_pass_mtl.h b/impeller/renderer/backend/metal/render_pass_mtl.h index 8458a96c8aa83..fd9d4bd80b46d 100644 --- a/impeller/renderer/backend/metal/render_pass_mtl.h +++ b/impeller/renderer/backend/metal/render_pass_mtl.h @@ -25,7 +25,9 @@ class RenderPassMTL final : public RenderPass { std::string label_; bool is_valid_ = false; - RenderPassMTL(id buffer, RenderTarget target); + RenderPassMTL(std::weak_ptr context, + RenderTarget target, + id buffer); // |RenderPass| bool IsValid() const override; @@ -34,8 +36,7 @@ class RenderPassMTL final : public RenderPass { void OnSetLabel(std::string label) override; // |RenderPass| - bool EncodeCommands( - const std::shared_ptr& transients_allocator) const override; + bool OnEncodeCommands(const Context& context) const override; bool EncodeCommands(const std::shared_ptr& transients_allocator, id pass) const; diff --git a/impeller/renderer/backend/metal/render_pass_mtl.mm b/impeller/renderer/backend/metal/render_pass_mtl.mm index 5f38a6215774b..e8c825af36b2b 100644 --- a/impeller/renderer/backend/metal/render_pass_mtl.mm +++ b/impeller/renderer/backend/metal/render_pass_mtl.mm @@ -22,17 +22,19 @@ static bool ConfigureResolveTextureAttachment( const Attachment& desc, MTLRenderPassAttachmentDescriptor* attachment) { - if (desc.store_action == StoreAction::kMultisampleResolve && - !desc.resolve_texture) { + bool needs_resolve = + desc.store_action == StoreAction::kMultisampleResolve || + desc.store_action == StoreAction::kStoreAndMultisampleResolve; + + if (needs_resolve && !desc.resolve_texture) { VALIDATION_LOG << "Resolve store action specified on attachment but no " "resolve texture was specified."; return false; } - if (desc.resolve_texture && - desc.store_action != StoreAction::kMultisampleResolve) { - VALIDATION_LOG << "Resolve store action specified but there was no " - "resolve attachment."; + if (desc.resolve_texture && !needs_resolve) { + VALIDATION_LOG << "A resolve texture was specified even though the store " + "action doesn't require it."; return false; } @@ -128,8 +130,10 @@ static bool ConfigureStencilAttachment( return result; } -RenderPassMTL::RenderPassMTL(id buffer, RenderTarget target) - : RenderPass(std::move(target)), +RenderPassMTL::RenderPassMTL(std::weak_ptr context, + RenderTarget target, + id buffer) + : RenderPass(std::move(context), std::move(target)), buffer_(buffer), desc_(ToMTLRenderPassDescriptor(GetRenderTarget())) { if (!buffer_ || !desc_ || !render_target_.IsValid()) { @@ -151,8 +155,7 @@ static bool ConfigureStencilAttachment( label_ = std::move(label); } -bool RenderPassMTL::EncodeCommands( - const std::shared_ptr& transients_allocator) const { +bool RenderPassMTL::OnEncodeCommands(const Context& context) const { TRACE_EVENT0("impeller", "RenderPassMTL::EncodeCommands"); if (!IsValid()) { return false; @@ -173,7 +176,7 @@ static bool ConfigureStencilAttachment( fml::ScopedCleanupClosure auto_end( [render_command_encoder]() { [render_command_encoder endEncoding]; }); - return EncodeCommands(transients_allocator, render_command_encoder); + return EncodeCommands(context.GetResourceAllocator(), render_command_encoder); } //----------------------------------------------------------------------------- @@ -299,6 +302,36 @@ bool SetSampler(ShaderStage stage, return false; } + void SetViewport(const Viewport& viewport) { + if (viewport_.has_value() && viewport_.value() == viewport) { + return; + } + [encoder_ setViewport:MTLViewport{ + .originX = viewport.rect.origin.x, + .originY = viewport.rect.origin.y, + .width = viewport.rect.size.width, + .height = viewport.rect.size.height, + .znear = viewport.depth_range.z_near, + .zfar = viewport.depth_range.z_far, + }]; + viewport_ = viewport; + } + + void SetScissor(const IRect& scissor) { + if (scissor_.has_value() && scissor_.value() == scissor) { + return; + } + [encoder_ + setScissorRect:MTLScissorRect{ + .x = static_cast(scissor.origin.x), + .y = static_cast(scissor.origin.y), + .width = static_cast(scissor.size.width), + .height = + static_cast(scissor.size.height), + }]; + scissor_ = scissor; + } + private: struct BufferOffsetPair { id buffer = nullptr; @@ -314,6 +347,8 @@ bool SetSampler(ShaderStage stage, std::map buffers_; std::map textures_; std::map samplers_; + std::optional viewport_; + std::optional scissor_; }; static bool Bind(PassBindingsCache& pass, @@ -423,6 +458,11 @@ static bool Bind(PassBindingsCache& pass, PipelineMTL::Cast(*command.pipeline).GetMTLRenderPipelineState()); pass_bindings.SetDepthStencilState( PipelineMTL::Cast(*command.pipeline).GetMTLDepthStencilState()); + pass_bindings.SetViewport(command.viewport.value_or( + {.rect = Rect::MakeSize(GetRenderTargetSize())})); + pass_bindings.SetScissor( + command.scissor.value_or(IRect::MakeSize(GetRenderTargetSize()))); + [encoder setFrontFacingWinding:pipeline_desc.GetWindingOrder() == WindingOrder::kClockwise ? MTLWindingClockwise @@ -430,27 +470,6 @@ static bool Bind(PassBindingsCache& pass, [encoder setCullMode:ToMTLCullMode(pipeline_desc.GetCullMode())]; [encoder setStencilReferenceValue:command.stencil_reference]; - auto v = command.viewport.value_or( - {.rect = Rect::MakeSize(GetRenderTargetSize())}); - MTLViewport viewport = { - .originX = v.rect.origin.x, - .originY = v.rect.origin.y, - .width = v.rect.size.width, - .height = v.rect.size.height, - .znear = v.depth_range.z_near, - .zfar = v.depth_range.z_far, - }; - [encoder setViewport:viewport]; - - auto s = command.scissor.value_or(IRect::MakeSize(GetRenderTargetSize())); - MTLScissorRect scissor = { - .x = static_cast(s.origin.x), - .y = static_cast(s.origin.y), - .width = static_cast(s.size.width), - .height = static_cast(s.size.height), - }; - [encoder setScissorRect:scissor]; - if (!bind_stage_resources(command.vertex_bindings, ShaderStage::kVertex)) { return false; } @@ -474,18 +493,31 @@ static bool Bind(PassBindingsCache& pass, if (!mtl_index_buffer) { return false; } + FML_DCHECK(command.index_count * (command.index_type == IndexType::k16bit ? 2 : 4) == command.index_buffer.range.length); - // Returns void. All error checking must be done by this point. - [encoder drawIndexedPrimitives:ToMTLPrimitiveType(command.primitive_type) - indexCount:command.index_count - indexType:ToMTLIndexType(command.index_type) - indexBuffer:mtl_index_buffer - indexBufferOffset:command.index_buffer.range.offset - instanceCount:command.instance_count - baseVertex:command.base_vertex - baseInstance:0u]; + + if (command.instance_count != 1u) { +#if TARGET_OS_SIMULATOR + VALIDATION_LOG << "iOS Simulator does not support instanced rendering."; + return false; +#endif + [encoder drawIndexedPrimitives:ToMTLPrimitiveType(command.primitive_type) + indexCount:command.index_count + indexType:ToMTLIndexType(command.index_type) + indexBuffer:mtl_index_buffer + indexBufferOffset:command.index_buffer.range.offset + instanceCount:command.instance_count + baseVertex:command.base_vertex + baseInstance:0u]; + } else { + [encoder drawIndexedPrimitives:ToMTLPrimitiveType(command.primitive_type) + indexCount:command.index_count + indexType:ToMTLIndexType(command.index_type) + indexBuffer:mtl_index_buffer + indexBufferOffset:command.index_buffer.range.offset]; + } } return true; } diff --git a/impeller/renderer/backend/metal/surface_mtl.mm b/impeller/renderer/backend/metal/surface_mtl.mm index 0e391544f7eca..216469d3bbfac 100644 --- a/impeller/renderer/backend/metal/surface_mtl.mm +++ b/impeller/renderer/backend/metal/surface_mtl.mm @@ -24,7 +24,11 @@ return nullptr; } - auto current_drawable = [layer nextDrawable]; + id current_drawable = nil; + { + TRACE_EVENT0("impeller", "WaitForNextDrawable"); + current_drawable = [layer nextDrawable]; + } if (!current_drawable) { VALIDATION_LOG << "Could not acquire current drawable."; @@ -48,7 +52,7 @@ static_cast(current_drawable.texture.height)}; color0_tex_desc.usage = static_cast(TextureUsage::kRenderTarget); - auto msaa_tex = context->GetPermanentsAllocator()->CreateTexture( + auto msaa_tex = context->GetResourceAllocator()->CreateTexture( StorageMode::kDeviceTransient, color0_tex_desc); if (!msaa_tex) { VALIDATION_LOG << "Could not allocate MSAA resolve texture."; @@ -78,7 +82,7 @@ stencil0_tex.size = color0_tex_desc.size; stencil0_tex.usage = static_cast(TextureUsage::kRenderTarget); - auto stencil_texture = context->GetPermanentsAllocator()->CreateTexture( + auto stencil_texture = context->GetResourceAllocator()->CreateTexture( StorageMode::kDeviceTransient, stencil0_tex); if (!stencil_texture) { diff --git a/impeller/renderer/backend/vulkan/BUILD.gn b/impeller/renderer/backend/vulkan/BUILD.gn index bde65b844f691..8657983329ebb 100644 --- a/impeller/renderer/backend/vulkan/BUILD.gn +++ b/impeller/renderer/backend/vulkan/BUILD.gn @@ -14,6 +14,8 @@ impeller_component("vulkan") { "capabilities_vk.h", "command_buffer_vk.cc", "command_buffer_vk.h", + "command_pool_vk.cc", + "command_pool_vk.h", "context_vk.cc", "context_vk.h", "device_buffer_vk.cc", @@ -34,8 +36,14 @@ impeller_component("vulkan") { "shader_function_vk.h", "shader_library_vk.cc", "shader_library_vk.h", + "surface_producer_vk.cc", + "surface_producer_vk.h", "surface_vk.cc", "surface_vk.h", + "swapchain_details_vk.cc", + "swapchain_details_vk.h", + "swapchain_vk.cc", + "swapchain_vk.h", "texture_vk.cc", "texture_vk.h", "vertex_descriptor_vk.cc", diff --git a/impeller/renderer/backend/vulkan/allocator_vk.cc b/impeller/renderer/backend/vulkan/allocator_vk.cc index c572363e6d1f7..9df48d6a0211b 100644 --- a/impeller/renderer/backend/vulkan/allocator_vk.cc +++ b/impeller/renderer/backend/vulkan/allocator_vk.cc @@ -10,6 +10,8 @@ _Pragma("GCC diagnostic ignored \"-Wthread-safety-analysis\""); #define VMA_IMPLEMENTATION #include "impeller/renderer/backend/vulkan/allocator_vk.h" #include "impeller/renderer/backend/vulkan/device_buffer_vk.h" +#include "impeller/renderer/backend/vulkan/formats_vk.h" +#include "impeller/renderer/backend/vulkan/texture_vk.h" #include @@ -61,7 +63,56 @@ bool AllocatorVK::IsValid() const { std::shared_ptr AllocatorVK::CreateTexture( StorageMode mode, const TextureDescriptor& desc) { - FML_UNREACHABLE(); + auto image_create_info = vk::ImageCreateInfo{}; + image_create_info.imageType = vk::ImageType::e2D; + image_create_info.format = ToVKImageFormat(desc.format); + image_create_info.extent.width = desc.size.width; + image_create_info.extent.height = desc.size.height; + image_create_info.samples = ToVKSampleCount(desc.sample_count); + image_create_info.mipLevels = desc.mip_count; + + // TODO (kaushikiska): should we read these from desc? + image_create_info.extent.depth = 1; + image_create_info.arrayLayers = 1; + + image_create_info.tiling = vk::ImageTiling::eOptimal; + image_create_info.initialLayout = vk::ImageLayout::eUndefined; + image_create_info.usage = vk::ImageUsageFlagBits::eSampled | + vk::ImageUsageFlagBits::eColorAttachment; + + VmaAllocationCreateInfo alloc_create_info = {}; + alloc_create_info.usage = VMA_MEMORY_USAGE_AUTO; + // docs recommend using `VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT` for image + // allocations, but setting them to be host visible for now. + alloc_create_info.flags = + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | + VMA_ALLOCATION_CREATE_MAPPED_BIT; + + auto create_info_native = + static_cast(image_create_info); + + VkImage img; + VmaAllocation allocation; + VmaAllocationInfo allocation_info; + auto result = vk::Result{vmaCreateImage(allocator_, &create_info_native, + &alloc_create_info, &img, &allocation, + &allocation_info)}; + if (result != vk::Result::eSuccess) { + VALIDATION_LOG << "Unable to allocate an image"; + return nullptr; + } + + auto texture_info = std::make_unique(TextureInfoVK{ + .backing_type = TextureBackingTypeVK::kAllocatedTexture, + .allocated_texture = + { + .allocator = &allocator_, + .allocation = allocation, + .allocation_info = allocation_info, + .image = img, + }, + }); + return std::make_shared(desc, &context_, std::move(texture_info)); } // |Allocator| @@ -102,4 +153,11 @@ std::shared_ptr AllocatorVK::CreateBuffer(StorageMode mode, std::move(device_allocation)); } +// |Allocator| +ISize AllocatorVK::GetMaxTextureSizeSupported() const { + // TODO(magicianA): Get correct max texture size for Vulkan. + // 4096 is the required limit, see below: + // https://registry.khronos.org/vulkan/specs/1.2-extensions/html/vkspec.html#limits-minmax + return {4096, 4096}; +} } // namespace impeller diff --git a/impeller/renderer/backend/vulkan/allocator_vk.h b/impeller/renderer/backend/vulkan/allocator_vk.h index 58141f2093870..aa254d0d94315 100644 --- a/impeller/renderer/backend/vulkan/allocator_vk.h +++ b/impeller/renderer/backend/vulkan/allocator_vk.h @@ -45,6 +45,9 @@ class AllocatorVK final : public Allocator { StorageMode mode, const TextureDescriptor& desc) override; + // |Allocator| + ISize GetMaxTextureSizeSupported() const override; + FML_DISALLOW_COPY_AND_ASSIGN(AllocatorVK); }; diff --git a/impeller/renderer/backend/vulkan/command_buffer_vk.cc b/impeller/renderer/backend/vulkan/command_buffer_vk.cc index 0a33906c1f2de..c06701d515f18 100644 --- a/impeller/renderer/backend/vulkan/command_buffer_vk.cc +++ b/impeller/renderer/backend/vulkan/command_buffer_vk.cc @@ -5,29 +5,128 @@ #include "impeller/renderer/backend/vulkan/command_buffer_vk.h" #include "flutter/fml/logging.h" +#include "impeller/base/validation.h" +#include "impeller/renderer/backend/vulkan/context_vk.h" +#include "impeller/renderer/backend/vulkan/formats_vk.h" +#include "impeller/renderer/backend/vulkan/render_pass_vk.h" +#include "impeller/renderer/command_buffer.h" #include "impeller/renderer/render_target.h" namespace impeller { -CommandBufferVK::CommandBufferVK() = default; +std::shared_ptr CommandBufferVK::Create( + std::weak_ptr context, + vk::Device device, + vk::CommandPool command_pool, + SurfaceProducerVK* surface_producer) { + vk::CommandBufferAllocateInfo allocate_info; + allocate_info.setLevel(vk::CommandBufferLevel::ePrimary); + allocate_info.setCommandBufferCount(1); + allocate_info.setCommandPool(command_pool); + + auto res = device.allocateCommandBuffersUnique(allocate_info); + if (res.result != vk::Result::eSuccess) { + VALIDATION_LOG << "Failed to allocate command buffer: " + << vk::to_string(res.result); + return nullptr; + } + + vk::UniqueCommandBuffer cmd = std::move(res.value[0]); + return std::make_shared(context, device, surface_producer, + std::move(cmd)); +} + +CommandBufferVK::CommandBufferVK(std::weak_ptr context, + vk::Device device, + SurfaceProducerVK* surface_producer, + vk::UniqueCommandBuffer command_buffer) + : CommandBuffer(context), + device_(device), + command_buffer_(std::move(command_buffer)), + surface_producer_(surface_producer) { + is_valid_ = true; +} CommandBufferVK::~CommandBufferVK() = default; void CommandBufferVK::SetLabel(const std::string& label) const { - FML_UNREACHABLE(); + if (auto context = context_.lock()) { + reinterpret_cast(context.get()) + ->SetDebugName(*command_buffer_, label); + } } bool CommandBufferVK::IsValid() const { - FML_UNREACHABLE(); + return is_valid_; } -bool CommandBufferVK::SubmitCommands(CompletionCallback callback) { - FML_UNREACHABLE(); +bool CommandBufferVK::OnSubmitCommands(CompletionCallback callback) { + bool result = surface_producer_->Submit(*command_buffer_); + + if (callback) { + callback(result ? CommandBuffer::Status::kCompleted + : CommandBuffer::Status::kError); + } + + return result; } std::shared_ptr CommandBufferVK::OnCreateRenderPass( RenderTarget target) const { - FML_UNREACHABLE(); + vk::CommandBufferBeginInfo begin_info; + auto res = command_buffer_->begin(begin_info); + if (res != vk::Result::eSuccess) { + VALIDATION_LOG << "Failed to begin command buffer: " << vk::to_string(res); + return nullptr; + } + + std::vector color_attachments; + for (const auto& [k, attachment] : target.GetColorAttachments()) { + const TextureDescriptor& tex_desc = + attachment.texture->GetTextureDescriptor(); + + vk::AttachmentDescription color_attachment; + color_attachment.setFormat(ToVKImageFormat(tex_desc.format)); + color_attachment.setSamples(ToVKSampleCountFlagBits(tex_desc.sample_count)); + color_attachment.setLoadOp(ToVKAttachmentLoadOp(attachment.load_action)); + color_attachment.setStoreOp(ToVKAttachmentStoreOp(attachment.store_action)); + + color_attachment.setStencilLoadOp(vk::AttachmentLoadOp::eDontCare); + color_attachment.setStencilStoreOp(vk::AttachmentStoreOp::eDontCare); + color_attachment.setInitialLayout(vk::ImageLayout::eUndefined); + color_attachment.setFinalLayout(vk::ImageLayout::ePresentSrcKHR); + + color_attachments.push_back(color_attachment); + } + + // TODO (kaushikiska): support depth and stencil attachments. + + vk::AttachmentReference color_attachment_ref; + color_attachment_ref.setAttachment(0); + color_attachment_ref.setLayout(vk::ImageLayout::eColorAttachmentOptimal); + + vk::SubpassDescription subpass_desc; + subpass_desc.setPipelineBindPoint(vk::PipelineBindPoint::eGraphics); + subpass_desc.setColorAttachmentCount(color_attachments.size()); + subpass_desc.setPColorAttachments(&color_attachment_ref); + + vk::RenderPassCreateInfo render_pass_create; + render_pass_create.setAttachmentCount(color_attachments.size()); + render_pass_create.setPAttachments(color_attachments.data()); + render_pass_create.setSubpassCount(1); + render_pass_create.setPSubpasses(&subpass_desc); + + auto render_pass_create_res = + device_.createRenderPassUnique(render_pass_create); + if (render_pass_create_res.result != vk::Result::eSuccess) { + VALIDATION_LOG << "Failed to create render pass: " + << vk::to_string(render_pass_create_res.result); + return nullptr; + } + + return std::make_shared( + context_, std::move(target), *command_buffer_, + std::move(render_pass_create_res.value)); } std::shared_ptr CommandBufferVK::OnCreateBlitPass() const { diff --git a/impeller/renderer/backend/vulkan/command_buffer_vk.h b/impeller/renderer/backend/vulkan/command_buffer_vk.h index cb166f8689ecb..ee5cb0bfec2b0 100644 --- a/impeller/renderer/backend/vulkan/command_buffer_vk.h +++ b/impeller/renderer/backend/vulkan/command_buffer_vk.h @@ -5,19 +5,35 @@ #pragma once #include "flutter/fml/macros.h" +#include "impeller/renderer/backend/vulkan/surface_producer_vk.h" +#include "impeller/renderer/backend/vulkan/vk.h" #include "impeller/renderer/command_buffer.h" namespace impeller { class CommandBufferVK final : public CommandBuffer { public: + static std::shared_ptr Create( + std::weak_ptr context, + vk::Device device, + vk::CommandPool command_pool, + SurfaceProducerVK* surface_producer); + + CommandBufferVK(std::weak_ptr context, + vk::Device device, + SurfaceProducerVK* surface_producer, + vk::UniqueCommandBuffer command_buffer); + // |CommandBuffer| ~CommandBufferVK() override; private: - friend class ContextMTL; + friend class ContextVK; - CommandBufferVK(); + vk::Device device_; + vk::UniqueCommandBuffer command_buffer_; + SurfaceProducerVK* surface_producer_; + bool is_valid_ = false; // |CommandBuffer| void SetLabel(const std::string& label) const override; @@ -26,7 +42,7 @@ class CommandBufferVK final : public CommandBuffer { bool IsValid() const override; // |CommandBuffer| - bool SubmitCommands(CompletionCallback callback) override; + bool OnSubmitCommands(CompletionCallback callback) override; // |CommandBuffer| std::shared_ptr OnCreateRenderPass( diff --git a/impeller/renderer/backend/vulkan/command_pool_vk.cc b/impeller/renderer/backend/vulkan/command_pool_vk.cc new file mode 100644 index 0000000000000..065582402419b --- /dev/null +++ b/impeller/renderer/backend/vulkan/command_pool_vk.cc @@ -0,0 +1,33 @@ +// 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 "impeller/renderer/backend/vulkan/command_pool_vk.h" + +namespace impeller { + +std::unique_ptr CommandPoolVK::Create(vk::Device device, + uint32_t queue_index) { + vk::CommandPoolCreateInfo create_info; + create_info.setQueueFamilyIndex(queue_index); + + auto res = device.createCommandPoolUnique(create_info); + if (res.result != vk::Result::eSuccess) { + FML_CHECK(false) << "Failed to create command pool: " + << vk::to_string(res.result); + return nullptr; + } + + return std::make_unique(std::move(res.value)); +} + +vk::CommandPool CommandPoolVK::Get() const { + return *command_pool_; +} + +CommandPoolVK::CommandPoolVK(vk::UniqueCommandPool command_pool) + : command_pool_(std::move(command_pool)) {} + +CommandPoolVK::~CommandPoolVK() = default; + +} // namespace impeller diff --git a/impeller/renderer/backend/vulkan/command_pool_vk.h b/impeller/renderer/backend/vulkan/command_pool_vk.h new file mode 100644 index 0000000000000..034813ed49acf --- /dev/null +++ b/impeller/renderer/backend/vulkan/command_pool_vk.h @@ -0,0 +1,29 @@ +// 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. + +#pragma once + +#include "flutter/fml/macros.h" +#include "impeller/renderer/backend/vulkan/vk.h" + +namespace impeller { + +class CommandPoolVK { + public: + static std::unique_ptr Create(vk::Device device, + uint32_t queue_index); + + explicit CommandPoolVK(vk::UniqueCommandPool command_pool); + + ~CommandPoolVK(); + + vk::CommandPool Get() const; + + private: + vk::UniqueCommandPool command_pool_; + + FML_DISALLOW_COPY_AND_ASSIGN(CommandPoolVK); +}; + +} // namespace impeller diff --git a/impeller/renderer/backend/vulkan/context_vk.cc b/impeller/renderer/backend/vulkan/context_vk.cc index 08006255503aa..77bf5533afdd1 100644 --- a/impeller/renderer/backend/vulkan/context_vk.cc +++ b/impeller/renderer/backend/vulkan/context_vk.cc @@ -12,9 +12,14 @@ #include "flutter/fml/build_config.h" #include "flutter/fml/trace_event.h" #include "impeller/base/validation.h" +#include "impeller/base/work_queue_common.h" #include "impeller/renderer/backend/vulkan/allocator_vk.h" #include "impeller/renderer/backend/vulkan/capabilities_vk.h" +#include "impeller/renderer/backend/vulkan/command_buffer_vk.h" +#include "impeller/renderer/backend/vulkan/surface_producer_vk.h" +#include "impeller/renderer/backend/vulkan/swapchain_details_vk.h" #include "impeller/renderer/backend/vulkan/vk.h" +#include "vulkan/vulkan.hpp" VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE @@ -27,6 +32,10 @@ static std::set kRequiredDeviceExtensions = { #endif }; +#if FML_OS_MACOSX +static const char* MVK_MACOS_SURFACE_EXT = "VK_MVK_macos_surface"; +#endif + static bool HasRequiredQueues(const vk::PhysicalDevice& device) { auto present_flags = vk::QueueFlags{}; for (const auto& queue : device.getQueueFamilyProperties()) { @@ -132,6 +141,23 @@ static std::optional PickQueue(const vk::PhysicalDevice& device, return std::nullopt; } +static std::optional PickPresentQueue(const vk::PhysicalDevice& device, + vk::SurfaceKHR surface) { + const auto families = device.getQueueFamilyProperties(); + for (size_t i = 0u; i < families.size(); i++) { + auto res = device.getSurfaceSupportKHR(i, surface); + if (res.result != vk::Result::eSuccess) { + continue; + } + vk::Bool32 present_supported = res.value; + if (present_supported) { + return QueueVK{.family = i, .index = 0}; + } + } + VALIDATION_LOG << "No present queue found."; + return std::nullopt; +} + std::shared_ptr ContextVK::Create( PFN_vkGetInstanceProcAddr proc_address_callback, const std::vector>& shader_libraries_data, @@ -205,6 +231,14 @@ ContextVK::ContextVK( // is a requirement for opting into Molten VK on Mac. enabled_extensions.push_back( VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); + + // Required for glfw macOS surfaces. + if (!capabilities->HasExtension(MVK_MACOS_SURFACE_EXT)) { + VALIDATION_LOG << "On Mac: Required extension " << MVK_MACOS_SURFACE_EXT + << " absent."; + return; + } + enabled_extensions.push_back(MVK_MACOS_SURFACE_EXT); #endif // FML_OS_MACOSX //---------------------------------------------------------------------------- @@ -308,6 +342,8 @@ ContextVK::ContextVK( auto compute_queue = PickQueue(physical_device.value(), vk::QueueFlagBits::eCompute); + physical_device_ = physical_device.value(); + if (!graphics_queue.has_value() || !transfer_queue.has_value() || !compute_queue.has_value()) { VALIDATION_LOG << "Could not pick device queues."; @@ -373,6 +409,13 @@ ContextVK::ContextVK( return; } + auto work_queue = WorkQueueCommon::Create(); + + if (!work_queue) { + VALIDATION_LOG << "Could not create workqueue."; + return; + } + instance_ = std::move(instance.value); debug_messenger_ = std::move(debug_messenger); device_ = std::move(device.value); @@ -380,12 +423,15 @@ ContextVK::ContextVK( shader_library_ = std::move(shader_library); sampler_library_ = std::move(sampler_library); pipeline_library_ = std::move(pipeline_library); + work_queue_ = std::move(work_queue); graphics_queue_ = device_->getQueue(graphics_queue->family, graphics_queue->index); compute_queue_ = device_->getQueue(compute_queue->family, compute_queue->index); transfer_queue_ = device_->getQueue(transfer_queue->family, transfer_queue->index); + graphics_command_pool_ = + CommandPoolVK::Create(*device_, graphics_queue->index); is_valid_ = true; } @@ -395,11 +441,7 @@ bool ContextVK::IsValid() const { return is_valid_; } -std::shared_ptr ContextVK::GetPermanentsAllocator() const { - return allocator_; -} - -std::shared_ptr ContextVK::GetTransientsAllocator() const { +std::shared_ptr ContextVK::GetResourceAllocator() const { return allocator_; } @@ -415,12 +457,52 @@ std::shared_ptr ContextVK::GetPipelineLibrary() const { return pipeline_library_; } -std::shared_ptr ContextVK::CreateRenderCommandBuffer() const { - FML_UNREACHABLE(); +// |Context| +std::shared_ptr ContextVK::GetWorkQueue() const { + return work_queue_; } -std::shared_ptr ContextVK::CreateTransferCommandBuffer() const { - FML_UNREACHABLE(); +std::shared_ptr ContextVK::CreateCommandBuffer() const { + return CommandBufferVK::Create(weak_from_this(), *device_, + graphics_command_pool_->Get(), + surface_producer_.get()); +} + +vk::Instance ContextVK::GetInstance() const { + return *instance_; +} + +std::unique_ptr ContextVK::AcquireSurface() { + return surface_producer_->AcquireSurface(); +} + +void ContextVK::SetupSwapchain(vk::UniqueSurfaceKHR surface) { + surface_ = std::move(surface); + auto present_queue_out = PickPresentQueue(physical_device_, *surface_); + if (!present_queue_out.has_value()) { + return; + } + present_queue_ = + device_->getQueue(present_queue_out->family, present_queue_out->index); + + auto swapchain_details = + SwapchainDetailsVK::Create(physical_device_, *surface_); + if (!swapchain_details) { + return; + } + swapchain_ = SwapchainVK::Create(*device_, *surface_, *swapchain_details); + auto weak_this = weak_from_this(); + surface_producer_ = SurfaceProducerVK::Create( + weak_this, { + .device = *device_, + .graphics_queue = graphics_queue_, + .present_queue = present_queue_, + .swapchain = swapchain_.get(), + }); +} + +bool ContextVK::SupportsOffscreenMSAA() const { + return true; } } // namespace impeller diff --git a/impeller/renderer/backend/vulkan/context_vk.h b/impeller/renderer/backend/vulkan/context_vk.h index 92e8db97335cd..ab63afd0610d2 100644 --- a/impeller/renderer/backend/vulkan/context_vk.h +++ b/impeller/renderer/backend/vulkan/context_vk.h @@ -10,9 +10,12 @@ #include "flutter/fml/macros.h" #include "flutter/fml/mapping.h" #include "impeller/base/backend_cast.h" +#include "impeller/renderer/backend/vulkan/command_pool_vk.h" #include "impeller/renderer/backend/vulkan/pipeline_library_vk.h" #include "impeller/renderer/backend/vulkan/sampler_library_vk.h" #include "impeller/renderer/backend/vulkan/shader_library_vk.h" +#include "impeller/renderer/backend/vulkan/surface_producer_vk.h" +#include "impeller/renderer/backend/vulkan/swapchain_vk.h" #include "impeller/renderer/backend/vulkan/vk.h" #include "impeller/renderer/context.h" @@ -54,10 +57,17 @@ class ContextVK final : public Context, public BackendCast { return true; } + vk::Instance GetInstance() const; + + void SetupSwapchain(vk::UniqueSurfaceKHR surface); + + std::unique_ptr AcquireSurface(); + private: std::shared_ptr worker_task_runner_; vk::UniqueInstance instance_; vk::UniqueDebugUtilsMessengerEXT debug_messenger_; + vk::PhysicalDevice physical_device_; vk::UniqueDevice device_; std::shared_ptr allocator_; std::shared_ptr shader_library_; @@ -66,6 +76,12 @@ class ContextVK final : public Context, public BackendCast { vk::Queue graphics_queue_; vk::Queue compute_queue_; vk::Queue transfer_queue_; + vk::Queue present_queue_; + vk::UniqueSurfaceKHR surface_; + std::unique_ptr swapchain_; + std::unique_ptr graphics_command_pool_; + std::unique_ptr surface_producer_; + std::shared_ptr work_queue_; bool is_valid_ = false; ContextVK( @@ -76,10 +92,7 @@ class ContextVK final : public Context, public BackendCast { const std::string& label); // |Context| - std::shared_ptr GetPermanentsAllocator() const override; - - // |Context| - std::shared_ptr GetTransientsAllocator() const override; + std::shared_ptr GetResourceAllocator() const override; // |Context| std::shared_ptr GetShaderLibrary() const override; @@ -91,10 +104,13 @@ class ContextVK final : public Context, public BackendCast { std::shared_ptr GetPipelineLibrary() const override; // |Context| - std::shared_ptr CreateRenderCommandBuffer() const override; + std::shared_ptr CreateCommandBuffer() const override; + + // |Context| + std::shared_ptr GetWorkQueue() const override; // |Context| - std::shared_ptr CreateTransferCommandBuffer() const override; + bool SupportsOffscreenMSAA() const override; FML_DISALLOW_COPY_AND_ASSIGN(ContextVK); }; diff --git a/impeller/renderer/backend/vulkan/formats_vk.h b/impeller/renderer/backend/vulkan/formats_vk.h index 64becdf10d946..00cd5efa3cf56 100644 --- a/impeller/renderer/backend/vulkan/formats_vk.h +++ b/impeller/renderer/backend/vulkan/formats_vk.h @@ -6,6 +6,7 @@ #include "flutter/fml/macros.h" #include "impeller/renderer/backend/vulkan/vk.h" +#include "impeller/renderer/descriptor_set_layout.h" #include "impeller/renderer/formats.h" #include "impeller/renderer/shader_types.h" @@ -132,4 +133,160 @@ constexpr std::optional ToVKShaderStageFlagBits( FML_UNREACHABLE(); } +constexpr vk::Format ToVKImageFormat(PixelFormat format) { + switch (format) { + case PixelFormat::kUnknown: + return vk::Format::eUndefined; + case PixelFormat::kA8UNormInt: + return vk::Format::eA8B8G8R8UnormPack32; + case PixelFormat::kR8G8B8A8UNormInt: + return vk::Format::eR8G8B8A8Unorm; + case PixelFormat::kR8G8B8A8UNormIntSRGB: + return vk::Format::eR8G8B8A8Srgb; + case PixelFormat::kB8G8R8A8UNormInt: + return vk::Format::eB8G8R8A8Unorm; + case PixelFormat::kB8G8R8A8UNormIntSRGB: + return vk::Format::eB8G8R8A8Srgb; + case PixelFormat::kS8UInt: + return vk::Format::eS8Uint; + } +} + +constexpr PixelFormat ToPixelFormat(vk::Format format) { + switch (format) { + case vk::Format::eUndefined: + return PixelFormat::kUnknown; + + case vk::Format::eA8B8G8R8UnormPack32: + return PixelFormat::kA8UNormInt; + + case vk::Format::eR8G8B8A8Unorm: + return PixelFormat::kR8G8B8A8UNormInt; + + case vk::Format::eR8G8B8A8Srgb: + return PixelFormat::kR8G8B8A8UNormIntSRGB; + + case vk::Format::eB8G8R8A8Unorm: + return PixelFormat::kB8G8R8A8UNormInt; + + case vk::Format::eB8G8R8A8Srgb: + return PixelFormat::kB8G8R8A8UNormIntSRGB; + + case vk::Format::eS8Uint: + return PixelFormat::kS8UInt; + + default: + return PixelFormat::kUnknown; + } +} + +constexpr vk::SampleCountFlagBits ToVKSampleCount(SampleCount sample_count) { + switch (sample_count) { + case SampleCount::kCount1: + return vk::SampleCountFlagBits::e1; + case SampleCount::kCount4: + return vk::SampleCountFlagBits::e4; + } +} + +constexpr vk::Filter ToVKSamplerMinMagFilter(MinMagFilter filter) { + switch (filter) { + case MinMagFilter::kNearest: + return vk::Filter::eNearest; + case MinMagFilter::kLinear: + return vk::Filter::eLinear; + } + + FML_UNREACHABLE(); +} + +constexpr vk::SamplerMipmapMode ToVKSamplerMipmapMode(MipFilter filter) { + vk::SamplerCreateInfo sampler_info; + switch (filter) { + case MipFilter::kNearest: + return vk::SamplerMipmapMode::eNearest; + case MipFilter::kLinear: + return vk::SamplerMipmapMode::eLinear; + case MipFilter::kNone: + return vk::SamplerMipmapMode::eNearest; + } + + FML_UNREACHABLE(); +} + +constexpr vk::SamplerAddressMode ToVKSamplerAddressMode( + SamplerAddressMode mode) { + switch (mode) { + case SamplerAddressMode::kRepeat: + return vk::SamplerAddressMode::eRepeat; + case SamplerAddressMode::kMirror: + return vk::SamplerAddressMode::eMirroredRepeat; + case SamplerAddressMode::kClampToEdge: + return vk::SamplerAddressMode::eClampToEdge; + } + + FML_UNREACHABLE(); +} + +constexpr vk::ShaderStageFlags ToVkShaderStage(ShaderStage stage) { + switch (stage) { + case ShaderStage::kUnknown: + return vk::ShaderStageFlagBits::eAll; + case ShaderStage::kFragment: + return vk::ShaderStageFlagBits::eFragment; + case ShaderStage::kTessellationControl: + return vk::ShaderStageFlagBits::eTessellationControl; + case ShaderStage::kTessellationEvaluation: + return vk::ShaderStageFlagBits::eTessellationEvaluation; + case ShaderStage::kCompute: + return vk::ShaderStageFlagBits::eCompute; + case ShaderStage::kVertex: + return vk::ShaderStageFlagBits::eVertex; + } +} + +constexpr vk::DescriptorSetLayoutBinding ToVKDescriptorSetLayoutBinding( + const DescriptorSetLayout& layout) { + vk::DescriptorSetLayoutBinding binding; + binding.binding = layout.binding; + binding.descriptorCount = layout.descriptor_count; + vk::DescriptorType desc_type = vk::DescriptorType(); + switch (layout.descriptor_type) { + case DescriptorType::kSampledImage: + desc_type = vk::DescriptorType::eCombinedImageSampler; + break; + case DescriptorType::kUniformBuffer: + desc_type = vk::DescriptorType::eUniformBuffer; + break; + } + binding.descriptorType = desc_type; + binding.stageFlags = ToVkShaderStage(layout.shader_stage); + return binding; +} + +constexpr vk::AttachmentLoadOp ToVKAttachmentLoadOp(LoadAction load_action) { + switch (load_action) { + case LoadAction::kLoad: + return vk::AttachmentLoadOp::eLoad; + case LoadAction::kClear: + return vk::AttachmentLoadOp::eClear; + case LoadAction::kDontCare: + return vk::AttachmentLoadOp::eDontCare; + } +} + +constexpr vk::AttachmentStoreOp ToVKAttachmentStoreOp( + StoreAction store_action) { + switch (store_action) { + case StoreAction::kStore: + return vk::AttachmentStoreOp::eStore; + case StoreAction::kDontCare: + return vk::AttachmentStoreOp::eDontCare; + case StoreAction::kMultisampleResolve: + case StoreAction::kStoreAndMultisampleResolve: + // TODO (kaushikiska): vulkan doesn't support multisample resolve. + return vk::AttachmentStoreOp::eDontCare; + } +} + } // namespace impeller diff --git a/impeller/renderer/backend/vulkan/pipeline_library_vk.cc b/impeller/renderer/backend/vulkan/pipeline_library_vk.cc index b207723825eef..931181c23b373 100644 --- a/impeller/renderer/backend/vulkan/pipeline_library_vk.cc +++ b/impeller/renderer/backend/vulkan/pipeline_library_vk.cc @@ -155,7 +155,9 @@ std::optional PipelineLibraryVK::CreateRenderPass( vk::SubpassDescription subpass_info; subpass_info.setPipelineBindPoint(vk::PipelineBindPoint::eGraphics); subpass_info.setColorAttachments(color_attachment_references); - subpass_info.setResolveAttachments(resolve_attachment_references); + if (sample_count != SampleCount::kCount1) { + subpass_info.setResolveAttachments(resolve_attachment_references); + } if (depth_stencil_attachment_reference.has_value()) { subpass_info.setPDepthStencilAttachment( &depth_stencil_attachment_reference.value()); @@ -280,13 +282,58 @@ std::unique_ptr PipelineLibraryVK::CreatePipeline( return nullptr; } + // only 1 stream of data is supported for now. + vk::VertexInputBindingDescription binding_description = {}; + binding_description.setBinding(0); + binding_description.setInputRate(vk::VertexInputRate::eVertex); + + std::vector attr_descs; + uint32_t stride = 0; + const auto& stage_inputs = desc.GetVertexDescriptor()->GetStageInputs(); + for (const ShaderStageIOSlot& stage_in : stage_inputs) { + vk::VertexInputAttributeDescription attr_desc; + attr_desc.setBinding(stage_in.binding); + attr_desc.setLocation(stage_in.location); + attr_desc.setFormat(vk::Format::eR8G8B8A8Unorm); + attr_desc.setOffset(stride); + attr_descs.push_back(attr_desc); + stride += stage_in.bit_width * stage_in.vec_size; + } + + binding_description.setStride(stride); + + vk::PipelineVertexInputStateCreateInfo vertex_input_state; + vertex_input_state.setVertexAttributeDescriptions(attr_descs); + vertex_input_state.setVertexBindingDescriptionCount(1); + vertex_input_state.setPVertexBindingDescriptions(&binding_description); + + pipeline_info.setPVertexInputState(&vertex_input_state); + //---------------------------------------------------------------------------- /// Pipeline Layout a.k.a the descriptor sets and uniforms. /// - std::vector descriptor_set_layouts; - // TODO(106377): Wire this up from the C++ generated headers. + std::vector bindings = {}; + + for (auto layout : desc.GetVertexDescriptor()->GetDescriptorSetLayouts()) { + auto vk_desc_layout = ToVKDescriptorSetLayoutBinding(layout); + bindings.push_back(vk_desc_layout); + } + + vk::DescriptorSetLayoutCreateInfo descriptor_set_create; + descriptor_set_create.setBindings(bindings); + + auto descriptor_set_create_res = + device_.createDescriptorSetLayoutUnique(descriptor_set_create); + if (descriptor_set_create_res.result != vk::Result::eSuccess) { + VALIDATION_LOG << "unable to create uniform descriptors"; + return nullptr; + } + + vk::UniqueDescriptorSetLayout descriptor_set_layout = + std::move(descriptor_set_create_res.value); + vk::PipelineLayoutCreateInfo pipeline_layout_info; - pipeline_layout_info.setSetLayouts(descriptor_set_layouts); + pipeline_layout_info.setSetLayouts(descriptor_set_layout.get()); auto pipeline_layout = device_.createPipelineLayoutUnique(pipeline_layout_info); if (pipeline_layout.result != vk::Result::eSuccess) { @@ -297,9 +344,13 @@ std::unique_ptr PipelineLibraryVK::CreatePipeline( } pipeline_info.setLayout(pipeline_layout.value.get()); - // TODO(WIP) - // pipeline_info.setPVertexInputState(&vertex_input_state); - // pipeline_info.setPDepthStencilState(&depth_stencil_state_); + vk::PipelineDepthStencilStateCreateInfo depth_stencil_state; + depth_stencil_state.setDepthTestEnable(true); + depth_stencil_state.setDepthWriteEnable(true); + depth_stencil_state.setDepthCompareOp(vk::CompareOp::eLess); + depth_stencil_state.setDepthBoundsTestEnable(false); + depth_stencil_state.setStencilTestEnable(false); + pipeline_info.setPDepthStencilState(&depth_stencil_state); // See the note in the header about why this is a reader lock. ReaderLock lock(cache_mutex_); diff --git a/impeller/renderer/backend/vulkan/render_pass_vk.cc b/impeller/renderer/backend/vulkan/render_pass_vk.cc index 2db32a77b01ab..70da066fdbb40 100644 --- a/impeller/renderer/backend/vulkan/render_pass_vk.cc +++ b/impeller/renderer/backend/vulkan/render_pass_vk.cc @@ -4,8 +4,49 @@ #include "impeller/renderer/backend/vulkan/render_pass_vk.h" +#include "fml/logging.h" +#include "impeller/renderer/backend/vulkan/texture_vk.h" + namespace impeller { -// +RenderPassVK::RenderPassVK(std::weak_ptr context, + RenderTarget target, + vk::CommandBuffer command_buffer, + vk::UniqueRenderPass render_pass) + : RenderPass(context, target), + command_buffer_(command_buffer), + render_pass_(std::move(render_pass)) { + is_valid_ = true; +} + +RenderPassVK::~RenderPassVK() = default; + +bool RenderPassVK::IsValid() const { + return is_valid_; +} + +void RenderPassVK::OnSetLabel(std::string label) { + label_ = std::move(label); +} + +bool RenderPassVK::OnEncodeCommands(const Context& context) const { + if (!IsValid()) { + return false; + } + if (commands_.empty()) { + return true; + } + const auto& render_target = GetRenderTarget(); + if (!render_target.HasColorAttachment(0u)) { + return false; + } + const auto& color0 = render_target.GetColorAttachments().at(0u); + const auto& depth0 = render_target.GetDepthAttachment(); + const auto& stencil0 = render_target.GetStencilAttachment(); + + auto& wrapped_texture = TextureVK::Cast(*color0.texture); + + FML_UNREACHABLE(); +} } // namespace impeller diff --git a/impeller/renderer/backend/vulkan/render_pass_vk.h b/impeller/renderer/backend/vulkan/render_pass_vk.h index afb942f798024..595765c7902d8 100644 --- a/impeller/renderer/backend/vulkan/render_pass_vk.h +++ b/impeller/renderer/backend/vulkan/render_pass_vk.h @@ -5,6 +5,7 @@ #pragma once #include "flutter/fml/macros.h" +#include "impeller/renderer/backend/vulkan/vk.h" #include "impeller/renderer/render_pass.h" #include "impeller/renderer/render_target.h" @@ -12,13 +13,21 @@ namespace impeller { class RenderPassVK final : public RenderPass { public: + RenderPassVK(std::weak_ptr context, + RenderTarget target, + vk::CommandBuffer command_buffer, + vk::UniqueRenderPass render_pass); + // |RenderPass| ~RenderPassVK() override; private: friend class CommandBufferVK; - RenderPassVK(RenderTarget target); + vk::CommandBuffer command_buffer_; + vk::UniqueRenderPass render_pass_; + std::string label_ = ""; + bool is_valid_ = false; // |RenderPass| bool IsValid() const override; @@ -27,8 +36,7 @@ class RenderPassVK final : public RenderPass { void OnSetLabel(std::string label) override; // |RenderPass| - bool EncodeCommands( - const std::shared_ptr& transients_allocator) const override; + bool OnEncodeCommands(const Context& context) const override; FML_DISALLOW_COPY_AND_ASSIGN(RenderPassVK); }; diff --git a/impeller/renderer/backend/vulkan/sampler_library_vk.cc b/impeller/renderer/backend/vulkan/sampler_library_vk.cc index 6c5c239f222c1..149c1ae957d4c 100644 --- a/impeller/renderer/backend/vulkan/sampler_library_vk.cc +++ b/impeller/renderer/backend/vulkan/sampler_library_vk.cc @@ -4,6 +4,9 @@ #include "impeller/renderer/backend/vulkan/sampler_library_vk.h" +#include "impeller/renderer/backend/vulkan/formats_vk.h" +#include "impeller/renderer/backend/vulkan/sampler_vk.h" + namespace impeller { SamplerLibraryVK::SamplerLibraryVK(vk::Device device) : device_(device) {} @@ -11,8 +14,45 @@ SamplerLibraryVK::SamplerLibraryVK(vk::Device device) : device_(device) {} SamplerLibraryVK::~SamplerLibraryVK() = default; std::shared_ptr SamplerLibraryVK::GetSampler( - SamplerDescriptor descriptor) { - FML_UNREACHABLE(); + SamplerDescriptor desc) { + auto found = samplers_.find(desc); + if (found != samplers_.end()) { + return found->second; + } + if (!device_) { + return nullptr; + } + + const auto mip_map = ToVKSamplerMipmapMode(desc.mip_filter); + + const auto min_filter = ToVKSamplerMinMagFilter(desc.min_filter); + const auto mag_filter = ToVKSamplerMinMagFilter(desc.mag_filter); + + const auto address_mode_u = ToVKSamplerAddressMode(desc.width_address_mode); + const auto address_mode_v = ToVKSamplerAddressMode(desc.height_address_mode); + const auto address_mode_w = ToVKSamplerAddressMode(desc.depth_address_mode); + + const auto sampler_create_info = vk::SamplerCreateInfo() + .setMagFilter(mag_filter) + .setMinFilter(min_filter) + .setAddressModeU(address_mode_u) + .setAddressModeV(address_mode_v) + .setAddressModeW(address_mode_w) + .setMipmapMode(mip_map); + + auto res = device_.createSamplerUnique(sampler_create_info); + if (res.result != vk::Result::eSuccess) { + FML_LOG(ERROR) << "Failed to create sampler: " << vk::to_string(res.result); + return nullptr; + } + + auto sampler = std::make_shared(desc, std::move(res.value)); + + if (!sampler->IsValid()) { + return nullptr; + } + samplers_[desc] = sampler; + return sampler; } } // namespace impeller diff --git a/impeller/renderer/backend/vulkan/sampler_library_vk.h b/impeller/renderer/backend/vulkan/sampler_library_vk.h index 8823377dcc001..72129ca496757 100644 --- a/impeller/renderer/backend/vulkan/sampler_library_vk.h +++ b/impeller/renderer/backend/vulkan/sampler_library_vk.h @@ -26,7 +26,7 @@ class SamplerLibraryVK final vk::Device device_; SamplerMap samplers_; - SamplerLibraryVK(vk::Device device); + explicit SamplerLibraryVK(vk::Device device); // |SamplerLibrary| std::shared_ptr GetSampler( diff --git a/impeller/renderer/backend/vulkan/sampler_vk.cc b/impeller/renderer/backend/vulkan/sampler_vk.cc index 30d21e0af5d27..161fa57378222 100644 --- a/impeller/renderer/backend/vulkan/sampler_vk.cc +++ b/impeller/renderer/backend/vulkan/sampler_vk.cc @@ -5,7 +5,15 @@ #include "impeller/renderer/backend/vulkan/sampler_vk.h" namespace impeller { +SamplerVK::~SamplerVK() {} -// +SamplerVK::SamplerVK(SamplerDescriptor desc, vk::UniqueSampler sampler) + : Sampler(desc), sampler_(std::move(sampler)) { + is_valid_ = true; +} + +bool SamplerVK::IsValid() const { + return is_valid_; +} } // namespace impeller diff --git a/impeller/renderer/backend/vulkan/sampler_vk.h b/impeller/renderer/backend/vulkan/sampler_vk.h index fce2a4bd11939..15d08205fa754 100644 --- a/impeller/renderer/backend/vulkan/sampler_vk.h +++ b/impeller/renderer/backend/vulkan/sampler_vk.h @@ -6,7 +6,9 @@ #include "flutter/fml/macros.h" #include "impeller/base/backend_cast.h" +#include "impeller/renderer/backend/vulkan/vk.h" #include "impeller/renderer/sampler.h" +#include "vulkan/vulkan_handles.hpp" namespace impeller { @@ -14,7 +16,7 @@ class SamplerLibraryVK; class SamplerVK final : public Sampler, public BackendCast { public: - SamplerVK(); + SamplerVK(SamplerDescriptor desc, vk::UniqueSampler sampler); // |Sampler| ~SamplerVK() override; @@ -22,7 +24,8 @@ class SamplerVK final : public Sampler, public BackendCast { private: friend SamplerLibraryVK; - SamplerVK(SamplerDescriptor desc); + vk::UniqueSampler sampler_; + bool is_valid_ = false; // |Sampler| bool IsValid() const override; diff --git a/impeller/renderer/backend/vulkan/shader_library_vk.cc b/impeller/renderer/backend/vulkan/shader_library_vk.cc index f7811e2c7ecf8..e7bb8ec661c82 100644 --- a/impeller/renderer/backend/vulkan/shader_library_vk.cc +++ b/impeller/renderer/backend/vulkan/shader_library_vk.cc @@ -11,11 +11,11 @@ namespace impeller { -static ShaderStage ToShaderStage(Blob::ShaderType type) { +static ShaderStage ToShaderStage(BlobShaderType type) { switch (type) { - case Blob::ShaderType::kVertex: + case BlobShaderType::kVertex: return ShaderStage::kVertex; - case Blob::ShaderType::kFragment: + case BlobShaderType::kFragment: return ShaderStage::kFragment; } FML_UNREACHABLE(); diff --git a/impeller/renderer/backend/vulkan/surface_producer_vk.cc b/impeller/renderer/backend/vulkan/surface_producer_vk.cc new file mode 100644 index 0000000000000..da23dad77df1e --- /dev/null +++ b/impeller/renderer/backend/vulkan/surface_producer_vk.cc @@ -0,0 +1,171 @@ +// 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 "impeller/renderer/backend/vulkan/surface_producer_vk.h" + +#include + +#include "fml/logging.h" +#include "impeller/base/validation.h" +#include "impeller/renderer/backend/vulkan/surface_vk.h" + +namespace impeller { + +std::unique_ptr SurfaceProducerVK::Create( + std::weak_ptr context, + const SurfaceProducerCreateInfoVK& create_info) { + auto surface_producer = + std::make_unique(context, create_info); + if (!surface_producer->SetupSyncObjects()) { + FML_LOG(ERROR) << "Failed to setup sync objects."; + return nullptr; + } + + return surface_producer; +} + +SurfaceProducerVK::SurfaceProducerVK( + std::weak_ptr context, + const SurfaceProducerCreateInfoVK& create_info) + : context_(context), create_info_(create_info) {} + +SurfaceProducerVK::~SurfaceProducerVK() = default; + +std::unique_ptr SurfaceProducerVK::AcquireSurface() { + auto fence_wait_res = create_info_.device.waitForFences({*in_flight_fence_}, + VK_TRUE, UINT64_MAX); + if (fence_wait_res != vk::Result::eSuccess) { + VALIDATION_LOG << "Failed to wait for fence: " + << vk::to_string(fence_wait_res); + return nullptr; + } + + auto fence_reset_res = create_info_.device.resetFences({*in_flight_fence_}); + if (fence_reset_res != vk::Result::eSuccess) { + VALIDATION_LOG << "Failed to reset fence: " + << vk::to_string(fence_reset_res); + return nullptr; + } + + uint32_t image_index; + auto acuire_image_res = create_info_.device.acquireNextImageKHR( + create_info_.swapchain->GetSwapchain(), UINT64_MAX, + *image_available_semaphore_, {}, &image_index); + + if (acuire_image_res != vk::Result::eSuccess && + acuire_image_res != vk::Result::eSuboptimalKHR) { + VALIDATION_LOG << "Failed to acquire next image: " + << vk::to_string(acuire_image_res); + return nullptr; + } + + if (acuire_image_res == vk::Result::eSuboptimalKHR) { + VALIDATION_LOG << "Suboptimal image acquired."; + } + + SurfaceVK::SwapCallback swap_callback = [this, image_index]() { + return Present(image_index); + }; + + if (auto context = context_.lock()) { + ContextVK* context_vk = reinterpret_cast(context.get()); + return SurfaceVK::WrapSwapchainImage( + create_info_.swapchain->GetSwapchainImage(image_index), context_vk, + std::move(swap_callback)); + } else { + return nullptr; + } +} + +bool SurfaceProducerVK::SetupSyncObjects() { + vk::SemaphoreCreateInfo semaphore_create_info; + + { + auto image_avail_res = + create_info_.device.createSemaphoreUnique(semaphore_create_info); + if (image_avail_res.result != vk::Result::eSuccess) { + VALIDATION_LOG << "Failed to create image available semaphore: " + << vk::to_string(image_avail_res.result); + return false; + } + image_available_semaphore_ = std::move(image_avail_res.value); + } + + { + auto render_finished_res = + create_info_.device.createSemaphoreUnique(semaphore_create_info); + if (render_finished_res.result != vk::Result::eSuccess) { + FML_LOG(ERROR) << "Failed to create render finished semaphore: " + << vk::to_string(render_finished_res.result); + return false; + } + render_finished_semaphore_ = std::move(render_finished_res.value); + } + + vk::FenceCreateInfo fence_create_info; + fence_create_info.flags = vk::FenceCreateFlagBits::eSignaled; + { + auto fence_res = create_info_.device.createFenceUnique(fence_create_info); + if (fence_res.result != vk::Result::eSuccess) { + VALIDATION_LOG << "Failed to create fence: " + << vk::to_string(fence_res.result); + return false; + } + in_flight_fence_ = std::move(fence_res.value); + } + + return true; +} + +bool SurfaceProducerVK::Submit(vk::CommandBuffer buffer) { + vk::SubmitInfo submit_info; + std::array wait_stages = { + vk::PipelineStageFlagBits::eColorAttachmentOutput}; + submit_info.setWaitDstStageMask(wait_stages); + + std::array wait_semaphores = {*image_available_semaphore_}; + submit_info.setWaitSemaphores(wait_semaphores); + + std::array signal_semaphores = { + *render_finished_semaphore_}; + submit_info.setSignalSemaphores(signal_semaphores); + + std::array command_buffers = {buffer}; + submit_info.setCommandBuffers(command_buffers); + + auto graphics_submit_res = + create_info_.graphics_queue.submit({submit_info}, *in_flight_fence_); + if (graphics_submit_res != vk::Result::eSuccess) { + FML_LOG(ERROR) << "Failed to submit graphics queue: " + << vk::to_string(graphics_submit_res); + return false; + } + + return true; +} + +bool SurfaceProducerVK::Present(uint32_t image_index) { + vk::PresentInfoKHR present_info; + + std::array signal_semaphores = { + *render_finished_semaphore_}; + present_info.setWaitSemaphores(signal_semaphores); + + std::array swapchains = { + create_info_.swapchain->GetSwapchain()}; + present_info.setSwapchains(swapchains); + + std::array image_indices = {image_index}; + present_info.setImageIndices(image_indices); + + auto present_res = create_info_.present_queue.presentKHR(present_info); + if (present_res != vk::Result::eSuccess) { + FML_LOG(ERROR) << "Failed to present: " << vk::to_string(present_res); + return false; + } + + return true; +} + +} // namespace impeller diff --git a/impeller/renderer/backend/vulkan/surface_producer_vk.h b/impeller/renderer/backend/vulkan/surface_producer_vk.h new file mode 100644 index 0000000000000..debd9236f3d2f --- /dev/null +++ b/impeller/renderer/backend/vulkan/surface_producer_vk.h @@ -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. + +#pragma once + +#include + +#include "flutter/fml/macros.h" +#include "impeller/renderer/backend/vulkan/swapchain_vk.h" +#include "impeller/renderer/backend/vulkan/vk.h" +#include "impeller/renderer/surface.h" + +namespace impeller { + +struct SurfaceProducerCreateInfoVK { + vk::Device device; + vk::Queue graphics_queue; + vk::Queue present_queue; + SwapchainVK* swapchain; +}; + +class SurfaceProducerVK { + public: + static std::unique_ptr Create( + std::weak_ptr context, + const SurfaceProducerCreateInfoVK& create_info); + + SurfaceProducerVK(std::weak_ptr context, + const SurfaceProducerCreateInfoVK& create_info); + + ~SurfaceProducerVK(); + + std::unique_ptr AcquireSurface(); + + bool Submit(vk::CommandBuffer buffer); + + private: + std::weak_ptr context_; + + bool SetupSyncObjects(); + + bool Present(uint32_t image_index); + + const SurfaceProducerCreateInfoVK create_info_; + + // sync objects + vk::UniqueSemaphore image_available_semaphore_; + vk::UniqueSemaphore render_finished_semaphore_; + vk::UniqueFence in_flight_fence_; + + FML_DISALLOW_COPY_AND_ASSIGN(SurfaceProducerVK); +}; + +} // namespace impeller diff --git a/impeller/renderer/backend/vulkan/surface_vk.cc b/impeller/renderer/backend/vulkan/surface_vk.cc index 1b737032463fa..9319d814bbeb5 100644 --- a/impeller/renderer/backend/vulkan/surface_vk.cc +++ b/impeller/renderer/backend/vulkan/surface_vk.cc @@ -4,8 +4,78 @@ #include "impeller/renderer/backend/vulkan/surface_vk.h" +#include "impeller/renderer/backend/vulkan/texture_vk.h" +#include "impeller/renderer/surface.h" + namespace impeller { +std::unique_ptr SurfaceVK::WrapSwapchainImage( + SwapchainImageVK* swapchain_image, + ContextVK* context, + SwapCallback swap_callback) { + if (!swapchain_image) { + return nullptr; + } + + TextureDescriptor color0_tex; + color0_tex.type = TextureType::kTexture2D; + color0_tex.format = swapchain_image->GetPixelFormat(); + color0_tex.size = swapchain_image->GetSize(); + color0_tex.usage = static_cast(TextureUsage::kRenderTarget); + color0_tex.sample_count = SampleCount::kCount1; + + ColorAttachment color0; + auto color_texture_info = std::make_unique(TextureInfoVK{ + .backing_type = TextureBackingTypeVK::kWrappedTexture, + .wrapped_texture = + { + .swapchain_image = swapchain_image, + }, + }); + color0.texture = std::make_shared(std::move(color0_tex), context, + std::move(color_texture_info)); + color0.clear_color = Color::DarkSlateGray(); + color0.load_action = LoadAction::kClear; + color0.store_action = StoreAction::kStore; + + TextureDescriptor stencil0_tex; + stencil0_tex.type = TextureType::kTexture2D; + stencil0_tex.format = swapchain_image->GetPixelFormat(); + stencil0_tex.size = swapchain_image->GetSize(); + stencil0_tex.usage = + static_cast(TextureUsage::kRenderTarget); + stencil0_tex.sample_count = SampleCount::kCount1; + + StencilAttachment stencil0; + stencil0.clear_stencil = 0; + auto stencil_texture_info = std::make_unique(TextureInfoVK{ + .backing_type = TextureBackingTypeVK::kWrappedTexture, + .wrapped_texture = + { + .swapchain_image = swapchain_image, + }, + }); + stencil0.texture = std::make_shared( + std::move(stencil0_tex), context, std::move(stencil_texture_info)); + stencil0.load_action = LoadAction::kClear; + stencil0.store_action = StoreAction::kDontCare; + + RenderTarget render_target_desc; + render_target_desc.SetColorAttachment(color0, 0u); + + return std::unique_ptr(new SurfaceVK(std::move(render_target_desc), + swapchain_image, + std::move(swap_callback))); +} + +SurfaceVK::SurfaceVK(RenderTarget target, + SwapchainImageVK* swapchain_image, + SwapCallback swap_callback) + : Surface(target) {} + +SurfaceVK::~SurfaceVK() = default; -// +bool SurfaceVK::Present() const { + return swap_callback_ ? swap_callback_() : false; +} } // namespace impeller diff --git a/impeller/renderer/backend/vulkan/surface_vk.h b/impeller/renderer/backend/vulkan/surface_vk.h index c9d7e1b69202c..ac6797e6afa02 100644 --- a/impeller/renderer/backend/vulkan/surface_vk.h +++ b/impeller/renderer/backend/vulkan/surface_vk.h @@ -4,19 +4,34 @@ #pragma once +#include + #include "flutter/fml/macros.h" -#include "impeller/renderer/context.h" +#include "impeller/renderer/backend/vulkan/context_vk.h" +#include "impeller/renderer/backend/vulkan/swapchain_vk.h" #include "impeller/renderer/surface.h" namespace impeller { class SurfaceVK final : public Surface { public: + using SwapCallback = std::function; + + static std::unique_ptr WrapSwapchainImage( + SwapchainImageVK* swapchain_image, + ContextVK* context, + SwapCallback swap_callback); + + SurfaceVK(RenderTarget target, + SwapchainImageVK* swapchain_image, + SwapCallback swap_callback); + // |Surface| ~SurfaceVK() override; private: - SurfaceVK(RenderTarget target); + const SwapchainImageVK* swapchain_image_; + SwapCallback swap_callback_; // |Surface| bool Present() const override; diff --git a/impeller/renderer/backend/vulkan/swapchain_details_vk.cc b/impeller/renderer/backend/vulkan/swapchain_details_vk.cc new file mode 100644 index 0000000000000..726e04c87f0b2 --- /dev/null +++ b/impeller/renderer/backend/vulkan/swapchain_details_vk.cc @@ -0,0 +1,107 @@ +// 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 "impeller/renderer/backend/vulkan/swapchain_details_vk.h" + +#include "impeller/base/validation.h" + +namespace impeller { + +std::unique_ptr SwapchainDetailsVK::Create( + vk::PhysicalDevice physical_device, + vk::SurfaceKHR surface) { + FML_DCHECK(surface) << "surface provided as nullptr"; + + auto capabilities_res = physical_device.getSurfaceCapabilitiesKHR(surface); + if (capabilities_res.result != vk::Result::eSuccess) { + VALIDATION_LOG << "Failed to get surface capabilities: " + << vk::to_string(capabilities_res.result); + return nullptr; + } + vk::SurfaceCapabilitiesKHR capabilities = capabilities_res.value; + + auto surface_formats_res = physical_device.getSurfaceFormatsKHR(surface); + if (surface_formats_res.result != vk::Result::eSuccess) { + VALIDATION_LOG << "Failed to get surface formats: " + << vk::to_string(surface_formats_res.result); + return nullptr; + } + std::vector surface_formats = surface_formats_res.value; + + auto surface_present_modes_res = + physical_device.getSurfacePresentModesKHR(surface); + if (surface_present_modes_res.result != vk::Result::eSuccess) { + VALIDATION_LOG << "Failed to get surface present modes: " + << vk::to_string(surface_present_modes_res.result); + return nullptr; + } + std::vector surface_present_modes = + surface_present_modes_res.value; + + return std::make_unique(capabilities, surface_formats, + surface_present_modes); +} + +vk::SurfaceFormatKHR SwapchainDetailsVK::PickSurfaceFormat() const { + for (const auto& format : surface_formats_) { + if ((format.format == vk::Format::eR8G8B8A8Unorm || + format.format == vk::Format::eB8G8R8A8Unorm) && + format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear) { + return format; + } + } + + VALIDATION_LOG << "Picking a sub-optimal surface format."; + return surface_formats_[0]; +} + +vk::PresentModeKHR SwapchainDetailsVK::PickPresentationMode() const { + for (const auto& mode : present_modes_) { + if (mode == vk::PresentModeKHR::eMailbox) { + return mode; + } + } + + VALIDATION_LOG << "Picking a sub-optimal presentation mode."; + // Vulkan spec dictates that FIFO is always available. + return vk::PresentModeKHR::eFifo; +} + +vk::Extent2D SwapchainDetailsVK::PickExtent() const { + if (surface_capabilities_.currentExtent.width != + std::numeric_limits::max()) { + return surface_capabilities_.currentExtent; + } + + vk::Extent2D actual_extent = { + std::max(surface_capabilities_.minImageExtent.width, + std::min(surface_capabilities_.maxImageExtent.width, + surface_capabilities_.currentExtent.width)), + std::max(surface_capabilities_.minImageExtent.height, + std::min(surface_capabilities_.maxImageExtent.height, + surface_capabilities_.currentExtent.height))}; + return actual_extent; +} + +uint32_t SwapchainDetailsVK::GetImageCount() const { + uint32_t image_count = surface_capabilities_.minImageCount; + // for triple buffering + return image_count + 1; +} + +vk::SurfaceTransformFlagBitsKHR SwapchainDetailsVK::GetTransform() const { + return surface_capabilities_.currentTransform; +} + +SwapchainDetailsVK::SwapchainDetailsVK( + vk::SurfaceCapabilitiesKHR capabilities, + std::vector surface_formats, + std::vector surface_present_modes) + : surface_capabilities_(capabilities), + surface_formats_(surface_formats), + present_modes_(surface_present_modes) {} + +SwapchainDetailsVK::~SwapchainDetailsVK() = default; + +} // namespace impeller diff --git a/impeller/renderer/backend/vulkan/swapchain_details_vk.h b/impeller/renderer/backend/vulkan/swapchain_details_vk.h new file mode 100644 index 0000000000000..1cfb2b1233c3f --- /dev/null +++ b/impeller/renderer/backend/vulkan/swapchain_details_vk.h @@ -0,0 +1,42 @@ +// 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. + +#pragma once + +#include "flutter/fml/macros.h" +#include "impeller/renderer/backend/vulkan/vk.h" + +namespace impeller { + +class SwapchainDetailsVK { + public: + static std::unique_ptr Create( + vk::PhysicalDevice physical_device, + vk::SurfaceKHR surface); + + SwapchainDetailsVK(vk::SurfaceCapabilitiesKHR capabilities, + std::vector surface_formats, + std::vector surface_present_modes); + + ~SwapchainDetailsVK(); + + vk::SurfaceFormatKHR PickSurfaceFormat() const; + + vk::PresentModeKHR PickPresentationMode() const; + + vk::Extent2D PickExtent() const; + + uint32_t GetImageCount() const; + + vk::SurfaceTransformFlagBitsKHR GetTransform() const; + + private: + vk::SurfaceCapabilitiesKHR surface_capabilities_; + std::vector surface_formats_; + std::vector present_modes_; + + FML_DISALLOW_COPY_AND_ASSIGN(SwapchainDetailsVK); +}; + +} // namespace impeller diff --git a/impeller/renderer/backend/vulkan/swapchain_vk.cc b/impeller/renderer/backend/vulkan/swapchain_vk.cc new file mode 100644 index 0000000000000..df88759c745de --- /dev/null +++ b/impeller/renderer/backend/vulkan/swapchain_vk.cc @@ -0,0 +1,147 @@ +// 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 "impeller/renderer/backend/vulkan/swapchain_vk.h" + +#include "fml/logging.h" +#include "impeller/base/validation.h" +#include "impeller/renderer/backend/vulkan/formats_vk.h" + +namespace impeller { + +std::unique_ptr SwapchainVK::Create(vk::Device device, + vk::SurfaceKHR surface, + SwapchainDetailsVK& details) { + vk::SurfaceFormatKHR surface_format = details.PickSurfaceFormat(); + vk::PresentModeKHR present_mode = details.PickPresentationMode(); + vk::Extent2D extent = details.PickExtent(); + + vk::SwapchainCreateInfoKHR create_info; + create_info.surface = surface; + create_info.imageFormat = surface_format.format; + create_info.imageColorSpace = surface_format.colorSpace; + create_info.presentMode = present_mode; + create_info.imageExtent = extent; + + create_info.minImageCount = details.GetImageCount(); + create_info.imageArrayLayers = 1; + create_info.imageUsage = vk::ImageUsageFlagBits::eColorAttachment; + create_info.preTransform = details.GetTransform(); + create_info.compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque; + create_info.clipped = VK_TRUE; + + create_info.imageSharingMode = vk::SharingMode::eExclusive; + { + // TODO (kaushikiska): This needs to change if graphics queue is not the + // same as present queue which is rare. + create_info.queueFamilyIndexCount = 0; + create_info.pQueueFamilyIndices = nullptr; + } + + create_info.oldSwapchain = nullptr; + + auto swapchain_res = device.createSwapchainKHRUnique(create_info); + if (swapchain_res.result != vk::Result::eSuccess) { + VALIDATION_LOG << "Failed to create swapchain: " + << vk::to_string(swapchain_res.result); + return nullptr; + } + + auto swapchain = std::make_unique( + device, std::move(swapchain_res.value), surface_format.format, extent); + if (!swapchain->CreateSwapchainImages()) { + VALIDATION_LOG << "Failed to create swapchain images."; + return nullptr; + } + + return swapchain; +} + +bool SwapchainVK::CreateSwapchainImages() { + FML_DCHECK(swapchain_images_.empty()) << "Swapchain images already created"; + auto res = device_.getSwapchainImagesKHR(*swapchain_); + if (res.result != vk::Result::eSuccess) { + FML_CHECK(false) << "Failed to get swapchain images: " + << vk::to_string(res.result); + return false; + } + + std::vector images = res.value; + for (const auto& image : images) { + vk::ImageViewCreateInfo create_info; + create_info.image = image; + create_info.viewType = vk::ImageViewType::e2D; + create_info.format = image_format_; + + create_info.components.r = vk::ComponentSwizzle::eIdentity; + create_info.components.g = vk::ComponentSwizzle::eIdentity; + create_info.components.b = vk::ComponentSwizzle::eIdentity; + create_info.components.a = vk::ComponentSwizzle::eIdentity; + + // TODO (kaushikiska): This has to match up to the texture we will be + // rendering to (TextureVK). + create_info.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; + create_info.subresourceRange.baseMipLevel = 0; + create_info.subresourceRange.levelCount = 1; + create_info.subresourceRange.baseArrayLayer = 0; + create_info.subresourceRange.layerCount = 1; + + auto img_view_res = device_.createImageViewUnique(create_info); + if (img_view_res.result != vk::Result::eSuccess) { + VALIDATION_LOG << "Failed to create image view: " + << vk::to_string(img_view_res.result); + return false; + } + + swapchain_images_.push_back(std::make_unique( + image, std::move(img_view_res.value), image_format_, extent_)); + } + + return true; +} + +SwapchainVK::SwapchainVK(vk::Device device, + vk::UniqueSwapchainKHR swapchain, + vk::Format image_format, + vk::Extent2D extent) + : device_(device), + swapchain_(std::move(swapchain)), + image_format_(image_format), + extent_(extent) {} + +SwapchainVK::~SwapchainVK() = default; + +SwapchainImageVK::SwapchainImageVK(vk::Image image, + vk::UniqueImageView image_view, + vk::Format image_format, + vk::Extent2D extent) + : image_(image), + image_view_(std::move(image_view)), + image_format_(image_format), + extent_(extent) {} + +vk::SwapchainKHR SwapchainVK::GetSwapchain() const { + return *swapchain_; +} + +SwapchainImageVK* SwapchainVK::GetSwapchainImage(uint32_t image_index) const { + FML_DCHECK(image_index < swapchain_images_.size()); + return swapchain_images_[image_index].get(); +} + +PixelFormat SwapchainImageVK::GetPixelFormat() const { + return ToPixelFormat(image_format_); +} + +ISize SwapchainImageVK::GetSize() const { + return ISize(extent_.width, extent_.height); +} + +vk::Image SwapchainImageVK::GetImage() const { + return image_; +} + +SwapchainImageVK::~SwapchainImageVK() = default; + +} // namespace impeller diff --git a/impeller/renderer/backend/vulkan/swapchain_vk.h b/impeller/renderer/backend/vulkan/swapchain_vk.h new file mode 100644 index 0000000000000..c57a8800d073b --- /dev/null +++ b/impeller/renderer/backend/vulkan/swapchain_vk.h @@ -0,0 +1,69 @@ +// 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. + +#pragma once + +#include + +#include "flutter/fml/macros.h" +#include "impeller/geometry/size.h" +#include "impeller/renderer/backend/vulkan/swapchain_details_vk.h" +#include "impeller/renderer/surface.h" + +namespace impeller { + +class SwapchainImageVK { + public: + SwapchainImageVK(vk::Image image, + vk::UniqueImageView image_view, + vk::Format image_format, + vk::Extent2D extent); + + ~SwapchainImageVK(); + + PixelFormat GetPixelFormat() const; + + ISize GetSize() const; + + vk::Image GetImage() const; + + private: + vk::Image image_; + vk::UniqueImageView image_view_; + vk::Format image_format_; + vk::Extent2D extent_; + + FML_DISALLOW_COPY_AND_ASSIGN(SwapchainImageVK); +}; + +class SwapchainVK { + public: + static std::unique_ptr Create(vk::Device device, + vk::SurfaceKHR surface, + SwapchainDetailsVK& details); + + SwapchainVK(vk::Device device, + vk::UniqueSwapchainKHR swapchain, + vk::Format image_format, + vk::Extent2D extent); + + ~SwapchainVK(); + + vk::SwapchainKHR GetSwapchain() const; + + SwapchainImageVK* GetSwapchainImage(uint32_t image_index) const; + + private: + bool CreateSwapchainImages(); + + vk::Device device_; + vk::UniqueSwapchainKHR swapchain_; + vk::Format image_format_; + vk::Extent2D extent_; + std::vector> swapchain_images_; + + FML_DISALLOW_COPY_AND_ASSIGN(SwapchainVK); +}; + +} // namespace impeller diff --git a/impeller/renderer/backend/vulkan/texture_vk.cc b/impeller/renderer/backend/vulkan/texture_vk.cc index 7d29259d81f89..9a6950ee7be2b 100644 --- a/impeller/renderer/backend/vulkan/texture_vk.cc +++ b/impeller/renderer/backend/vulkan/texture_vk.cc @@ -6,6 +6,82 @@ namespace impeller { -// +TextureVK::TextureVK(TextureDescriptor desc, + ContextVK* context, + std::unique_ptr texture_info) + : Texture(desc), + context_(context), + texture_info_(std::move(texture_info)) {} + +TextureVK::~TextureVK() { + if (!IsWrapped() && IsValid()) { + const auto& texture = texture_info_->allocated_texture; + vmaDestroyImage(*texture.allocator, texture.image, texture.allocation); + } +} + +void TextureVK::SetLabel(std::string_view label) { + context_->SetDebugName(GetImage(), label); +} + +bool TextureVK::OnSetContents(const uint8_t* contents, + size_t length, + size_t slice) { + if (IsWrapped()) { + FML_LOG(ERROR) << "Cannot set contents of a wrapped texture"; + return false; + } + + if (!IsValid() || !contents) { + return false; + } + + const auto& desc = GetTextureDescriptor(); + + // Out of bounds access. + if (length != desc.GetByteSizeOfBaseMipLevel()) { + VALIDATION_LOG << "illegal to set contents for invalid size"; + return false; + } + + // currently we are only supporting 2d textures, no cube textures etc. + auto mapping = texture_info_->allocated_texture.allocation_info.pMappedData; + memcpy(mapping, contents, length); + + return true; +} + +bool TextureVK::OnSetContents(std::shared_ptr mapping, + size_t slice) { + // Vulkan has no threading restrictions. So we can pass this data along to the + // client rendering API immediately. + return OnSetContents(mapping->GetMapping(), mapping->GetSize(), slice); +} + +bool TextureVK::IsValid() const { + switch (texture_info_->backing_type) { + case TextureBackingTypeVK::kAllocatedTexture: + return texture_info_->allocated_texture.image; + case TextureBackingTypeVK::kWrappedTexture: + return texture_info_->wrapped_texture.swapchain_image; + } +} + +ISize TextureVK::GetSize() const { + return GetTextureDescriptor().size; +} + +bool TextureVK::IsWrapped() const { + return texture_info_->backing_type == TextureBackingTypeVK::kWrappedTexture; +} + +vk::Image TextureVK::GetImage() const { + switch (texture_info_->backing_type) { + case TextureBackingTypeVK::kAllocatedTexture: + return texture_info_->allocated_texture.image; + case TextureBackingTypeVK::kWrappedTexture: + return texture_info_->wrapped_texture.swapchain_image->GetImage(); + } +} } // namespace impeller diff --git a/impeller/renderer/backend/vulkan/texture_vk.h b/impeller/renderer/backend/vulkan/texture_vk.h index f4bc6ad4f8d5e..832133c972b71 100644 --- a/impeller/renderer/backend/vulkan/texture_vk.h +++ b/impeller/renderer/backend/vulkan/texture_vk.h @@ -6,18 +6,54 @@ #include "flutter/fml/macros.h" #include "impeller/base/backend_cast.h" +#include "impeller/renderer/backend/vulkan/context_vk.h" +#include "impeller/renderer/backend/vulkan/swapchain_vk.h" +#include "impeller/renderer/backend/vulkan/vk.h" #include "impeller/renderer/texture.h" namespace impeller { +enum class TextureBackingTypeVK { + kAllocatedTexture, + kWrappedTexture, +}; + +struct WrappedTextureInfoVK { + SwapchainImageVK* swapchain_image = nullptr; +}; + +struct AllocatedTextureInfoVK { + VmaAllocator* allocator = nullptr; + VmaAllocation allocation = nullptr; + VmaAllocationInfo allocation_info = {}; + VkImage image = nullptr; +}; + +struct TextureInfoVK { + TextureBackingTypeVK backing_type; + union { + WrappedTextureInfoVK wrapped_texture; + AllocatedTextureInfoVK allocated_texture; + }; +}; + class TextureVK final : public Texture, public BackendCast { public: - TextureVK(TextureDescriptor desc); + TextureVK(TextureDescriptor desc, + ContextVK* context, + std::unique_ptr texture_info); // |Texture| ~TextureVK() override; + bool IsWrapped() const; + + vk::Image GetImage() const; + private: + ContextVK* context_; + std::unique_ptr texture_info_; + // |Texture| void SetLabel(std::string_view label) override; diff --git a/impeller/renderer/command_buffer.cc b/impeller/renderer/command_buffer.cc index 75ff9e700f78a..6429afaf3131a 100644 --- a/impeller/renderer/command_buffer.cc +++ b/impeller/renderer/command_buffer.cc @@ -4,15 +4,29 @@ #include "impeller/renderer/command_buffer.h" +#include "flutter/fml/trace_event.h" #include "impeller/renderer/render_pass.h" #include "impeller/renderer/render_target.h" namespace impeller { -CommandBuffer::CommandBuffer() = default; +CommandBuffer::CommandBuffer(std::weak_ptr context) + : context_(std::move(context)) {} CommandBuffer::~CommandBuffer() = default; +bool CommandBuffer::SubmitCommands(CompletionCallback callback) { + TRACE_EVENT0("impeller", "CommandBuffer::SubmitCommands"); + if (!IsValid()) { + // Already committed or was never valid. Either way, this is caller error. + if (callback) { + callback(Status::kError); + } + return false; + } + return OnSubmitCommands(callback); +} + bool CommandBuffer::SubmitCommands() { return SubmitCommands(nullptr); } diff --git a/impeller/renderer/command_buffer.h b/impeller/renderer/command_buffer.h index 92328a0ae3197..28158e15561ad 100644 --- a/impeller/renderer/command_buffer.h +++ b/impeller/renderer/command_buffer.h @@ -59,7 +59,7 @@ class CommandBuffer { /// /// @param[in] callback The completion callback. /// - [[nodiscard]] virtual bool SubmitCommands(CompletionCallback callback) = 0; + [[nodiscard]] bool SubmitCommands(CompletionCallback callback); [[nodiscard]] bool SubmitCommands(); @@ -82,13 +82,17 @@ class CommandBuffer { std::shared_ptr CreateBlitPass() const; protected: - CommandBuffer(); + std::weak_ptr context_; + + explicit CommandBuffer(std::weak_ptr context); virtual std::shared_ptr OnCreateRenderPass( RenderTarget render_target) const = 0; virtual std::shared_ptr OnCreateBlitPass() const = 0; + [[nodiscard]] virtual bool OnSubmitCommands(CompletionCallback callback) = 0; + private: FML_DISALLOW_COPY_AND_ASSIGN(CommandBuffer); }; diff --git a/impeller/renderer/context.h b/impeller/renderer/context.h index 412361e3f35cd..6db6d7bc994de 100644 --- a/impeller/renderer/context.h +++ b/impeller/renderer/context.h @@ -16,24 +16,18 @@ class SamplerLibrary; class CommandBuffer; class PipelineLibrary; class Allocator; +class WorkQueue; -class Context { +class Context : public std::enable_shared_from_this { public: virtual ~Context(); virtual bool IsValid() const = 0; //---------------------------------------------------------------------------- - /// @return An allocator suitable for allocations that persist between - /// frames. + /// @return A resource allocator. /// - virtual std::shared_ptr GetPermanentsAllocator() const = 0; - - //---------------------------------------------------------------------------- - /// @return An allocator suitable for allocations that used only for one - /// frame or render pass. - /// - virtual std::shared_ptr GetTransientsAllocator() const = 0; + virtual std::shared_ptr GetResourceAllocator() const = 0; virtual std::shared_ptr GetShaderLibrary() const = 0; @@ -41,13 +35,14 @@ class Context { virtual std::shared_ptr GetPipelineLibrary() const = 0; - virtual std::shared_ptr CreateRenderCommandBuffer() const = 0; + virtual std::shared_ptr CreateCommandBuffer() const = 0; - virtual std::shared_ptr CreateTransferCommandBuffer() - const = 0; + virtual std::shared_ptr GetWorkQueue() const = 0; virtual bool HasThreadingRestrictions() const; + virtual bool SupportsOffscreenMSAA() const = 0; + protected: Context(); diff --git a/impeller/renderer/descriptor_set_layout.h b/impeller/renderer/descriptor_set_layout.h new file mode 100644 index 0000000000000..d853f0a663086 --- /dev/null +++ b/impeller/renderer/descriptor_set_layout.h @@ -0,0 +1,27 @@ +// 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. + +#pragma once + +#include +#include + +#include "flutter/fml/macros.h" +#include "impeller/renderer/shader_types.h" + +namespace impeller { + +enum class DescriptorType { + kSampledImage, + kUniformBuffer, +}; + +struct DescriptorSetLayout { + uint32_t binding; + DescriptorType descriptor_type; + uint32_t descriptor_count; + ShaderStage shader_stage; +}; + +} // namespace impeller diff --git a/impeller/renderer/formats.h b/impeller/renderer/formats.h index ebf318f42ca26..0e8ce849a23c4 100644 --- a/impeller/renderer/formats.h +++ b/impeller/renderer/formats.h @@ -99,6 +99,7 @@ enum class StoreAction { kDontCare, kStore, kMultisampleResolve, + kStoreAndMultisampleResolve, }; constexpr bool CanClearAttachment(LoadAction action) { @@ -115,6 +116,7 @@ constexpr bool CanClearAttachment(LoadAction action) { constexpr bool CanDiscardAttachmentWhenDone(StoreAction action) { switch (action) { case StoreAction::kStore: + case StoreAction::kStoreAndMultisampleResolve: return false; case StoreAction::kDontCare: case StoreAction::kMultisampleResolve: @@ -184,11 +186,19 @@ enum class PrimitiveType { struct DepthRange { Scalar z_near = 0.0; Scalar z_far = 1.0; + + constexpr bool operator==(const DepthRange& other) const { + return z_near == other.z_near && z_far == other.z_far; + } }; struct Viewport { Rect rect; DepthRange depth_range; + + constexpr bool operator==(const Viewport& other) const { + return rect == other.rect && depth_range == other.depth_range; + } }; enum class MinMagFilter { diff --git a/impeller/renderer/pipeline_builder.h b/impeller/renderer/pipeline_builder.h index 44cd202997cd9..e583b88300c8b 100644 --- a/impeller/renderer/pipeline_builder.h +++ b/impeller/renderer/pipeline_builder.h @@ -93,6 +93,22 @@ struct PipelineBuilder { << VertexShader::kLabel << "'."; return false; } + if (!vertex_descriptor->SetDescriptorSetLayouts( + VertexShader::kDescriptorSetLayouts)) { + VALIDATION_LOG << "Cound not configure vertex descriptor set layout for" + " pipeline named '" + << VertexShader::kLabel << "'."; + return false; + } + + if (!vertex_descriptor->SetDescriptorSetLayouts( + FragmentShader::kDescriptorSetLayouts)) { + VALIDATION_LOG << "Cound not configure vertex descriptor set layout for" + " pipeline named '" + << VertexShader::kLabel << "'."; + return false; + } + desc.SetVertexDescriptor(std::move(vertex_descriptor)); } diff --git a/impeller/renderer/platform.h b/impeller/renderer/platform.h index c2783d7581e08..6076e810b3d17 100644 --- a/impeller/renderer/platform.h +++ b/impeller/renderer/platform.h @@ -12,7 +12,7 @@ namespace impeller { constexpr size_t DefaultUniformAlignment() { -#if FML_OS_IOS +#if FML_OS_IOS && !TARGET_OS_SIMULATOR return 16u; #elif FML_OS_MACOSX return 256u; diff --git a/impeller/renderer/render_pass.cc b/impeller/renderer/render_pass.cc index f3db163cb1994..ba084ff442b33 100644 --- a/impeller/renderer/render_pass.cc +++ b/impeller/renderer/render_pass.cc @@ -6,8 +6,10 @@ namespace impeller { -RenderPass::RenderPass(RenderTarget target) - : render_target_(std::move(target)), +RenderPass::RenderPass(std::weak_ptr context, + RenderTarget target) + : context_(std::move(context)), + render_target_(std::move(target)), transients_buffer_(HostBuffer::Create()) {} RenderPass::~RenderPass() = default; @@ -63,4 +65,13 @@ bool RenderPass::AddCommand(Command command) { return true; } +bool RenderPass::EncodeCommands() const { + auto context = context_.lock(); + // The context could have been collected in the meantime. + if (!context) { + return false; + } + return OnEncodeCommands(*context); +} + } // namespace impeller diff --git a/impeller/renderer/render_pass.h b/impeller/renderer/render_pass.h index e026dc69fd223..4386d5c9e00b0 100644 --- a/impeller/renderer/render_pass.h +++ b/impeller/renderer/render_pass.h @@ -51,23 +51,23 @@ class RenderPass { //---------------------------------------------------------------------------- /// @brief Encode the recorded commands to the underlying command buffer. /// - /// @param transients_allocator The transients allocator. - /// /// @return If the commands were encoded to the underlying command /// buffer. /// - virtual bool EncodeCommands( - const std::shared_ptr& transients_allocator) const = 0; + bool EncodeCommands() const; protected: + const std::weak_ptr context_; const RenderTarget render_target_; std::shared_ptr transients_buffer_; std::vector commands_; - RenderPass(RenderTarget target); + RenderPass(std::weak_ptr context, RenderTarget target); virtual void OnSetLabel(std::string label) = 0; + virtual bool OnEncodeCommands(const Context& context) const = 0; + private: FML_DISALLOW_COPY_AND_ASSIGN(RenderPass); }; diff --git a/impeller/renderer/render_target.cc b/impeller/renderer/render_target.cc index 835ef4f5e16e5..da74b6a3c2041 100644 --- a/impeller/renderer/render_target.cc +++ b/impeller/renderer/render_target.cc @@ -136,7 +136,8 @@ std::shared_ptr RenderTarget::GetRenderTargetTexture() const { if (found == colors_.end()) { return nullptr; } - return found->second.texture; + return found->second.resolve_texture ? found->second.resolve_texture + : found->second.texture; } RenderTarget& RenderTarget::SetColorAttachment(ColorAttachment attachment, @@ -204,27 +205,117 @@ RenderTarget RenderTarget::CreateOffscreen(const Context& context, color0.clear_color = Color::BlackTransparent(); color0.load_action = color_load_action; color0.store_action = color_store_action; - color0.texture = context.GetPermanentsAllocator()->CreateTexture( + color0.texture = context.GetResourceAllocator()->CreateTexture( color_storage_mode, color_tex0); if (!color0.texture) { return {}; } - color0.texture->SetLabel(SPrintF("%sColorTexture", label.c_str())); + color0.texture->SetLabel(SPrintF("%s Color Texture", label.c_str())); StencilAttachment stencil0; stencil0.load_action = stencil_load_action; stencil0.store_action = stencil_store_action; stencil0.clear_stencil = 0u; - stencil0.texture = context.GetPermanentsAllocator()->CreateTexture( + stencil0.texture = context.GetResourceAllocator()->CreateTexture( stencil_storage_mode, stencil_tex0); if (!stencil0.texture) { return {}; } - stencil0.texture->SetLabel(SPrintF("%sStencilTexture", label.c_str())); + stencil0.texture->SetLabel(SPrintF("%s Stencil Texture", label.c_str())); + + RenderTarget target; + target.SetColorAttachment(std::move(color0), 0u); + target.SetStencilAttachment(std::move(stencil0)); + + return target; +} + +RenderTarget RenderTarget::CreateOffscreenMSAA( + const Context& context, + ISize size, + std::string label, + StorageMode color_storage_mode, + StorageMode color_resolve_storage_mode, + LoadAction color_load_action, + StoreAction color_store_action, + StorageMode stencil_storage_mode, + LoadAction stencil_load_action, + StoreAction stencil_store_action) { + if (size.IsEmpty()) { + return {}; + } + + // Create MSAA color texture. + + TextureDescriptor color0_tex_desc; + color0_tex_desc.type = TextureType::kTexture2DMultisample; + color0_tex_desc.sample_count = SampleCount::kCount4; + color0_tex_desc.format = PixelFormat::kDefaultColor; + color0_tex_desc.size = size; + color0_tex_desc.usage = static_cast(TextureUsage::kRenderTarget) | + static_cast(TextureUsage::kShaderRead); + + auto color0_msaa_tex = context.GetResourceAllocator()->CreateTexture( + color_storage_mode, color0_tex_desc); + if (!color0_msaa_tex) { + VALIDATION_LOG << "Could not create multisample color texture."; + return {}; + } + color0_msaa_tex->SetLabel( + SPrintF("%s Color Texture (Multisample)", label.c_str())); + + // Create color resolve texture. + + TextureDescriptor color0_resolve_tex_desc; + color0_resolve_tex_desc.format = PixelFormat::kDefaultColor; + color0_resolve_tex_desc.size = size; + color0_resolve_tex_desc.usage = + static_cast(TextureUsage::kRenderTarget) | + static_cast(TextureUsage::kShaderRead); + + auto color0_resolve_tex = context.GetResourceAllocator()->CreateTexture( + color_resolve_storage_mode, color0_resolve_tex_desc); + if (!color0_resolve_tex) { + VALIDATION_LOG << "Could not create color texture."; + return {}; + } + color0_resolve_tex->SetLabel(SPrintF("%s Color Texture", label.c_str())); + + // Color attachment. + + ColorAttachment color0; + color0.clear_color = Color::BlackTransparent(); + color0.load_action = color_load_action; + color0.store_action = color_store_action; + color0.texture = color0_msaa_tex; + color0.resolve_texture = color0_resolve_tex; + + // Create MSAA stencil texture. + + TextureDescriptor stencil_tex0; + stencil_tex0.type = TextureType::kTexture2DMultisample; + stencil_tex0.sample_count = SampleCount::kCount4; + stencil_tex0.format = PixelFormat::kDefaultStencil; + stencil_tex0.size = size; + stencil_tex0.usage = + static_cast(TextureUsage::kRenderTarget); + + StencilAttachment stencil0; + stencil0.load_action = stencil_load_action; + stencil0.store_action = stencil_store_action; + stencil0.clear_stencil = 0u; + stencil0.texture = context.GetResourceAllocator()->CreateTexture( + stencil_storage_mode, stencil_tex0); + + if (!stencil0.texture) { + return {}; + } + + stencil0.texture->SetLabel(SPrintF("%s Stencil Texture", label.c_str())); RenderTarget target; target.SetColorAttachment(std::move(color0), 0u); diff --git a/impeller/renderer/render_target.h b/impeller/renderer/render_target.h index 786e56ee7e295..b81f32e0add06 100644 --- a/impeller/renderer/render_target.h +++ b/impeller/renderer/render_target.h @@ -30,6 +30,18 @@ class RenderTarget { LoadAction stencil_load_action = LoadAction::kClear, StoreAction stencil_store_action = StoreAction::kDontCare); + static RenderTarget CreateOffscreenMSAA( + const Context& context, + ISize size, + std::string label = "Offscreen MSAA", + StorageMode color_storage_mode = StorageMode::kDeviceTransient, + StorageMode color_resolve_storage_mode = StorageMode::kDevicePrivate, + LoadAction color_load_action = LoadAction::kClear, + StoreAction color_store_action = StoreAction::kMultisampleResolve, + StorageMode stencil_storage_mode = StorageMode::kDeviceTransient, + LoadAction stencil_load_action = LoadAction::kClear, + StoreAction stencil_store_action = StoreAction::kDontCare); + RenderTarget(); ~RenderTarget(); diff --git a/impeller/renderer/renderer.cc b/impeller/renderer/renderer.cc index 9fd707a7eaaf0..c4c431c9ed8a8 100644 --- a/impeller/renderer/renderer.cc +++ b/impeller/renderer/renderer.cc @@ -53,8 +53,11 @@ bool Renderer::Render(std::unique_ptr surface, return false; } + const auto present_result = surface->Present(); + frames_in_flight_sema_->Signal(); - return surface->Present(); + + return present_result; } std::shared_ptr Renderer::GetContext() const { diff --git a/impeller/renderer/renderer_unittests.cc b/impeller/renderer/renderer_unittests.cc index 5131479dd1c12..17f64e6f506f8 100644 --- a/impeller/renderer/renderer_unittests.cc +++ b/impeller/renderer/renderer_unittests.cc @@ -66,7 +66,7 @@ TEST_P(RendererTest, CanCreateBoxPrimitive) { {{100, 800, 0.0}, {0.0, 1.0}}, // 4 }); auto vertex_buffer = - vertex_builder.CreateVertexBuffer(*context->GetPermanentsAllocator()); + vertex_builder.CreateVertexBuffer(*context->GetResourceAllocator()); ASSERT_TRUE(vertex_buffer); auto bridge = CreateTextureForFixture("bay_bridge.jpg"); @@ -145,9 +145,8 @@ TEST_P(RendererTest, CanRenderPerspectiveCube) { VertexBuffer vertex_buffer; { - auto device_buffer = - context->GetPermanentsAllocator()->CreateBufferWithCopy( - reinterpret_cast(&cube), sizeof(cube)); + auto device_buffer = context->GetResourceAllocator()->CreateBufferWithCopy( + reinterpret_cast(&cube), sizeof(cube)); vertex_buffer.vertex_buffer = { .buffer = device_buffer, .range = Range(offsetof(Cube, vertices), sizeof(Cube::vertices))}; @@ -231,7 +230,7 @@ TEST_P(RendererTest, CanRenderMultiplePrimitives) { {{100, 800, 0.0}, {0.0, 1.0}}, // 4 }); auto vertex_buffer = - vertex_builder.CreateVertexBuffer(*context->GetPermanentsAllocator()); + vertex_builder.CreateVertexBuffer(*context->GetResourceAllocator()); ASSERT_TRUE(vertex_buffer); auto bridge = CreateTextureForFixture("bay_bridge.jpg"); @@ -303,7 +302,7 @@ TEST_P(RendererTest, CanRenderToTexture) { {{100, 800, 0.0}, {0.0, 1.0}}, // 4 }); auto vertex_buffer = - vertex_builder.CreateVertexBuffer(*context->GetPermanentsAllocator()); + vertex_builder.CreateVertexBuffer(*context->GetResourceAllocator()); ASSERT_TRUE(vertex_buffer); auto bridge = CreateTextureForFixture("bay_bridge.jpg"); @@ -328,7 +327,7 @@ TEST_P(RendererTest, CanRenderToTexture) { texture_descriptor.usage = static_cast(TextureUsage::kRenderTarget); - color0.texture = context->GetPermanentsAllocator()->CreateTexture( + color0.texture = context->GetResourceAllocator()->CreateTexture( StorageMode::kHostVisible, texture_descriptor); ASSERT_TRUE(color0); @@ -343,13 +342,13 @@ TEST_P(RendererTest, CanRenderToTexture) { stencil_texture_desc.format = PixelFormat::kS8UInt; stencil_texture_desc.usage = static_cast(TextureUsage::kRenderTarget); - stencil0.texture = context->GetPermanentsAllocator()->CreateTexture( + stencil0.texture = context->GetResourceAllocator()->CreateTexture( StorageMode::kDeviceTransient, stencil_texture_desc); RenderTarget r2t_desc; r2t_desc.SetColorAttachment(color0, 0u); r2t_desc.SetStencilAttachment(stencil0); - auto cmd_buffer = context->CreateRenderCommandBuffer(); + auto cmd_buffer = context->CreateCommandBuffer(); r2t_pass = cmd_buffer->CreateRenderPass(r2t_desc); ASSERT_TRUE(r2t_pass && r2t_pass->IsValid()); } @@ -379,7 +378,7 @@ TEST_P(RendererTest, CanRenderToTexture) { VS::BindUniformBuffer( cmd, r2t_pass->GetTransientsBuffer().EmplaceUniform(uniforms)); ASSERT_TRUE(r2t_pass->AddCommand(std::move(cmd))); - ASSERT_TRUE(r2t_pass->EncodeCommands(context->GetTransientsAllocator())); + ASSERT_TRUE(r2t_pass->EncodeCommands()); } #if IMPELLER_ENABLE_METAL @@ -465,7 +464,7 @@ TEST_P(RendererTest, CanBlitTextureToTexture) { texture_desc.usage = static_cast(TextureUsage::kRenderTarget) | static_cast(TextureUsage::kShaderRead); - auto texture = context->GetPermanentsAllocator()->CreateTexture( + auto texture = context->GetResourceAllocator()->CreateTexture( StorageMode::kHostVisible, texture_desc); ASSERT_TRUE(texture); @@ -488,11 +487,11 @@ TEST_P(RendererTest, CanBlitTextureToTexture) { {{0, size.y}, {0.0, 1.0}}, // 4 }); auto vertex_buffer = - vertex_builder.CreateVertexBuffer(*context->GetTransientsAllocator()); + vertex_builder.CreateVertexBuffer(*context->GetResourceAllocator()); ASSERT_TRUE(vertex_buffer); Renderer::RenderCallback callback = [&](RenderTarget& render_target) { - auto buffer = context->CreateRenderCommandBuffer(); + auto buffer = context->CreateCommandBuffer(); if (!buffer) { return false; } @@ -512,7 +511,7 @@ TEST_P(RendererTest, CanBlitTextureToTexture) { // Blit `bridge` to the top left corner of the texture. pass->AddCopy(bridge, texture); - pass->EncodeCommands(context->GetTransientsAllocator()); + pass->EncodeCommands(context->GetResourceAllocator()); } { @@ -544,7 +543,7 @@ TEST_P(RendererTest, CanBlitTextureToTexture) { pass->AddCommand(std::move(cmd)); } - pass->EncodeCommands(context->GetTransientsAllocator()); + pass->EncodeCommands(); } if (!buffer->SubmitCommands()) { @@ -584,7 +583,7 @@ TEST_P(RendererTest, CanGenerateMipmaps) { {{0, size.y}, {0.0, 1.0}}, // 4 }); auto vertex_buffer = - vertex_builder.CreateVertexBuffer(*context->GetPermanentsAllocator()); + vertex_builder.CreateVertexBuffer(*context->GetResourceAllocator()); ASSERT_TRUE(vertex_buffer); bool first_frame = true; @@ -613,7 +612,7 @@ TEST_P(RendererTest, CanGenerateMipmaps) { ImGui::SliderFloat("LOD", &lod, 0, boston->GetMipCount() - 1); ImGui::End(); - auto buffer = context->CreateRenderCommandBuffer(); + auto buffer = context->CreateCommandBuffer(); if (!buffer) { return false; } @@ -628,7 +627,7 @@ TEST_P(RendererTest, CanGenerateMipmaps) { pass->GenerateMipmap(boston, "Boston Mipmap"); - pass->EncodeCommands(context->GetTransientsAllocator()); + pass->EncodeCommands(context->GetResourceAllocator()); } first_frame = false; @@ -665,7 +664,7 @@ TEST_P(RendererTest, CanGenerateMipmaps) { pass->AddCommand(std::move(cmd)); } - pass->EncodeCommands(context->GetTransientsAllocator()); + pass->EncodeCommands(); } if (!buffer->SubmitCommands()) { diff --git a/impeller/renderer/shader_types.h b/impeller/renderer/shader_types.h index 7750032b030e9..fe4af812f165a 100644 --- a/impeller/renderer/shader_types.h +++ b/impeller/renderer/shader_types.h @@ -9,7 +9,9 @@ #include #include "flutter/fml/hash_combine.h" +#include "flutter/fml/logging.h" #include "impeller/geometry/matrix.h" +#include "impeller/runtime_stage/runtime_types.h" namespace impeller { @@ -22,6 +24,22 @@ enum class ShaderStage { kCompute, }; +constexpr ShaderStage ToShaderStage(RuntimeShaderStage stage) { + switch (stage) { + case RuntimeShaderStage::kVertex: + return ShaderStage::kVertex; + case RuntimeShaderStage::kFragment: + return ShaderStage::kFragment; + case RuntimeShaderStage::kCompute: + return ShaderStage::kCompute; + case RuntimeShaderStage::kTessellationControl: + return ShaderStage::kTessellationControl; + case RuntimeShaderStage::kTessellationEvaluation: + return ShaderStage::kTessellationEvaluation; + } + FML_UNREACHABLE(); +} + enum class ShaderType { kUnknown, kVoid, diff --git a/impeller/renderer/snapshot.h b/impeller/renderer/snapshot.h index a0fe419b069bf..65a4c9a73cfb7 100644 --- a/impeller/renderer/snapshot.h +++ b/impeller/renderer/snapshot.h @@ -11,6 +11,7 @@ #include "flutter/fml/macros.h" #include "impeller/geometry/matrix.h" #include "impeller/geometry/rect.h" +#include "impeller/renderer/sampler_descriptor.h" #include "impeller/renderer/texture.h" namespace impeller { @@ -18,12 +19,14 @@ namespace impeller { class ContentContext; class Entity; -/// Represents a texture and its intended draw position. +/// Represents a texture and its intended draw transform/sampler configuration. struct Snapshot { std::shared_ptr texture; /// The transform that should be applied to this texture for rendering. Matrix transform; + SamplerDescriptor sampler_descriptor; + std::optional GetCoverage() const; /// @brief Get the transform that converts screen space coordinates to the UV diff --git a/impeller/renderer/surface.h b/impeller/renderer/surface.h index 0ab7e53fc2c90..2a9377c5aa668 100644 --- a/impeller/renderer/surface.h +++ b/impeller/renderer/surface.h @@ -18,7 +18,7 @@ class Surface { public: Surface(); - Surface(RenderTarget target_desc); + explicit Surface(RenderTarget target_desc); virtual ~Surface(); diff --git a/impeller/renderer/vertex_descriptor.cc b/impeller/renderer/vertex_descriptor.cc index a40efb31c5eb3..963a7478a1696 100644 --- a/impeller/renderer/vertex_descriptor.cc +++ b/impeller/renderer/vertex_descriptor.cc @@ -20,6 +20,16 @@ bool VertexDescriptor::SetStageInputs( return true; } +bool VertexDescriptor::SetDescriptorSetLayouts( + const DescriptorSetLayout desc_set_layout[], + size_t count) { + desc_set_layouts_.reserve(desc_set_layouts_.size() + count); + for (size_t i = 0; i < count; i++) { + desc_set_layouts_.emplace_back(desc_set_layout[i]); + } + return true; +} + // |Comparable| size_t VertexDescriptor::GetHash() const { auto seed = fml::HashCombine(); @@ -38,4 +48,9 @@ const std::vector& VertexDescriptor::GetStageInputs() const { return inputs_; } +const std::vector& +VertexDescriptor::GetDescriptorSetLayouts() const { + return desc_set_layouts_; +} + } // namespace impeller diff --git a/impeller/renderer/vertex_descriptor.h b/impeller/renderer/vertex_descriptor.h index 4bef9b86d3644..dffe6694f7caf 100644 --- a/impeller/renderer/vertex_descriptor.h +++ b/impeller/renderer/vertex_descriptor.h @@ -8,6 +8,7 @@ #include "flutter/fml/macros.h" #include "impeller/base/comparable.h" +#include "impeller/renderer/descriptor_set_layout.h" #include "impeller/renderer/shader_types.h" namespace impeller { @@ -36,11 +37,22 @@ class VertexDescriptor final : public Comparable { return SetStageInputs(inputs.data(), inputs.size()); } + template + bool SetDescriptorSetLayouts( + const std::array& inputs) { + return SetDescriptorSetLayouts(inputs.data(), inputs.size()); + } + bool SetStageInputs(const ShaderStageIOSlot* const stage_inputs[], size_t count); + bool SetDescriptorSetLayouts(const DescriptorSetLayout desc_set_layout[], + size_t count); + const std::vector& GetStageInputs() const; + const std::vector& GetDescriptorSetLayouts() const; + // |Comparable| std::size_t GetHash() const override; @@ -49,6 +61,7 @@ class VertexDescriptor final : public Comparable { private: std::vector inputs_; + std::vector desc_set_layouts_; FML_DISALLOW_COPY_AND_ASSIGN(VertexDescriptor); }; diff --git a/impeller/runtime_stage/BUILD.gn b/impeller/runtime_stage/BUILD.gn index e0fe2502263ae..b36a7f9017c88 100644 --- a/impeller/runtime_stage/BUILD.gn +++ b/impeller/runtime_stage/BUILD.gn @@ -20,11 +20,11 @@ impeller_component("runtime_stage") { sources = [ "runtime_stage.cc", "runtime_stage.h", + "runtime_types.h", ] public_deps = [ ":runtime_stage_flatbuffers", "../base", - "../renderer", "//flutter/fml", ] } diff --git a/impeller/runtime_stage/runtime_stage.cc b/impeller/runtime_stage/runtime_stage.cc index 01955ee373adc..c75f33b425730 100644 --- a/impeller/runtime_stage/runtime_stage.cc +++ b/impeller/runtime_stage/runtime_stage.cc @@ -43,18 +43,18 @@ static RuntimeUniformType ToType(fb::UniformDataType type) { FML_UNREACHABLE(); } -static ShaderStage ToShaderStage(fb::Stage stage) { +static RuntimeShaderStage ToShaderStage(fb::Stage stage) { switch (stage) { case fb::Stage::kVertex: - return ShaderStage::kVertex; + return RuntimeShaderStage::kVertex; case fb::Stage::kFragment: - return ShaderStage::kFragment; + return RuntimeShaderStage::kFragment; case fb::Stage::kCompute: - return ShaderStage::kCompute; + return RuntimeShaderStage::kCompute; case fb::Stage::kTessellationControl: - return ShaderStage::kTessellationControl; + return RuntimeShaderStage::kTessellationControl; case fb::Stage::kTessellationEvaluation: - return ShaderStage::kTessellationEvaluation; + return RuntimeShaderStage::kTessellationEvaluation; } FML_UNREACHABLE(); } @@ -85,6 +85,7 @@ RuntimeStage::RuntimeStage(std::shared_ptr payload) desc.dimensions = RuntimeUniformDimensions{ static_cast(i->rows()), static_cast(i->columns())}; desc.bit_width = i->bit_width(); + desc.array_elements = i->array_elements(); uniforms_.emplace_back(std::move(desc)); } } @@ -127,7 +128,7 @@ const std::string& RuntimeStage::GetEntrypoint() const { return entrypoint_; } -ShaderStage RuntimeStage::GetShaderStage() const { +RuntimeShaderStage RuntimeStage::GetShaderStage() const { return stage_; } diff --git a/impeller/runtime_stage/runtime_stage.fbs b/impeller/runtime_stage/runtime_stage.fbs index 1391fc565a5a8..bb3d04e00edab 100644 --- a/impeller/runtime_stage/runtime_stage.fbs +++ b/impeller/runtime_stage/runtime_stage.fbs @@ -41,6 +41,7 @@ table UniformDescription { bit_width: uint64; rows: uint64; columns: uint64; + array_elements: uint64; } table RuntimeStage { diff --git a/impeller/runtime_stage/runtime_stage.h b/impeller/runtime_stage/runtime_stage.h index 4f8f9b81488f9..fcf5d529d9bf5 100644 --- a/impeller/runtime_stage/runtime_stage.h +++ b/impeller/runtime_stage/runtime_stage.h @@ -9,51 +9,19 @@ #include "flutter/fml/macros.h" #include "flutter/fml/mapping.h" -#include "impeller/renderer/shader_function.h" -#include "impeller/renderer/shader_types.h" +#include "impeller/runtime_stage/runtime_types.h" namespace impeller { -enum RuntimeUniformType { - kBoolean, - kSignedByte, - kUnsignedByte, - kSignedShort, - kUnsignedShort, - kSignedInt, - kUnsignedInt, - kSignedInt64, - kUnsignedInt64, - kHalfFloat, - kFloat, - kDouble, - kSampledImage, -}; - -struct RuntimeUniformDimensions { - size_t rows = 0; - size_t cols = 0; -}; - -struct RuntimeUniformDescription { - std::string name; - size_t location = 0u; - RuntimeUniformType type = kFloat; - RuntimeUniformDimensions dimensions; - size_t bit_width; -}; - -size_t SizeOfRuntimeUniformType(RuntimeUniformType type); - class RuntimeStage { public: - RuntimeStage(std::shared_ptr payload); + explicit RuntimeStage(std::shared_ptr payload); ~RuntimeStage(); bool IsValid() const; - ShaderStage GetShaderStage() const; + RuntimeShaderStage GetShaderStage() const; const std::vector& GetUniforms() const; @@ -64,7 +32,7 @@ class RuntimeStage { const std::shared_ptr& GetCodeMapping() const; private: - ShaderStage stage_ = ShaderStage::kUnknown; + RuntimeShaderStage stage_ = RuntimeShaderStage::kVertex; std::shared_ptr payload_; std::string entrypoint_; std::shared_ptr code_mapping_; diff --git a/impeller/runtime_stage/runtime_stage_playground.cc b/impeller/runtime_stage/runtime_stage_playground.cc index af1dc536cf349..096030843315a 100644 --- a/impeller/runtime_stage/runtime_stage_playground.cc +++ b/impeller/runtime_stage/runtime_stage_playground.cc @@ -9,6 +9,7 @@ #include "flutter/fml/make_copyable.h" #include "flutter/testing/testing.h" #include "impeller/renderer/shader_library.h" +#include "impeller/renderer/shader_types.h" namespace impeller { @@ -34,7 +35,8 @@ bool RuntimeStagePlayground::RegisterStage(const RuntimeStage& stage) { auto future = registration.get_future(); auto library = GetContext()->GetShaderLibrary(); GetContext()->GetShaderLibrary()->RegisterFunction( - stage.GetEntrypoint(), stage.GetShaderStage(), stage.GetCodeMapping(), + stage.GetEntrypoint(), ToShaderStage(stage.GetShaderStage()), + stage.GetCodeMapping(), fml::MakeCopyable([reg = std::move(registration)](bool result) mutable { reg.set_value(result); })); diff --git a/impeller/runtime_stage/runtime_stage_unittests.cc b/impeller/runtime_stage/runtime_stage_unittests.cc index 66e3a83da6fb8..108d59d0742e5 100644 --- a/impeller/runtime_stage/runtime_stage_unittests.cc +++ b/impeller/runtime_stage/runtime_stage_unittests.cc @@ -13,6 +13,7 @@ #include "impeller/renderer/pipeline_descriptor.h" #include "impeller/renderer/pipeline_library.h" #include "impeller/renderer/shader_library.h" +#include "impeller/renderer/shader_types.h" #include "impeller/runtime_stage/runtime_stage.h" #include "impeller/runtime_stage/runtime_stage_playground.h" @@ -29,7 +30,7 @@ TEST(RuntimeStageTest, CanReadValidBlob) { ASSERT_GT(fixture->GetSize(), 0u); RuntimeStage stage(std::move(fixture)); ASSERT_TRUE(stage.IsValid()); - ASSERT_EQ(stage.GetShaderStage(), ShaderStage::kFragment); + ASSERT_EQ(stage.GetShaderStage(), RuntimeShaderStage::kFragment); } TEST(RuntimeStageTest, CanRejectInvalidBlob) { @@ -199,9 +200,9 @@ TEST_P(RuntimeStageTest, CanRegisterStage) { auto future = registration.get_future(); auto library = GetContext()->GetShaderLibrary(); library->RegisterFunction( - stage.GetEntrypoint(), // - stage.GetShaderStage(), // - stage.GetCodeMapping(), // + stage.GetEntrypoint(), // + ToShaderStage(stage.GetShaderStage()), // + stage.GetCodeMapping(), // fml::MakeCopyable([reg = std::move(registration)](bool result) mutable { reg.set_value(result); })); diff --git a/impeller/runtime_stage/runtime_types.h b/impeller/runtime_stage/runtime_types.h new file mode 100644 index 0000000000000..7b3f06b0504bf --- /dev/null +++ b/impeller/runtime_stage/runtime_types.h @@ -0,0 +1,50 @@ +// 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. + +#pragma once + +#include +#include + +namespace impeller { + +enum RuntimeUniformType { + kBoolean, + kSignedByte, + kUnsignedByte, + kSignedShort, + kUnsignedShort, + kSignedInt, + kUnsignedInt, + kSignedInt64, + kUnsignedInt64, + kHalfFloat, + kFloat, + kDouble, + kSampledImage, +}; + +enum class RuntimeShaderStage { + kVertex, + kFragment, + kCompute, + kTessellationControl, + kTessellationEvaluation, +}; + +struct RuntimeUniformDimensions { + size_t rows = 0; + size_t cols = 0; +}; + +struct RuntimeUniformDescription { + std::string name; + size_t location = 0u; + RuntimeUniformType type = RuntimeUniformType::kFloat; + RuntimeUniformDimensions dimensions; + size_t bit_width; + size_t array_elements; +}; + +} // namespace impeller diff --git a/impeller/tools/impeller.gni b/impeller/tools/impeller.gni index 89b37a427bbd4..ba7ee027eae4d 100644 --- a/impeller/tools/impeller.gni +++ b/impeller/tools/impeller.gni @@ -10,9 +10,6 @@ declare_args() { # Whether playgrounds are enabled for unit tests. impeller_enable_playground = false - # Whether Impeller is supported on the platform. - impeller_supports_platform = true - # Whether the Metal backend is enabled. impeller_enable_metal = is_mac || is_ios @@ -36,11 +33,6 @@ declare_args() { } declare_args() { - # Whether Impeller shaders are supported on the platform. - impeller_shaders_supports_platform = - impeller_enable_metal || impeller_enable_opengles || - impeller_enable_vulkan - # Whether Impeller supports rendering on the platform. impeller_supports_rendering = impeller_enable_metal || impeller_enable_opengles || @@ -61,7 +53,7 @@ template("impeller_component") { group(target_name) { not_needed(invoker, "*") } - } else if (impeller_supports_platform) { + } else { target_type = "source_set" if (defined(invoker.target_type)) { target_type = invoker.target_type @@ -96,10 +88,6 @@ template("impeller_component") { cflags_objcc += flutter_cflags_objcc_arc } } - } else { - group(target_name) { - not_needed(invoker, "*") - } } } @@ -586,7 +574,7 @@ template("impeller_shaders") { } } - if (!impeller_shaders_supports_platform) { + if (!impeller_supports_rendering) { not_needed(invoker, "*") } diff --git a/impeller/typographer/backends/skia/text_render_context_skia.cc b/impeller/typographer/backends/skia/text_render_context_skia.cc index fb9bf2df3395f..5a0123ab88e5b 100644 --- a/impeller/typographer/backends/skia/text_render_context_skia.cc +++ b/impeller/typographer/backends/skia/text_render_context_skia.cc @@ -248,7 +248,7 @@ std::shared_ptr TextRenderContextSkia::CreateGlyphAtlas( // --------------------------------------------------------------------------- // Step 6: Upload the atlas as a texture. // --------------------------------------------------------------------------- - auto texture = UploadGlyphTextureAtlas(GetContext()->GetTransientsAllocator(), + auto texture = UploadGlyphTextureAtlas(GetContext()->GetResourceAllocator(), bitmap, atlas_size); if (!texture) { return nullptr; diff --git a/lib/snapshot/BUILD.gn b/lib/snapshot/BUILD.gn index 2c05b6cc4ffde..618640efe53be 100644 --- a/lib/snapshot/BUILD.gn +++ b/lib/snapshot/BUILD.gn @@ -20,6 +20,10 @@ group("generate_snapshot_bins") { if (host_os == "mac" && target_os == "mac") { deps += [ ":create_macos_gen_snapshots" ] } + if (target_cpu == "x64" || target_cpu == "arm64") { + deps += + [ "//third_party/dart/runtime/bin:analyze_snapshot($host_toolchain)" ] + } } compiled_action("generate_snapshot_bin") { diff --git a/lib/ui/BUILD.gn b/lib/ui/BUILD.gn index 81433a10e754e..d9f1a893cfbdb 100644 --- a/lib/ui/BUILD.gn +++ b/lib/ui/BUILD.gn @@ -146,6 +146,7 @@ source_set("ui") { deps = [ "//flutter/assets", "//flutter/common", + "//flutter/common/graphics", "//flutter/display_list", "//flutter/fml", "//flutter/impeller/runtime_stage", @@ -187,6 +188,7 @@ if (enable_unittests) { dart_main = "fixtures/ui_test.dart" fixtures = [ "fixtures/DashInNooglerHat.jpg", + "fixtures/DashInNooglerHat%20WithSpace.jpg", "fixtures/Horizontal.jpg", "fixtures/Horizontal.png", "fixtures/hello_loop_2.gif", @@ -229,6 +231,7 @@ if (enable_unittests) { "painting/vertices_unittests.cc", "semantics/semantics_update_builder_unittests.cc", "window/platform_configuration_unittests.cc", + "window/platform_message_response_dart_unittests.cc", "window/pointer_data_packet_converter_unittests.cc", ] diff --git a/lib/ui/compositing.dart b/lib/ui/compositing.dart index 0ed1581569aac..843be9631a0e5 100644 --- a/lib/ui/compositing.dart +++ b/lib/ui/compositing.dart @@ -38,6 +38,7 @@ class Scene extends NativeFieldWrapperClass1 { external String? _toImageSync(int width, int height, _Image outImage); /// Creates a raster image representation of the current state of the scene. + /// /// This is a slow operation that is performed on a background thread. /// /// Callers must dispose the [Image] when they are done with it. If the result @@ -240,7 +241,7 @@ class SceneBuilder extends NativeFieldWrapperClass1 { // // The key is the layer used. The value is the description of what the layer // is used for, e.g. "pushOpacity" or "addRetained". - Map _usedLayers = {}; + final Map _usedLayers = {}; // In debug mode checks that the `layer` is only used once in a given scene. bool _debugCheckUsedOnce(EngineLayer layer, String usage) { @@ -625,6 +626,10 @@ class SceneBuilder extends NativeFieldWrapperClass1 { /// {@macro dart.ui.sceneBuilder.oldLayerVsRetained} /// /// See [pop] for details about the operation stack, and [Clip] for different clip modes. + @Deprecated( + 'Use a clip and canvas operations directly (See RenderPhysicalModel). ' + 'This feature was deprecated after v3.1.0-0.0.pre.', + ) PhysicalShapeEngineLayer pushPhysicalShape({ required Path path, required double elevation, diff --git a/lib/ui/compositing/scene.cc b/lib/ui/compositing/scene.cc index 734a67ef2481c..5a8601b90effe 100644 --- a/lib/ui/compositing/scene.cc +++ b/lib/ui/compositing/scene.cc @@ -5,6 +5,7 @@ #include "flutter/lib/ui/compositing/scene.h" #include "flutter/fml/trace_event.h" +#include "flutter/lib/ui/painting/display_list_deferred_image_gpu.h" #include "flutter/lib/ui/painting/image.h" #include "flutter/lib/ui/painting/picture.h" #include "flutter/lib/ui/ui_dart_state.h" @@ -42,7 +43,7 @@ Scene::Scene(std::shared_ptr rootLayer, ->get_window(0) ->viewport_metrics(); - layer_tree_ = std::make_unique( + layer_tree_ = std::make_shared( SkISize::Make(viewport_metrics.physical_width, viewport_metrics.physical_height), static_cast(viewport_metrics.device_pixel_ratio)); @@ -69,12 +70,7 @@ Dart_Handle Scene::toImageSync(uint32_t width, return tonic::ToDart("Scene did not contain a layer tree."); } - auto picture = layer_tree_->Flatten(SkRect::MakeWH(width, height)); - if (!picture) { - return tonic::ToDart("Could not flatten scene into a layer tree."); - } - - Picture::RasterizeToImageSync(picture, width, height, raw_image_handle); + Scene::RasterizeToImage(width, height, raw_image_handle); return Dart_Null(); } @@ -87,15 +83,32 @@ Dart_Handle Scene::toImage(uint32_t width, return tonic::ToDart("Scene did not contain a layer tree."); } - auto picture = layer_tree_->Flatten(SkRect::MakeWH(width, height)); - if (!picture) { - return tonic::ToDart("Could not flatten scene into a layer tree."); - } + return Picture::RasterizeLayerTreeToImage(std::move(layer_tree_), width, + height, raw_image_callback); +} - return Picture::RasterizeToImage(picture, width, height, raw_image_callback); +void Scene::RasterizeToImage(uint32_t width, + uint32_t height, + Dart_Handle raw_image_handle) { + auto* dart_state = UIDartState::Current(); + if (!dart_state) { + return; + } + auto unref_queue = dart_state->GetSkiaUnrefQueue(); + auto snapshot_delegate = dart_state->GetSnapshotDelegate(); + auto raster_task_runner = dart_state->GetTaskRunners().GetRasterTaskRunner(); + + auto image = CanvasImage::Create(); + const SkImageInfo image_info = SkImageInfo::Make( + width, height, kRGBA_8888_SkColorType, kPremul_SkAlphaType); + auto dl_image = DlDeferredImageGPU::MakeFromLayerTree( + image_info, std::move(layer_tree_), std::move(snapshot_delegate), + std::move(raster_task_runner), std::move(unref_queue)); + image->set_image(dl_image); + image->AssociateWithDartWrapper(raw_image_handle); } -std::unique_ptr Scene::takeLayerTree() { +std::shared_ptr Scene::takeLayerTree() { return std::move(layer_tree_); } diff --git a/lib/ui/compositing/scene.h b/lib/ui/compositing/scene.h index 1cf95aad4ec43..aca797836fcb1 100644 --- a/lib/ui/compositing/scene.h +++ b/lib/ui/compositing/scene.h @@ -26,7 +26,7 @@ class Scene : public RefCountedDartWrappable { bool checkerboardRasterCacheImages, bool checkerboardOffscreenLayers); - std::unique_ptr takeLayerTree(); + std::shared_ptr takeLayerTree(); Dart_Handle toImageSync(uint32_t width, uint32_t height, @@ -34,17 +34,26 @@ class Scene : public RefCountedDartWrappable { Dart_Handle toImage(uint32_t width, uint32_t height, - Dart_Handle image_callback); + Dart_Handle raw_image_handle); void dispose(); private: - explicit Scene(std::shared_ptr rootLayer, - uint32_t rasterizerTracingThreshold, - bool checkerboardRasterCacheImages, - bool checkerboardOffscreenLayers); - - std::unique_ptr layer_tree_; + Scene(std::shared_ptr rootLayer, + uint32_t rasterizerTracingThreshold, + bool checkerboardRasterCacheImages, + bool checkerboardOffscreenLayers); + + void RasterizeToImage(uint32_t width, + uint32_t height, + Dart_Handle raw_image_handle); + + // This is a shared_ptr to support flattening the layer tree from the UI + // thread onto the raster thread - allowing access to the texture registry + // required to render TextureLayers. + // + // No longer valid after calling `takeLayerTree`. + std::shared_ptr layer_tree_; }; } // namespace flutter diff --git a/lib/ui/compositing/scene_builder.cc b/lib/ui/compositing/scene_builder.cc index 5406c67675c02..9a543c284a18a 100644 --- a/lib/ui/compositing/scene_builder.cc +++ b/lib/ui/compositing/scene_builder.cc @@ -139,8 +139,8 @@ void SceneBuilder::pushOpacity(Dart_Handle layer_handle, void SceneBuilder::pushColorFilter(Dart_Handle layer_handle, const ColorFilter* color_filter, fml::RefPtr oldLayer) { - auto layer = std::make_shared( - color_filter->filter()->skia_object()); + auto layer = + std::make_shared(color_filter->filter()); PushLayer(layer); EngineLayer::MakeRetained(layer_handle, layer); @@ -152,8 +152,8 @@ void SceneBuilder::pushColorFilter(Dart_Handle layer_handle, void SceneBuilder::pushImageFilter(Dart_Handle layer_handle, const ImageFilter* image_filter, fml::RefPtr oldLayer) { - auto layer = std::make_shared( - image_filter->filter()->skia_object()); + auto layer = + std::make_shared(image_filter->filter()); PushLayer(layer); EngineLayer::MakeRetained(layer_handle, layer); @@ -189,8 +189,7 @@ void SceneBuilder::pushShaderMask(Dart_Handle layer_handle, maskRectBottom); auto sampling = ImageFilter::SamplingFromIndex(filterQualityIndex); auto layer = std::make_shared( - shader->shader(sampling)->skia_object(), rect, - static_cast(blendMode)); + shader->shader(sampling), rect, static_cast(blendMode)); PushLayer(layer); EngineLayer::MakeRetained(layer_handle, layer); diff --git a/lib/ui/dart_ui.cc b/lib/ui/dart_ui.cc index 61d35c5cd2a5c..c669b920576be 100644 --- a/lib/ui/dart_ui.cc +++ b/lib/ui/dart_ui.cc @@ -185,6 +185,7 @@ typedef CanvasPath Path; V(ImageFilter, initColorFilter, 2) \ V(ImageFilter, initComposeFilter, 3) \ V(ImageFilter, initMatrix, 3) \ + V(ImageShader, dispose, 1) \ V(ImageShader, initWithImage, 6) \ V(ImmutableBuffer, dispose, 1) \ V(ImmutableBuffer, length, 1) \ diff --git a/lib/ui/fixtures/DashInNooglerHat%20WithSpace.jpg b/lib/ui/fixtures/DashInNooglerHat%20WithSpace.jpg new file mode 100644 index 0000000000000..488fdb4d5215c Binary files /dev/null and b/lib/ui/fixtures/DashInNooglerHat%20WithSpace.jpg differ diff --git a/lib/ui/fixtures/shaders/general_shaders/BUILD.gn b/lib/ui/fixtures/shaders/general_shaders/BUILD.gn index 548b3b0499df5..640823715e8f2 100644 --- a/lib/ui/fixtures/shaders/general_shaders/BUILD.gn +++ b/lib/ui/fixtures/shaders/general_shaders/BUILD.gn @@ -11,10 +11,12 @@ if (enable_unittests) { "blue_green_sampler.frag", "children_and_uniforms.frag", "functions.frag", + "no_builtin_redefinition.frag", "no_uniforms.frag", "simple.frag", "uniforms.frag", "uniforms_sorted.frag", + "uniform_arrays.frag", ] group("general_shaders") { diff --git a/lib/ui/fixtures/shaders/general_shaders/no_builtin_redefinition.frag b/lib/ui/fixtures/shaders/general_shaders/no_builtin_redefinition.frag new file mode 100644 index 0000000000000..4e74fe27b86b2 --- /dev/null +++ b/lib/ui/fixtures/shaders/general_shaders/no_builtin_redefinition.frag @@ -0,0 +1,38 @@ +#version 320 es + +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +precision highp float; + +layout ( location = 0 ) out vec4 oColor; + +layout ( location = 0 ) uniform float a; // should be 1.0 + +float saturate(float x) { + return clamp(x, 0.0, 1.0); +} + +float addA(float x) { + return x + a; +} + +vec2 pairWithA(float x) { + return vec2(x, a); +} + +vec3 composedFunction(float x) { + return vec3(addA(x), pairWithA(x)); +} + +float multiParam(float x, float y, float z) { + return x * y * z * a; +} + +void main() { + float x = saturate(addA(0.0)); // x = 0 + 1; + vec3 v3 = composedFunction(x); // v3 = vec3(2, 1, 1); + x = multiParam(v3.x, v3.y, v3.z); // x = 2 * 1 * 1 * 1; + oColor = vec4(0.0, x / 2.0, 0.0, 1.0); // vec4(0, 1, 0, 1); +} diff --git a/lib/ui/fixtures/shaders/general_shaders/uniform_arrays.frag b/lib/ui/fixtures/shaders/general_shaders/uniform_arrays.frag new file mode 100644 index 0000000000000..64878088a9f9d --- /dev/null +++ b/lib/ui/fixtures/shaders/general_shaders/uniform_arrays.frag @@ -0,0 +1,51 @@ +#version 100 core + +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +precision highp float; + +layout ( location = 0 ) out vec4 oColor; + +layout ( location = 1 ) uniform float floatArray[2]; +layout ( location = 3 ) uniform vec2 vec2Array[2]; +layout ( location = 7 ) uniform vec3 vec3Array[2]; +layout ( location = 13 ) uniform mat2 mat2Array[2]; +layout ( location = 21 ) uniform float float2dArray[2][2]; + +void main() { + vec4 badColor = vec4(1.0, 0, 0, 1.0); + vec4 goodColor = vec4(0, 1.0, 0, 1.0); + + // The test populates the uniforms with strictly increasing values, so if + // out-of-order values are read out of the uniforms, then the bad color that + // causes the test to fail is returned. + if (floatArray[0] >= floatArray[1] || + floatArray[1] >= vec2Array[0].x || + vec2Array[0].x >= vec2Array[0].y || + vec2Array[0].y >= vec2Array[1].x || + vec2Array[1].x >= vec2Array[1].y || + vec2Array[1].y >= vec3Array[0].x || + vec3Array[0].x >= vec3Array[0].y || + vec3Array[0].y >= vec3Array[0].z || + vec3Array[0].z >= vec3Array[1].x || + vec3Array[1].x >= vec3Array[1].y || + vec3Array[1].y >= vec3Array[1].z || + vec3Array[1].z >= mat2Array[0][0][0] || + mat2Array[0][0][0] >= mat2Array[0][0][1] || + mat2Array[0][0][1] >= mat2Array[0][1][0] || + mat2Array[0][1][0] >= mat2Array[0][1][1] || + mat2Array[0][1][1] >= mat2Array[1][0][0] || + mat2Array[1][0][0] >= mat2Array[1][0][1] || + mat2Array[1][0][1] >= mat2Array[1][1][0] || + mat2Array[1][1][0] >= mat2Array[1][1][1] || + mat2Array[1][1][1] >= float2dArray[0][0] || + float2dArray[0][0] >= float2dArray[0][1] || + float2dArray[0][1] >= float2dArray[1][0] || + float2dArray[1][0] >= float2dArray[1][1]) { + oColor = badColor; + } else { + oColor = goodColor; + } +} diff --git a/lib/ui/fixtures/shaders/general_shaders/uniforms_sorted.frag b/lib/ui/fixtures/shaders/general_shaders/uniforms_sorted.frag index e7e191fe49e96..3c97433f004ba 100644 --- a/lib/ui/fixtures/shaders/general_shaders/uniforms_sorted.frag +++ b/lib/ui/fixtures/shaders/general_shaders/uniforms_sorted.frag @@ -37,10 +37,6 @@ const float PI_ROTATE_LEFT = PI * -0.0078125; const float ONE_THIRD = 1./3.; const vec2 TURBULENCE_SCALE = vec2(0.8); -float saturate(float x) { - return clamp(x, 0.0, 1.0); -} - float triangle_noise(highp vec2 n) { n = fract(n * vec2(5.3987, 5.4421)); n += dot(n.yx, n.xy + vec2(21.5351, 14.3137)); @@ -65,7 +61,7 @@ float soft_circle(vec2 uv, vec2 xy, float radius, float blur) { float soft_ring(vec2 uv, vec2 xy, float radius, float thickness, float blur) { float circle_outer = soft_circle(uv, xy, radius + thickness, blur); float circle_inner = soft_circle(uv, xy, max(radius - thickness, 0.0), blur); - return saturate(circle_outer - circle_inner); + return clamp(circle_outer - circle_inner, 0.0, 1.0); } float circle_grid(vec2 resolution, vec2 p, vec2 xy, vec2 rotation, float cell_diameter) { @@ -82,7 +78,7 @@ float sparkle(vec2 uv, float t) { s += threshold(n + sin(PI * (t + 0.35)), 0.1, 0.15); s += threshold(n + sin(PI * (t + 0.7)), 0.2, 0.25); s += threshold(n + sin(PI * (t + 1.05)), 0.3, 0.35); - return saturate(s) * 0.55; + return clamp(s, 0.0, 1.0) * 0.55; } float turbulence(vec2 uv) { @@ -91,7 +87,7 @@ float turbulence(vec2 uv) { float g2 = circle_grid(TURBULENCE_SCALE, uv_scale, u_circle2, u_rotation2, 0.2); float g3 = circle_grid(TURBULENCE_SCALE, uv_scale, u_circle3, u_rotation3, 0.275); float v = (g1 * g1 + g2 - g3) * 0.5; - return saturate(0.45 + 0.8 * v); + return clamp(0.45 + 0.8 * v, 0.0, 1.0); } void main() { diff --git a/lib/ui/fixtures/ui_test.dart b/lib/ui/fixtures/ui_test.dart index b0c7cdb697a1c..55281721199be 100644 --- a/lib/ui/fixtures/ui_test.dart +++ b/lib/ui/fixtures/ui_test.dart @@ -228,6 +228,21 @@ void frameCallback(_Image, int) { print('called back'); } +@pragma('vm:entry-point') +void platformMessageResponseTest() { + _callPlatformMessageResponseDart((ByteData? result) { + if (result is UnmodifiableByteDataView && + result.lengthInBytes == 100) { + _finishCallResponse(true); + } else { + _finishCallResponse(false); + } + }); +} + +void _callPlatformMessageResponseDart(void Function(ByteData? result) callback) native 'CallPlatformMessageResponseDart'; +void _finishCallResponse(bool didPass) native 'FinishCallResponse'; + @pragma('vm:entry-point') void messageCallback(dynamic data) {} diff --git a/lib/ui/hash_codes.dart b/lib/ui/hash_codes.dart index 94afd258d3568..ea0f5b9162ba7 100644 --- a/lib/ui/hash_codes.dart +++ b/lib/ui/hash_codes.dart @@ -4,6 +4,7 @@ part of dart.ui; // Examples can assume: +// // ignore_for_file: deprecated_member_use // int foo = 0; // int bar = 0; // List quux = []; @@ -43,7 +44,7 @@ class _Jenkins { /// For example: /// /// ```dart -/// int get hashCode => hashValues(foo, bar, hashList(quux), baz); // ignore: deprecated_member_use +/// int get hashCode => hashValues(foo, bar, hashList(quux), baz); /// ``` /// /// ## Deprecation diff --git a/lib/ui/hooks.dart b/lib/ui/hooks.dart index 5a14b4e3f8035..1cdbf90baeebf 100644 --- a/lib/ui/hooks.dart +++ b/lib/ui/hooks.dart @@ -5,7 +5,6 @@ part of dart.ui; @pragma('vm:entry-point') -// ignore: unused_element void _updateWindowMetrics( Object id, double devicePixelRatio, diff --git a/lib/ui/painting.dart b/lib/ui/painting.dart index bd5e93f5e62ab..aee511203699d 100644 --- a/lib/ui/painting.dart +++ b/lib/ui/painting.dart @@ -1090,6 +1090,14 @@ enum Clip { /// Most APIs on [Canvas] take a [Paint] object to describe the style /// to use for that operation. class Paint { + /// Constructs an empty [Paint] object with all fields initialized to + /// their defaults. + Paint() { + if (enableDithering) { + _dither = true; + } + } + // Paint objects are encoded in two buffers: // // * _data is binary data in four-byte fields, each of which is either a @@ -1150,14 +1158,6 @@ 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. /// @@ -1398,6 +1398,12 @@ class Paint { return _objects?[_kShaderIndex] as Shader?; } set shader(Shader? value) { + assert(() { + if (value is ImageShader) { + assert(!value.debugDisposed, 'Attempted to set a disposed shader to $this'); + } + return true; + }()); _ensureObjectsInitialized()[_kShaderIndex] = value; } @@ -1878,7 +1884,7 @@ class _Image extends NativeFieldWrapperClass1 { @FfiNative)>('Image::dispose') external void _dispose(); - Set _handles = {}; + final Set _handles = {}; @override String toString() => '[$width\u00D7$height]'; @@ -2350,9 +2356,6 @@ class Path extends NativeFieldWrapperClass1 { @pragma('vm:entry-point') Path() { _constructor(); } - @FfiNative('Path::Create') - external void _constructor(); - /// Avoids creating a new native backing for the path for methods that will /// create it later, such as [Path.from], [shift] and [transform]. Path._(); @@ -2367,6 +2370,9 @@ class Path extends NativeFieldWrapperClass1 { return clonedPath; } + @FfiNative('Path::Create') + external void _constructor(); + @FfiNative, Handle)>('Path::clone') external void _clone(Path outPath); @@ -2848,7 +2854,7 @@ class PathMetricIterator implements Iterator { PathMetricIterator._(this._pathMeasure) : assert(_pathMeasure != null); PathMetric? _pathMetric; - _PathMeasure _pathMeasure; + final _PathMeasure _pathMeasure; @override PathMetric get current { @@ -2895,6 +2901,10 @@ class PathMetric { contourIndex = _measure.currentContourIndex; /// Return the total length of the current contour. + /// + /// The length may be calculated from an approximation of the geometry + /// originally added. For this reason, it is not recommended to rely on + /// this property for mathematically correct lengths of common shapes. final double length; /// Whether the contour is closed. @@ -3576,41 +3586,31 @@ class _ComposeImageFilter implements ImageFilter { /// ImageFilter, because we want ImageFilter to be efficiently comparable, so that /// widgets can check for ImageFilter equality to avoid repainting. class _ImageFilter extends NativeFieldWrapperClass1 { - @FfiNative('ImageFilter::Create') - external void _constructor(); - /// Creates an image filter that applies a Gaussian blur. _ImageFilter.blur(_GaussianBlurImageFilter filter) : assert(filter != null), - creator = filter { // ignore: prefer_initializing_formals + creator = filter { _constructor(); _initBlur(filter.sigmaX, filter.sigmaY, filter.tileMode.index); } - @FfiNative, Double, Double, Int32)>('ImageFilter::initBlur', isLeaf: true) - external void _initBlur(double sigmaX, double sigmaY, int tileMode); - /// Creates an image filter that dilates each input pixel's channel values /// to the max value within the given radii along the x and y axes. _ImageFilter.dilate(_DilateImageFilter filter) : assert(filter != null), - creator = filter { // ignore: prefer_initializing_formals + creator = filter { _constructor(); _initDilate(filter.radiusX, filter.radiusY); } - @FfiNative, Double, Double)>('ImageFilter::initDilate', isLeaf: true) - external void _initDilate(double radiusX, double radiusY); /// Create a filter that erodes each input pixel's channel values /// to the minimum channel value within the given radii along the x and y axes. _ImageFilter.erode(_ErodeImageFilter filter) : assert(filter != null), - creator = filter { // ignore: prefer_initializing_formals + creator = filter { _constructor(); _initErode(filter.radiusX, filter.radiusY); } - @FfiNative, Double, Double)>('ImageFilter::initErode', isLeaf: true) - external void _initErode(double radiusX, double radiusY); /// Creates an image filter that applies a matrix transformation. /// @@ -3618,7 +3618,7 @@ class _ImageFilter extends NativeFieldWrapperClass1 { /// when used with [BackdropFilter] would magnify the background image. _ImageFilter.matrix(_MatrixImageFilter filter) : assert(filter != null), - creator = filter { // ignore: prefer_initializing_formals + creator = filter { if (filter.data.length != 16) { throw ArgumentError('"matrix4" must have 16 entries.'); } @@ -3626,31 +3626,43 @@ class _ImageFilter extends NativeFieldWrapperClass1 { _initMatrix(filter.data, filter.filterQuality.index); } - @FfiNative, Handle, Int32)>('ImageFilter::initMatrix') - external void _initMatrix(Float64List matrix4, int filterQuality); - /// Converts a color filter to an image filter. _ImageFilter.fromColorFilter(ColorFilter filter) : assert(filter != null), - creator = filter { // ignore: prefer_initializing_formals + creator = filter { _constructor(); final _ColorFilter? nativeFilter = filter._toNativeColorFilter(); _initColorFilter(nativeFilter); } - @FfiNative, Pointer)>('ImageFilter::initColorFilter') - external void _initColorFilter(_ColorFilter? colorFilter); - /// Composes `_innerFilter` with `_outerFilter`. _ImageFilter.composed(_ComposeImageFilter filter) : assert(filter != null), - creator = filter { // ignore: prefer_initializing_formals + creator = filter { _constructor(); final _ImageFilter nativeFilterInner = filter.innerFilter._toNativeImageFilter(); final _ImageFilter nativeFilterOuter = filter.outerFilter._toNativeImageFilter(); _initComposed(nativeFilterOuter, nativeFilterInner); } + @FfiNative('ImageFilter::Create') + external void _constructor(); + + @FfiNative, Double, Double, Int32)>('ImageFilter::initBlur', isLeaf: true) + external void _initBlur(double sigmaX, double sigmaY, int tileMode); + + @FfiNative, Double, Double)>('ImageFilter::initDilate', isLeaf: true) + external void _initDilate(double radiusX, double radiusY); + + @FfiNative, Double, Double)>('ImageFilter::initErode', isLeaf: true) + external void _initErode(double radiusX, double radiusY); + + @FfiNative, Handle, Int32)>('ImageFilter::initMatrix') + external void _initMatrix(Float64List matrix4, int filterQuality); + + @FfiNative, Pointer)>('ImageFilter::initColorFilter') + external void _initColorFilter(_ColorFilter? colorFilter); + @FfiNative, Pointer, Pointer)>('ImageFilter::initComposeFilter') external void _initComposed(_ImageFilter outerFilter, _ImageFilter innerFilter); @@ -3803,9 +3815,6 @@ Float32List _encodeTwoPoints(Offset pointA, Offset pointB) { /// * [Gradient](https://api.flutter.dev/flutter/painting/Gradient-class.html), the class in the [painting] library. /// class Gradient extends Shader { - @FfiNative('Gradient::Create') - external void _constructor(); - /// Creates a linear gradient from `from` to `to`. /// /// If `colorStops` is provided, `colorStops[i]` is a number from 0.0 to 1.0 @@ -3849,9 +3858,6 @@ class Gradient extends Shader { _initLinear(endPointsBuffer, colorsBuffer, colorStopsBuffer, tileMode.index, matrix4); } - @FfiNative, Handle, Handle, Handle, Int32, Handle)>('Gradient::initLinear') - external void _initLinear(Float32List endPoints, Int32List colors, Float32List? colorStops, int tileMode, Float64List? matrix4); - /// Creates a radial gradient centered at `center` that ends at `radius` /// distance from the center. /// @@ -3912,30 +3918,6 @@ class Gradient extends Shader { } } - @FfiNative, Double, Double, Double, Handle, Handle, Int32, Handle)>('Gradient::initRadial') - external void _initRadial( - double centerX, - double centerY, - double radius, - Int32List colors, - Float32List? colorStops, - int tileMode, - Float64List? matrix4); - - @FfiNative, Double, Double, Double, Double, Double, Double, Handle, Handle, Int32, Handle)>( - 'Gradient::initTwoPointConical') - external void _initConical( - double startX, - double startY, - double startRadius, - double endX, - double endY, - double endRadius, - Int32List colors, - Float32List? colorStops, - int tileMode, - Float64List? matrix4); - /// Creates a sweep gradient centered at `center` that starts at `startAngle` /// and ends at `endAngle`. /// @@ -3986,6 +3968,36 @@ class Gradient extends Shader { _initSweep(center.dx, center.dy, colorsBuffer, colorStopsBuffer, tileMode.index, startAngle, endAngle, matrix4); } + @FfiNative('Gradient::Create') + external void _constructor(); + + @FfiNative, Handle, Handle, Handle, Int32, Handle)>('Gradient::initLinear') + external void _initLinear(Float32List endPoints, Int32List colors, Float32List? colorStops, int tileMode, Float64List? matrix4); + + @FfiNative, Double, Double, Double, Handle, Handle, Int32, Handle)>('Gradient::initRadial') + external void _initRadial( + double centerX, + double centerY, + double radius, + Int32List colors, + Float32List? colorStops, + int tileMode, + Float64List? matrix4); + + @FfiNative, Double, Double, Double, Double, Double, Double, Handle, Handle, Int32, Handle)>( + 'Gradient::initTwoPointConical') + external void _initConical( + double startX, + double startY, + double startRadius, + double endX, + double endY, + double endRadius, + Int32List colors, + Float32List? colorStops, + int tileMode, + Float64List? matrix4); + @FfiNative, Double, Double, Handle, Handle, Int32, Double, Double, Handle)>('Gradient::initSweep') external void _initSweep( double centerX, @@ -4025,6 +4037,7 @@ class ImageShader extends Shader { FilterQuality? filterQuality, }) : assert(image != null), // image is checked on the engine side + assert(!image.debugDisposed), assert(tmx != null), assert(tmy != null), assert(matrix4 != null), @@ -4044,6 +4057,41 @@ class ImageShader extends Shader { @FfiNative, Pointer, Int32, Int32, Int32, Handle)>('ImageShader::initWithImage') external String? _initWithImage(_Image image, int tmx, int tmy, int filterQualityIndex, Float64List matrix4); + + bool _debugDisposed = false; + + /// Whether [dispose] has been called. + /// + /// This must only be used when asserts are enabled. Otherwise, it will throw. + bool get debugDisposed { + late bool disposed; + assert(() { + disposed = _debugDisposed; + return true; + }()); + return disposed; + } + + /// Release the resources used by this object. The object is no longer usable + /// after this method is called. + /// + /// The underlying memory allocated by this object will be retained beyond + /// this call if it is still needed by another object that has not been + /// disposed. For example, an [Picture] that has not been disposed that + /// refers to this [ImageShader] may keep its underlying resources alive. + void dispose() { + assert(() { + assert(!_debugDisposed); + _debugDisposed = true; + return true; + }()); + _dispose(); + } + + /// This can't be a leaf call because the native function calls Dart API + /// (Dart_SetNativeInstanceField). + @FfiNative)>('ImageShader::dispose') + external void _dispose(); } /// An instance of [FragmentProgram] creates [Shader] objects (as used by [Paint.shader]) that run SPIR-V code. @@ -4054,6 +4102,15 @@ class ImageShader extends Shader { /// [A current specification of valid SPIR-V is here.](https://github.com/flutter/engine/blob/main/lib/spirv/README.md) /// class FragmentProgram extends NativeFieldWrapperClass1 { + @pragma('vm:entry-point') + FragmentProgram._fromAsset(String assetKey) { + _constructor(); + final String result = _initFromAsset(assetKey); + if (result.isNotEmpty) { + throw result; // ignore: only_throw_errors + } + } + // TODO(zra): Document custom shaders on the website and add a link to it // here. https://github.com/flutter/flutter/issues/107929. /// Creates a fragment program from the asset with key [assetKey]. @@ -4062,13 +4119,18 @@ class FragmentProgram extends NativeFieldWrapperClass1 { /// compiler. The constructed object should then be reused via the [shader] /// method to create [Shader] objects that can be used by [Shader.paint]. static Future fromAsset(String assetKey) { - final FragmentProgram? program = _shaderRegistry[assetKey]?.target; + // The flutter tool converts all asset keys with spaces into URI + // encoded paths (replacing ' ' with '%20', for example). We perform + // the same encoding here so that users can load assets with the same + // key they have written in the pubspec. + final String encodedKey = Uri(path: Uri.encodeFull(assetKey)).path; + final FragmentProgram? program = _shaderRegistry[encodedKey]?.target; if (program != null) { return Future.value(program); } return Future.microtask(() { - final FragmentProgram program = FragmentProgram._fromAsset(assetKey); - _shaderRegistry[assetKey] = WeakReference(program); + final FragmentProgram program = FragmentProgram._fromAsset(encodedKey); + _shaderRegistry[encodedKey] = WeakReference(program); return program; }); } @@ -4078,18 +4140,9 @@ class FragmentProgram extends NativeFieldWrapperClass1 { // so that the case where an in-use program is requested again can be fast, // but programs that are no longer referenced are not retained because of the // cache. - static Map> _shaderRegistry = + static final Map> _shaderRegistry = >{}; - @pragma('vm:entry-point') - FragmentProgram._fromAsset(String assetKey) { - _constructor(); - final String result = _initFromAsset(assetKey); - if (result.isNotEmpty) { - throw result; // ignore: only_throw_errors - } - } - static void _reinitializeShader(String assetKey) { // If a shader for the assent isn't already registered, then there's no // need to reinitialize it. The new shader will be loaded and initialized @@ -4758,7 +4811,7 @@ class Canvas extends NativeFieldWrapperClass1 { /// canvas.clipPath(Path() /// ..addRect(const Rect.fromLTRB(80, 10, 100, 20)) /// ..addRect(const Rect.fromLTRB(10, 80, 20, 100))); - /// ... + /// // ... /// } /// ``` /// @@ -5241,7 +5294,7 @@ class Canvas extends NativeFieldWrapperClass1 { /// ], null, null, null, paint); /// } /// - /// ... + /// // ... /// } /// ``` /// @@ -5287,7 +5340,7 @@ class Canvas extends NativeFieldWrapperClass1 { /// ], BlendMode.srcIn, null, paint); /// } /// - /// ... + /// // ... /// } /// ``` /// @@ -5425,7 +5478,7 @@ class Canvas extends NativeFieldWrapperClass1 { /// canvas.drawRawAtlas(spriteAtlas, transformList, rectList, null, null, null, paint); /// } /// - /// ... + /// // ... /// } /// ``` /// @@ -5494,7 +5547,7 @@ class Canvas extends NativeFieldWrapperClass1 { /// canvas.drawRawAtlas(spriteAtlas, transformList, rectList, colorList, BlendMode.srcIn, null, paint); /// } /// - /// ... + /// // ... /// } /// ``` /// @@ -5949,9 +6002,14 @@ class ImmutableBuffer extends NativeFieldWrapperClass1 { /// /// Throws an [Exception] if the asset does not exist. static Future fromAsset(String assetKey) { + // The flutter tool converts all asset keys with spaces into URI + // encoded paths (replacing ' ' with '%20', for example). We perform + // the same encoding here so that users can load assets with the same + // key they have written in the pubspec. + final String encodedKey = Uri(path: Uri.encodeFull(assetKey)).path; final ImmutableBuffer instance = ImmutableBuffer._(0); return _futurize((_Callback callback) { - return instance._initFromAsset(assetKey, callback); + return instance._initFromAsset(encodedKey, callback); }).then((int length) => instance.._length = length); } @@ -6010,17 +6068,6 @@ class ImmutableBuffer extends NativeFieldWrapperClass1 { class ImageDescriptor extends NativeFieldWrapperClass1 { ImageDescriptor._(); - /// Creates an image descriptor from encoded data in a supported format. - static Future encoded(ImmutableBuffer buffer) { - final ImageDescriptor descriptor = ImageDescriptor._(); - return _futurize((_Callback callback) { - return descriptor._initEncoded(buffer, callback); - }).then((_) => descriptor); - } - - @FfiNative, Handle)>('ImageDescriptor::initEncoded') - external String? _initEncoded(ImmutableBuffer buffer, _Callback callback); - /// Creates an image descriptor from raw image pixels. /// /// The `pixels` parameter is the pixel data. They are packed in bytes in the @@ -6045,6 +6092,17 @@ class ImageDescriptor extends NativeFieldWrapperClass1 { _initRaw(this, buffer, width, height, rowBytes ?? -1, pixelFormat.index); } + /// Creates an image descriptor from encoded data in a supported format. + static Future encoded(ImmutableBuffer buffer) { + final ImageDescriptor descriptor = ImageDescriptor._(); + return _futurize((_Callback callback) { + return descriptor._initEncoded(buffer, callback); + }).then((_) => descriptor); + } + + @FfiNative, Handle)>('ImageDescriptor::initEncoded') + external String? _initEncoded(ImmutableBuffer buffer, _Callback callback); + @FfiNative('ImageDescriptor::initRaw') external static void _initRaw(ImageDescriptor outDescriptor, ImmutableBuffer buffer, int width, int height, int rowBytes, int pixelFormat); diff --git a/lib/ui/painting/display_list_deferred_image_gpu.cc b/lib/ui/painting/display_list_deferred_image_gpu.cc index 3868cf1f06c0f..14391c2128a74 100644 --- a/lib/ui/painting/display_list_deferred_image_gpu.cc +++ b/lib/ui/painting/display_list_deferred_image_gpu.cc @@ -4,20 +4,58 @@ #include "flutter/lib/ui/painting/display_list_deferred_image_gpu.h" +#include "display_list_deferred_image_gpu.h" +#include "third_party/skia/include/core/SkColorSpace.h" + namespace flutter { -sk_sp DlDeferredImageGPU::Make(SkISize size) { - return sk_sp(new DlDeferredImageGPU(size)); +sk_sp DlDeferredImageGPU::Make( + const SkImageInfo& image_info, + sk_sp display_list, + fml::WeakPtr snapshot_delegate, + fml::RefPtr raster_task_runner, + fml::RefPtr unref_queue) { + return sk_sp(new DlDeferredImageGPU( + ImageWrapper::Make(image_info, std::move(display_list), + std::move(snapshot_delegate), raster_task_runner, + std::move(unref_queue)), + raster_task_runner)); +} + +sk_sp DlDeferredImageGPU::MakeFromLayerTree( + const SkImageInfo& image_info, + std::shared_ptr layer_tree, + fml::WeakPtr snapshot_delegate, + fml::RefPtr raster_task_runner, + fml::RefPtr unref_queue) { + return sk_sp(new DlDeferredImageGPU( + ImageWrapper::MakeFromLayerTree( + image_info, std::move(layer_tree), std::move(snapshot_delegate), + raster_task_runner, std::move(unref_queue)), + raster_task_runner)); } -DlDeferredImageGPU::DlDeferredImageGPU(SkISize size) : size_(size) {} +DlDeferredImageGPU::DlDeferredImageGPU( + std::shared_ptr image_wrapper, + fml::RefPtr raster_task_runner) + : image_wrapper_(std::move(image_wrapper)), + raster_task_runner_(std::move(raster_task_runner)) {} // |DlImage| -DlDeferredImageGPU::~DlDeferredImageGPU() = default; +DlDeferredImageGPU::~DlDeferredImageGPU() { + fml::TaskRunner::RunNowOrPostTask( + raster_task_runner_, [image_wrapper = std::move(image_wrapper_)]() { + if (!image_wrapper) { + return; + } + image_wrapper->Unregister(); + image_wrapper->DeleteTexture(); + }); +} // |DlImage| sk_sp DlDeferredImageGPU::skia_image() const { - return image_; + return image_wrapper_ ? image_wrapper_->CreateSkiaImage() : nullptr; }; // |DlImage| @@ -26,45 +64,158 @@ std::shared_ptr DlDeferredImageGPU::impeller_texture() return nullptr; } +// |DlImage| +bool DlDeferredImageGPU::isOpaque() const { + return image_wrapper_ ? image_wrapper_->image_info().isOpaque() : false; +} + // |DlImage| bool DlDeferredImageGPU::isTextureBacked() const { - if (auto image = skia_image()) { - return image->isTextureBacked(); - } - return false; + return image_wrapper_ ? image_wrapper_->isTextureBacked() : false; } // |DlImage| SkISize DlDeferredImageGPU::dimensions() const { - return size_; + return image_wrapper_ ? image_wrapper_->image_info().dimensions() + : SkISize::MakeEmpty(); } // |DlImage| size_t DlDeferredImageGPU::GetApproximateByteSize() const { - // This call is accessed on the UI thread, and image_ may not be available - // yet. The image is not mipmapped and it's created using N32 pixels, so this - // is safe. - if (size_.isEmpty()) { - return sizeof(this); + return sizeof(this) + (image_wrapper_ + ? image_wrapper_->image_info().computeMinByteSize() + : 0); +} + +std::optional DlDeferredImageGPU::get_error() const { + return image_wrapper_ ? image_wrapper_->get_error() : std::nullopt; +} + +std::shared_ptr +DlDeferredImageGPU::ImageWrapper::Make( + const SkImageInfo& image_info, + sk_sp display_list, + fml::WeakPtr snapshot_delegate, + fml::RefPtr raster_task_runner, + fml::RefPtr unref_queue) { + auto wrapper = std::shared_ptr(new ImageWrapper( + image_info, std::move(display_list), std::move(snapshot_delegate), + std::move(raster_task_runner), std::move(unref_queue))); + wrapper->SnapshotDisplayList(); + return wrapper; +} + +std::shared_ptr +DlDeferredImageGPU::ImageWrapper::MakeFromLayerTree( + const SkImageInfo& image_info, + std::shared_ptr layer_tree, + fml::WeakPtr snapshot_delegate, + fml::RefPtr raster_task_runner, + fml::RefPtr unref_queue) { + auto wrapper = std::shared_ptr( + new ImageWrapper(image_info, nullptr, std::move(snapshot_delegate), + std::move(raster_task_runner), std::move(unref_queue))); + wrapper->SnapshotDisplayList(std::move(layer_tree)); + return wrapper; +} + +DlDeferredImageGPU::ImageWrapper::ImageWrapper( + const SkImageInfo& image_info, + sk_sp display_list, + fml::WeakPtr snapshot_delegate, + fml::RefPtr raster_task_runner, + fml::RefPtr unref_queue) + : image_info_(image_info), + display_list_(std::move(display_list)), + snapshot_delegate_(std::move(snapshot_delegate)), + raster_task_runner_(std::move(raster_task_runner)), + unref_queue_(std::move(unref_queue)) {} + +void DlDeferredImageGPU::ImageWrapper::OnGrContextCreated() { + FML_DCHECK(raster_task_runner_->RunsTasksOnCurrentThread()); + SnapshotDisplayList(); +} + +void DlDeferredImageGPU::ImageWrapper::OnGrContextDestroyed() { + FML_DCHECK(raster_task_runner_->RunsTasksOnCurrentThread()); + + DeleteTexture(); +} + +sk_sp DlDeferredImageGPU::ImageWrapper::CreateSkiaImage() const { + FML_DCHECK(raster_task_runner_->RunsTasksOnCurrentThread()); + + if (texture_.isValid() && context_) { + return SkImage::MakeFromTexture( + context_.get(), texture_, kTopLeft_GrSurfaceOrigin, + image_info_.colorType(), image_info_.alphaType(), + image_info_.refColorSpace()); } - return sizeof(this) + size_.width() * size_.height() * 4; + return image_; } -void DlDeferredImageGPU::set_image(sk_sp image) { - FML_DCHECK(image); - FML_DCHECK(image->dimensions() == size_); - image_ = std::move(image); +bool DlDeferredImageGPU::ImageWrapper::isTextureBacked() const { + return texture_.isValid(); } -void DlDeferredImageGPU::set_error(const std::string& error) { - FML_DCHECK(!image_); - std::scoped_lock lock(error_mutex_); - error_ = std::move(error); +void DlDeferredImageGPU::ImageWrapper::SnapshotDisplayList( + std::shared_ptr layer_tree) { + fml::TaskRunner::RunNowOrPostTask( + raster_task_runner_, + [weak_this = weak_from_this(), layer_tree = std::move(layer_tree)]() { + auto wrapper = weak_this.lock(); + if (!wrapper) { + return; + } + auto snapshot_delegate = wrapper->snapshot_delegate_; + if (!snapshot_delegate) { + return; + } + if (layer_tree) { + auto display_list = + layer_tree->Flatten(SkRect::MakeWH(wrapper->image_info_.width(), + wrapper->image_info_.height()), + snapshot_delegate->GetTextureRegistry(), + snapshot_delegate->GetGrContext()); + wrapper->display_list_ = std::move(display_list); + } + auto result = snapshot_delegate->MakeGpuImage(wrapper->display_list_, + wrapper->image_info_); + if (result->texture.isValid()) { + wrapper->texture_ = result->texture; + wrapper->context_ = std::move(result->context); + wrapper->texture_registry_ = + wrapper->snapshot_delegate_->GetTextureRegistry(); + wrapper->texture_registry_->RegisterContextListener( + reinterpret_cast(wrapper.get()), weak_this); + } else if (result->image) { + wrapper->image_ = std::move(result->image); + } else { + std::scoped_lock lock(wrapper->error_mutex_); + wrapper->error_ = std::move(result->error); + } + }); } -std::optional DlDeferredImageGPU::get_error() const { +std::optional DlDeferredImageGPU::ImageWrapper::get_error() { std::scoped_lock lock(error_mutex_); return error_; } +void DlDeferredImageGPU::ImageWrapper::Unregister() { + if (texture_registry_) { + texture_registry_->UnregisterContextListener( + reinterpret_cast(this)); + } +} + +void DlDeferredImageGPU::ImageWrapper::DeleteTexture() { + if (texture_.isValid()) { + unref_queue_->DeleteTexture(std::move(texture_)); + texture_ = GrBackendTexture(); + } + image_.reset(); + context_.reset(); +} + } // namespace flutter diff --git a/lib/ui/painting/display_list_deferred_image_gpu.h b/lib/ui/painting/display_list_deferred_image_gpu.h index 0cec807fe7a99..5f04eb25d0765 100644 --- a/lib/ui/painting/display_list_deferred_image_gpu.h +++ b/lib/ui/painting/display_list_deferred_image_gpu.h @@ -5,28 +5,53 @@ #ifndef FLUTTER_LIB_UI_PAINTING_DISPLAY_LIST_DEFERRED_IMAGE_GPU_H_ #define FLUTTER_LIB_UI_PAINTING_DISPLAY_LIST_DEFERRED_IMAGE_GPU_H_ +#include #include +#include "flutter/common/graphics/texture.h" +#include "flutter/display_list/display_list.h" #include "flutter/display_list/display_list_image.h" +#include "flutter/flow/layers/layer_tree.h" #include "flutter/flow/skia_gpu_object.h" #include "flutter/fml/macros.h" +#include "flutter/fml/memory/weak_ptr.h" +#include "flutter/lib/ui/io_manager.h" +#include "flutter/lib/ui/snapshot_delegate.h" namespace flutter { class DlDeferredImageGPU final : public DlImage { public: - static sk_sp Make(SkISize size); + static sk_sp Make( + const SkImageInfo& image_info, + sk_sp display_list, + fml::WeakPtr snapshot_delegate, + fml::RefPtr raster_task_runner, + fml::RefPtr unref_queue); + + static sk_sp MakeFromLayerTree( + const SkImageInfo& image_info, + std::shared_ptr layer_tree, + fml::WeakPtr snapshot_delegate, + fml::RefPtr raster_task_runner, + fml::RefPtr unref_queue); // |DlImage| ~DlDeferredImageGPU() override; // |DlImage| // This method is only safe to call from the raster thread. + // Callers must not hold long term references to this image and + // only use it for the immediate painting operation. It must be + // collected on the raster task runner. sk_sp skia_image() const override; // |DlImage| std::shared_ptr impeller_texture() const override; + // |DlImage| + bool isOpaque() const override; + // |DlImage| bool isTextureBacked() const override; @@ -36,12 +61,6 @@ class DlDeferredImageGPU final : public DlImage { // |DlImage| virtual size_t GetApproximateByteSize() const override; - // This method must only be called from the raster thread. - void set_image(sk_sp image); - - // This method is safe to call from any thread. - void set_error(const std::string& error); - // |DlImage| // This method is safe to call from any thread. std::optional get_error() const override; @@ -52,12 +71,72 @@ class DlDeferredImageGPU final : public DlImage { } private: - sk_sp image_; - SkISize size_; - mutable std::mutex error_mutex_; - std::optional error_; - - explicit DlDeferredImageGPU(SkISize size); + class ImageWrapper final : public std::enable_shared_from_this, + public ContextListener { + public: + static std::shared_ptr Make( + const SkImageInfo& image_info, + sk_sp display_list, + fml::WeakPtr snapshot_delegate, + fml::RefPtr raster_task_runner, + fml::RefPtr unref_queue); + + static std::shared_ptr MakeFromLayerTree( + const SkImageInfo& image_info, + std::shared_ptr layer_tree, + fml::WeakPtr snapshot_delegate, + fml::RefPtr raster_task_runner, + fml::RefPtr unref_queue); + + const SkImageInfo image_info() const { return image_info_; } + const GrBackendTexture& texture() const { return texture_; } + bool isTextureBacked() const; + std::optional get_error(); + sk_sp CreateSkiaImage() const; + void Unregister(); + void DeleteTexture(); + + private: + const SkImageInfo image_info_; + sk_sp display_list_; + fml::WeakPtr snapshot_delegate_; + fml::RefPtr raster_task_runner_; + fml::RefPtr unref_queue_; + std::shared_ptr texture_registry_; + + mutable std::mutex error_mutex_; + std::optional error_; + + GrBackendTexture texture_; + sk_sp context_; + // May be used if this image is not texture backed. + sk_sp image_; + + ImageWrapper(const SkImageInfo& image_info, + sk_sp display_list, + fml::WeakPtr snapshot_delegate, + fml::RefPtr raster_task_runner, + fml::RefPtr unref_queue); + + // If a layer tree is provided, it will be flattened during the raster + // thread task spwaned by this method. After being flattened into a display + // list, the image wrapper will be updated to hold this display list and the + // layer tree can be dropped. + void SnapshotDisplayList(std::shared_ptr layer_tree = nullptr); + + // |ContextListener| + void OnGrContextCreated() override; + + // |ContextListener| + void OnGrContextDestroyed() override; + }; + + const std::shared_ptr image_wrapper_; + + fml::RefPtr raster_task_runner_; + + DlDeferredImageGPU(std::shared_ptr image_wrapper, + fml::RefPtr raster_task_runner); FML_DISALLOW_COPY_AND_ASSIGN(DlDeferredImageGPU); }; diff --git a/lib/ui/painting/display_list_image_gpu.cc b/lib/ui/painting/display_list_image_gpu.cc index a9e209a9e14eb..ff52840568cf1 100644 --- a/lib/ui/painting/display_list_image_gpu.cc +++ b/lib/ui/painting/display_list_image_gpu.cc @@ -29,6 +29,14 @@ std::shared_ptr DlImageGPU::impeller_texture() const { return nullptr; } +// |DlImage| +bool DlImageGPU::isOpaque() const { + if (auto image = skia_image()) { + return image->isOpaque(); + } + return false; +} + // |DlImage| bool DlImageGPU::isTextureBacked() const { if (auto image = skia_image()) { diff --git a/lib/ui/painting/display_list_image_gpu.h b/lib/ui/painting/display_list_image_gpu.h index 9631a333c0473..dbc7febc465ce 100644 --- a/lib/ui/painting/display_list_image_gpu.h +++ b/lib/ui/painting/display_list_image_gpu.h @@ -24,6 +24,9 @@ class DlImageGPU final : public DlImage { // |DlImage| std::shared_ptr impeller_texture() const override; + // |DlImage| + bool isOpaque() const override; + // |DlImage| bool isTextureBacked() const override; diff --git a/lib/ui/painting/fragment_program.cc b/lib/ui/painting/fragment_program.cc index a20e813167350..e55c09f3eac94 100644 --- a/lib/ui/painting/fragment_program.cc +++ b/lib/ui/painting/fragment_program.cc @@ -65,6 +65,9 @@ std::string FragmentProgram::initFromAsset(std::string asset_name) { size_t size = uniform_description.dimensions.rows * uniform_description.dimensions.cols * uniform_description.bit_width / 8u; + if (uniform_description.array_elements > 0) { + size *= uniform_description.array_elements; + } other_uniforms_bytes += size; } } @@ -109,7 +112,8 @@ fml::RefPtr FragmentProgram::shader(Dart_Handle shader, uniform_floats[i] = uniforms[i]; } uniforms.Release(); - std::vector> sk_samplers(sampler_shaders.size()); + std::vector> dl_samplers( + sampler_shaders.size()); for (size_t i = 0; i < sampler_shaders.size(); i++) { DlImageSampling sampling = DlImageSampling::kNearestNeighbor; ImageShader* image_shader = sampler_shaders[i]; @@ -119,13 +123,14 @@ fml::RefPtr FragmentProgram::shader(Dart_Handle shader, // contain a value to be used if the developer did not specify a preference // when they constructed the ImageShader, so we will use kNearest which is // the default filterQuality in a Paint object. - sk_samplers[i] = image_shader->shader(sampling)->skia_object(); + dl_samplers[i] = image_shader->shader(sampling); uniform_floats[uniform_count + 2 * i] = image_shader->width(); uniform_floats[uniform_count + 2 * i + 1] = image_shader->height(); } - auto sk_shader = runtime_effect_->makeShader( - std::move(uniform_data), sk_samplers.data(), sk_samplers.size()); - return FragmentShader::Create(shader, std::move(sk_shader)); + return FragmentShader::Create( + shader, + DlColorSource::MakeRuntimeEffect(runtime_effect_, std::move(dl_samplers), + std::move(uniform_data))); } void FragmentProgram::Create(Dart_Handle wrapper) { diff --git a/lib/ui/painting/fragment_shader.cc b/lib/ui/painting/fragment_shader.cc index c7fa991bf07e1..39cee53482bed 100644 --- a/lib/ui/painting/fragment_shader.cc +++ b/lib/ui/painting/fragment_shader.cc @@ -34,15 +34,17 @@ std::shared_ptr FragmentShader::shader( return source_; } -fml::RefPtr FragmentShader::Create(Dart_Handle dart_handle, - sk_sp shader) { +fml::RefPtr FragmentShader::Create( + Dart_Handle dart_handle, + std::shared_ptr shader) { auto fragment_shader = fml::MakeRefCounted(std::move(shader)); fragment_shader->AssociateWithDartWrapper(dart_handle); return fragment_shader; } -FragmentShader::FragmentShader(sk_sp shader) - : source_(DlColorSource::From(shader)) {} +FragmentShader::FragmentShader( + std::shared_ptr shader) + : source_(std::move(shader)) {} FragmentShader::~FragmentShader() = default; diff --git a/lib/ui/painting/fragment_shader.h b/lib/ui/painting/fragment_shader.h index 3936f4475300f..f3aa48a156577 100644 --- a/lib/ui/painting/fragment_shader.h +++ b/lib/ui/painting/fragment_shader.h @@ -25,15 +25,16 @@ class FragmentShader : public Shader { public: ~FragmentShader() override; - static fml::RefPtr Create(Dart_Handle dart_handle, - sk_sp shader); + static fml::RefPtr Create( + Dart_Handle dart_handle, + std::shared_ptr shader); std::shared_ptr shader(DlImageSampling) override; private: - explicit FragmentShader(sk_sp shader); + explicit FragmentShader(std::shared_ptr shader); - std::shared_ptr source_; + std::shared_ptr source_; }; } // namespace flutter diff --git a/lib/ui/painting/image.cc b/lib/ui/painting/image.cc index 91a6c6eea7a08..c0c8f47beada6 100644 --- a/lib/ui/painting/image.cc +++ b/lib/ui/painting/image.cc @@ -40,15 +40,9 @@ void CanvasImage::dispose() { } size_t CanvasImage::GetAllocationSize() const { - auto size = sizeof(this); - if (image_) { - size += image_->GetApproximateByteSize(); - } - // The VM will assert if we set a value larger than or close to - // std::numeric_limits::max(). - // https://github.com/dart-lang/sdk/issues/49332 - return std::clamp( - size, static_cast(0), - static_cast(std::numeric_limits::max() / 10)); + // We don't actually want Dart's GC to use the size of this object to make GC + // decisions, as it is generally both created and disposed in the framework. + // This is similar to why we do not report the sizes of engine layers. + return sizeof(*this); } } // namespace flutter diff --git a/lib/ui/painting/image_decoder_impeller.cc b/lib/ui/painting/image_decoder_impeller.cc index 72c3cba4b471c..29b43314cfd79 100644 --- a/lib/ui/painting/image_decoder_impeller.cc +++ b/lib/ui/painting/image_decoder_impeller.cc @@ -58,7 +58,8 @@ static std::optional ToPixelFormat(SkColorType type) { std::shared_ptr ImageDecoderImpeller::DecompressTexture( ImageDescriptor* descriptor, - SkISize target_size) { + SkISize target_size, + impeller::ISize max_texture_size) { TRACE_EVENT0("impeller", __FUNCTION__); if (!descriptor) { FML_DLOG(ERROR) << "Invalid descriptor."; @@ -70,6 +71,10 @@ std::shared_ptr ImageDecoderImpeller::DecompressTexture( << "Uncompressed images are not implemented in Impeller yet."; return nullptr; } + target_size.set(std::min(static_cast(max_texture_size.width), + target_size.width()), + std::min(static_cast(max_texture_size.height), + target_size.height())); const SkISize source_size = descriptor->image_info().dimensions(); auto decode_size = descriptor->get_scaled_dimensions(std::max( @@ -149,7 +154,7 @@ static sk_sp UploadTexture(std::shared_ptr context, texture_descriptor.format = pixel_format.value(); texture_descriptor.size = {image_info.width(), image_info.height()}; - auto texture = context->GetPermanentsAllocator()->CreateTexture( + auto texture = context->GetResourceAllocator()->CreateTexture( impeller::StorageMode::kHostVisible, texture_descriptor); if (!texture) { FML_DLOG(ERROR) << "Could not create Impeller texture."; @@ -200,8 +205,12 @@ void ImageDecoderImpeller::Decode(fml::RefPtr descriptor, io_runner = runners_.GetIOTaskRunner(), // result // ]() { + auto max_size_supported = + context->GetResourceAllocator()->GetMaxTextureSizeSupported(); + // Always decompress on the concurrent runner. - auto bitmap = DecompressTexture(raw_descriptor, target_size); + auto bitmap = + DecompressTexture(raw_descriptor, target_size, max_size_supported); if (!bitmap) { result(nullptr); return; diff --git a/lib/ui/painting/image_decoder_impeller.h b/lib/ui/painting/image_decoder_impeller.h index c5d38c135491d..921419f11ef5b 100644 --- a/lib/ui/painting/image_decoder_impeller.h +++ b/lib/ui/painting/image_decoder_impeller.h @@ -9,6 +9,7 @@ #include "flutter/fml/macros.h" #include "flutter/lib/ui/painting/image_decoder.h" +#include "impeller/geometry/size.h" namespace impeller { class Context; @@ -33,7 +34,8 @@ class ImageDecoderImpeller final : public ImageDecoder { static std::shared_ptr DecompressTexture( ImageDescriptor* descriptor, - SkISize target_size); + SkISize target_size, + impeller::ISize max_texture_size); private: using FutureContext = std::shared_future>; diff --git a/lib/ui/painting/image_decoder_unittests.cc b/lib/ui/painting/image_decoder_unittests.cc index 6f143ace9ed50..658ec8d97864e 100644 --- a/lib/ui/painting/image_decoder_unittests.cc +++ b/lib/ui/painting/image_decoder_unittests.cc @@ -503,10 +503,14 @@ TEST(ImageDecoderTest, VerifySimpleDecoding) { SkISize::Make(6, 2)); #if IMPELLER_SUPPORTS_RENDERING - ASSERT_EQ(ImageDecoderImpeller::DecompressTexture(descriptor.get(), - SkISize::Make(6, 2)) + ASSERT_EQ(ImageDecoderImpeller::DecompressTexture( + descriptor.get(), SkISize::Make(6, 2), {100, 100}) ->dimensions(), SkISize::Make(6, 2)); + ASSERT_EQ(ImageDecoderImpeller::DecompressTexture( + descriptor.get(), SkISize::Make(60, 20), {10, 10}) + ->dimensions(), + SkISize::Make(10, 10)); #endif // IMPELLER_SUPPORTS_RENDERING } diff --git a/lib/ui/painting/image_encoding.cc b/lib/ui/painting/image_encoding.cc index b3149f80d60bc..1e28774932910 100644 --- a/lib/ui/painting/image_encoding.cc +++ b/lib/ui/painting/image_encoding.cc @@ -60,21 +60,6 @@ void InvokeDataCallback(std::unique_ptr callback, DartInvoke(callback->value(), {dart_data}); } -static void ConvertGpuImageToRaster( - sk_sp dl_image, - std::function)> encode_task, - fml::RefPtr raster_task_runner) { - fml::TaskRunner::RunNowOrPostTask( - raster_task_runner, [dl_image, encode_task = std::move(encode_task)]() { - auto image = dl_image->skia_image(); - if (image == nullptr) { - encode_task(nullptr); - return; - } - encode_task(image->makeRasterImage()); - }); -} - void ConvertImageToRaster( sk_sp dl_image, std::function)> encode_task, @@ -83,43 +68,48 @@ void ConvertImageToRaster( fml::WeakPtr resource_context, fml::WeakPtr snapshot_delegate, const std::shared_ptr& is_gpu_disabled_sync_switch) { - auto image = dl_image->skia_image(); - - // Check validity of the image. - if (image == nullptr) { - FML_LOG(ERROR) << "Image was null."; - encode_task(nullptr); - return; - } + // If the owning_context is kRaster, we can't access it on this task runner. + if (dl_image->owning_context() != DlImage::OwningContext::kRaster) { + auto image = dl_image->skia_image(); + + // Check validity of the image. + if (image == nullptr) { + FML_LOG(ERROR) << "Image was null."; + encode_task(nullptr); + return; + } - auto dimensions = image->dimensions(); + auto dimensions = image->dimensions(); - if (dimensions.isEmpty()) { - FML_LOG(ERROR) << "Image dimensions were empty."; - encode_task(nullptr); - return; - } + 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; - } + 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; + 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 raster thread // to prevent concurrent usage of the image on both the IO and raster threads. - raster_task_runner->PostTask([image, encode_task = std::move(encode_task), + raster_task_runner->PostTask([dl_image, encode_task = std::move(encode_task), resource_context, snapshot_delegate, - io_task_runner, is_gpu_disabled_sync_switch]() { - if (!snapshot_delegate) { + io_task_runner, is_gpu_disabled_sync_switch, + raster_task_runner]() { + auto image = dl_image->skia_image(); + if (!image || !snapshot_delegate) { io_task_runner->PostTask( [encode_task = std::move(encode_task)]() mutable { encode_task(nullptr); @@ -132,8 +122,9 @@ void ConvertImageToRaster( io_task_runner->PostTask([image, encode_task = std::move(encode_task), raster_image = std::move(raster_image), - resource_context, - is_gpu_disabled_sync_switch]() mutable { + resource_context, is_gpu_disabled_sync_switch, + owning_context = dl_image->owning_context(), + raster_task_runner]() 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, @@ -142,6 +133,9 @@ void ConvertImageToRaster( image, resource_context, is_gpu_disabled_sync_switch); } encode_task(raster_image); + if (owning_context == DlImage::OwningContext::kRaster) { + raster_task_runner->PostTask([image = std::move(image)]() {}); + } }); }); } @@ -233,7 +227,9 @@ void EncodeImageAndInvokeDataCallback( [callback = std::move(callback)](sk_sp encoded) mutable { InvokeDataCallback(std::move(callback), std::move(encoded)); }); - + // The static leak checker gets confused by the use of fml::MakeCopyable in + // EncodeImage. + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) 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); @@ -244,17 +240,9 @@ void EncodeImageAndInvokeDataCallback( }; FML_DCHECK(image); - switch (image->owning_context()) { - case DlImage::OwningContext::kRaster: - ConvertGpuImageToRaster(std::move(image), encode_task, - raster_task_runner); - break; - case DlImage::OwningContext::kIO: - ConvertImageToRaster(std::move(image), encode_task, raster_task_runner, - io_task_runner, resource_context, snapshot_delegate, - is_gpu_disabled_sync_switch); - break; - } + ConvertImageToRaster(std::move(image), encode_task, raster_task_runner, + io_task_runner, resource_context, snapshot_delegate, + is_gpu_disabled_sync_switch); } } // namespace @@ -277,6 +265,8 @@ Dart_Handle EncodeImage(CanvasImage* canvas_image, const auto& task_runners = UIDartState::Current()->GetTaskRunners(); + // The static leak checker gets confused by the use of fml::MakeCopyable. + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) task_runners.GetIOTaskRunner()->PostTask(fml::MakeCopyable( [callback = std::move(callback), image = canvas_image->image(), image_format, ui_task_runner = task_runners.GetUITaskRunner(), diff --git a/lib/ui/painting/image_encoding_unittests.cc b/lib/ui/painting/image_encoding_unittests.cc index 7b6d0eb8c1965..22dd425ffc21d 100644 --- a/lib/ui/painting/image_encoding_unittests.cc +++ b/lib/ui/painting/image_encoding_unittests.cc @@ -125,6 +125,7 @@ TEST_F(ShellTest, EncodeImageAccessesSyncSwitch) { image_handle, tonic::DartWrappable::kPeerIndex, &peer); ASSERT_FALSE(Dart_IsError(result)); CanvasImage* canvas_image = reinterpret_cast(peer); + ASSERT_EQ(canvas_image->GetAllocationSize(), sizeof(*canvas_image)); int64_t format = -1; result = Dart_IntegerToInt64(format_handle, &format); diff --git a/lib/ui/painting/image_shader.cc b/lib/ui/painting/image_shader.cc index 589cc35daa37e..bd00fd1766e78 100644 --- a/lib/ui/painting/image_shader.cc +++ b/lib/ui/painting/image_shader.cc @@ -32,17 +32,7 @@ Dart_Handle ImageShader::initWithImage(CanvasImage* image, return ToDart("ImageShader constructor called with non-genuine Image."); } - if (image->image()->owning_context() != DlImage::OwningContext::kIO) { - // TODO(dnfield): it should be possible to support this - // https://github.com/flutter/flutter/issues/105085 - return ToDart("ImageShader constructor with GPU image is not supported."); - } - - auto raw_sk_image = image->image()->skia_image(); - if (!raw_sk_image) { - return ToDart("ImageShader constructor with Impeller is not supported."); - } - sk_image_ = UIDartState::CreateGPUObject(std::move(raw_sk_image)); + image_ = image->image(); tonic::Float64List matrix4(matrix_handle); SkMatrix local_matrix = ToSkMatrix(matrix4); matrix4.Release(); @@ -51,7 +41,7 @@ Dart_Handle ImageShader::initWithImage(CanvasImage* image, sampling_is_locked_ ? ImageFilter::SamplingFromIndex(filter_quality_index) : DlImageSampling::kLinear; cached_shader_ = UIDartState::CreateGPUObject(sk_make_sp( - sk_image_.skia_object(), ToDl(tmx), ToDl(tmy), sampling, &local_matrix)); + image_, ToDl(tmx), ToDl(tmy), sampling, &local_matrix)); return Dart_Null(); } @@ -72,11 +62,17 @@ std::shared_ptr ImageShader::shader(DlImageSampling sampling) { } int ImageShader::width() { - return sk_image_.skia_object()->width(); + return image_->width(); } int ImageShader::height() { - return sk_image_.skia_object()->height(); + return image_->height(); +} + +void ImageShader::dispose() { + cached_shader_.reset(); + image_.reset(); + ClearDartWrapper(); } ImageShader::ImageShader() = default; diff --git a/lib/ui/painting/image_shader.h b/lib/ui/painting/image_shader.h index 3cbf9ad8ea95c..d130c5d53c390 100644 --- a/lib/ui/painting/image_shader.h +++ b/lib/ui/painting/image_shader.h @@ -35,10 +35,12 @@ class ImageShader : public Shader { int width(); int height(); + void dispose(); + private: ImageShader(); - flutter::SkiaGPUObject sk_image_; + sk_sp image_; bool sampling_is_locked_; flutter::SkiaGPUObject cached_shader_; diff --git a/lib/ui/painting/multi_frame_codec.cc b/lib/ui/painting/multi_frame_codec.cc index 892dc7649e57a..0e949df080669 100644 --- a/lib/ui/painting/multi_frame_codec.cc +++ b/lib/ui/painting/multi_frame_codec.cc @@ -169,6 +169,8 @@ void MultiFrameCodec::State::GetNextFrameAndInvokeCallback( } nextFrameIndex_ = (nextFrameIndex_ + 1) % frameCount_; + // The static leak checker gets confused by the use of fml::MakeCopyable. + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) ui_task_runner->PostTask(fml::MakeCopyable([callback = std::move(callback), image = std::move(image), duration, trace_id]() mutable { diff --git a/lib/ui/painting/paint.cc b/lib/ui/painting/paint.cc index 23b07b25d51bb..9749c220f2ed0 100644 --- a/lib/ui/painting/paint.cc +++ b/lib/ui/painting/paint.cc @@ -95,10 +95,18 @@ const SkPaint* Paint::paint(SkPaint& paint) const { Dart_Handle shader = values[kShaderIndex]; if (!Dart_IsNull(shader)) { - Shader* decoded = tonic::DartConverter::FromDart(shader); - auto sampling = - ImageFilter::SamplingFromIndex(uint_data[kFilterQualityIndex]); - paint.setShader(decoded->shader(sampling)->skia_object()); + if (Shader* decoded = tonic::DartConverter::FromDart(shader)) { + auto sampling = + ImageFilter::SamplingFromIndex(uint_data[kFilterQualityIndex]); + auto color_source = decoded->shader(sampling); + // TODO(dnfield): Remove this restriction. + // This currently is only used by paragraph code. Once SkParagraph does + // not need to take an SkPaint, we won't be restricted in this way + // because we will not need to access the shader on the UI task runner. + if (color_source->owning_context() != DlImage::OwningContext::kRaster) { + paint.setShader(color_source->skia_object()); + } + } } Dart_Handle color_filter = values[kColorFilterIndex]; @@ -220,10 +228,13 @@ bool Paint::sync_to(DisplayListBuilder* builder, if (Dart_IsNull(shader)) { builder->setColorSource(nullptr); } else { - Shader* decoded = tonic::DartConverter::FromDart(shader); - auto sampling = - ImageFilter::SamplingFromIndex(uint_data[kFilterQualityIndex]); - builder->setColorSource(decoded->shader(sampling).get()); + if (Shader* decoded = tonic::DartConverter::FromDart(shader)) { + auto sampling = + ImageFilter::SamplingFromIndex(uint_data[kFilterQualityIndex]); + builder->setColorSource(decoded->shader(sampling).get()); + } else { + builder->setColorSource(nullptr); + } } } diff --git a/lib/ui/painting/picture.cc b/lib/ui/painting/picture.cc index 2869c3fc779e7..c79c5c08a3688 100644 --- a/lib/ui/painting/picture.cc +++ b/lib/ui/painting/picture.cc @@ -59,29 +59,20 @@ void Picture::RasterizeToImageSync(sk_sp display_list, uint32_t height, Dart_Handle raw_image_handle) { auto* dart_state = UIDartState::Current(); + if (!dart_state) { + return; + } auto unref_queue = dart_state->GetSkiaUnrefQueue(); auto snapshot_delegate = dart_state->GetSnapshotDelegate(); auto raster_task_runner = dart_state->GetTaskRunners().GetRasterTaskRunner(); auto image = CanvasImage::Create(); - auto dl_image = DlDeferredImageGPU::Make(SkISize::Make(width, height)); + const SkImageInfo image_info = SkImageInfo::Make( + width, height, kRGBA_8888_SkColorType, kPremul_SkAlphaType); + auto dl_image = DlDeferredImageGPU::Make( + image_info, std::move(display_list), std::move(snapshot_delegate), + std::move(raster_task_runner), std::move(unref_queue)); image->set_image(dl_image); - - fml::TaskRunner::RunNowOrPostTask( - raster_task_runner, - [snapshot_delegate, unref_queue, dl_image = std::move(dl_image), - display_list = std::move(display_list)]() { - sk_sp sk_image; - std::string error; - std::tie(sk_image, error) = snapshot_delegate->MakeGpuImage( - display_list, dl_image->dimensions()); - if (sk_image) { - dl_image->set_image(std::move(sk_image)); - } else { - dl_image->set_error(std::move(error)); - } - }); - image->AssociateWithDartWrapper(raw_image_handle); } @@ -104,11 +95,21 @@ Dart_Handle Picture::RasterizeToImage(sk_sp display_list, Dart_Handle raw_image_callback) { return RasterizeToImage( [display_list](SkCanvas* canvas) { display_list->RenderTo(canvas); }, - width, height, raw_image_callback); + nullptr, width, height, raw_image_callback); +} + +Dart_Handle Picture::RasterizeLayerTreeToImage( + std::shared_ptr layer_tree, + uint32_t width, + uint32_t height, + Dart_Handle raw_image_callback) { + return RasterizeToImage(nullptr, std::move(layer_tree), width, height, + raw_image_callback); } Dart_Handle Picture::RasterizeToImage( std::function draw_callback, + std::shared_ptr layer_tree, uint32_t width, uint32_t height, Dart_Handle raw_image_callback) { @@ -136,6 +137,8 @@ Dart_Handle Picture::RasterizeToImage( auto picture_bounds = SkISize::Make(width, height); auto ui_task = + // The static leak checker gets confused by the use of fml::MakeCopyable. + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) fml::MakeCopyable([image_callback = std::move(image_callback), unref_queue](sk_sp raster_image) mutable { auto dart_state = image_callback->dart_state().lock(); @@ -165,10 +168,25 @@ Dart_Handle Picture::RasterizeToImage( // Kick things off on the raster rask runner. fml::TaskRunner::RunNowOrPostTask( - raster_task_runner, [ui_task_runner, snapshot_delegate, draw_callback, - picture_bounds, ui_task] { - sk_sp raster_image = snapshot_delegate->MakeRasterSnapshot( - draw_callback, picture_bounds); + raster_task_runner, + [ui_task_runner, snapshot_delegate, draw_callback, picture_bounds, + ui_task, layer_tree = std::move(layer_tree)] { + sk_sp raster_image; + if (layer_tree) { + auto display_list = layer_tree->Flatten( + SkRect::MakeWH(picture_bounds.width(), picture_bounds.height()), + snapshot_delegate->GetTextureRegistry(), + snapshot_delegate->GetGrContext()); + + raster_image = snapshot_delegate->MakeRasterSnapshot( + [display_list](SkCanvas* canvas) { + display_list->RenderTo(canvas); + }, + picture_bounds); + } else { + raster_image = snapshot_delegate->MakeRasterSnapshot(draw_callback, + picture_bounds); + } fml::TaskRunner::RunNowOrPostTask( ui_task_runner, diff --git a/lib/ui/painting/picture.h b/lib/ui/painting/picture.h index 1cb390a182138..77fda55884c3d 100644 --- a/lib/ui/painting/picture.h +++ b/lib/ui/painting/picture.h @@ -6,6 +6,7 @@ #define FLUTTER_LIB_UI_PAINTING_PICTURE_H_ #include "flutter/display_list/display_list.h" +#include "flutter/flow/layers/layer_tree.h" #include "flutter/flow/skia_gpu_object.h" #include "flutter/lib/ui/dart_wrapper.h" #include "flutter/lib/ui/painting/image.h" @@ -51,8 +52,19 @@ class Picture : public RefCountedDartWrappable { uint32_t height, Dart_Handle raw_image_callback); + static Dart_Handle RasterizeLayerTreeToImage( + std::shared_ptr layer_tree, + uint32_t width, + uint32_t height, + Dart_Handle raw_image_callback); + + // Callers may provide either a draw_callback (which should reference a + // display list) or a layer tree. If a layer tree is provided, it will be + // flattened on the raster thread. In this case the draw callback will be + // ignored. static Dart_Handle RasterizeToImage( std::function draw_callback, + std::shared_ptr layer_tree, uint32_t width, uint32_t height, Dart_Handle raw_image_callback); diff --git a/lib/ui/platform_dispatcher.dart b/lib/ui/platform_dispatcher.dart index 8f3fa8295a8b2..9a3d7e9f6b73e 100644 --- a/lib/ui/platform_dispatcher.dart +++ b/lib/ui/platform_dispatcher.dart @@ -69,6 +69,10 @@ const double _kUnsetGestureSetting = -1.0; // See embedder.cc::kFlutterKeyDataChannel for more information. const String _kFlutterKeyDataChannel = 'flutter/keydata'; +@pragma('vm:entry-point') +ByteData? _wrapUnmodifiableByteData(ByteData? byteData) => + byteData == null ? null : UnmodifiableByteDataView(byteData); + /// Platform event dispatcher singleton. /// /// The most basic interface to the host operating system's interface. @@ -141,10 +145,10 @@ class PlatformDispatcher { /// /// If any of their configurations change, [onMetricsChanged] will be called. Iterable get views => _views.values; - Map _views = {}; + final Map _views = {}; // A map of opaque platform view identifiers to view configurations. - Map _viewConfigurations = {}; + final Map _viewConfigurations = {}; /// A callback that is invoked whenever the [ViewConfiguration] of any of the /// [views] changes. @@ -1454,8 +1458,6 @@ class FrameTiming { ]); } - static final int _dataLength = FramePhase.values.length + _FrameTimingInfo.values.length; - /// Construct [FrameTiming] with raw timestamps in microseconds. /// /// List [timestamps] must have the same number of elements as @@ -1463,8 +1465,9 @@ class FrameTiming { /// /// This constructor is usually only called by the Flutter engine, or a test. /// To get the [FrameTiming] of your app, see [PlatformDispatcher.onReportTimings]. - FrameTiming._(this._data) - : assert(_data.length == _dataLength); + FrameTiming._(this._data) : assert(_data.length == _dataLength); + + static final int _dataLength = FramePhase.values.length + _FrameTimingInfo.values.length; /// This is a raw timestamp in microseconds from some epoch. The epoch in all /// [FrameTiming] is the same, but it may not match [DateTime]'s epoch. diff --git a/lib/ui/plugins.dart b/lib/ui/plugins.dart index 24948f4b2217b..50543d2acc0bc 100644 --- a/lib/ui/plugins.dart +++ b/lib/ui/plugins.dart @@ -45,9 +45,9 @@ class PluginUtilities { // extended directly. factory PluginUtilities._() => throw UnsupportedError('Namespace'); - static Map _forwardCache = + static final Map _forwardCache = {}; - static Map _backwardCache = + static final Map _backwardCache = {}; /// Get a handle to a named top-level or static callback function which can diff --git a/lib/ui/snapshot_delegate.h b/lib/ui/snapshot_delegate.h index 7c87a01c865d9..3b59ffed9fb23 100644 --- a/lib/ui/snapshot_delegate.h +++ b/lib/ui/snapshot_delegate.h @@ -7,18 +7,57 @@ #include +#include "flutter/common/graphics/texture.h" #include "flutter/display_list/display_list.h" #include "third_party/skia/include/core/SkImage.h" #include "third_party/skia/include/core/SkPicture.h" #include "third_party/skia/include/core/SkPromiseImageTexture.h" #include "third_party/skia/include/gpu/GrContextThreadSafeProxy.h" +#include "third_party/skia/include/gpu/GrDirectContext.h" + namespace flutter { class SnapshotDelegate { public: - virtual std::pair, std::string> MakeGpuImage( + struct GpuImageResult { + GpuImageResult(const GrBackendTexture& p_texture, + sk_sp p_context, + sk_sp p_image = nullptr, + const std::string& p_error = "") + : texture(p_texture), + context(std::move(p_context)), + image(std::move(p_image)), + error(p_error) {} + + const GrBackendTexture texture; + // If texture.isValid() == true, this is a pointer to a GrDirectContext that + // can be used to create an image from the texture. + sk_sp context; + // If MakeGpuImage could not create a GPU resident image, a raster copy + // is available in this member and texture.isValid() is false. + sk_sp image; + + // A non-empty string containing an error message if neither a GPU backed + // texture nor a raster backed image could be created. + const std::string error; + }; + + //---------------------------------------------------------------------------- + /// @brief Gets the registry of external textures currently in use by the + /// rasterizer. These textures may be updated at a cadence + /// different from that of the Flutter application. When an + /// external texture is referenced in the Flutter layer tree, that + /// texture is composited within the Flutter layer tree. + /// + /// @return A pointer to the external texture registry. + /// + virtual std::shared_ptr GetTextureRegistry() = 0; + + virtual GrDirectContext* GetGrContext() = 0; + + virtual std::unique_ptr MakeGpuImage( sk_sp display_list, - SkISize picture_size) = 0; + const SkImageInfo& image_info) = 0; virtual sk_sp MakeRasterSnapshot( std::function draw_callback, diff --git a/lib/ui/text.dart b/lib/ui/text.dart index c9e68c42b6979..3627aa28ac367 100644 --- a/lib/ui/text.dart +++ b/lib/ui/text.dart @@ -14,37 +14,40 @@ enum FontStyle { /// The thickness of the glyphs used to draw the text class FontWeight { - const FontWeight._(this.index); + const FontWeight._(this.index, this.value); /// The encoded integer value of this font weight. final int index; + /// The thickness value of this font weight. + final int value; + /// Thin, the least thick - static const FontWeight w100 = FontWeight._(0); + static const FontWeight w100 = FontWeight._(0, 100); /// Extra-light - static const FontWeight w200 = FontWeight._(1); + static const FontWeight w200 = FontWeight._(1, 200); /// Light - static const FontWeight w300 = FontWeight._(2); + static const FontWeight w300 = FontWeight._(2, 300); /// Normal / regular / plain - static const FontWeight w400 = FontWeight._(3); + static const FontWeight w400 = FontWeight._(3, 400); /// Medium - static const FontWeight w500 = FontWeight._(4); + static const FontWeight w500 = FontWeight._(4, 500); /// Semi-bold - static const FontWeight w600 = FontWeight._(5); + static const FontWeight w600 = FontWeight._(5, 600); /// Bold - static const FontWeight w700 = FontWeight._(6); + static const FontWeight w700 = FontWeight._(6, 700); /// Extra-bold - static const FontWeight w800 = FontWeight._(7); + static const FontWeight w800 = FontWeight._(7, 800); /// Black, the most thick - static const FontWeight w900 = FontWeight._(8); + static const FontWeight w900 = FontWeight._(8, 900); /// The default font weight. static const FontWeight normal = w400; @@ -2976,7 +2979,7 @@ class ParagraphBuilder extends NativeFieldWrapperClass1 { /// The scales of the placeholders in the paragraph. List get placeholderScales => _placeholderScales; - List _placeholderScales = []; + final List _placeholderScales = []; final TextLeadingDistribution _defaultLeadingDistribution; /// Applies the given style to the added text until [pop] is called. diff --git a/lib/ui/text/font_collection.cc b/lib/ui/text/font_collection.cc index 3ca89d964dff8..3a5327b6fec03 100644 --- a/lib/ui/text/font_collection.cc +++ b/lib/ui/text/font_collection.cc @@ -45,6 +45,22 @@ void FontCollection::SetupDefaultFontManager( collection_->SetupDefaultFontManager(font_initialization_data); } +// Font manifest yaml format: +// +// flutter: +// fonts: +// - family: Raleway +// fonts: +// - asset: fonts/Raleway-Regular.ttf +// - asset: fonts/Raleway-Italic.ttf +// style: italic +// - family: RobotoMono +// fonts: +// - asset: fonts/RobotoMono-Regular.ttf +// - asset: fonts/RobotoMono-Bold.ttf +// weight: 700 +// +// Structure described in https://docs.flutter.dev/cookbook/design/fonts void FontCollection::RegisterFonts( std::shared_ptr asset_manager) { std::unique_ptr manifest_mapping = @@ -65,8 +81,6 @@ void FontCollection::RegisterFonts( return; } - // Structure described in https://flutter.io/custom-fonts/ - if (!document.IsArray()) { return; } diff --git a/lib/ui/ui.dart b/lib/ui/ui.dart index b61743c7c5087..2695a80916ee4 100644 --- a/lib/ui/ui.dart +++ b/lib/ui/ui.dart @@ -16,10 +16,10 @@ import 'dart:collection' as collection; import 'dart:convert'; import 'dart:developer' as developer; import 'dart:ffi'; -import 'dart:io'; // ignore: unused_import +import 'dart:io'; import 'dart:isolate' show SendPort; import 'dart:math' as math; -import 'dart:nativewrappers'; // ignore: unused_import +import 'dart:nativewrappers'; import 'dart:typed_data'; part 'annotations.dart'; diff --git a/lib/ui/window/platform_message_response_dart.cc b/lib/ui/window/platform_message_response_dart.cc index 39d55f41d736b..66c07c31c36ed 100644 --- a/lib/ui/window/platform_message_response_dart.cc +++ b/lib/ui/window/platform_message_response_dart.cc @@ -17,6 +17,11 @@ static std::atomic platform_message_counter = 1; namespace flutter { namespace { + +void MappingFinalizer(void* isolate_callback_data, void* peer) { + delete static_cast(peer); +} + template void PostCompletion(Callback&& callback, const TaskRunner& ui_task_runner, @@ -63,11 +68,35 @@ PlatformMessageResponseDart::~PlatformMessageResponseDart() { } void PlatformMessageResponseDart::Complete(std::unique_ptr data) { - PostCompletion(std::move(callback_), ui_task_runner_, &is_complete_, channel_, - [data = std::move(data)] { - return tonic::DartByteData::Create(data->GetMapping(), - data->GetSize()); - }); + PostCompletion( + std::move(callback_), ui_task_runner_, &is_complete_, channel_, + [data = std::move(data)]() mutable { + Dart_Handle byte_buffer; + intptr_t size = data->GetSize(); + if (data->GetSize() > tonic::DartByteData::kExternalSizeThreshold) { + const void* mapping = data->GetMapping(); + byte_buffer = Dart_NewUnmodifiableExternalTypedDataWithFinalizer( + /*type=*/Dart_TypedData_kByteData, + /*data=*/mapping, + /*length=*/size, + /*peer=*/data.release(), + /*external_allocation_size=*/size, + /*callback=*/MappingFinalizer); + } else { + Dart_Handle mutable_byte_buffer = + tonic::DartByteData::Create(data->GetMapping(), data->GetSize()); + Dart_Handle ui_lib = Dart_LookupLibrary( + tonic::DartConverter().ToDart("dart:ui")); + FML_DCHECK(!(Dart_IsNull(ui_lib) || Dart_IsError(ui_lib))); + byte_buffer = Dart_Invoke(ui_lib, + tonic::DartConverter().ToDart( + "_wrapUnmodifiableByteData"), + 1, &mutable_byte_buffer); + FML_DCHECK(!(Dart_IsNull(byte_buffer) || Dart_IsError(byte_buffer))); + } + + return byte_buffer; + }); } void PlatformMessageResponseDart::CompleteEmpty() { diff --git a/lib/ui/window/platform_message_response_dart_unittests.cc b/lib/ui/window/platform_message_response_dart_unittests.cc new file mode 100644 index 0000000000000..b4a90db65a71d --- /dev/null +++ b/lib/ui/window/platform_message_response_dart_unittests.cc @@ -0,0 +1,73 @@ +// 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/common/task_runners.h" +#include "flutter/fml/mapping.h" +#include "flutter/fml/synchronization/waitable_event.h" +#include "flutter/lib/ui/window/platform_message_response_dart.h" +#include "flutter/runtime/dart_vm.h" +#include "flutter/shell/common/shell_test.h" +#include "flutter/shell/common/thread_host.h" +#include "flutter/testing/testing.h" + +namespace flutter { +namespace testing { + +TEST_F(ShellTest, PlatformMessageResponseDart) { + bool did_pass = false; + auto message_latch = std::make_shared(); + TaskRunners task_runners("test", // label + GetCurrentTaskRunner(), // platform + CreateNewThread(), // raster + CreateNewThread(), // ui + CreateNewThread() // io + ); + + auto nativeCallPlatformMessageResponseDart = + [ui_task_runner = + task_runners.GetUITaskRunner()](Dart_NativeArguments args) { + auto dart_state = std::make_shared(); + auto response = fml::MakeRefCounted( + tonic::DartPersistentValue(tonic::DartState::Current(), + Dart_GetNativeArgument(args, 0)), + ui_task_runner, "foobar"); + uint8_t* data = static_cast(malloc(100)); + auto mapping = std::make_unique(data, 100); + response->Complete(std::move(mapping)); + }; + + AddNativeCallback("CallPlatformMessageResponseDart", + CREATE_NATIVE_ENTRY(nativeCallPlatformMessageResponseDart)); + + auto nativeFinishCallResponse = [message_latch, + &did_pass](Dart_NativeArguments args) { + did_pass = + tonic::DartConverter::FromDart(Dart_GetNativeArgument(args, 0)); + message_latch->Signal(); + }; + + AddNativeCallback("FinishCallResponse", + CREATE_NATIVE_ENTRY(nativeFinishCallResponse)); + + Settings settings = CreateSettingsForFixture(); + + std::unique_ptr shell = + CreateShell(std::move(settings), std::move(task_runners)); + + ASSERT_TRUE(shell->IsSetup()); + auto configuration = RunConfiguration::InferFromSettings(settings); + configuration.SetEntrypoint("platformMessageResponseTest"); + + shell->RunEngine(std::move(configuration), [](auto result) { + ASSERT_EQ(result, Engine::RunStatus::Success); + }); + + message_latch->Wait(); + + ASSERT_TRUE(did_pass); + DestroyShell(std::move(shell), std::move(task_runners)); +} + +} // namespace testing +} // namespace flutter diff --git a/lib/web_ui/README.md b/lib/web_ui/README.md index 4284463585a3c..f4e87cb31c6eb 100644 --- a/lib/web_ui/README.md +++ b/lib/web_ui/README.md @@ -108,27 +108,6 @@ Changing parameters in the browser lock is effective immediately when running tests locally. To make changes effective on LUCI follow instructions in [Rolling Browsers][#rolling-browsers]. -#### Local testing in Safari using the iOS Simulator - -1. If you haven't already, install Xcode. -2. The iOS version and device type used by web engine tests are specified in - the [browser_lock.yaml][2] file. Install the iOS Simulator version using: - Xcode > Preferences > Components -3. Run `xcrun simctl list devices`. If the simulator you want is not installed - use step 4. -4. Use felt to create a simulator: - -``` -felt create_simulator -``` - -To run tests on ios-safari use the one of the following commands: - -``` -felt test --browser=ios-safari -felt test --browser=ios-safari test/alarm_clock_test.dart -``` - ### Rolling browsers When running tests on LUCI using Chromium, LUCI uses the version of Chromium @@ -226,6 +205,19 @@ directly), follow these steps to roll to the new version: If you have questions, contact the Flutter Web team on Flutter Discord on the #hackers-web-🌍 channel. +### Rolling Noto Font Data + +In order to generate new data for the Noto fallback fonts, you will need +a GoogleFonts API key. Once you have one, run: + +``` +./dev/felt generate-fallback-font-data --key= +``` + +This will generate the file `lib/src/engine/canvaskit/font_fallback_data.dart` with +the latest data from GoogleFonts. This generated file should then be rolled in with +a PR to the engine. + ### Configuration files `browser_lock.yaml` contains the version of browsers we use to test Flutter for diff --git a/lib/web_ui/dev/browser_lock.dart b/lib/web_ui/dev/browser_lock.dart index 08b7a62eba5e6..5439ab648ee60 100644 --- a/lib/web_ui/dev/browser_lock.dart +++ b/lib/web_ui/dev/browser_lock.dart @@ -27,13 +27,11 @@ class BrowserLock { BrowserLock._fromYaml(YamlMap yaml) : chromeLock = ChromeLock._fromYaml(yaml['chrome'] as YamlMap), firefoxLock = FirefoxLock._fromYaml(yaml['firefox'] as YamlMap), - edgeLock = EdgeLock._fromYaml(yaml['edge'] as YamlMap), - safariIosLock = SafariIosLock._fromYaml(yaml['safari_ios'] as YamlMap); + edgeLock = EdgeLock._fromYaml(yaml['edge'] as YamlMap); final ChromeLock chromeLock; final FirefoxLock firefoxLock; final EdgeLock edgeLock; - final SafariIosLock safariIosLock; } class ChromeLock { @@ -68,22 +66,3 @@ class EdgeLock { final String launcherVersion; } - -class SafariIosLock { - SafariIosLock._fromYaml(YamlMap yaml) : - majorVersion = yaml['major_version'] as int, - minorVersion = yaml['minor_version'] as int, - device = yaml['device'] as String, - heightOfHeader = yaml['height_of_header'] as int, - heightOfFooter = yaml['height_of_footer'] as int, - scaleFactor = yaml['scale_factor'] as double; - - final int majorVersion; - final int minorVersion; - final String device; - final int heightOfHeader; - final int heightOfFooter; - final double scaleFactor; - - String get simulatorDescription => '$device with iOS $majorVersion.$minorVersion'; -} diff --git a/lib/web_ui/dev/browser_lock.yaml b/lib/web_ui/dev/browser_lock.yaml index d6f26f21ab262..f816d27932093 100644 --- a/lib/web_ui/dev/browser_lock.yaml +++ b/lib/web_ui/dev/browser_lock.yaml @@ -25,30 +25,3 @@ firefox: edge: launcher_version: '1.2.0.0' - -safari_ios: - # Make sure this version is the same version supported by LUCI macOS bots. - # XCode on these bots will be updated once a year, do not forget to update - # `heightOfHeader` during this time. - major_version: 13 - minor_version: 0 - device: 'iPhone 11' - # `xcrun simctl` command is used to take screenshots. It takes the screenshot - # of the entire simulator. Therefore we need to crop all the parts other than - # the browsers' content. This file must be in sync with the local and LUCI - # versions of macOS, iOS Simulator, and Xcode. - # `heightOfHeader` is the number of pixels taken by the phone's header menu - # and the browsers address bar. - # TODO: https://github.com/flutter/flutter/issues/65672 - height_of_header: 189 - # `heightOfFooter` is the number of pixels taken by the phone's navigation - # menu. - height_of_footer: 250 - # Most of the time tests use a portion of the screen to compare goldens - # instead of the entire screen. This area is reprented by a rectangle - # when taking screenshots. However the rectangle dimensions are in logical - # coordinates. In order to convert these coordinates to coordinates on the - # phone screeen we enlarge or shrink the area by applying a linear - # transformation by a scale_factor (a.k.a. we perform isotropic scaling). - # This value will be differ depending on the phone. - scale_factor: 3.00 diff --git a/lib/web_ui/dev/browser_process.dart b/lib/web_ui/dev/browser_process.dart index a546540f04533..104dafc51c893 100644 --- a/lib/web_ui/dev/browser_process.dart +++ b/lib/web_ui/dev/browser_process.dart @@ -10,25 +10,6 @@ import 'package:stack_trace/stack_trace.dart'; import 'package:typed_data/typed_buffers.dart'; class BrowserProcess { - /// The underlying process. - /// - /// This will fire once the process has started successfully. - Future get _process => _processCompleter.future; - final Completer _processCompleter = Completer(); - - /// Whether [close] has been called. - bool _closed = false; - - /// A future that completes when the browser exits. - /// - /// If there's a problem starting or running the browser, this will complete - /// with an error. - Future get onExit => _onExitCompleter.future; - final Completer _onExitCompleter = Completer(); - - /// Standard IO streams for the underlying browser process. - final List> _ioSubscriptions = >[]; - /// Creates a new browser. /// /// Clients pass in [startBrowser], which asynchronously returns the browser @@ -103,6 +84,25 @@ class BrowserProcess { }); } + /// The underlying process. + /// + /// This will fire once the process has started successfully. + Future get _process => _processCompleter.future; + final Completer _processCompleter = Completer(); + + /// Whether [close] has been called. + bool _closed = false; + + /// A future that completes when the browser exits. + /// + /// If there's a problem starting or running the browser, this will complete + /// with an error. + Future get onExit => _onExitCompleter.future; + final Completer _onExitCompleter = Completer(); + + /// Standard IO streams for the underlying browser process. + final List> _ioSubscriptions = >[]; + /// Kills the browser process. /// /// Returns the same [Future] as [onExit], except that it won't emit diff --git a/lib/web_ui/dev/chrome.dart b/lib/web_ui/dev/chrome.dart index d41cf6102ee5c..18d8a74a85e2d 100644 --- a/lib/web_ui/dev/chrome.dart +++ b/lib/web_ui/dev/chrome.dart @@ -58,11 +58,6 @@ class ChromeEnvironment implements BrowserEnvironment { /// /// Any errors starting or running the process are reported through [onExit]. class Chrome extends Browser { - final BrowserProcess _process; - - @override - final Future remoteDebuggerUrl; - /// Starts a new instance of Chrome open to the given [url], which may be a /// [Uri] or a [String]. factory Chrome(Uri url, BrowserInstallation installation, {bool debug = false}) { @@ -122,6 +117,11 @@ class Chrome extends Browser { Chrome._(this._process, this.remoteDebuggerUrl); + final BrowserProcess _process; + + @override + final Future remoteDebuggerUrl; + @override Future get onExit => _process.onExit; diff --git a/lib/web_ui/dev/chrome_installer.dart b/lib/web_ui/dev/chrome_installer.dart index 565952aab9328..dd3cafec47aed 100644 --- a/lib/web_ui/dev/chrome_installer.dart +++ b/lib/web_ui/dev/chrome_installer.dart @@ -100,17 +100,17 @@ class ChromeInstaller { ); } - static Future latest() async { - final String latestVersion = await fetchLatestChromeVersion(); - return ChromeInstaller(version: latestVersion); - } - ChromeInstaller._({ required this.version, required this.chromeInstallationDir, required this.versionDir, }); + static Future latest() async { + final String latestVersion = await fetchLatestChromeVersion(); + return ChromeInstaller(version: latestVersion); + } + /// Chrome version managed by this installer. final String version; @@ -196,7 +196,8 @@ class ChromeInstaller { stopwatch.stop(); print( - 'INFO: The unzip took ${stopwatch.elapsedMilliseconds ~/ 1000} seconds.'); + 'The unzip took ${stopwatch.elapsedMilliseconds ~/ 1000} seconds.' + ); } else { // We have to unzip into a temporary directory and then copy the files // out because our tests expect the files to be direct children of the diff --git a/lib/web_ui/dev/common.dart b/lib/web_ui/dev/common.dart index addbc68445eda..b73dc7908ceca 100644 --- a/lib/web_ui/dev/common.dart +++ b/lib/web_ui/dev/common.dart @@ -11,7 +11,6 @@ import 'browser_lock.dart'; import 'chrome.dart'; import 'edge.dart'; import 'firefox.dart'; -import 'safari_ios.dart'; import 'safari_macos.dart'; /// The port number for debugging. @@ -231,14 +230,12 @@ const String kChrome = 'chrome'; const String kEdge = 'edge'; const String kFirefox = 'firefox'; const String kSafari = 'safari'; -const String kSafariIos = 'ios-safari'; const List kAllBrowserNames = [ kChrome, kEdge, kFirefox, kSafari, - kSafariIos, ]; /// Creates an environment for a browser. @@ -254,8 +251,6 @@ BrowserEnvironment getBrowserEnvironment(String browserName) { return FirefoxEnvironment(); case kSafari: return SafariMacOsEnvironment(); - case kSafariIos: - return SafariIosEnvironment(); } throw UnsupportedError('Browser $browserName is not supported.'); } diff --git a/lib/web_ui/dev/create_simulator.dart b/lib/web_ui/dev/create_simulator.dart deleted file mode 100644 index 85fd076c4d6ed..0000000000000 --- a/lib/web_ui/dev/create_simulator.dart +++ /dev/null @@ -1,38 +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. - -import 'dart:async'; - -import 'package:args/command_runner.dart'; -import 'package:simulators/simulator_manager.dart'; - -import 'browser_lock.dart'; -import 'utils.dart'; - -class CreateSimulatorCommand extends Command with ArgUtils { - @override - String get name => 'create_simulator'; - - @override - String get description => 'Creates mobile simulators.'; - - @override - FutureOr run() async { - final IosSimulatorManager iosSimulatorManager = IosSimulatorManager(); - try { - final SafariIosLock lock = browserLock.safariIosLock; - final IosSimulator simulator = await iosSimulatorManager.createSimulator( - lock.majorVersion, - lock.minorVersion, - lock.device, - ); - print('INFO: Simulator created ${simulator.toString()}'); - } catch (e) { - throw Exception('Error creating requested simulator. You can use Xcode ' - 'to install more versions: XCode > Preferences > Components.' - ' Exception: $e'); - } - return true; - } -} diff --git a/lib/web_ui/dev/edge.dart b/lib/web_ui/dev/edge.dart index 61c27c030e3b0..032071ae8b3dc 100644 --- a/lib/web_ui/dev/edge.dart +++ b/lib/web_ui/dev/edge.dart @@ -46,8 +46,6 @@ class EdgeEnvironment implements BrowserEnvironment { /// /// Any errors starting or running the process are reported through [onExit]. class Edge extends Browser { - final BrowserProcess _process; - /// Starts a new instance of Safari open to the given [url], which may be a /// [Uri] or a [String]. factory Edge(Uri url) { @@ -75,6 +73,8 @@ class Edge extends Browser { Edge._(this._process); + final BrowserProcess _process; + @override Future get onExit => _process.onExit; diff --git a/lib/web_ui/dev/edge_installation.dart b/lib/web_ui/dev/edge_installation.dart index c10e220cb0985..28e3e0599175c 100644 --- a/lib/web_ui/dev/edge_installation.dart +++ b/lib/web_ui/dev/edge_installation.dart @@ -70,6 +70,8 @@ Future getEdgeInstallation( /// /// See: https://github.com/MicrosoftEdge/edge-launcher class EdgeLauncher { + EdgeLauncher(); + /// Path to the directory that contains `MicrosoftEdgeLauncher.exe`. io.Directory get launcherInstallationDir => io.Directory( path.join(environment.webUiDartToolDir.path, 'microsoftedgelauncher', @@ -90,8 +92,6 @@ class EdgeLauncher { String get windowsEdgeLauncherDownloadUrl => 'https://github.com/MicrosoftEdge/edge-launcher/releases/download/$version/MicrosoftEdgeLauncher.exe'; - EdgeLauncher(); - /// Install the launcher if it does not exist in this system. Future install() async { // Checks if the `MicrosoftEdgeLauncher` executable exists. diff --git a/lib/web_ui/dev/environment.dart b/lib/web_ui/dev/environment.dart index 7fe5f90834b06..1c5530ba2a0d0 100644 --- a/lib/web_ui/dev/environment.dart +++ b/lib/web_ui/dev/environment.dart @@ -169,13 +169,6 @@ class Environment { 'test_results', )); - /// Path to the screenshots taken by iOS simulator. - io.Directory get webUiSimulatorScreenshotsDirectory => - io.Directory(pathlib.join( - webUiDartToolDir.path, - 'ios_screenshots', - )); - /// Path to the script that clones the Flutter repo. io.File get cloneFlutterScript => io.File(pathlib.join( engineToolsDir.path, diff --git a/lib/web_ui/dev/felt.dart b/lib/web_ui/dev/felt.dart index 6088acbc0ebed..2e6e6d156cf46 100644 --- a/lib/web_ui/dev/felt.dart +++ b/lib/web_ui/dev/felt.dart @@ -8,8 +8,8 @@ import 'package:args/command_runner.dart'; import 'build.dart'; import 'clean.dart'; -import 'create_simulator.dart'; import 'exceptions.dart'; +import 'generate_fallback_font_data.dart'; import 'licenses.dart'; import 'run.dart'; import 'test_runner.dart'; @@ -21,7 +21,7 @@ CommandRunner runner = CommandRunner( ) ..addCommand(BuildCommand()) ..addCommand(CleanCommand()) - ..addCommand(CreateSimulatorCommand()) + ..addCommand(GenerateFallbackFontDataCommand()) ..addCommand(LicensesCommand()) ..addCommand(RunCommand()) ..addCommand(TestCommand()); diff --git a/lib/web_ui/dev/firefox.dart b/lib/web_ui/dev/firefox.dart index de0c7fe13b53b..48772f59cd857 100644 --- a/lib/web_ui/dev/firefox.dart +++ b/lib/web_ui/dev/firefox.dart @@ -54,11 +54,6 @@ class FirefoxEnvironment implements BrowserEnvironment { /// /// Any errors starting or running the process are reported through [onExit]. class Firefox extends Browser { - final BrowserProcess _process; - - @override - final Future remoteDebuggerUrl; - /// Starts a new instance of Firefox open to the given [url], which may be a /// [Uri] or a [String]. factory Firefox(Uri url, FirefoxEnvironment firefoxEnvironment, {bool debug = false}) { @@ -116,6 +111,11 @@ user_pref("dom.max_script_run_time", 0); Firefox._(this._process, this.remoteDebuggerUrl); + final BrowserProcess _process; + + @override + final Future remoteDebuggerUrl; + @override Future get onExit => _process.onExit; diff --git a/lib/web_ui/dev/firefox_installer.dart b/lib/web_ui/dev/firefox_installer.dart index 2d48c16e025ce..3f074b5b27db9 100644 --- a/lib/web_ui/dev/firefox_installer.dart +++ b/lib/web_ui/dev/firefox_installer.dart @@ -91,6 +91,12 @@ class FirefoxInstaller { ); } + FirefoxInstaller._({ + required this.version, + required this.firefoxInstallationDir, + required this.versionDir, + }); + static Future latest() async { final String latestVersion = io.Platform.isLinux ? await fetchLatestFirefoxVersionLinux() @@ -98,12 +104,6 @@ class FirefoxInstaller { return FirefoxInstaller(version: latestVersion); } - FirefoxInstaller._({ - required this.version, - required this.firefoxInstallationDir, - required this.versionDir, - }); - /// Firefox version managed by this installer. final String version; diff --git a/lib/web_ui/dev/generate_fallback_font_data.dart b/lib/web_ui/dev/generate_fallback_font_data.dart new file mode 100644 index 0000000000000..7ae4aae65eb8c --- /dev/null +++ b/lib/web_ui/dev/generate_fallback_font_data.dart @@ -0,0 +1,289 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:convert' show jsonDecode; +import 'dart:io' as io; + +import 'package:args/command_runner.dart'; +import 'package:http/http.dart' as http; +import 'package:path/path.dart' as path; + +import 'environment.dart'; +import 'exceptions.dart'; +import 'utils.dart'; + +class GenerateFallbackFontDataCommand extends Command + with ArgUtils { + GenerateFallbackFontDataCommand() { + argParser.addOption( + 'key', + defaultsTo: '', + help: 'The Google Fonts API key. Used to get data about fonts hosted on ' + 'Google Fonts.', + ); + argParser.addFlag( + 'download-test-fonts', + defaultsTo: true, + help: 'Whether to download the Noto fonts into a local folder to use in' + 'tests.', + ); + } + + @override + final String name = 'generate-fallback-font-data'; + + @override + final String description = 'Generate fallback font data from GoogleFonts'; + + String get apiKey => stringArg('key'); + + bool get downloadTestFonts => boolArg('download-test-fonts'); + + @override + Future run() async { + await _generateFallbackFontData(); + return true; + } + + Future _generateFallbackFontData() async { + if (apiKey.isEmpty) { + throw UsageException('No Google Fonts API key provided', argParser.usage); + } + final http.Client client = http.Client(); + final http.Response response = await client.get(Uri.parse( + 'https://www.googleapis.com/webfonts/v1/webfonts?key=$apiKey')); + if (response.statusCode != 200) { + throw ToolExit('Failed to download Google Fonts list.'); + } + final Map googleFontsResult = + jsonDecode(response.body) as Map; + final List> fontDatas = + (googleFontsResult['items'] as List) + .cast>(); + final Map urlForFamily = {}; + for (final Map fontData in fontDatas) { + if (fallbackFonts.contains(fontData['family'])) { + final Uri uri = Uri.parse(fontData['files']['regular'] as String); + urlForFamily[fontData['family'] as String] = uri; + } + } + final Map charsetForFamily = {}; + final io.Directory fontDir = downloadTestFonts + ? await io.Directory(path.join( + environment.webUiBuildDir.path, + 'assets', + 'noto', + )).create(recursive: true) + : await io.Directory.systemTemp.createTemp('fonts'); + // Delete old fonts in the font directory. + await for (final io.FileSystemEntity file in fontDir.list()) { + await file.delete(); + } + for (final String family in fallbackFonts) { + print('Downloading $family...'); + final Uri uri = urlForFamily[family]!; + final http.Response fontResponse = await client.get(uri); + if (fontResponse.statusCode != 200) { + throw ToolExit('Failed to download font for $family'); + } + final io.File fontFile = + io.File(path.join(fontDir.path, path.basename(uri.path))); + await fontFile.writeAsBytes(fontResponse.bodyBytes); + final io.ProcessResult fcQueryResult = + await io.Process.run('fc-query', [ + '--format=%{charset}', + '--', + fontFile.path, + ]); + final String encodedCharset = fcQueryResult.stdout as String; + charsetForFamily[family] = encodedCharset; + } + + final StringBuffer sb = StringBuffer(); + + sb.writeln('// Copyright 2013 The Flutter Authors. All rights reserved.'); + sb.writeln('// Use of this source code is governed by a BSD-style license ' + 'that can be'); + sb.writeln('// found in the LICENSE file.'); + sb.writeln(); + sb.writeln('// DO NOT EDIT! This file is generated. See:'); + sb.writeln('// dev/generate_fallback_font_data.dart'); + sb.writeln("import 'noto_font.dart';"); + sb.writeln(); + sb.writeln('final List fallbackFonts = ['); + for (final String family in fallbackFonts) { + sb.write(" NotoFont.fromFlatRanges('$family', '${urlForFamily[family]!}', " + '['); + for (final String range in charsetForFamily[family]!.split(' ')) { + String? start; + String? end; + final List parts = range.split('-'); + if (parts.length == 1) { + start = parts[0]; + end = parts[0]; + } else { + start = parts[0]; + end = parts[1]; + } + sb.write('0x$start,0x$end,'); + } + sb.writeln(']),'); + } + sb.writeln('];'); + + final io.File fontDataFile = io.File(path.join( + environment.webUiRootDir.path, + 'lib', + 'src', + 'engine', + 'canvaskit', + 'font_fallback_data.dart', + )); + await fontDataFile.writeAsString(sb.toString()); + } +} + +const List fallbackFonts = [ + 'Noto Sans', + 'Noto Emoji', + 'Noto Sans Symbols', + 'Noto Sans Symbols 2', + 'Noto Sans Adlam', + 'Noto Sans Anatolian Hieroglyphs', + 'Noto Sans Arabic', + 'Noto Sans Armenian', + 'Noto Sans Avestan', + 'Noto Sans Balinese', + 'Noto Sans Bamum', + 'Noto Sans Bassa Vah', + 'Noto Sans Batak', + 'Noto Sans Bengali', + 'Noto Sans Bhaiksuki', + 'Noto Sans Brahmi', + 'Noto Sans Buginese', + 'Noto Sans Buhid', + 'Noto Sans Canadian Aboriginal', + 'Noto Sans Carian', + 'Noto Sans Caucasian Albanian', + 'Noto Sans Chakma', + 'Noto Sans Cham', + 'Noto Sans Cherokee', + 'Noto Sans Coptic', + 'Noto Sans Cuneiform', + 'Noto Sans Cypriot', + 'Noto Sans Deseret', + 'Noto Sans Devanagari', + 'Noto Sans Duployan', + 'Noto Sans Egyptian Hieroglyphs', + 'Noto Sans Elbasan', + 'Noto Sans Elymaic', + 'Noto Sans Georgian', + 'Noto Sans Glagolitic', + 'Noto Sans Gothic', + 'Noto Sans Grantha', + 'Noto Sans Gujarati', + 'Noto Sans Gunjala Gondi', + 'Noto Sans Gurmukhi', + 'Noto Sans HK', + 'Noto Sans Hanunoo', + 'Noto Sans Hatran', + 'Noto Sans Hebrew', + 'Noto Sans Imperial Aramaic', + 'Noto Sans Indic Siyaq Numbers', + 'Noto Sans Inscriptional Pahlavi', + 'Noto Sans Inscriptional Parthian', + 'Noto Sans JP', + 'Noto Sans Javanese', + 'Noto Sans KR', + 'Noto Sans Kaithi', + 'Noto Sans Kannada', + 'Noto Sans Kayah Li', + 'Noto Sans Kharoshthi', + 'Noto Sans Khmer', + 'Noto Sans Khojki', + 'Noto Sans Khudawadi', + 'Noto Sans Lao', + 'Noto Sans Lepcha', + 'Noto Sans Limbu', + 'Noto Sans Linear A', + 'Noto Sans Linear B', + 'Noto Sans Lisu', + 'Noto Sans Lycian', + 'Noto Sans Lydian', + 'Noto Sans Mahajani', + 'Noto Sans Malayalam', + 'Noto Sans Mandaic', + 'Noto Sans Manichaean', + 'Noto Sans Marchen', + 'Noto Sans Masaram Gondi', + 'Noto Sans Math', + 'Noto Sans Mayan Numerals', + 'Noto Sans Medefaidrin', + 'Noto Sans Meetei Mayek', + 'Noto Sans Meroitic', + 'Noto Sans Miao', + 'Noto Sans Modi', + 'Noto Sans Mongolian', + 'Noto Sans Mro', + 'Noto Sans Multani', + 'Noto Sans Myanmar', + 'Noto Sans N Ko', + 'Noto Sans Nabataean', + 'Noto Sans New Tai Lue', + 'Noto Sans Newa', + 'Noto Sans Nushu', + 'Noto Sans Ogham', + 'Noto Sans Ol Chiki', + 'Noto Sans Old Hungarian', + 'Noto Sans Old Italic', + 'Noto Sans Old North Arabian', + 'Noto Sans Old Permic', + 'Noto Sans Old Persian', + 'Noto Sans Old Sogdian', + 'Noto Sans Old South Arabian', + 'Noto Sans Old Turkic', + 'Noto Sans Oriya', + 'Noto Sans Osage', + 'Noto Sans Osmanya', + 'Noto Sans Pahawh Hmong', + 'Noto Sans Palmyrene', + 'Noto Sans Pau Cin Hau', + 'Noto Sans Phags Pa', + 'Noto Sans Phoenician', + 'Noto Sans Psalter Pahlavi', + 'Noto Sans Rejang', + 'Noto Sans Runic', + 'Noto Sans SC', + 'Noto Sans Saurashtra', + 'Noto Sans Sharada', + 'Noto Sans Shavian', + 'Noto Sans Siddham', + 'Noto Sans Sinhala', + 'Noto Sans Sogdian', + 'Noto Sans Sora Sompeng', + 'Noto Sans Soyombo', + 'Noto Sans Sundanese', + 'Noto Sans Syloti Nagri', + 'Noto Sans Syriac', + 'Noto Sans TC', + 'Noto Sans Tagalog', + 'Noto Sans Tagbanwa', + 'Noto Sans Tai Le', + 'Noto Sans Tai Tham', + 'Noto Sans Tai Viet', + 'Noto Sans Takri', + 'Noto Sans Tamil', + 'Noto Sans Tamil Supplement', + 'Noto Sans Telugu', + 'Noto Sans Thaana', + 'Noto Sans Thai', + 'Noto Sans Tifinagh', + 'Noto Sans Tirhuta', + 'Noto Sans Ugaritic', + 'Noto Sans Vai', + 'Noto Sans Wancho', + 'Noto Sans Warang Citi', + 'Noto Sans Yi', + 'Noto Sans Zanabazar Square', +]; diff --git a/lib/web_ui/dev/safari_installation.dart b/lib/web_ui/dev/safari_installation.dart deleted file mode 100644 index 497ae06dc7f86..0000000000000 --- a/lib/web_ui/dev/safari_installation.dart +++ /dev/null @@ -1,90 +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. - -import 'dart:async'; -import 'dart:io' as io; - -import 'package:simulators/simulator_manager.dart'; - -import 'browser_lock.dart'; -import 'common.dart'; -import 'utils.dart'; - -/// Returns [IosSimulator] if the [Platform] is `macOS` and simulator -/// is started. -/// -/// Throws an [StateError] if these two conditions are not met. -IosSimulator get iosSimulator { - if (!io.Platform.isMacOS) { - throw StateError('iOS Simulator is only available on macOS machines.'); - } - if (_iosSimulator == null) { - throw StateError( - 'iOS Simulator not started. Please first call initIOSSimulator method', - ); - } - return _iosSimulator!; -} -IosSimulator? _iosSimulator; - -/// Inializes and boots an [IosSimulator] using the [iosMajorVersion], -/// [iosMinorVersion] and [iosDevice] arguments. -Future initIosSimulator() async { - if (_iosSimulator != null) { - throw StateError('_iosSimulator can only be initialized once'); - } - final IosSimulatorManager iosSimulatorManager = IosSimulatorManager(); - final IosSimulator simulator; - final SafariIosLock lock = browserLock.safariIosLock; - try { - simulator = await iosSimulatorManager.getSimulator( - lock.majorVersion, - lock.minorVersion, - lock.device, - ); - _iosSimulator = simulator; - } catch (e) { - io.stderr.writeln( - 'Error getting iOS Simulator for ${lock.simulatorDescription}.\n' - 'Try running `felt create` command before running tests.', - ); - rethrow; - } - - if (!simulator.booted) { - await simulator.boot(); - print('INFO: Simulator ${simulator.id} booted.'); - cleanupCallbacks.add(() async { - await simulator.shutdown(); - print('INFO: Simulator ${simulator.id} shutdown.'); - }); - } -} - -/// 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. -Future getOrInstallSafari({ - 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; - - // 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.getMacApplicationLauncher(), - ); -} diff --git a/lib/web_ui/dev/safari_ios.dart b/lib/web_ui/dev/safari_ios.dart deleted file mode 100644 index df856de4fff83..0000000000000 --- a/lib/web_ui/dev/safari_ios.dart +++ /dev/null @@ -1,209 +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. - -import 'dart:async'; -import 'dart:io' as io; -import 'dart:math' as math; - -import 'package:image/image.dart'; -import 'package:path/path.dart' as path; -import 'package:test_api/src/backend/runtime.dart'; -import 'package:uuid/uuid.dart'; - -import 'browser.dart'; -import 'browser_lock.dart'; -import 'browser_process.dart'; -import 'environment.dart'; -import 'safari_installation.dart'; -import 'utils.dart'; - -/// Info about the screen layout for the current version of iOS Safari. -/// -/// This is used to properly take screenshots of the browser. -class SafariScreenInfo { - final int heightOfHeader; - final int heightOfFooter; - final double scaleFactor; - - SafariScreenInfo(this.heightOfHeader, this.heightOfFooter, this.scaleFactor); -} - -/// Provides an environment for the mobile variant of Safari running in an iOS -/// simulator. -class SafariIosEnvironment implements BrowserEnvironment { - late final SafariScreenInfo _screenInfo; - - @override - final String name = 'Safari iOS'; - - @override - Future launchBrowserInstance(Uri url, {bool debug = false}) async { - return SafariIos(url, _screenInfo); - } - - @override - Runtime get packageTestRuntime => Runtime.safari; - - @override - Future prepare() async { - final SafariIosLock lock = browserLock.safariIosLock; - _screenInfo = SafariScreenInfo( - lock.heightOfHeader, lock.heightOfFooter, lock.scaleFactor); - - /// Create the directory to use for taking screenshots, if it does not - /// exists. - if (!environment.webUiSimulatorScreenshotsDirectory.existsSync()) { - environment.webUiSimulatorScreenshotsDirectory.createSync(); - } - // Temporary directories are deleted in the clenaup phase of after `felt` - // runs the tests. - temporaryDirectories.add(environment.webUiSimulatorScreenshotsDirectory); - - await initIosSimulator(); - } - - @override - Future cleanup() async {} - - @override - String get packageTestConfigurationYamlFile => 'dart_test_safari.yaml'; -} - -/// Runs an instance of Safari for iOS (i.e. mobile 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 SafariIos extends Browser { - final BrowserProcess _process; - final SafariScreenInfo _screenInfo; - - /// Starts a new instance of Safari open to the given [url], which may be a - /// [Uri]. - factory SafariIos(Uri url, SafariScreenInfo screenInfo) { - return SafariIos._(BrowserProcess(() async { - // iOS-Safari - // Uses `xcrun simctl`. It is a command line utility to control the - // Simulator. For more details on interacting with the simulator: - // https://developer.apple.com/library/archive/documentation/IDEs/Conceptual/iOS_Simulator_Guide/InteractingwiththeiOSSimulator/InteractingwiththeiOSSimulator.html - final io.Process process = await io.Process.start('xcrun', [ - 'simctl', - 'openurl', // Opens the url on Safari installed on the simulator. - 'booted', // The simulator is already booted. - url.toString(), - ]); - - return process; - }), screenInfo); - } - - SafariIos._(this._process, this._screenInfo); - - @override - Future get onExit => _process.onExit; - - @override - Future close() => _process.close(); - - @override - bool get supportsScreenshots => true; - - @override - - /// Capture a screenshot of entire simulator. - /// - /// Example screenshot with dimensions: W x H. - /// - /// <---------- W -------------> - /// _____________________________ - /// | Phone Top bar (clock etc.) | Ʌ - /// |_____________________________| | - /// | Broswer search bar | | - /// |_____________________________| | - /// | Web page content | | - /// | | | - /// | | - /// | | H - /// | | - /// | | | - /// | | | - /// | | | - /// | | | - /// |_____________________________| | - /// | Phone footer bar | | - /// |_____________________________| V - /// - /// After taking the screenshot, the image is cropped as heigh as - /// [_heightOfHeader] and [_heightOfFooter] from the top and bottom parts - /// consecutively. Hence web content has the dimensions: - /// - /// W x (H - [_heightOfHeader] - [_heightOfFooter]) - /// - /// [region] is used to decide which part of the web content will be used in - /// test image. It includes starting coordinate x,y as well as height and - /// width of the area to capture. - /// - /// Uses simulator tool `xcrun simctl`'s 'screenshot' command. - @override - Future captureScreenshot(math.Rectangle? region) async { - final String screenshotTag = const Uuid().v4(); - - final String filename = 'screenshot$screenshotTag.png'; - - await iosSimulator.takeScreenshot( - filename, - environment.webUiSimulatorScreenshotsDirectory, - ); - - final io.File file = io.File(path.join( - environment.webUiSimulatorScreenshotsDirectory.path, filename)); - List imageBytes; - if (!file.existsSync()) { - throw Exception('Failed to read the screenshot $filename.'); - } - imageBytes = await file.readAsBytes(); - file.deleteSync(); - - final Image screenshot = decodePng(imageBytes)!; - // Create an image with no footer and header. The _heightOfHeader, - // _heightOfFooter values are already in real coordinates therefore - // they don't need to be scaled. - final Image content = copyCrop( - screenshot, - 0, - _screenInfo.heightOfHeader, - screenshot.width, - screenshot.height - - _screenInfo.heightOfFooter - - _screenInfo.heightOfHeader, - ); - - if (region == null) { - return content; - } else { - final math.Rectangle scaledRegion = _scaleScreenshotRegion(region); - return copyCrop( - content, - scaledRegion.left.toInt(), - scaledRegion.top.toInt(), - scaledRegion.width.toInt(), - scaledRegion.height.toInt(), - ); - } - } - - /// Perform a linear transform on the screenshot region to convert its - /// dimensions from linear coordinated to coordinated on the phone screen. - /// This uniform/isotropic scaling is done using [_scaleFactor]. - math.Rectangle _scaleScreenshotRegion(math.Rectangle region) { - return math.Rectangle( - region.left * _screenInfo.scaleFactor, - region.top * _screenInfo.scaleFactor, - region.width * _screenInfo.scaleFactor, - region.height * _screenInfo.scaleFactor, - ); - } -} diff --git a/lib/web_ui/dev/test_platform.dart b/lib/web_ui/dev/test_platform.dart index ad15dce141bbf..2a80684c7b4f5 100644 --- a/lib/web_ui/dev/test_platform.dart +++ b/lib/web_ui/dev/test_platform.dart @@ -43,6 +43,57 @@ import 'environment.dart' as env; /// Custom test platform that serves web engine unit tests. class BrowserPlatform extends PlatformPlugin { + BrowserPlatform._({ + required this.browserEnvironment, + required this.server, + required this.isDebug, + required this.doUpdateScreenshotGoldens, + required this.packageConfig, + required this.skiaClient, + required this.overridePathToCanvasKit, + }) { + // The cascade of request handlers. + final shelf.Cascade cascade = shelf.Cascade() + // The web socket that carries the test channels for running tests and + // reporting restuls. See [_browserManagerFor] and [BrowserManager.start] + // for details on how the channels are established. + .add(_webSocketHandler.handler) + + // Serves /packages/* requests; fetches files and sources from + // pubspec dependencies. + // + // Includes: + // * Requests for Dart sources from source maps + // * Assets that are part of the engine sources, such as Ahem.ttf + .add(_packageUrlHandler) + .add(_canvasKitOverrideHandler) + + // Serves files from the web_ui/build/ directory at the root (/) URL path. + .add(buildDirectoryHandler) + .add(_testImageListingHandler) + + // Serves the initial HTML for the test. + .add(_testBootstrapHandler) + + // Serves files from the root of web_ui. + // + // This is needed because sourcemaps refer to local files, i.e. those + // that don't come from package dependencies, relative to web_ui/. + // + // Examples of URLs that this handles: + // * /test/alarm_clock_test.dart + // * /lib/src/engine/alarm_clock.dart + .add(createStaticHandler(env.environment.webUiRootDir.path)) + + // Serves absolute package URLs (i.e. not /packages/* but /Users/user/*/hosted/pub.dartlang.org/*). + // This handler goes last, after all more specific handlers failed to handle the request. + .add(_createAbsolutePackageUrlHandler()) + .add(_screenshotHandler) + .add(_fileNotFoundCatcher); + + server.mount(cascade.handler); + } + /// Starts the server. /// /// [browserEnvironment] provides the browser environment to run the test. @@ -105,57 +156,6 @@ class BrowserPlatform extends PlatformPlugin { final String? overridePathToCanvasKit; - BrowserPlatform._({ - required this.browserEnvironment, - required this.server, - required this.isDebug, - required this.doUpdateScreenshotGoldens, - required this.packageConfig, - required this.skiaClient, - required this.overridePathToCanvasKit, - }) { - // The cascade of request handlers. - final shelf.Cascade cascade = shelf.Cascade() - // The web socket that carries the test channels for running tests and - // reporting restuls. See [_browserManagerFor] and [BrowserManager.start] - // for details on how the channels are established. - .add(_webSocketHandler.handler) - - // Serves /packages/* requests; fetches files and sources from - // pubspec dependencies. - // - // Includes: - // * Requests for Dart sources from source maps - // * Assets that are part of the engine sources, such as Ahem.ttf - .add(_packageUrlHandler) - .add(_canvasKitOverrideHandler) - - // Serves files from the web_ui/build/ directory at the root (/) URL path. - .add(buildDirectoryHandler) - .add(_testImageListingHandler) - - // Serves the initial HTML for the test. - .add(_testBootstrapHandler) - - // Serves files from the root of web_ui. - // - // This is needed because sourcemaps refer to local files, i.e. those - // that don't come from package dependencies, relative to web_ui/. - // - // Examples of URLs that this handles: - // * /test/alarm_clock_test.dart - // * /lib/src/engine/alarm_clock.dart - .add(createStaticHandler(env.environment.webUiRootDir.path)) - - // Serves absolute package URLs (i.e. not /packages/* but /Users/user/*/hosted/pub.dartlang.org/*). - // This handler goes last, after all more specific handlers failed to handle the request. - .add(_createAbsolutePackageUrlHandler()) - .add(_screenshotHandler) - .add(_fileNotFoundCatcher); - - server.mount(cascade.handler); - } - /// If a path to a custom local build of CanvasKit was specified, serve from /// there instead of serving the default CanvasKit in the build/ directory. Future _canvasKitOverrideHandler( @@ -323,7 +323,7 @@ class BrowserPlatform extends PlatformPlugin { if (!(await browserManager).supportsScreenshots) { print( - 'INFO: Skipping screenshot check for $filename. Current browser/OS ' + 'Skipping screenshot check for $filename. Current browser/OS ' 'combination does not support screenshots.', ); return shelf.Response.ok(json.encode('OK')); @@ -629,6 +629,47 @@ class OneOffHandler { /// This is in charge of telling the browser which test suites to load and /// converting its responses into [Suite] objects. class BrowserManager { + /// Creates a new BrowserManager that communicates with the browser over + /// [webSocket]. + BrowserManager._(this.packageConfig, this._browser, this._browserEnvironment, + WebSocketChannel webSocket) { + // The duration should be short enough that the debugging console is open as + // soon as the user is done setting breakpoints, but long enough that a test + // doing a lot of synchronous work doesn't trigger a false positive. + // + // Start this canceled because we don't want it to start ticking until we + // get some response from the iframe. + _timer = RestartableTimer(const Duration(seconds: 3), () { + for (final RunnerSuiteController controller in _controllers) { + controller.setDebugging(true); + } + }) + ..cancel(); + + // Whenever we get a message, no matter which child channel it's for, we the + // know browser is still running code which means the user isn't debugging. + _channel = MultiChannel(webSocket + .cast() + .transform(jsonDocument) + .changeStream((Stream stream) { + return stream.map((Object? message) { + if (!_closed) { + _timer.reset(); + } + for (final RunnerSuiteController controller in _controllers) { + controller.setDebugging(false); + } + + return message; + }); + })); + + _environment = _loadBrowserEnvironment(); + _channel.stream.listen( + (dynamic message) => _onMessage(message as Map), + onDone: close); + } + final PackageConfig packageConfig; /// The browser instance that this is connected to via [_channel]. @@ -762,47 +803,6 @@ class BrowserManager { return browserEnvironment.launchBrowserInstance(url, debug: debug); } - /// Creates a new BrowserManager that communicates with the browser over - /// [webSocket]. - BrowserManager._(this.packageConfig, this._browser, this._browserEnvironment, - WebSocketChannel webSocket) { - // The duration should be short enough that the debugging console is open as - // soon as the user is done setting breakpoints, but long enough that a test - // doing a lot of synchronous work doesn't trigger a false positive. - // - // Start this canceled because we don't want it to start ticking until we - // get some response from the iframe. - _timer = RestartableTimer(const Duration(seconds: 3), () { - for (final RunnerSuiteController controller in _controllers) { - controller.setDebugging(true); - } - }) - ..cancel(); - - // Whenever we get a message, no matter which child channel it's for, we the - // know browser is still running code which means the user isn't debugging. - _channel = MultiChannel(webSocket - .cast() - .transform(jsonDocument) - .changeStream((Stream stream) { - return stream.map((Object? message) { - if (!_closed) { - _timer.reset(); - } - for (final RunnerSuiteController controller in _controllers) { - controller.setDebugging(false); - } - - return message; - }); - })); - - _environment = _loadBrowserEnvironment(); - _channel.stream.listen( - (dynamic message) => _onMessage(message as Map), - onDone: close); - } - /// Loads [_BrowserEnvironment]. Future<_BrowserEnvironment> _loadBrowserEnvironment() async { return _BrowserEnvironment(this, await _browser.observatoryUrl, @@ -954,6 +954,9 @@ class BrowserManager { /// /// All methods forward directly to [BrowserManager]. class _BrowserEnvironment implements Environment { + _BrowserEnvironment(this._manager, this.observatoryUrl, + this.remoteDebuggerUrl, this.onRestart); + final BrowserManager _manager; @override @@ -968,9 +971,6 @@ class _BrowserEnvironment implements Environment { @override final Stream onRestart; - _BrowserEnvironment(this._manager, this.observatoryUrl, - this.remoteDebuggerUrl, this.onRestart); - @override CancelableOperation displayPause() => _manager._displayPause(); } diff --git a/lib/web_ui/dev/utils.dart b/lib/web_ui/dev/utils.dart index 0986178e421dd..21c580e9c1ae5 100644 --- a/lib/web_ui/dev/utils.dart +++ b/lib/web_ui/dev/utils.dart @@ -276,7 +276,7 @@ Future runFlutter( if (exitCode != 0) { throw ToolExit( 'ERROR: Failed to run $executable with ' - 'arguments ${arguments.toString()}. Exited with exit code $exitCode', + 'arguments $arguments. Exited with exit code $exitCode', exitCode: exitCode, ); } diff --git a/lib/web_ui/dev/webdriver_browser.dart b/lib/web_ui/dev/webdriver_browser.dart index 1f17bf1101100..8c1c82a9b01c1 100644 --- a/lib/web_ui/dev/webdriver_browser.dart +++ b/lib/web_ui/dev/webdriver_browser.dart @@ -79,14 +79,14 @@ abstract class WebDriverBrowserEnvironment extends BrowserEnvironment { } class WebDriverBrowser extends Browser { - final WebDriver _driver; - final Uri _url; - final Completer _onExitCompleter = Completer(); - WebDriverBrowser(this._driver, this._url) { _driver.get(_url); } + final WebDriver _driver; + final Uri _url; + final Completer _onExitCompleter = Completer(); + @override Future close() async { await (await _driver.window).close(); diff --git a/lib/web_ui/lib/canvas.dart b/lib/web_ui/lib/canvas.dart index c9359b6930781..1bcb8173c594e 100644 --- a/lib/web_ui/lib/canvas.dart +++ b/lib/web_ui/lib/canvas.dart @@ -29,21 +29,11 @@ class Vertices { List? colors, List? indices, }) { - if (engine.useCanvasKit) { - return engine.CkVertices( - mode, - positions, - textureCoordinates: textureCoordinates, - colors: colors, - indices: indices, - ); - } - return engine.SurfaceVertices( - mode, + return engine.renderer.createVertices(mode, positions, + textureCoordinates: textureCoordinates, colors: colors, - indices: indices, - ); + indices: indices); } factory Vertices.raw( VertexMode mode, @@ -52,45 +42,23 @@ class Vertices { Int32List? colors, Uint16List? indices, }) { - if (engine.useCanvasKit) { - return engine.CkVertices.raw( - mode, - positions, - textureCoordinates: textureCoordinates, - colors: colors, - indices: indices, - ); - } - return engine.SurfaceVertices.raw( - mode, + return engine.renderer.createVerticesRaw(mode, positions, + textureCoordinates: textureCoordinates, colors: colors, - indices: indices, - ); + indices: indices); } } abstract class PictureRecorder { - factory PictureRecorder() { - if (engine.useCanvasKit) { - return engine.CkPictureRecorder(); - } else { - return engine.EnginePictureRecorder(); - } - } + factory PictureRecorder() => engine.renderer.createPictureRecorder(); bool get isRecording; Picture endRecording(); } abstract class Canvas { - factory Canvas(PictureRecorder recorder, [Rect? cullRect]) { - if (engine.useCanvasKit) { - return engine.CanvasKitCanvas(recorder, cullRect); - } else { - return engine.SurfaceCanvas( - recorder as engine.EnginePictureRecorder, cullRect); - } - } + factory Canvas(PictureRecorder recorder, [Rect? cullRect]) => + engine.renderer.createCanvas(recorder, cullRect); void save(); void saveLayer(Rect? bounds, Paint paint); void restore(); diff --git a/lib/web_ui/lib/compositing.dart b/lib/web_ui/lib/compositing.dart index 96c51cbeeecf4..11154ad6608e9 100644 --- a/lib/web_ui/lib/compositing.dart +++ b/lib/web_ui/lib/compositing.dart @@ -33,13 +33,9 @@ abstract class ShaderMaskEngineLayer implements EngineLayer {} abstract class PhysicalShapeEngineLayer implements EngineLayer {} abstract class SceneBuilder { - factory SceneBuilder() { - if (engine.useCanvasKit) { - return engine.LayerSceneBuilder(); - } else { - return engine.SurfaceSceneBuilder(); - } - } + factory SceneBuilder() => + engine.renderer.createSceneBuilder(); + OffsetEngineLayer pushOffset( double dx, double dy, { @@ -89,6 +85,10 @@ abstract class SceneBuilder { ShaderMaskEngineLayer? oldLayer, FilterQuality filterQuality = FilterQuality.low, }); + @Deprecated( + 'Use a clip and canvas operations directly (See RenderPhysicalModel). ' + 'This feature was deprecated after v3.1.0-0.0.pre.', + ) PhysicalShapeEngineLayer pushPhysicalShape({ required Path path, required double elevation, diff --git a/lib/web_ui/lib/geometry.dart b/lib/web_ui/lib/geometry.dart index 3e3fb4bc47431..3d60b13e92601 100644 --- a/lib/web_ui/lib/geometry.dart +++ b/lib/web_ui/lib/geometry.dart @@ -4,13 +4,12 @@ // See https://github.com/flutter/engine/blob/main/lib/ui/geometry.dart for // documentation of APIs. -// ignore_for_file: public_member_api_docs part of ui; abstract class OffsetBase { const OffsetBase(this._dx, this._dy) - : assert(_dx != null), // ignore: unnecessary_null_comparison - assert(_dy != null); // ignore: unnecessary_null_comparison + : assert(_dx != null), + assert(_dy != null); final double _dx; final double _dy; @@ -58,7 +57,7 @@ class Offset extends OffsetBase { Offset operator %(double operand) => Offset(dx % operand, dy % operand); Rect operator &(Size other) => Rect.fromLTWH(dx, dy, other.width, other.height); static Offset? lerp(Offset? a, Offset? b, double t) { - assert(t != null); // ignore: unnecessary_null_comparison + assert(t != null); if (b == null) { if (a == null) { return null; @@ -149,7 +148,7 @@ class Size extends OffsetBase { Size get flipped => Size(height, width); static Size? lerp(Size? a, Size? b, double t) { - assert(t != null); // ignore: unnecessary_null_comparison + assert(t != null); if (b == null) { if (a == null) { return null; @@ -182,10 +181,10 @@ class Size extends OffsetBase { class Rect { const Rect.fromLTRB(this.left, this.top, this.right, this.bottom) - : assert(left != null), // ignore: unnecessary_null_comparison - assert(top != null), // ignore: unnecessary_null_comparison - assert(right != null), // ignore: unnecessary_null_comparison - assert(bottom != null); // ignore: unnecessary_null_comparison + : assert(left != null), + assert(top != null), + assert(right != null), + assert(bottom != null); const Rect.fromLTWH(double left, double top, double width, double height) : this.fromLTRB(left, top, left + width, top + height); @@ -292,7 +291,7 @@ class Rect { } static Rect? lerp(Rect? a, Rect? b, double t) { - assert(t != null); // ignore: unnecessary_null_comparison + assert(t != null); if (b == null) { if (a == null) { return null; @@ -350,7 +349,7 @@ class Radius { Radius operator ~/(double operand) => Radius.elliptical((x ~/ operand).toDouble(), (y ~/ operand).toDouble()); Radius operator %(double operand) => Radius.elliptical(x % operand, y % operand); static Radius? lerp(Radius? a, Radius? b, double t) { - assert(t != null); // ignore: unnecessary_null_comparison + assert(t != null); if (b == null) { if (a == null) { return null; @@ -548,18 +547,18 @@ class RRect { this.blRadiusX = 0.0, this.blRadiusY = 0.0, bool uniformRadii = false, - }) : assert(left != null), // ignore: unnecessary_null_comparison - assert(top != null), // ignore: unnecessary_null_comparison - assert(right != null), // ignore: unnecessary_null_comparison - assert(bottom != null), // ignore: unnecessary_null_comparison - assert(tlRadiusX != null), // ignore: unnecessary_null_comparison - assert(tlRadiusY != null), // ignore: unnecessary_null_comparison - assert(trRadiusX != null), // ignore: unnecessary_null_comparison - assert(trRadiusY != null), // ignore: unnecessary_null_comparison - assert(brRadiusX != null), // ignore: unnecessary_null_comparison - assert(brRadiusY != null), // ignore: unnecessary_null_comparison - assert(blRadiusX != null), // ignore: unnecessary_null_comparison - assert(blRadiusY != null), // ignore: unnecessary_null_comparison + }) : assert(left != null), + assert(top != null), + assert(right != null), + assert(bottom != null), + assert(tlRadiusX != null), + assert(tlRadiusY != null), + assert(trRadiusX != null), + assert(trRadiusY != null), + assert(brRadiusX != null), + assert(brRadiusY != null), + assert(blRadiusX != null), + assert(blRadiusY != null), webOnlyUniformRadii = uniformRadii; final double left; @@ -806,7 +805,7 @@ class RRect { } static RRect? lerp(RRect? a, RRect? b, double t) { - assert(t != null); // ignore: unnecessary_null_comparison + assert(t != null); if (b == null) { if (a == null) { return null; diff --git a/lib/web_ui/lib/initialization.dart b/lib/web_ui/lib/initialization.dart index 8ee067b5ef8cf..e19f24103cdf4 100644 --- a/lib/web_ui/lib/initialization.dart +++ b/lib/web_ui/lib/initialization.dart @@ -112,6 +112,7 @@ set debugEmulateFlutterTesterEnvironment(bool value) { engine.window.webOnlyDebugPhysicalSizeOverride = logicalSize * window.devicePixelRatio; } + engine.debugDisableFontFallbacks = value; } bool _debugEmulateFlutterTesterEnvironment = false; diff --git a/lib/web_ui/lib/painting.dart b/lib/web_ui/lib/painting.dart index 1c562648e899c..0edd2faeb0dbd 100644 --- a/lib/web_ui/lib/painting.dart +++ b/lib/web_ui/lib/painting.dart @@ -3,19 +3,18 @@ // found in the LICENSE file. // For documentation see https://github.com/flutter/engine/blob/main/lib/ui/painting.dart -// ignore_for_file: public_member_api_docs part of ui; // ignore: unused_element, Used in Shader assert. bool _offsetIsValid(Offset offset) { - assert(offset != null, 'Offset argument was null.'); // ignore: unnecessary_null_comparison + assert(offset != null, 'Offset argument was null.'); assert(!offset.dx.isNaN && !offset.dy.isNaN, 'Offset argument contained a NaN value.'); return true; } // ignore: unused_element, Used in Shader assert. bool _matrix4IsValid(Float32List matrix4) { - assert(matrix4 != null, 'Matrix4 argument was null.'); // ignore: unnecessary_null_comparison + assert(matrix4 != null, 'Matrix4 argument was null.'); assert(matrix4.length == 16, 'Matrix4 must have 16 entries.'); return true; } @@ -37,7 +36,7 @@ Color _scaleAlpha(Color a, double factor) { } class Color { - const Color(int value) : this.value = value & 0xFFFFFFFF;// ignore: unnecessary_this + const Color(int value) : value = value & 0xFFFFFFFF; const Color.fromARGB(int a, int r, int g, int b) : value = (((a & 0xff) << 24) | ((r & 0xff) << 16) | @@ -94,7 +93,7 @@ class Color { } static Color? lerp(Color? a, Color? b, double t) { - assert(t != null); // ignore: unnecessary_null_comparison + assert(t != null); if (b == null) { if (a == null) { return null; @@ -146,7 +145,7 @@ class Color { } static int getAlphaFromOpacity(double opacity) { - assert(opacity != null); // ignore: unnecessary_null_comparison + assert(opacity != null); return (opacity.clamp(0.0, 1.0) * 255).round(); } @@ -233,7 +232,7 @@ enum Clip { } abstract class Paint { - factory Paint() => engine.useCanvasKit ? engine.CkPaint() : engine.SurfacePaint(); + factory Paint() => engine.renderer.createPaint(); static bool enableDithering = false; BlendMode get blendMode; set blendMode(BlendMode value); @@ -283,10 +282,13 @@ abstract class Gradient extends Shader { Float64List? matrix4, ]) { final Float32List? matrix = matrix4 == null ? null : engine.toMatrix32(matrix4); - return engine.useCanvasKit - ? engine.CkGradientLinear( - from, to, colors, colorStops, tileMode, matrix) - : engine.GradientLinear(from, to, colors, colorStops, tileMode, matrix); + return engine.renderer.createLinearGradient( + from, + to, + colors, + colorStops, + tileMode, + matrix); } factory Gradient.radial( @@ -304,17 +306,13 @@ abstract class Gradient extends Shader { // If focal == center and the focal radius is 0.0, it's still a regular radial gradient final Float32List? matrix32 = matrix4 != null ? engine.toMatrix32(matrix4) : null; if (focal == null || (focal == center && focalRadius == 0.0)) { - return engine.useCanvasKit - ? engine.CkGradientRadial(center, radius, colors, colorStops, tileMode, matrix32) - : engine.GradientRadial(center, radius, colors, colorStops, tileMode, matrix32); + return engine.renderer.createRadialGradient( + center, radius, colors, colorStops, tileMode, matrix32); } else { assert(center != Offset.zero || focal != Offset.zero); // will result in exception(s) in Skia side - return engine.useCanvasKit - ? engine.CkGradientConical( - focal, focalRadius, center, radius, colors, colorStops, tileMode, matrix32) - : engine.GradientConical( - focal, focalRadius, center, radius, colors, colorStops, tileMode, matrix32); + return engine.renderer.createConicalGradient( + focal, focalRadius, center, radius, colors, colorStops, tileMode, matrix32); } } factory Gradient.sweep( @@ -325,11 +323,14 @@ abstract class Gradient extends Shader { double startAngle = 0.0, double endAngle = math.pi * 2, Float64List? matrix4, - ]) => engine.useCanvasKit - ? engine.CkGradientSweep(center, colors, colorStops, tileMode, startAngle, - endAngle, matrix4 != null ? engine.toMatrix32(matrix4) : null) - : engine.GradientSweep(center, colors, colorStops, tileMode, startAngle, - endAngle, matrix4 != null ? engine.toMatrix32(matrix4) : null); + ]) => engine.renderer.createSweepGradient( + center, + colors, + colorStops, + tileMode, + startAngle, + endAngle, + matrix4 != null ? engine.toMatrix32(matrix4) : null); } abstract class Image { @@ -369,8 +370,8 @@ class MaskFilter { const MaskFilter.blur( this._style, this._sigma, - ) : assert(_style != null), // ignore: unnecessary_null_comparison - assert(_sigma != null); // ignore: unnecessary_null_comparison + ) : assert(_style != null), + assert(_sigma != null); final BlurStyle _style; final double _sigma; @@ -399,45 +400,31 @@ enum FilterQuality { } class ImageFilter { - factory ImageFilter.blur({double sigmaX = 0.0, double sigmaY = 0.0, TileMode tileMode = TileMode.clamp}) { - if (engine.useCanvasKit) { - return engine.CkImageFilter.blur(sigmaX: sigmaX, sigmaY: sigmaY, tileMode: tileMode); - } - // TODO(ferhat): implement TileMode. - return engine.EngineImageFilter.blur(sigmaX: sigmaX, sigmaY: sigmaY, tileMode: tileMode); - } + factory ImageFilter.blur({ + double sigmaX = 0.0, + double sigmaY = 0.0, + TileMode tileMode = TileMode.clamp + }) => engine.renderer.createBlurImageFilter( + sigmaX: sigmaX, + sigmaY: sigmaY, + tileMode: tileMode + ); - // ignore: avoid_unused_constructor_parameters - factory ImageFilter.dilate({ double radiusX = 0.0, double radiusY = 0.0 }) { - // TODO(fzyzcjy): implement dilate. https://github.com/flutter/flutter/issues/101085 - throw UnimplementedError( - 'ImageFilter.dilate not implemented for web platform.'); - } + factory ImageFilter.dilate({ double radiusX = 0.0, double radiusY = 0.0 }) => + engine.renderer.createDilateImageFilter(radiusX: radiusX, radiusY: radiusY); - // ignore: avoid_unused_constructor_parameters - factory ImageFilter.erode({ double radiusX = 0.0, double radiusY = 0.0 }) { - // TODO(fzyzcjy): implement erode. https://github.com/flutter/flutter/issues/101085 - throw UnimplementedError( - 'ImageFilter.erode not implemented for web platform.'); - } + factory ImageFilter.erode({ double radiusX = 0.0, double radiusY = 0.0 }) => + engine.renderer.createErodeImageFilter(radiusX: radiusX, radiusY: radiusY); factory ImageFilter.matrix(Float64List matrix4, {FilterQuality filterQuality = FilterQuality.low}) { if (matrix4.length != 16) { throw ArgumentError('"matrix4" must have 16 entries.'); } - if (engine.useCanvasKit) { - return engine.CkImageFilter.matrix(matrix: matrix4, filterQuality: filterQuality); - } - // TODO(yjbanov): implement FilterQuality. - return engine.EngineImageFilter.matrix(matrix: matrix4, filterQuality: filterQuality); + return engine.renderer.createMatrixImageFilter(matrix4, filterQuality: filterQuality); } - // TODO(ferhat): add implementation and remove the "ignore". - // ignore: avoid_unused_constructor_parameters - ImageFilter.compose({required ImageFilter outer, required ImageFilter inner}) { - throw UnimplementedError( - 'ImageFilter.compose not implemented for web platform.'); - } + factory ImageFilter.compose({required ImageFilter outer, required ImageFilter inner}) => + engine.renderer.composeImageFilters(outer: outer, inner: inner); } enum ImageByteFormat { @@ -478,48 +465,26 @@ Future instantiateImageCodec( int? targetWidth, int? targetHeight, bool allowUpscaling = true, -}) async { - if (engine.useCanvasKit) { - return engine.skiaInstantiateImageCodec(list, targetWidth, targetHeight); - } else { - final engine.DomBlob blob = engine.createDomBlob([list.buffer]); - return engine.HtmlBlobCodec(blob); - } -} +}) => engine.renderer.instantiateImageCodec( + list, + targetWidth: targetWidth, + targetHeight: targetHeight, + allowUpscaling: allowUpscaling); Future instantiateImageCodecFromBuffer( ImmutableBuffer buffer, { int? targetWidth, int? targetHeight, bool allowUpscaling = true, -}) async { - if (engine.useCanvasKit) { - return engine.skiaInstantiateImageCodec(buffer._list!, targetWidth, targetHeight); - } else { - final engine.DomBlob blob = engine.createDomBlob([buffer._list!.buffer]); - return engine.HtmlBlobCodec(blob); - } -} +}) => engine.renderer.instantiateImageCodec( + buffer._list!, + targetWidth: targetWidth, + targetHeight: targetHeight, + allowUpscaling: allowUpscaling); Future webOnlyInstantiateImageCodecFromUrl(Uri uri, - {engine.WebOnlyImageCodecChunkCallback? chunkCallback}) { - if (engine.useCanvasKit) { - return engine.skiaInstantiateWebImageCodec( - uri.toString(), chunkCallback); - } else { - return engine.futurize((engine.Callback callback) => - _instantiateImageCodecFromUrl(uri, chunkCallback, callback)); - } -} - -String? _instantiateImageCodecFromUrl( - Uri uri, - engine.WebOnlyImageCodecChunkCallback? chunkCallback, - engine.Callback callback, -) { - callback(engine.HtmlCodec(uri.toString(), chunkCallback: chunkCallback)); - return null; -} + {engine.WebOnlyImageCodecChunkCallback? chunkCallback}) => + engine.renderer.instantiateImageCodecFromUrl(uri, chunkCallback: chunkCallback); void decodeImageFromList(Uint8List list, ImageDecoderCallback callback) { _decodeImageFromListAsync(list, callback); @@ -536,7 +501,7 @@ Future _decodeImageFromListAsync(Uint8List list, ImageDecoderCallback call // The `pixels` should be the scanlined raw pixels, 4 bytes per pixel, from left // to right, then from top to down. The order of the 4 bytes of pixels is // decided by `format`. -Future _createBmp( +Future createBmp( Uint8List pixels, int width, int height, @@ -625,39 +590,24 @@ void decodeImageFromPixels( int? targetWidth, int? targetHeight, bool allowUpscaling = true, -}) { - if (engine.useCanvasKit) { - engine.skiaDecodeImageFromPixels( - pixels, - width, - height, - format, - callback, - rowBytes: rowBytes, - targetWidth: targetWidth, - targetHeight: targetHeight, - allowUpscaling: allowUpscaling, - ); - return; - } - - void executeCallback(Codec codec) { - codec.getNextFrame().then((FrameInfo frameInfo) { - callback(frameInfo.image); - }); - } - _createBmp(pixels, width, height, rowBytes ?? width, format).then( - executeCallback); - -} +}) => engine.renderer.decodeImageFromPixels( + pixels, + width, + height, + format, + callback, + rowBytes: rowBytes, + targetWidth: targetWidth, + targetHeight: targetHeight, + allowUpscaling: allowUpscaling); class Shadow { const Shadow({ this.color = const Color(_kColorDefault), this.offset = Offset.zero, this.blurRadius = 0.0, - }) : assert(color != null, 'Text shadow color was null.'), // ignore: unnecessary_null_comparison - assert(offset != null, 'Text shadow offset was null.'), // ignore: unnecessary_null_comparison + }) : assert(color != null, 'Text shadow color was null.'), + assert(offset != null, 'Text shadow offset was null.'), assert(blurRadius >= 0.0, 'Text shadow blur radius should be non-negative.'); static const int _kColorDefault = 0xFF000000; @@ -686,7 +636,7 @@ class Shadow { } static Shadow? lerp(Shadow? a, Shadow? b, double t) { - assert(t != null); // ignore: unnecessary_null_comparison + assert(t != null); if (b == null) { if (a == null) { return null; @@ -707,7 +657,7 @@ class Shadow { } static List? lerpList(List? a, List? b, double t) { - assert(t != null); // ignore: unnecessary_null_comparison + assert(t != null); if (a == null && b == null) { return null; } @@ -745,12 +695,14 @@ class Shadow { String toString() => 'TextShadow($color, $offset, $blurRadius)'; } -class ImageShader extends Shader { +abstract class ImageShader extends Shader { factory ImageShader(Image image, TileMode tmx, TileMode tmy, Float64List matrix4, { FilterQuality? filterQuality, - }) => engine.useCanvasKit - ? engine.CkImageShader(image, tmx, tmy, matrix4, filterQuality) - : engine.EngineImageShader(image, tmx, tmy, matrix4, filterQuality); + }) => engine.renderer.createImageShader(image, tmx, tmy, matrix4, filterQuality); + + void dispose(); + + bool get debugDisposed; } class ImmutableBuffer { @@ -768,7 +720,7 @@ class ImmutableBuffer { Uint8List? _list; int get length => _length; - int _length; + final int _length; bool get debugDisposed { late bool disposed; @@ -782,17 +734,6 @@ class ImmutableBuffer { } class ImageDescriptor { - ImageDescriptor._() - : _width = null, - _height = null, - _rowBytes = null, - _format = null; - static Future encoded(ImmutableBuffer buffer) async { - final ImageDescriptor descriptor = ImageDescriptor._(); - descriptor._data = buffer._list; - return descriptor; - } - // Not async because there's no expensive work to do here. ImageDescriptor.raw( ImmutableBuffer buffer, { @@ -807,6 +748,18 @@ class ImageDescriptor { _data = buffer._list; } + ImageDescriptor._() + : _width = null, + _height = null, + _rowBytes = null, + _format = null; + + static Future encoded(ImmutableBuffer buffer) async { + final ImageDescriptor descriptor = ImageDescriptor._(); + descriptor._data = buffer._list; + return descriptor; + } + Uint8List? _data; final int? _width; final int? _height; @@ -835,11 +788,13 @@ class ImageDescriptor { ); } - return _createBmp(_data!, width, height, _rowBytes ?? width, _format!); + return createBmp(_data!, width, height, _rowBytes ?? width, _format!); } } class FragmentProgram { + FragmentProgram._(); + static Future fromAsset(String assetKey) { throw UnsupportedError('FragmentProgram is not supported for the CanvasKit or HTML renderers.'); } @@ -848,8 +803,6 @@ class FragmentProgram { return Future.microtask(() => FragmentProgram.fromAsset(assetKey)); } - FragmentProgram._(); - Shader shader({ Float32List? floatUniforms, List? samplerUniforms, diff --git a/lib/web_ui/lib/path.dart b/lib/web_ui/lib/path.dart index 49cd141798048..47b80ecd084cc 100644 --- a/lib/web_ui/lib/path.dart +++ b/lib/web_ui/lib/path.dart @@ -5,23 +5,10 @@ part of ui; // For documentation see https://github.com/flutter/engine/blob/main/lib/ui/painting.dart -// ignore_for_file: public_member_api_docs abstract class Path { - factory Path() { - if (engine.useCanvasKit) { - return engine.CkPath(); - } else { - return engine.SurfacePath(); - } - } - factory Path.from(Path source) { - if (engine.useCanvasKit) { - return engine.CkPath.from(source as engine.CkPath); - } else { - return engine.SurfacePath.from(source as engine.SurfacePath); - } - } + factory Path() => engine.renderer.createPath(); + factory Path.from(Path source) => engine.renderer.copyPath(source); PathFillType get fillType; set fillType(PathFillType value); void moveTo(double x, double y); @@ -63,14 +50,8 @@ abstract class Path { Path transform(Float64List matrix4); // see https://skia.org/user/api/SkPath_Reference#SkPath_getBounds Rect getBounds(); - static Path combine(PathOperation operation, Path path1, Path path2) { - assert(path1 != null); // ignore: unnecessary_null_comparison - assert(path2 != null); // ignore: unnecessary_null_comparison - if (engine.useCanvasKit) { - return engine.CkPath.combine(operation, path1, path2); - } - throw UnimplementedError(); - } + static Path combine(PathOperation operation, Path path1, Path path2) => + engine.renderer.combinePaths(operation, path1, path2); PathMetrics computeMetrics({bool forceClosed = false}); } diff --git a/lib/web_ui/lib/path_metrics.dart b/lib/web_ui/lib/path_metrics.dart index d4324f1d4a476..5cf8df9552641 100644 --- a/lib/web_ui/lib/path_metrics.dart +++ b/lib/web_ui/lib/path_metrics.dart @@ -27,8 +27,8 @@ abstract class PathMetric { class Tangent { const Tangent(this.position, this.vector) - : assert(position != null), // ignore: unnecessary_null_comparison - assert(vector != null); // ignore: unnecessary_null_comparison + : assert(position != null), + assert(vector != null); factory Tangent.fromAngle(Offset position, double angle) { return Tangent(position, Offset(math.cos(angle), math.sin(angle))); } diff --git a/lib/web_ui/lib/platform_dispatcher.dart b/lib/web_ui/lib/platform_dispatcher.dart index c3cfcb4389e98..defcc6df15cdd 100644 --- a/lib/web_ui/lib/platform_dispatcher.dart +++ b/lib/web_ui/lib/platform_dispatcher.dart @@ -265,11 +265,11 @@ class FrameTiming { ]); } - static final int _dataLength = FramePhase.values.length + _FrameTimingInfo.values.length; - FrameTiming._(this._data) : assert(_data.length == _dataLength); + static final int _dataLength = FramePhase.values.length + _FrameTimingInfo.values.length; + int timestampInMicroseconds(FramePhase phase) => _data[phase.index]; Duration _rawDuration(FramePhase phase) => Duration(microseconds: _data[phase.index]); @@ -396,7 +396,7 @@ class Locale { const Locale( this._languageCode, [ this._countryCode, - ]) : assert(_languageCode != null), // ignore: unnecessary_null_comparison + ]) : assert(_languageCode != null), assert(_languageCode != ''), scriptCode = null; @@ -404,7 +404,7 @@ class Locale { String languageCode = 'und', this.scriptCode, String? countryCode, - }) : assert(languageCode != null), // ignore: unnecessary_null_comparison + }) : assert(languageCode != null), assert(languageCode != ''), _languageCode = languageCode, assert(scriptCode != ''), diff --git a/lib/web_ui/lib/pointer.dart b/lib/web_ui/lib/pointer.dart index 9ae2377904301..3106f903d34cd 100644 --- a/lib/web_ui/lib/pointer.dart +++ b/lib/web_ui/lib/pointer.dart @@ -151,6 +151,6 @@ class PointerData { class PointerDataPacket { const PointerDataPacket({this.data = const []}) - : assert(data != null); // ignore: unnecessary_null_comparison + : assert(data != null); final List data; } diff --git a/lib/web_ui/lib/semantics.dart b/lib/web_ui/lib/semantics.dart index 9a4ce2ccf91e8..10db1cfcc76c4 100644 --- a/lib/web_ui/lib/semantics.dart +++ b/lib/web_ui/lib/semantics.dart @@ -5,7 +5,7 @@ part of ui; class SemanticsAction { - const SemanticsAction._(this.index) : assert(index != null); // ignore: unnecessary_null_comparison + const SemanticsAction._(this.index) : assert(index != null); static const int _kTapIndex = 1 << 0; static const int _kLongPressIndex = 1 << 1; @@ -134,7 +134,7 @@ class SemanticsAction { } class SemanticsFlag { - const SemanticsFlag._(this.index) : assert(index != null); // ignore: unnecessary_null_comparison + const SemanticsFlag._(this.index) : assert(index != null); final int index; diff --git a/lib/web_ui/lib/src/engine.dart b/lib/web_ui/lib/src/engine.dart index 166f78ffac659..bbf9a5aacc5a4 100644 --- a/lib/web_ui/lib/src/engine.dart +++ b/lib/web_ui/lib/src/engine.dart @@ -26,19 +26,20 @@ export 'engine/canvaskit/canvaskit_canvas.dart'; export 'engine/canvaskit/color_filter.dart'; export 'engine/canvaskit/embedded_views.dart'; export 'engine/canvaskit/embedded_views_diff.dart'; +export 'engine/canvaskit/font_fallback_data.dart'; export 'engine/canvaskit/font_fallbacks.dart'; export 'engine/canvaskit/fonts.dart'; export 'engine/canvaskit/image.dart'; export 'engine/canvaskit/image_filter.dart'; export 'engine/canvaskit/image_wasm_codecs.dart'; export 'engine/canvaskit/image_web_codecs.dart'; -export 'engine/canvaskit/initialization.dart'; export 'engine/canvaskit/interval_tree.dart'; export 'engine/canvaskit/layer.dart'; export 'engine/canvaskit/layer_scene_builder.dart'; export 'engine/canvaskit/layer_tree.dart'; export 'engine/canvaskit/mask_filter.dart'; export 'engine/canvaskit/n_way_canvas.dart'; +export 'engine/canvaskit/noto_font.dart'; export 'engine/canvaskit/painting.dart'; export 'engine/canvaskit/path.dart'; export 'engine/canvaskit/path_metrics.dart'; @@ -46,6 +47,7 @@ export 'engine/canvaskit/picture.dart'; export 'engine/canvaskit/picture_recorder.dart'; export 'engine/canvaskit/raster_cache.dart'; export 'engine/canvaskit/rasterizer.dart'; +export 'engine/canvaskit/renderer.dart'; export 'engine/canvaskit/shader.dart'; export 'engine/canvaskit/skia_object_cache.dart'; export 'engine/canvaskit/surface.dart'; @@ -60,6 +62,7 @@ export 'engine/dom.dart'; export 'engine/embedder.dart'; export 'engine/engine_canvas.dart'; export 'engine/font_change_util.dart'; +export 'engine/fonts.dart'; export 'engine/frame_reference.dart'; export 'engine/host_node.dart'; export 'engine/html/backdrop_filter.dart'; @@ -88,6 +91,7 @@ export 'engine/html/picture.dart'; export 'engine/html/platform_view.dart'; export 'engine/html/recording_canvas.dart'; export 'engine/html/render_vertices.dart'; +export 'engine/html/renderer.dart'; export 'engine/html/scene.dart'; export 'engine/html/scene_builder.dart'; export 'engine/html/shader_mask.dart'; @@ -121,6 +125,7 @@ export 'engine/plugins.dart'; export 'engine/pointer_binding.dart'; export 'engine/pointer_converter.dart'; export 'engine/profiler.dart'; +export 'engine/renderer.dart'; export 'engine/rrect_renderer.dart'; export 'engine/safe_browser_api.dart'; export 'engine/semantics/accessibility.dart'; diff --git a/lib/web_ui/lib/src/engine/assets.dart b/lib/web_ui/lib/src/engine/assets.dart index 2a3617f036eff..ed87697b6e9f6 100644 --- a/lib/web_ui/lib/src/engine/assets.dart +++ b/lib/web_ui/lib/src/engine/assets.dart @@ -14,14 +14,14 @@ import 'util.dart'; /// The assets are resolved relative to [assetsDir] inside the directory /// containing the currently executing JS script. class AssetManager { + /// Initializes [AssetManager] with path to assets relative to baseUrl. + const AssetManager({this.assetsDir = _defaultAssetsDir}); + static const String _defaultAssetsDir = 'assets'; /// The directory containing the assets. final String assetsDir; - /// Initializes [AssetManager] with path to assets relative to baseUrl. - const AssetManager({this.assetsDir = _defaultAssetsDir}); - String? get _baseUrl { return domWindow.document .querySelectorAll('meta') @@ -92,15 +92,15 @@ class AssetManager { /// Thrown to indicate http failure during asset loading. class AssetManagerException implements Exception { + /// Initializes exception with request url and http status. + AssetManagerException(this.url, this.httpStatus); + /// Http request url for asset. final String url; /// Http status of response. final int httpStatus; - /// Initializes exception with request url and http status. - AssetManagerException(this.url, this.httpStatus); - @override String toString() => 'Failed to load asset at "$url" ($httpStatus)'; } diff --git a/lib/web_ui/lib/src/engine/browser_detection.dart b/lib/web_ui/lib/src/engine/browser_detection.dart index 24ab65bcfebd1..6e263e2ac5593 100644 --- a/lib/web_ui/lib/src/engine/browser_detection.dart +++ b/lib/web_ui/lib/src/engine/browser_detection.dart @@ -236,8 +236,16 @@ bool get isIOS15 { domWindow.navigator.userAgent.contains('OS 15_'); } +/// If set to true pretends that the current browser is iOS Safari. +/// +/// Useful for tests. Do not use in production code. +@visibleForTesting +bool debugEmulateIosSafari = false; + /// Returns true if the browser is iOS Safari, false otherwise. -bool get isIosSafari => +bool get isIosSafari => debugEmulateIosSafari || _isActualIosSafari; + +bool get _isActualIosSafari => browserEngine == BrowserEngine.webkit && operatingSystem == OperatingSystem.iOs; diff --git a/lib/web_ui/lib/src/engine/canvas_pool.dart b/lib/web_ui/lib/src/engine/canvas_pool.dart index dd59a4d141e9f..08e8aca82172d 100644 --- a/lib/web_ui/lib/src/engine/canvas_pool.dart +++ b/lib/web_ui/lib/src/engine/canvas_pool.dart @@ -45,6 +45,10 @@ import 'window.dart'; /// can be reused, [CanvasPool] will move canvas(s) from pool to reusablePool /// to prevent reallocation. class CanvasPool extends _SaveStackTracking { + /// Initializes canvas pool for target size and dpi. + CanvasPool(this._widthInBitmapPixels, this._heightInBitmapPixels, + this._density); + DomCanvasRenderingContext2D? _context; ContextStateHandle? _contextHandle; final int _widthInBitmapPixels, _heightInBitmapPixels; @@ -59,10 +63,6 @@ class CanvasPool extends _SaveStackTracking { int _saveContextCount = 0; final double _density; - /// Initializes canvas pool for target size and dpi. - CanvasPool(this._widthInBitmapPixels, this._heightInBitmapPixels, - this._density); - /// Initializes canvas pool to be hosted on a surface. void mount(DomHTMLElement rootElement) { _rootElement = rootElement; @@ -607,7 +607,7 @@ class CanvasPool extends _SaveStackTracking { } // Float buffer used for path iteration. - static Float32List _runBuffer = Float32List(PathRefIterator.kMaxBufferSize); + static final Float32List _runBuffer = Float32List(PathRefIterator.kMaxBufferSize); /// 'Runs' the given [path] by applying all of its commands to the canvas. void _runPath(DomCanvasRenderingContext2D ctx, SurfacePath path) { @@ -872,14 +872,14 @@ class CanvasPool extends _SaveStackTracking { /// See https://www.w3.org/TR/2dcontext/ for defaults used in this class /// to initialize current values. class ContextStateHandle { + /// Initializes context state for a [CanvasPool]. + ContextStateHandle(this._canvasPool, this.context, this.density); + /// Associated canvas element context tracked by this context state. final DomCanvasRenderingContext2D context; final CanvasPool _canvasPool; /// Dpi of context. final double density; - - /// Initializes context state for a [CanvasPool]. - ContextStateHandle(this._canvasPool, this.context, this.density); ui.BlendMode? _currentBlendMode = ui.BlendMode.srcOver; ui.StrokeCap? _currentStrokeCap = ui.StrokeCap.butt; ui.StrokeJoin? _currentStrokeJoin = ui.StrokeJoin.miter; diff --git a/lib/web_ui/lib/src/engine/canvaskit/canvas.dart b/lib/web_ui/lib/src/engine/canvaskit/canvas.dart index eacb0db6831b4..b499f95abe3a6 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/canvas.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/canvas.dart @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// ignore_for_file: public_member_api_docs import 'dart:math' as math; import 'dart:typed_data'; @@ -27,6 +26,8 @@ final SkClipOp _clipOpIntersect = canvasKit.ClipOp.Intersect; /// This is intentionally not memory-managing the underlying [SkCanvas]. See /// the docs on [SkCanvas], which explain the reason. class CkCanvas { + CkCanvas(this.skCanvas); + // Cubic equation coefficients recommended by Mitchell & Netravali // in their paper on cubic interpolation. static const double _kMitchellNetravali_B = 1.0 / 3.0; @@ -34,8 +35,6 @@ class CkCanvas { final SkCanvas skCanvas; - CkCanvas(this.skCanvas); - int? get saveCount => skCanvas.getSaveCount(); void clear(ui.Color color) { @@ -654,11 +653,11 @@ class CkRestoreToCountCommand extends CkPaintCommand { } class CkTranslateCommand extends CkPaintCommand { + CkTranslateCommand(this.dx, this.dy); + final double dx; final double dy; - CkTranslateCommand(this.dx, this.dy); - @override void apply(SkCanvas canvas) { canvas.translate(dx, dy); @@ -666,11 +665,11 @@ class CkTranslateCommand extends CkPaintCommand { } class CkScaleCommand extends CkPaintCommand { + CkScaleCommand(this.sx, this.sy); + final double sx; final double sy; - CkScaleCommand(this.sx, this.sy); - @override void apply(SkCanvas canvas) { canvas.scale(sx, sy); @@ -678,10 +677,10 @@ class CkScaleCommand extends CkPaintCommand { } class CkRotateCommand extends CkPaintCommand { - final double radians; - CkRotateCommand(this.radians); + final double radians; + @override void apply(SkCanvas canvas) { canvas.rotate(radians * 180.0 / math.pi, 0.0, 0.0); @@ -689,10 +688,10 @@ class CkRotateCommand extends CkPaintCommand { } class CkTransformCommand extends CkPaintCommand { - final Float32List matrix4; - CkTransformCommand(this.matrix4); + final Float32List matrix4; + @override void apply(SkCanvas canvas) { canvas.concat(toSkM44FromFloat32(matrix4)); @@ -700,11 +699,11 @@ class CkTransformCommand extends CkPaintCommand { } class CkSkewCommand extends CkPaintCommand { + CkSkewCommand(this.sx, this.sy); + final double sx; final double sy; - CkSkewCommand(this.sx, this.sy); - @override void apply(SkCanvas canvas) { canvas.skew(sx, sy); @@ -712,12 +711,12 @@ class CkSkewCommand extends CkPaintCommand { } class CkClipRectCommand extends CkPaintCommand { + CkClipRectCommand(this.rect, this.clipOp, this.doAntiAlias); + final ui.Rect rect; final ui.ClipOp clipOp; final bool doAntiAlias; - CkClipRectCommand(this.rect, this.clipOp, this.doAntiAlias); - @override void apply(SkCanvas canvas) { canvas.clipRect( @@ -776,11 +775,11 @@ class CkDrawAtlasCommand extends CkPaintCommand { } class CkClipRRectCommand extends CkPaintCommand { + CkClipRRectCommand(this.rrect, this.doAntiAlias); + final ui.RRect rrect; final bool doAntiAlias; - CkClipRRectCommand(this.rrect, this.doAntiAlias); - @override void apply(SkCanvas canvas) { canvas.clipRRect( @@ -792,11 +791,11 @@ class CkClipRRectCommand extends CkPaintCommand { } class CkClipPathCommand extends CkPaintCommand { + CkClipPathCommand(this.path, this.doAntiAlias); + final CkPath path; final bool doAntiAlias; - CkClipPathCommand(this.path, this.doAntiAlias); - @override void apply(SkCanvas canvas) { canvas.clipPath( @@ -808,11 +807,11 @@ class CkClipPathCommand extends CkPaintCommand { } class CkDrawColorCommand extends CkPaintCommand { + CkDrawColorCommand(this.color, this.blendMode); + final ui.Color color; final ui.BlendMode blendMode; - CkDrawColorCommand(this.color, this.blendMode); - @override void apply(SkCanvas canvas) { canvas.drawColorInt( @@ -823,12 +822,12 @@ class CkDrawColorCommand extends CkPaintCommand { } class CkDrawLineCommand extends CkPaintCommand { + CkDrawLineCommand(this.p1, this.p2, this.paint); + final ui.Offset p1; final ui.Offset p2; final CkPaint paint; - CkDrawLineCommand(this.p1, this.p2, this.paint); - @override void apply(SkCanvas canvas) { canvas.drawLine( @@ -842,10 +841,10 @@ class CkDrawLineCommand extends CkPaintCommand { } class CkDrawPaintCommand extends CkPaintCommand { - final CkPaint paint; - CkDrawPaintCommand(this.paint); + final CkPaint paint; + @override void apply(SkCanvas canvas) { canvas.drawPaint(paint.skiaObject); @@ -853,10 +852,11 @@ class CkDrawPaintCommand extends CkPaintCommand { } class CkDrawVerticesCommand extends CkPaintCommand { + CkDrawVerticesCommand(this.vertices, this.blendMode, this.paint); + final CkVertices vertices; final ui.BlendMode blendMode; final CkPaint paint; - CkDrawVerticesCommand(this.vertices, this.blendMode, this.paint); @override void apply(SkCanvas canvas) { @@ -869,10 +869,11 @@ class CkDrawVerticesCommand extends CkPaintCommand { } class CkDrawPointsCommand extends CkPaintCommand { + CkDrawPointsCommand(this.pointMode, this.points, this.paint); + final Float32List points; final ui.PointMode pointMode; final CkPaint paint; - CkDrawPointsCommand(this.pointMode, this.points, this.paint); @override void apply(SkCanvas canvas) { @@ -885,11 +886,11 @@ class CkDrawPointsCommand extends CkPaintCommand { } class CkDrawRectCommand extends CkPaintCommand { + CkDrawRectCommand(this.rect, this.paint); + final ui.Rect rect; final CkPaint paint; - CkDrawRectCommand(this.rect, this.paint); - @override void apply(SkCanvas canvas) { canvas.drawRect(toSkRect(rect), paint.skiaObject); @@ -897,11 +898,11 @@ class CkDrawRectCommand extends CkPaintCommand { } class CkDrawRRectCommand extends CkPaintCommand { + CkDrawRRectCommand(this.rrect, this.paint); + final ui.RRect rrect; final CkPaint paint; - CkDrawRRectCommand(this.rrect, this.paint); - @override void apply(SkCanvas canvas) { canvas.drawRRect( @@ -912,12 +913,12 @@ class CkDrawRRectCommand extends CkPaintCommand { } class CkDrawDRRectCommand extends CkPaintCommand { + CkDrawDRRectCommand(this.outer, this.inner, this.paint); + final ui.RRect outer; final ui.RRect inner; final CkPaint paint; - CkDrawDRRectCommand(this.outer, this.inner, this.paint); - @override void apply(SkCanvas canvas) { canvas.drawDRRect( @@ -929,11 +930,11 @@ class CkDrawDRRectCommand extends CkPaintCommand { } class CkDrawOvalCommand extends CkPaintCommand { + CkDrawOvalCommand(this.rect, this.paint); + final ui.Rect rect; final CkPaint paint; - CkDrawOvalCommand(this.rect, this.paint); - @override void apply(SkCanvas canvas) { canvas.drawOval( @@ -944,12 +945,12 @@ class CkDrawOvalCommand extends CkPaintCommand { } class CkDrawCircleCommand extends CkPaintCommand { + CkDrawCircleCommand(this.c, this.radius, this.paint); + final ui.Offset c; final double radius; final CkPaint paint; - CkDrawCircleCommand(this.c, this.radius, this.paint); - @override void apply(SkCanvas canvas) { canvas.drawCircle( @@ -962,11 +963,11 @@ class CkDrawCircleCommand extends CkPaintCommand { } class CkDrawPathCommand extends CkPaintCommand { + CkDrawPathCommand(this.path, this.paint); + final CkPath path; final CkPaint paint; - CkDrawPathCommand(this.path, this.paint); - @override void apply(SkCanvas canvas) { canvas.drawPath(path.skiaObject, paint.skiaObject); @@ -990,13 +991,13 @@ class CkDrawShadowCommand extends CkPaintCommand { } class CkDrawImageCommand extends CkPaintCommand { + CkDrawImageCommand(CkImage ckImage, this.offset, this.paint) + : image = ckImage.clone(); + final CkImage image; final ui.Offset offset; final CkPaint paint; - CkDrawImageCommand(CkImage ckImage, this.offset, this.paint) - : image = ckImage.clone(); - @override void apply(SkCanvas canvas) { final ui.FilterQuality filterQuality = paint.filterQuality; @@ -1028,14 +1029,14 @@ class CkDrawImageCommand extends CkPaintCommand { } class CkDrawImageRectCommand extends CkPaintCommand { + CkDrawImageRectCommand(CkImage ckImage, this.src, this.dst, this.paint) + : image = ckImage.clone(); + final CkImage image; final ui.Rect src; final ui.Rect dst; final CkPaint paint; - CkDrawImageRectCommand(CkImage ckImage, this.src, this.dst, this.paint) - : image = ckImage.clone(); - @override void apply(SkCanvas canvas) { final ui.FilterQuality filterQuality = paint.filterQuality; @@ -1093,11 +1094,11 @@ class CkDrawImageNineCommand extends CkPaintCommand { } class CkDrawParagraphCommand extends CkPaintCommand { + CkDrawParagraphCommand(this.paragraph, this.offset); + final CkParagraph paragraph; final ui.Offset offset; - CkDrawParagraphCommand(this.paragraph, this.offset); - @override void apply(SkCanvas canvas) { canvas.drawParagraph( diff --git a/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart b/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart index 1652d51047ea2..6fdcc06831c93 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart @@ -7,8 +7,6 @@ /// Prefer keeping the original CanvasKit names so it is easier to locate /// the API behind these bindings in the Skia source code. // ignore_for_file: non_constant_identifier_names - -// ignore_for_file: public_member_api_docs @JS() library canvaskit_api; @@ -19,7 +17,9 @@ import 'dart:typed_data'; import 'package:js/js.dart'; import 'package:ui/ui.dart' as ui; +import '../configuration.dart'; import '../dom.dart'; +import '../initialization.dart'; import '../profiler.dart'; /// Entrypoint into the CanvasKit API. @@ -109,6 +109,7 @@ extension CanvasKitExtension on CanvasKit { external SkParagraphStyle ParagraphStyle( SkParagraphStyleProperties properties); external SkTextStyle TextStyle(SkTextStyleProperties properties); + external SkSurface MakeWebGLCanvasSurface(DomCanvasElement canvas); external SkSurface MakeSurface( int width, int height, @@ -2112,7 +2113,7 @@ class TypefaceFontProvider extends SkFontMgr { external factory TypefaceFontProvider(); } -extension TypefaceFontProviderExtension on SkFontMgr { +extension TypefaceFontProviderExtension on TypefaceFontProvider { external void registerFont(Uint8List font, String family); } @@ -2613,3 +2614,49 @@ void patchCanvasKitModule(DomHTMLScriptElement canvasKitScript) { } domDocument.head!.appendChild(canvasKitScript); } + +String get canvasKitBuildUrl => + configuration.canvasKitBaseUrl + (kProfileMode ? 'profiling/' : ''); +String get canvasKitJavaScriptBindingsUrl => + '${canvasKitBuildUrl}canvaskit.js'; +String canvasKitWasmModuleUrl(String canvasKitBase, String file) => + canvasKitBase + file; + +/// Download and initialize the CanvasKit module. +/// +/// Downloads the CanvasKit JavaScript, then calls `CanvasKitInit` to download +/// and intialize the CanvasKit wasm. +Future downloadCanvasKit() async { + await _downloadCanvasKitJs(); + final Completer canvasKitInitCompleter = Completer(); + final CanvasKitInitPromise canvasKitInitPromise = + CanvasKitInit(CanvasKitInitOptions( + locateFile: allowInterop((String file, String unusedBase) => + canvasKitWasmModuleUrl(canvasKitBuildUrl, file)), + )); + canvasKitInitPromise.then(allowInterop((CanvasKit ck) { + canvasKitInitCompleter.complete(ck); + })); + return canvasKitInitCompleter.future; +} + +/// Downloads the CanvasKit JavaScript file at [canvasKitBase]. +Future _downloadCanvasKitJs() { + final String canvasKitJavaScriptUrl = canvasKitJavaScriptBindingsUrl; + + final DomHTMLScriptElement canvasKitScript = createDomHTMLScriptElement(); + canvasKitScript.src = canvasKitJavaScriptUrl; + + final Completer canvasKitLoadCompleter = Completer(); + late DomEventListener callback; + void loadEventHandler(DomEvent _) { + canvasKitLoadCompleter.complete(); + canvasKitScript.removeEventListener('load', callback); + } + callback = allowInterop(loadEventHandler); + canvasKitScript.addEventListener('load', callback); + + patchCanvasKitModule(canvasKitScript); + + return canvasKitLoadCompleter.future; +} diff --git a/lib/web_ui/lib/src/engine/canvaskit/canvaskit_canvas.dart b/lib/web_ui/lib/src/engine/canvaskit/canvaskit_canvas.dart index ccb3268f47918..87c94858dd2c7 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/canvaskit_canvas.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/canvaskit_canvas.dart @@ -21,10 +21,8 @@ import 'vertices.dart'; /// An implementation of [ui.Canvas] that is backed by a CanvasKit canvas. class CanvasKitCanvas implements ui.Canvas { - final CkCanvas _canvas; - factory CanvasKitCanvas(ui.PictureRecorder recorder, [ui.Rect? cullRect]) { - assert(recorder != null); // ignore: unnecessary_null_comparison + assert(recorder != null); if (recorder.isRecording) { throw ArgumentError( '"recorder" must not already be associated with another Canvas.'); @@ -36,6 +34,8 @@ class CanvasKitCanvas implements ui.Canvas { CanvasKitCanvas._(this._canvas); + final CkCanvas _canvas; + @override void save() { _canvas.save(); @@ -43,7 +43,7 @@ class CanvasKitCanvas implements ui.Canvas { @override void saveLayer(ui.Rect? bounds, ui.Paint paint) { - assert(paint != null); // ignore: unnecessary_null_comparison + assert(paint != null); if (bounds == null) { _saveLayerWithoutBounds(paint); } else { @@ -94,7 +94,7 @@ class CanvasKitCanvas implements ui.Canvas { @override void transform(Float64List matrix4) { - assert(matrix4 != null); // ignore: unnecessary_null_comparison + assert(matrix4 != null); if (matrix4.length != 16) { throw ArgumentError('"matrix4" must have 16 entries.'); } @@ -114,8 +114,8 @@ class CanvasKitCanvas implements ui.Canvas { void clipRect(ui.Rect rect, {ui.ClipOp clipOp = ui.ClipOp.intersect, bool doAntiAlias = true}) { assert(rectIsValid(rect)); - assert(clipOp != null); // ignore: unnecessary_null_comparison - assert(doAntiAlias != null); // ignore: unnecessary_null_comparison + assert(clipOp != null); + assert(doAntiAlias != null); _clipRect(rect, clipOp, doAntiAlias); } @@ -126,7 +126,7 @@ class CanvasKitCanvas implements ui.Canvas { @override void clipRRect(ui.RRect rrect, {bool doAntiAlias = true}) { assert(rrectIsValid(rrect)); - assert(doAntiAlias != null); // ignore: unnecessary_null_comparison + assert(doAntiAlias != null); _clipRRect(rrect, doAntiAlias); } @@ -136,9 +136,8 @@ class CanvasKitCanvas implements ui.Canvas { @override void clipPath(ui.Path path, {bool doAntiAlias = true}) { - // ignore: unnecessary_null_comparison assert(path != null); // path is checked on the engine side - assert(doAntiAlias != null); // ignore: unnecessary_null_comparison + assert(doAntiAlias != null); _canvas.clipPath(path as CkPath, doAntiAlias); } @@ -159,8 +158,8 @@ class CanvasKitCanvas implements ui.Canvas { @override void drawColor(ui.Color color, ui.BlendMode blendMode) { - assert(color != null); // ignore: unnecessary_null_comparison - assert(blendMode != null); // ignore: unnecessary_null_comparison + assert(color != null); + assert(blendMode != null); _drawColor(color, blendMode); } @@ -172,7 +171,7 @@ class CanvasKitCanvas implements ui.Canvas { void drawLine(ui.Offset p1, ui.Offset p2, ui.Paint paint) { assert(offsetIsValid(p1)); assert(offsetIsValid(p2)); - assert(paint != null); // ignore: unnecessary_null_comparison + assert(paint != null); _drawLine(p1, p2, paint); } @@ -182,7 +181,7 @@ class CanvasKitCanvas implements ui.Canvas { @override void drawPaint(ui.Paint paint) { - assert(paint != null); // ignore: unnecessary_null_comparison + assert(paint != null); _drawPaint(paint); } @@ -193,7 +192,7 @@ class CanvasKitCanvas implements ui.Canvas { @override void drawRect(ui.Rect rect, ui.Paint paint) { assert(rectIsValid(rect)); - assert(paint != null); // ignore: unnecessary_null_comparison + assert(paint != null); _drawRect(rect, paint); } @@ -204,7 +203,7 @@ class CanvasKitCanvas implements ui.Canvas { @override void drawRRect(ui.RRect rrect, ui.Paint paint) { assert(rrectIsValid(rrect)); - assert(paint != null); // ignore: unnecessary_null_comparison + assert(paint != null); _drawRRect(rrect, paint); } @@ -216,7 +215,7 @@ class CanvasKitCanvas implements ui.Canvas { void drawDRRect(ui.RRect outer, ui.RRect inner, ui.Paint paint) { assert(rrectIsValid(outer)); assert(rrectIsValid(inner)); - assert(paint != null); // ignore: unnecessary_null_comparison + assert(paint != null); _drawDRRect(outer, inner, paint); } @@ -227,7 +226,7 @@ class CanvasKitCanvas implements ui.Canvas { @override void drawOval(ui.Rect rect, ui.Paint paint) { assert(rectIsValid(rect)); - assert(paint != null); // ignore: unnecessary_null_comparison + assert(paint != null); _drawOval(rect, paint); } @@ -238,7 +237,7 @@ class CanvasKitCanvas implements ui.Canvas { @override void drawCircle(ui.Offset c, double radius, ui.Paint paint) { assert(offsetIsValid(c)); - assert(paint != null); // ignore: unnecessary_null_comparison + assert(paint != null); _drawCircle(c, radius, paint); } @@ -250,7 +249,7 @@ class CanvasKitCanvas implements ui.Canvas { void drawArc(ui.Rect rect, double startAngle, double sweepAngle, bool useCenter, ui.Paint paint) { assert(rectIsValid(rect)); - assert(paint != null); // ignore: unnecessary_null_comparison + assert(paint != null); _drawArc(rect, startAngle, sweepAngle, useCenter, paint); } @@ -261,52 +260,47 @@ class CanvasKitCanvas implements ui.Canvas { @override void drawPath(ui.Path path, ui.Paint paint) { - // ignore: unnecessary_null_comparison assert(path != null); // path is checked on the engine side - assert(paint != null); // ignore: unnecessary_null_comparison + assert(paint != null); _canvas.drawPath(path as CkPath, paint as CkPaint); } @override void drawImage(ui.Image image, ui.Offset p, ui.Paint paint) { - // ignore: unnecessary_null_comparison assert(image != null); // image is checked on the engine side assert(offsetIsValid(p)); - assert(paint != null); // ignore: unnecessary_null_comparison + assert(paint != null); _canvas.drawImage(image as CkImage, p, paint as CkPaint); } @override void drawImageRect(ui.Image image, ui.Rect src, ui.Rect dst, ui.Paint paint) { - // ignore: unnecessary_null_comparison assert(image != null); // image is checked on the engine side assert(rectIsValid(src)); assert(rectIsValid(dst)); - assert(paint != null); // ignore: unnecessary_null_comparison + assert(paint != null); _canvas.drawImageRect(image as CkImage, src, dst, paint as CkPaint); } @override void drawImageNine( ui.Image image, ui.Rect center, ui.Rect dst, ui.Paint paint) { - // ignore: unnecessary_null_comparison assert(image != null); // image is checked on the engine side assert(rectIsValid(center)); assert(rectIsValid(dst)); - assert(paint != null); // ignore: unnecessary_null_comparison + assert(paint != null); _canvas.drawImageNine(image as CkImage, center, dst, paint as CkPaint); } @override void drawPicture(ui.Picture picture) { - // ignore: unnecessary_null_comparison assert(picture != null); // picture is checked on the engine side _canvas.drawPicture(picture as CkPicture); } @override void drawParagraph(ui.Paragraph paragraph, ui.Offset offset) { - assert(paragraph != null); // ignore: unnecessary_null_comparison + assert(paragraph != null); assert(offsetIsValid(offset)); _drawParagraph(paragraph, offset); } @@ -318,9 +312,9 @@ class CanvasKitCanvas implements ui.Canvas { @override void drawPoints( ui.PointMode pointMode, List points, ui.Paint paint) { - assert(pointMode != null); // ignore: unnecessary_null_comparison - assert(points != null); // ignore: unnecessary_null_comparison - assert(paint != null); // ignore: unnecessary_null_comparison + assert(pointMode != null); + assert(points != null); + assert(paint != null); final SkFloat32List skPoints = toMallocedSkPoints(points); _canvas.drawPoints( paint as CkPaint, @@ -333,9 +327,9 @@ class CanvasKitCanvas implements ui.Canvas { @override void drawRawPoints( ui.PointMode pointMode, Float32List points, ui.Paint paint) { - assert(pointMode != null); // ignore: unnecessary_null_comparison - assert(points != null); // ignore: unnecessary_null_comparison - assert(paint != null); // ignore: unnecessary_null_comparison + assert(pointMode != null); + assert(points != null); + assert(paint != null); if (points.length % 2 != 0) { throw ArgumentError('"points" must have an even number of values.'); } @@ -349,10 +343,9 @@ class CanvasKitCanvas implements ui.Canvas { @override void drawVertices( ui.Vertices vertices, ui.BlendMode blendMode, ui.Paint paint) { - // ignore: unnecessary_null_comparison assert(vertices != null); // vertices is checked on the engine side - assert(paint != null); // ignore: unnecessary_null_comparison - assert(blendMode != null); // ignore: unnecessary_null_comparison + assert(paint != null); + assert(blendMode != null); _canvas.drawVertices(vertices as CkVertices, blendMode, paint as CkPaint); } @@ -365,12 +358,11 @@ class CanvasKitCanvas implements ui.Canvas { ui.BlendMode? blendMode, ui.Rect? cullRect, ui.Paint paint) { - // ignore: unnecessary_null_comparison assert(atlas != null); // atlas is checked on the engine side - assert(transforms != null); // ignore: unnecessary_null_comparison - assert(rects != null); // ignore: unnecessary_null_comparison + assert(transforms != null); + assert(rects != null); assert(colors == null || colors.isEmpty || blendMode != null); - assert(paint != null); // ignore: unnecessary_null_comparison + assert(paint != null); final int rectCount = rects.length; if (transforms.length != rectCount) { @@ -418,12 +410,11 @@ class CanvasKitCanvas implements ui.Canvas { ui.BlendMode? blendMode, ui.Rect? cullRect, ui.Paint paint) { - // ignore: unnecessary_null_comparison assert(atlas != null); // atlas is checked on the engine side - assert(rstTransforms != null); // ignore: unnecessary_null_comparison - assert(rects != null); // ignore: unnecessary_null_comparison + assert(rstTransforms != null); + assert(rects != null); assert(colors == null || blendMode != null); - assert(paint != null); // ignore: unnecessary_null_comparison + assert(paint != null); final int rectCount = rects.length; if (rstTransforms.length != rectCount) { @@ -464,10 +455,9 @@ class CanvasKitCanvas implements ui.Canvas { @override void drawShadow(ui.Path path, ui.Color color, double elevation, bool transparentOccluder) { - // ignore: unnecessary_null_comparison assert(path != null); // path is checked on the engine side - assert(color != null); // ignore: unnecessary_null_comparison - assert(transparentOccluder != null); // ignore: unnecessary_null_comparison + assert(color != null); + assert(transparentOccluder != null); _drawShadow(path, color, elevation, transparentOccluder); } diff --git a/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart b/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart index c3ef04ac63a95..9e1f48e8bb189 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart @@ -15,18 +15,20 @@ import '../vector_math.dart'; import '../window.dart'; import 'canvas.dart'; import 'embedded_views_diff.dart'; -import 'initialization.dart'; import 'path.dart'; import 'picture_recorder.dart'; +import 'renderer.dart'; import 'surface.dart'; import 'surface_factory.dart'; /// This composites HTML views into the [ui.Scene]. class HtmlViewEmbedder { + HtmlViewEmbedder._(); + /// The [HtmlViewEmbedder] singleton. static HtmlViewEmbedder instance = HtmlViewEmbedder._(); - HtmlViewEmbedder._(); + DomElement get skiaSceneHost => CanvasKitRenderer.instance.sceneHost!; /// Force the view embedder to disable overlays. /// @@ -92,10 +94,10 @@ class HtmlViewEmbedder { final Set _viewsToRecomposite = {}; /// The list of view ids that should be composited, in order. - List _compositionOrder = []; + final List _compositionOrder = []; /// The most recent composition order. - List _activeCompositionOrder = []; + final List _activeCompositionOrder = []; /// The size of the frame, in physical pixels. ui.Size _frameSize = ui.window.physicalSize; @@ -270,7 +272,7 @@ class HtmlViewEmbedder { // If the chain was previously attached, attach it to the same position. if (headClipViewWasAttached) { - skiaSceneHost!.insertBefore(head, headClipViewNextSibling); + skiaSceneHost.insertBefore(head, headClipViewNextSibling); } return head; } @@ -398,7 +400,7 @@ class HtmlViewEmbedder { DomElement? _svgPathDefs; /// The nodes containing the SVG clip definitions needed to clip this view. - Map> _svgClipDefs = >{}; + final Map> _svgClipDefs = >{}; /// Ensures we add a container of SVG path defs to the DOM so they can /// be referred to in clip-path: url(#blah). @@ -408,7 +410,7 @@ class HtmlViewEmbedder { } _svgPathDefs = kSvgResourceHeader.cloneNode(false) as SVGElement; _svgPathDefs!.append(createSVGDefsElement()..id = 'sk_path_defs'); - skiaSceneHost!.append(_svgPathDefs!); + skiaSceneHost.append(_svgPathDefs!); } void submitFrame() { @@ -476,18 +478,18 @@ class HtmlViewEmbedder { } if (diffResult.addToBeginning) { final DomElement platformViewRoot = _viewClipChains[viewId]!.root; - skiaSceneHost!.insertBefore(platformViewRoot, elementToInsertBefore); + skiaSceneHost.insertBefore(platformViewRoot, elementToInsertBefore); final Surface? overlay = _overlays[viewId]; if (overlay != null) { - skiaSceneHost! + skiaSceneHost .insertBefore(overlay.htmlElement, elementToInsertBefore); } } else { final DomElement platformViewRoot = _viewClipChains[viewId]!.root; - skiaSceneHost!.append(platformViewRoot); + skiaSceneHost.append(platformViewRoot); final Surface? overlay = _overlays[viewId]; if (overlay != null) { - skiaSceneHost!.append(overlay.htmlElement); + skiaSceneHost.append(overlay.htmlElement); } } } @@ -500,11 +502,11 @@ class HtmlViewEmbedder { if (!overlayElement.isConnected!) { // This overlay wasn't added to the DOM. if (i == _compositionOrder.length - 1) { - skiaSceneHost!.append(overlayElement); + skiaSceneHost.append(overlayElement); } else { final int nextView = _compositionOrder[i + 1]; final DomElement nextElement = _viewClipChains[nextView]!.root; - skiaSceneHost!.insertBefore(overlayElement, nextElement); + skiaSceneHost.insertBefore(overlayElement, nextElement); } } } @@ -524,9 +526,9 @@ class HtmlViewEmbedder { final DomElement platformViewRoot = _viewClipChains[viewId]!.root; final Surface? overlay = _overlays[viewId]; - skiaSceneHost!.append(platformViewRoot); + skiaSceneHost.append(platformViewRoot); if (overlay != null) { - skiaSceneHost!.append(overlay.htmlElement); + skiaSceneHost.append(overlay.htmlElement); } _activeCompositionOrder.add(viewId); unusedViews.remove(viewId); @@ -714,14 +716,14 @@ class HtmlViewEmbedder { /// * The slot view in the stack (the actual contents of the platform view). /// * The number of clipping elements used last time the view was composited. class ViewClipChain { - DomElement _root; - DomElement _slot; - int _clipCount = -1; - ViewClipChain({required DomElement view}) : _root = view, _slot = view; + DomElement _root; + final DomElement _slot; + int _clipCount = -1; + DomElement get root => _root; DomElement get slot => _slot; int get clipCount => _clipCount; @@ -766,6 +768,17 @@ enum MutatorType { /// Stores mutation information like clipping or transform. class Mutator { + 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); + const Mutator._( this.type, this.rect, @@ -782,17 +795,6 @@ class Mutator { 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 || diff --git a/lib/web_ui/lib/src/engine/canvaskit/embedded_views_diff.dart b/lib/web_ui/lib/src/engine/canvaskit/embedded_views_diff.dart index f5cce723f2f9d..b07cc97711782 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/embedded_views_diff.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/embedded_views_diff.dart @@ -5,6 +5,10 @@ /// The results of diffing the current composition order with the active /// composition order. class ViewListDiffResult { + const ViewListDiffResult( + this.viewsToRemove, this.viewsToAdd, this.addToBeginning, + {this.viewToInsertBefore}); + /// Views which should be removed from the scene. final List viewsToRemove; @@ -20,10 +24,6 @@ class ViewListDiffResult { /// /// `null` if [addToBeginning] is `false`. final int? viewToInsertBefore; - - const ViewListDiffResult( - this.viewsToRemove, this.viewsToAdd, this.addToBeginning, - {this.viewToInsertBefore}); } /// Diff the composition order with the active composition order. It is diff --git a/lib/web_ui/lib/src/engine/canvaskit/font_fallback_data.dart b/lib/web_ui/lib/src/engine/canvaskit/font_fallback_data.dart new file mode 100644 index 0000000000000..41c64cf61c7c6 --- /dev/null +++ b/lib/web_ui/lib/src/engine/canvaskit/font_fallback_data.dart @@ -0,0 +1,151 @@ +// 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. + +// DO NOT EDIT! This file is generated. See: +// dev/generate_fallback_font_data.dart +import 'noto_font.dart'; + +final List fallbackFonts = [ + NotoFont.fromFlatRanges('Noto Sans', 'http://fonts.gstatic.com/s/notosans/v27/o-0IIpQlx3QUlC5A4PNb4j5Ba_2c7A.ttf', [0x20,0x7e,0xa0,0x377,0x37a,0x37f,0x384,0x38a,0x38c,0x38c,0x38e,0x3a1,0x3a3,0x3e1,0x3f0,0x52f,0x900,0x97f,0x1ab0,0x1ac0,0x1c80,0x1c88,0x1cd0,0x1cf6,0x1cf8,0x1cf9,0x1d00,0x1df9,0x1dfb,0x1f15,0x1f18,0x1f1d,0x1f20,0x1f45,0x1f48,0x1f4d,0x1f50,0x1f57,0x1f59,0x1f59,0x1f5b,0x1f5b,0x1f5d,0x1f5d,0x1f5f,0x1f7d,0x1f80,0x1fb4,0x1fb6,0x1fc4,0x1fc6,0x1fd3,0x1fd6,0x1fdb,0x1fdd,0x1fef,0x1ff2,0x1ff4,0x1ff6,0x1ffe,0x2000,0x2064,0x2066,0x2071,0x2074,0x208e,0x2090,0x209c,0x20a0,0x20bf,0x20f0,0x20f0,0x2100,0x215f,0x2184,0x2184,0x2189,0x2189,0x2212,0x2212,0x2215,0x2215,0x25cc,0x25cc,0x2c60,0x2c7f,0x2de0,0x2e52,0xa640,0xa69f,0xa700,0xa7bf,0xa7c2,0xa7ca,0xa7f5,0xa7ff,0xa830,0xa839,0xa8e0,0xa8ff,0xa92e,0xa92e,0xab30,0xab6b,0xfb00,0xfb06,0xfe00,0xfe00,0xfe20,0xfe2f,0xfeff,0xfeff,0xfffc,0xfffd,]), + NotoFont.fromFlatRanges('Noto Emoji', 'http://fonts.gstatic.com/s/notoemoji/v26/bMrnmSyK7YY-MEu6aWjPDs-ar6uWaGWuob-r0jwvS-FGJCMY.ttf', [0x20,0x20,0x23,0x23,0x2a,0x2a,0x30,0x39,0xa9,0xa9,0xae,0xae,0x200d,0x200d,0x203c,0x203c,0x2049,0x2049,0x20e3,0x20e3,0x2122,0x2122,0x2139,0x2139,0x2194,0x2199,0x21a9,0x21aa,0x231a,0x231b,0x2328,0x2328,0x23cf,0x23cf,0x23e9,0x23f3,0x23f8,0x23fa,0x24c2,0x24c2,0x25aa,0x25ab,0x25b6,0x25b6,0x25c0,0x25c0,0x25fb,0x25fe,0x2600,0x2604,0x260e,0x260e,0x2611,0x2611,0x2614,0x2615,0x2618,0x2618,0x261d,0x261d,0x2620,0x2620,0x2622,0x2623,0x2626,0x2626,0x262a,0x262a,0x262e,0x262f,0x2638,0x263a,0x2640,0x2640,0x2642,0x2642,0x2648,0x2653,0x265f,0x2660,0x2663,0x2663,0x2665,0x2666,0x2668,0x2668,0x267b,0x267b,0x267e,0x267f,0x2692,0x2697,0x2699,0x2699,0x269b,0x269c,0x26a0,0x26a1,0x26a7,0x26a7,0x26aa,0x26ab,0x26b0,0x26b1,0x26bd,0x26be,0x26c4,0x26c5,0x26c8,0x26c8,0x26ce,0x26cf,0x26d1,0x26d1,0x26d3,0x26d4,0x26e9,0x26ea,0x26f0,0x26f5,0x26f7,0x26fa,0x26fd,0x26fd,0x2702,0x2702,0x2705,0x2705,0x2708,0x270d,0x270f,0x270f,0x2712,0x2712,0x2714,0x2714,0x2716,0x2716,0x271d,0x271d,0x2721,0x2721,0x2728,0x2728,0x2733,0x2734,0x2744,0x2744,0x2747,0x2747,0x274c,0x274c,0x274e,0x274e,0x2753,0x2755,0x2757,0x2757,0x2763,0x2764,0x2795,0x2797,0x27a1,0x27a1,0x27b0,0x27b0,0x27bf,0x27bf,0x2934,0x2935,0x2b05,0x2b07,0x2b1b,0x2b1c,0x2b50,0x2b50,0x2b55,0x2b55,0x3030,0x3030,0x303d,0x303d,0x3297,0x3297,0x3299,0x3299,0xfe0e,0xfe0f,0x1f004,0x1f004,0x1f0cf,0x1f0cf,0x1f170,0x1f171,0x1f17e,0x1f17f,0x1f18e,0x1f18e,0x1f191,0x1f19a,0x1f1e6,0x1f1ff,0x1f201,0x1f202,0x1f21a,0x1f21a,0x1f22f,0x1f22f,0x1f232,0x1f23a,0x1f250,0x1f251,0x1f300,0x1f321,0x1f324,0x1f393,0x1f396,0x1f397,0x1f399,0x1f39b,0x1f39e,0x1f3f0,0x1f3f3,0x1f3f5,0x1f3f7,0x1f4fd,0x1f4ff,0x1f53d,0x1f549,0x1f54e,0x1f550,0x1f567,0x1f56f,0x1f570,0x1f573,0x1f57a,0x1f587,0x1f587,0x1f58a,0x1f58d,0x1f590,0x1f590,0x1f595,0x1f596,0x1f5a4,0x1f5a5,0x1f5a8,0x1f5a8,0x1f5b1,0x1f5b2,0x1f5bc,0x1f5bc,0x1f5c2,0x1f5c4,0x1f5d1,0x1f5d3,0x1f5dc,0x1f5de,0x1f5e1,0x1f5e1,0x1f5e3,0x1f5e3,0x1f5e8,0x1f5e8,0x1f5ef,0x1f5ef,0x1f5f3,0x1f5f3,0x1f5fa,0x1f64f,0x1f680,0x1f6c5,0x1f6cb,0x1f6d2,0x1f6d5,0x1f6d7,0x1f6dd,0x1f6e5,0x1f6e9,0x1f6e9,0x1f6eb,0x1f6ec,0x1f6f0,0x1f6f0,0x1f6f3,0x1f6fc,0x1f7e0,0x1f7eb,0x1f7f0,0x1f7f0,0x1f90c,0x1f93a,0x1f93c,0x1f945,0x1f947,0x1f9ff,0x1fa70,0x1fa74,0x1fa78,0x1fa7c,0x1fa80,0x1fa86,0x1fa90,0x1faac,0x1fab0,0x1faba,0x1fac0,0x1fac5,0x1fad0,0x1fad9,0x1fae0,0x1fae7,0x1faf0,0x1faf6,0xe0030,0xe0039,0xe0061,0xe007a,0xe007f,0xe007f,0xfe4e5,0xfe4ee,0xfe82c,0xfe82c,0xfe82e,0xfe837,]), + NotoFont.fromFlatRanges('Noto Sans Symbols', 'http://fonts.gstatic.com/s/notosanssymbols/v34/rP2up3q65FkAtHfwd-eIS2brbDN6gxP34F9jRRCe4W3gfQ8gavVFRkzrbQ.ttf', [0x20,0x20,0x30,0x39,0x41,0x5a,0x61,0x7a,0xa0,0xa0,0x20dd,0x20e0,0x20e2,0x20e4,0x2160,0x2183,0x2185,0x2188,0x218a,0x218b,0x2190,0x2199,0x2300,0x230f,0x2311,0x2315,0x2317,0x2317,0x231c,0x231f,0x2322,0x2323,0x2329,0x232a,0x232c,0x2335,0x237c,0x237c,0x2380,0x2394,0x2396,0x239a,0x23af,0x23af,0x23be,0x23cd,0x23d0,0x23db,0x23e2,0x23e8,0x2460,0x24ff,0x25cc,0x25cc,0x260a,0x260d,0x2613,0x2613,0x2624,0x262f,0x2638,0x263b,0x263d,0x2653,0x2669,0x267e,0x2690,0x269d,0x26a2,0x26a9,0x26ad,0x26bc,0x26ce,0x26ce,0x26e2,0x26ff,0x271d,0x2721,0x2776,0x2793,0x2921,0x2922,0x1f100,0x1f10c,0x1f110,0x1f16c,0x1f170,0x1f190,0x1f19b,0x1f1ac,0x1f546,0x1f549,0x1f54f,0x1f54f,0x1f610,0x1f610,0x1f700,0x1f773,]), + NotoFont.fromFlatRanges('Noto Sans Symbols 2', 'http://fonts.gstatic.com/s/notosanssymbols2/v15/I_uyMoGduATTei9eI8daxVHDyfisHr71ypPqfX71-AI.ttf', [0x20,0x20,0x23,0x23,0x2a,0x2a,0x30,0x39,0x7f,0xa0,0x2022,0x2022,0x20e2,0x20e3,0x21af,0x21af,0x21e6,0x21f0,0x21f3,0x21f3,0x2218,0x2219,0x2299,0x2299,0x22c4,0x22c6,0x2316,0x2316,0x2318,0x2318,0x231a,0x231b,0x2324,0x2328,0x232b,0x232b,0x237b,0x237b,0x237d,0x237f,0x2394,0x2394,0x23ce,0x23cf,0x23e9,0x23ea,0x23ed,0x23ef,0x23f1,0x2426,0x2440,0x244a,0x25a0,0x2609,0x260e,0x2612,0x2614,0x2623,0x2630,0x2637,0x263c,0x263c,0x2654,0x2668,0x267f,0x268f,0x269e,0x26a1,0x26aa,0x26ac,0x26bd,0x26cd,0x26cf,0x26e1,0x2700,0x2704,0x2706,0x2709,0x270b,0x271c,0x2722,0x2727,0x2729,0x274b,0x274d,0x274d,0x274f,0x2753,0x2756,0x2775,0x2794,0x2794,0x2798,0x27af,0x27b1,0x27be,0x2800,0x28ff,0x2981,0x2981,0x29bf,0x29bf,0x29eb,0x29eb,0x2b00,0x2b0d,0x2b12,0x2b2f,0x2b4d,0x2b73,0x2b76,0x2b95,0x2b97,0x2bfd,0x2bff,0x2bff,0x4dc0,0x4dff,0xfff9,0xfffb,0x10140,0x1018e,0x10190,0x1019c,0x101a0,0x101a0,0x101d0,0x101fd,0x102e0,0x102fb,0x10e60,0x10e7e,0x1d2e0,0x1d2f3,0x1d300,0x1d356,0x1d360,0x1d378,0x1f000,0x1f02b,0x1f030,0x1f093,0x1f0a0,0x1f0ae,0x1f0b1,0x1f0bf,0x1f0c1,0x1f0cf,0x1f0d1,0x1f0f5,0x1f30d,0x1f30f,0x1f315,0x1f315,0x1f31c,0x1f31c,0x1f321,0x1f32c,0x1f336,0x1f336,0x1f378,0x1f378,0x1f37d,0x1f37d,0x1f393,0x1f39f,0x1f3a7,0x1f3a7,0x1f3ac,0x1f3ae,0x1f3c2,0x1f3c2,0x1f3c4,0x1f3c4,0x1f3c6,0x1f3c6,0x1f3ca,0x1f3ce,0x1f3d4,0x1f3e0,0x1f3ed,0x1f3ed,0x1f3f1,0x1f3f3,0x1f3f5,0x1f3f7,0x1f408,0x1f408,0x1f415,0x1f415,0x1f41f,0x1f41f,0x1f426,0x1f426,0x1f43f,0x1f43f,0x1f441,0x1f442,0x1f446,0x1f449,0x1f44c,0x1f44e,0x1f453,0x1f453,0x1f46a,0x1f46a,0x1f47d,0x1f47d,0x1f4a3,0x1f4a3,0x1f4b0,0x1f4b0,0x1f4b3,0x1f4b3,0x1f4b9,0x1f4b9,0x1f4bb,0x1f4bb,0x1f4bf,0x1f4bf,0x1f4c8,0x1f4cb,0x1f4da,0x1f4da,0x1f4df,0x1f4df,0x1f4e4,0x1f4e6,0x1f4ea,0x1f4ed,0x1f4f7,0x1f4f7,0x1f4f9,0x1f4fb,0x1f4fd,0x1f4fe,0x1f503,0x1f503,0x1f507,0x1f50a,0x1f50d,0x1f50d,0x1f512,0x1f513,0x1f53e,0x1f545,0x1f54a,0x1f54a,0x1f550,0x1f579,0x1f57b,0x1f594,0x1f597,0x1f5a3,0x1f5a5,0x1f5fa,0x1f650,0x1f67f,0x1f687,0x1f687,0x1f68d,0x1f68d,0x1f691,0x1f691,0x1f694,0x1f694,0x1f698,0x1f698,0x1f6ad,0x1f6ad,0x1f6b2,0x1f6b2,0x1f6b9,0x1f6ba,0x1f6bc,0x1f6bc,0x1f6c6,0x1f6cb,0x1f6cd,0x1f6cf,0x1f6d3,0x1f6d7,0x1f6e0,0x1f6ea,0x1f6f0,0x1f6f3,0x1f6f7,0x1f6fc,0x1f780,0x1f7d8,0x1f7e0,0x1f7eb,0x1f800,0x1f80b,0x1f810,0x1f847,0x1f850,0x1f859,0x1f860,0x1f887,0x1f890,0x1f8ad,0x1f8b0,0x1f8b1,0x1f93b,0x1f93b,0x1f946,0x1f946,0x1fa00,0x1fa53,0x1fa60,0x1fa6d,0x1fa70,0x1fa74,0x1fa78,0x1fa7a,0x1fa80,0x1fa86,0x1fa90,0x1faa8,0x1fab0,0x1fab6,0x1fac0,0x1fac2,0x1fad0,0x1fad6,0x1fb00,0x1fb92,0x1fb94,0x1fbca,0x1fbf0,0x1fbf9,]), + NotoFont.fromFlatRanges('Noto Sans Adlam', 'http://fonts.gstatic.com/s/notosansadlam/v19/neIczCCpqp0s5pPusPamd81eMfjPonvqdbYxxpgufnv0TGnBZLwhuvk.ttf', [0x20,0x2f,0x3a,0x40,0x5b,0x5f,0x7b,0x7d,0xa0,0xa0,0xab,0xab,0xbb,0xbb,0x61f,0x61f,0x640,0x640,0x2013,0x2015,0x2018,0x201e,0x2020,0x2022,0x2026,0x2026,0x2030,0x2030,0x2039,0x203a,0x2044,0x2044,0x204f,0x204f,0x25cc,0x25cc,0x2e28,0x2e29,0x2e41,0x2e41,0x1e900,0x1e94b,0x1e950,0x1e959,0x1e95e,0x1e95f,]), + NotoFont.fromFlatRanges('Noto Sans Anatolian Hieroglyphs', 'http://fonts.gstatic.com/s/notosansanatolianhieroglyphs/v14/ijw9s4roRME5LLRxjsRb8A0gKPSWq4BbDmHHu6j2pEtUJzZWXybIymc5QYo.ttf', [0x20,0x20,0xa0,0xa0,0x200b,0x200b,0x14400,0x14646,]), + NotoFont.fromFlatRanges('Noto Sans Arabic', 'http://fonts.gstatic.com/s/notosansarabic/v18/nwpxtLGrOAZMl5nJ_wfgRg3DrWFZWsnVBJ_sS6tlqHHFlhQ5l3sQWIHPqzCfyGyvu3CBFQLaig.ttf', [0x20,0x21,0x2c,0x2e,0x30,0x3a,0xa0,0xa0,0xab,0xab,0xbb,0xbb,0x34f,0x34f,0x600,0x61c,0x61e,0x6ff,0x750,0x77f,0x8a0,0x8b4,0x8b6,0x8be,0x8d3,0x8ff,0x200b,0x2011,0x204f,0x204f,0x25cc,0x25cc,0x2e41,0x2e41,0xfb50,0xfbc1,0xfbd3,0xfd3f,0xfd50,0xfd8f,0xfd92,0xfdc7,0xfdf0,0xfdfd,0xfe70,0xfe74,0xfe76,0xfefc,]), + NotoFont.fromFlatRanges('Noto Sans Armenian', 'http://fonts.gstatic.com/s/notosansarmenian/v32/ZgN0jOZKPa7CHqq0h37c7ReDUubm2SEdFXp7ig73qtTY5idb74R9UdM3y2nZLorxb60iYy6zF3Eg.ttf', [0x20,0x20,0x2d,0x2d,0xa0,0xa0,0x308,0x308,0x531,0x556,0x559,0x58a,0x58d,0x58f,0x2010,0x2010,0x25cc,0x25cc,0xfb13,0xfb17,]), + NotoFont.fromFlatRanges('Noto Sans Avestan', 'http://fonts.gstatic.com/s/notosansavestan/v17/bWti7ejKfBziStx7lIzKOLQZKhIJkyu9SASLji8U.ttf', [0x20,0x7e,0xa0,0xa3,0xa5,0xa5,0xa7,0xab,0xae,0xb0,0xb4,0xb4,0xb6,0xb8,0xba,0xbb,0xbf,0x107,0x10a,0x113,0x116,0x11b,0x11e,0x123,0x126,0x127,0x12a,0x12b,0x12e,0x133,0x136,0x137,0x139,0x13e,0x141,0x148,0x14a,0x14d,0x150,0x15b,0x15e,0x161,0x164,0x165,0x16a,0x17e,0x1cd,0x1ce,0x218,0x21b,0x237,0x237,0x2c6,0x2c7,0x2c9,0x2c9,0x2d8,0x2dd,0x300,0x304,0x306,0x308,0x30a,0x30c,0x312,0x312,0x326,0x328,0x1e80,0x1e85,0x1e9e,0x1e9e,0x1ef2,0x1ef3,0x200c,0x200d,0x2013,0x2014,0x2018,0x201a,0x201c,0x201e,0x2022,0x2022,0x2026,0x2026,0x2039,0x203a,0x20ac,0x20ac,0x2122,0x2122,0x2212,0x2212,0x2e30,0x2e31,0x10b00,0x10b35,0x10b39,0x10b3f,]), + NotoFont.fromFlatRanges('Noto Sans Balinese', 'http://fonts.gstatic.com/s/notosansbalinese/v18/NaPwcYvSBuhTirw6IaFn6UrRDaqje-lpbbRtYf-Fwu2Ov7fdhE5Vd222PPY.ttf', [0x20,0x7e,0xa0,0xa3,0xa5,0xa5,0xa7,0xab,0xae,0xb0,0xb4,0xb4,0xb6,0xb8,0xba,0xbb,0xbf,0x107,0x10a,0x113,0x116,0x11b,0x11e,0x123,0x126,0x127,0x12a,0x12b,0x12e,0x133,0x136,0x137,0x139,0x13e,0x141,0x148,0x14a,0x14d,0x150,0x15b,0x15e,0x161,0x164,0x165,0x16a,0x17e,0x1cd,0x1ce,0x218,0x21b,0x237,0x237,0x2c6,0x2c7,0x2c9,0x2c9,0x2d8,0x2dd,0x300,0x304,0x306,0x308,0x30a,0x30c,0x312,0x312,0x326,0x328,0x1b00,0x1b4b,0x1b50,0x1b7c,0x1e80,0x1e85,0x1e9e,0x1e9e,0x1ef2,0x1ef3,0x200b,0x200d,0x2013,0x2014,0x2018,0x201a,0x201c,0x201e,0x2022,0x2022,0x2026,0x2026,0x2039,0x203a,0x20ac,0x20ac,0x2122,0x2122,0x2212,0x2212,0x25cc,0x25cc,]), + NotoFont.fromFlatRanges('Noto Sans Bamum', 'http://fonts.gstatic.com/s/notosansbamum/v18/uk-0EGK3o6EruUbnwovcbBTkkklK_Ya_PBHfNGTPEddO-_gLykxEkxA.ttf', [0x20,0x20,0xa0,0xa0,0xa6a0,0xa6f7,0x16800,0x16a38,]), + NotoFont.fromFlatRanges('Noto Sans Bassa Vah', 'http://fonts.gstatic.com/s/notosansbassavah/v15/PN_sRee-r3f7LnqsD5sax12gjZn7mBpL_4c2VNUQptE.ttf', [0x20,0x20,0xa0,0xa0,0x25cc,0x25cc,0x16ad0,0x16aed,0x16af0,0x16af5,]), + NotoFont.fromFlatRanges('Noto Sans Batak', 'http://fonts.gstatic.com/s/notosansbatak/v15/gok2H6TwAEdtF9N8-mdTCQvT-Zdgo4_PHuk74A.ttf', [0x20,0x20,0xa0,0xa0,0x1bc0,0x1bf3,0x1bfc,0x1bff,0x200b,0x200d,0x25cc,0x25cc,]), + NotoFont.fromFlatRanges('Noto Sans Bengali', 'http://fonts.gstatic.com/s/notosansbengali/v20/Cn-SJsCGWQxOjaGwMQ6fIiMywrNJIky6nvd8BjzVMvJx2mcSPVFpVEqE-6KmsolLudCk8izI0lc.ttf', [0x20,0x7e,0xa0,0xa3,0xa5,0xa5,0xa7,0xab,0xad,0xb0,0xb4,0xb4,0xb6,0xb8,0xba,0xbb,0xbf,0x107,0x10a,0x113,0x116,0x11b,0x11e,0x123,0x126,0x127,0x12a,0x12b,0x12e,0x133,0x136,0x137,0x139,0x13e,0x141,0x148,0x14a,0x14d,0x150,0x15b,0x15e,0x161,0x164,0x165,0x16a,0x17e,0x1cd,0x1ce,0x218,0x21b,0x237,0x237,0x2bc,0x2bc,0x2c6,0x2c7,0x2c9,0x2c9,0x2d8,0x2dd,0x300,0x304,0x306,0x308,0x30a,0x30c,0x312,0x312,0x326,0x328,0x951,0x952,0x964,0x965,0x980,0x983,0x985,0x98c,0x98f,0x990,0x993,0x9a8,0x9aa,0x9b0,0x9b2,0x9b2,0x9b6,0x9b9,0x9bc,0x9c4,0x9c7,0x9c8,0x9cb,0x9ce,0x9d7,0x9d7,0x9dc,0x9dd,0x9df,0x9e3,0x9e6,0x9fe,0x1cd0,0x1cd0,0x1cd2,0x1cd2,0x1cd5,0x1cd6,0x1cd8,0x1cd8,0x1ce1,0x1ce1,0x1cea,0x1cea,0x1ced,0x1ced,0x1cf2,0x1cf2,0x1cf5,0x1cf7,0x1e80,0x1e85,0x1e9e,0x1e9e,0x1ef2,0x1ef3,0x200b,0x200d,0x2010,0x2010,0x2013,0x2014,0x2018,0x201a,0x201c,0x201e,0x2022,0x2022,0x2026,0x2026,0x2039,0x203a,0x20ac,0x20ac,0x20b9,0x20b9,0x2122,0x2122,0x2212,0x2212,0x25cc,0x25cc,0xa8f1,0xa8f1,]), + NotoFont.fromFlatRanges('Noto Sans Bhaiksuki', 'http://fonts.gstatic.com/s/notosansbhaiksuki/v15/UcC63EosKniBH4iELXATsSBWdvUHXxhj8rLUdU4wh9U.ttf', [0x20,0x20,0xa0,0xa0,0x200b,0x200b,0x25cc,0x25cc,0x11c00,0x11c08,0x11c0a,0x11c36,0x11c38,0x11c45,0x11c50,0x11c6c,]), + NotoFont.fromFlatRanges('Noto Sans Brahmi', 'http://fonts.gstatic.com/s/notosansbrahmi/v15/vEFK2-VODB8RrNDvZSUmQQIIByV18tK1W77HtMo.ttf', [0x20,0x20,0xa0,0xa0,0x200b,0x200d,0x25cc,0x25cc,0x11000,0x1104d,0x11052,0x1106f,0x1107f,0x1107f,]), + NotoFont.fromFlatRanges('Noto Sans Buginese', 'http://fonts.gstatic.com/s/notosansbuginese/v15/esDM30ldNv-KYGGJpKGk18phe_7Da6_gtfuEXLmNtw.ttf', [0x20,0x20,0xa0,0xa0,0x1a00,0x1a1b,0x1a1e,0x1a1f,0x200b,0x200d,0x25cc,0x25cc,0xa9cf,0xa9cf,]), + NotoFont.fromFlatRanges('Noto Sans Buhid', 'http://fonts.gstatic.com/s/notosansbuhid/v17/Dxxy8jiXMW75w3OmoDXVWJD7YwzAe6tgnaFoGA.ttf', [0x20,0x7e,0xa0,0xa3,0xa5,0xa5,0xa7,0xab,0xae,0xb0,0xb4,0xb4,0xb6,0xb8,0xba,0xbb,0xbf,0x107,0x10a,0x113,0x116,0x11b,0x11e,0x123,0x126,0x127,0x12a,0x12b,0x12e,0x133,0x136,0x137,0x139,0x13e,0x141,0x148,0x14a,0x14d,0x150,0x15b,0x15e,0x161,0x164,0x165,0x16a,0x17e,0x1cd,0x1ce,0x218,0x21b,0x237,0x237,0x2c6,0x2c7,0x2c9,0x2c9,0x2d8,0x2dd,0x300,0x304,0x306,0x308,0x30a,0x30c,0x312,0x312,0x326,0x328,0x1735,0x1736,0x1740,0x1753,0x1e80,0x1e85,0x1e9e,0x1e9e,0x1ef2,0x1ef3,0x200b,0x200d,0x2013,0x2014,0x2018,0x201a,0x201c,0x201e,0x2022,0x2022,0x2026,0x2026,0x2039,0x203a,0x20ac,0x20ac,0x2122,0x2122,0x2212,0x2212,0x25cc,0x25cc,]), + NotoFont.fromFlatRanges('Noto Sans Canadian Aboriginal', 'http://fonts.gstatic.com/s/notosanscanadianaboriginal/v19/4C_TLjTuEqPj-8J01CwaGkiZ9os0iGVkezM1mUT-j_Lmlzda6uH_nnX1bzigWLn_yAsg0q0uhQ.ttf', [0x20,0x20,0xa0,0xa0,0x131,0x131,0x2c7,0x2c7,0x2d8,0x2db,0x307,0x307,0x1400,0x167f,0x18b0,0x18f5,0x25cc,0x25cc,]), + NotoFont.fromFlatRanges('Noto Sans Carian', 'http://fonts.gstatic.com/s/notosanscarian/v15/LDIpaoiONgYwA9Yc6f0gUILeMIOgs7ob9yGLmfI.ttf', [0x20,0x20,0xa0,0xa0,0x102a0,0x102d0,]), + NotoFont.fromFlatRanges('Noto Sans Caucasian Albanian', 'http://fonts.gstatic.com/s/notosanscaucasianalbanian/v16/nKKA-HM_FYFRJvXzVXaANsU0VzsAc46QGOkWytlTs-TXrYDmoVmRSZo.ttf', [0x20,0x20,0xa0,0xa0,0x304,0x304,0x331,0x331,0x25cc,0x25cc,0xfe20,0xfe2f,0x10530,0x10563,0x1056f,0x1056f,]), + NotoFont.fromFlatRanges('Noto Sans Chakma', 'http://fonts.gstatic.com/s/notosanschakma/v15/Y4GQYbJ8VTEp4t3MKJSMjg5OIzhi4JjTQhYBeYo.ttf', [0x20,0x20,0xa0,0xa0,0x9e6,0x9ef,0x1040,0x1049,0x200c,0x200d,0x25cc,0x25cc,0x11100,0x11134,0x11136,0x11146,]), + NotoFont.fromFlatRanges('Noto Sans Cham', 'http://fonts.gstatic.com/s/notosanscham/v19/pe06MIySN5pO62Z5YkFyQb_bbuRhe6D4yip43qfcERwcv7GykboaLg.ttf', [0x20,0x22,0x27,0x29,0x2c,0x2f,0x3a,0x3b,0x3f,0x3f,0xa0,0xa0,0xad,0xad,0x200c,0x200d,0x2010,0x2010,0x25cc,0x25cc,0xaa00,0xaa36,0xaa40,0xaa4d,0xaa50,0xaa59,0xaa5c,0xaa5f,]), + NotoFont.fromFlatRanges('Noto Sans Cherokee', 'http://fonts.gstatic.com/s/notosanscherokee/v17/KFOPCm6Yu8uF-29fiz9vQF9YWK6Z8O10cHNA0cSkZCHYWi5PDkm5rAffjl0.ttf', [0x20,0x20,0xa0,0xa0,0x300,0x302,0x304,0x304,0x30b,0x30c,0x323,0x324,0x330,0x331,0x13a0,0x13f5,0x13f8,0x13fd,0xab70,0xabbf,]), + NotoFont.fromFlatRanges('Noto Sans Coptic', 'http://fonts.gstatic.com/s/notosanscoptic/v15/iJWfBWmUZi_OHPqn4wq6kgqumOEd78u_VG0xR4Y.ttf', [0x20,0x20,0x2d,0x2d,0xa0,0xa0,0x300,0x302,0x304,0x305,0x307,0x308,0x323,0x323,0x33f,0x33f,0x361,0x361,0x374,0x375,0x3e2,0x3ef,0x1dcd,0x1dcd,0x2010,0x2010,0x25cc,0x25cc,0x2c80,0x2cf3,0x2cf9,0x2cff,0xfe24,0xfe26,0x102e0,0x102fb,]), + NotoFont.fromFlatRanges('Noto Sans Cuneiform', 'http://fonts.gstatic.com/s/notosanscuneiform/v15/bMrrmTWK7YY-MF22aHGGd7H8PhJtvBDWgb9JlRQueeQ.ttf', [0x20,0x20,0xa0,0xa0,0x12000,0x12399,0x12400,0x1246e,0x12470,0x12474,0x12480,0x12543,]), + NotoFont.fromFlatRanges('Noto Sans Cypriot', 'http://fonts.gstatic.com/s/notosanscypriot/v15/8AtzGta9PYqQDjyp79a6f8Cj-3a3cxIsK5MPpahF.ttf', [0x20,0x20,0xa0,0xa0,0x10800,0x10805,0x10808,0x10808,0x1080a,0x10835,0x10837,0x10838,0x1083c,0x1083c,0x1083f,0x1083f,]), + NotoFont.fromFlatRanges('Noto Sans Deseret', 'http://fonts.gstatic.com/s/notosansdeseret/v15/MwQsbgPp1eKH6QsAVuFb9AZM6MMr2Vq9ZnJSZtQG.ttf', [0x20,0x20,0xa0,0xa0,0x10400,0x1044f,]), + NotoFont.fromFlatRanges('Noto Sans Devanagari', 'http://fonts.gstatic.com/s/notosansdevanagari/v19/TuGoUUFzXI5FBtUq5a8bjKYTZjtRU6Sgv3NaV_SNmI0b8QQCQmHn6B2OHjbL_08AlXQly-AzoFoW4Ow.ttf', [0x20,0x7e,0xa0,0xa3,0xa5,0xa5,0xa7,0xab,0xad,0xb0,0xb4,0xb4,0xb6,0xb8,0xba,0xbb,0xbf,0x107,0x10a,0x113,0x116,0x11b,0x11e,0x123,0x126,0x127,0x12a,0x12b,0x12e,0x133,0x136,0x137,0x139,0x13e,0x141,0x148,0x14a,0x14d,0x150,0x15b,0x15e,0x161,0x164,0x165,0x16a,0x17e,0x1cd,0x1ce,0x218,0x21b,0x237,0x237,0x2bc,0x2bc,0x2c6,0x2c7,0x2c9,0x2c9,0x2d8,0x2dd,0x300,0x304,0x306,0x308,0x30a,0x30c,0x312,0x312,0x326,0x328,0x900,0x97f,0x1cd0,0x1cf6,0x1cf8,0x1cf9,0x1e80,0x1e85,0x1e9e,0x1e9e,0x1ef2,0x1ef3,0x200b,0x200d,0x2010,0x2010,0x2013,0x2014,0x2018,0x201a,0x201c,0x201e,0x2022,0x2022,0x2026,0x2026,0x2039,0x203a,0x20ac,0x20ac,0x20b9,0x20b9,0x20f0,0x20f0,0x2122,0x2122,0x2212,0x2212,0x25cc,0x25cc,0xa830,0xa839,0xa8e0,0xa8ff,]), + NotoFont.fromFlatRanges('Noto Sans Duployan', 'http://fonts.gstatic.com/s/notosansduployan/v16/gokzH7nwAEdtF9N8-mdTDx_X9JM5wsvrFsIn6WYDvA.ttf', [0x20,0x20,0xa0,0xa0,0x200c,0x200d,0x25cc,0x25cc,0x1bc00,0x1bc6a,0x1bc70,0x1bc7c,0x1bc80,0x1bc88,0x1bc90,0x1bc99,0x1bc9c,0x1bca3,]), + NotoFont.fromFlatRanges('Noto Sans Egyptian Hieroglyphs', 'http://fonts.gstatic.com/s/notosansegyptianhieroglyphs/v26/vEF42-tODB8RrNDvZSUmRhcQHzx1s7y_F9-j3qSzEcbEYindSVK8xRg7iw.ttf', [0x20,0x20,0xa0,0xa0,0x200c,0x200d,0x25cc,0x25cc,0x13000,0x1342e,]), + NotoFont.fromFlatRanges('Noto Sans Elbasan', 'http://fonts.gstatic.com/s/notosanselbasan/v15/-F6rfiZqLzI2JPCgQBnw400qp1trvHdlre4dFcFh.ttf', [0x20,0x20,0xa0,0xa0,0xb7,0xb7,0x305,0x305,0x391,0x3a1,0x3a3,0x3a9,0x3da,0x3da,0x3dc,0x3dc,0x3de,0x3de,0x25cc,0x25cc,0x10500,0x10527,]), + NotoFont.fromFlatRanges('Noto Sans Elymaic', 'http://fonts.gstatic.com/s/notosanselymaic/v15/UqyKK9YTJW5liNMhTMqe9vUFP65ZD4AjWOT0zi2V.ttf', [0x20,0x20,0xa0,0xa0,0x10fe0,0x10ff6,]), + NotoFont.fromFlatRanges('Noto Sans Georgian', 'http://fonts.gstatic.com/s/notosansgeorgian/v36/PlIaFke5O6RzLfvNNVSitxkr76PRHBC4Ytyq-Gof7PUs4S7zWn-8YDB09HFNdpvnzFj-f5WK0OQV.ttf', [0x20,0x7e,0xa0,0xa3,0xa5,0xa5,0xa7,0xab,0xae,0xb0,0xb4,0xb4,0xb6,0xb8,0xba,0xbb,0xbf,0x107,0x10a,0x113,0x116,0x11b,0x11e,0x123,0x126,0x127,0x12a,0x12b,0x12e,0x133,0x136,0x137,0x139,0x13e,0x141,0x148,0x14a,0x14d,0x150,0x15b,0x15e,0x161,0x164,0x165,0x16a,0x17e,0x1cd,0x1ce,0x218,0x21b,0x237,0x237,0x2c6,0x2c7,0x2c9,0x2c9,0x2d8,0x2dd,0x300,0x304,0x306,0x308,0x30a,0x30c,0x312,0x312,0x326,0x328,0x589,0x589,0x10a0,0x10c5,0x10c7,0x10c7,0x10cd,0x10cd,0x10d0,0x10ff,0x1c90,0x1cba,0x1cbd,0x1cbf,0x1e80,0x1e85,0x1e9e,0x1e9e,0x1ef2,0x1ef3,0x2010,0x2010,0x2013,0x2014,0x2018,0x201a,0x201c,0x201e,0x2022,0x2022,0x2026,0x2026,0x2039,0x203a,0x20ac,0x20ac,0x20be,0x20be,0x2122,0x2122,0x2212,0x2212,0x2d00,0x2d25,0x2d27,0x2d27,0x2d2d,0x2d2d,]), + NotoFont.fromFlatRanges('Noto Sans Glagolitic', 'http://fonts.gstatic.com/s/notosansglagolitic/v15/1q2ZY4-BBFBst88SU_tOj4J-4yuNF_HI4ERK4Amu7nM1.ttf', [0x20,0x20,0xa0,0xa0,0x303,0x303,0x305,0x305,0x484,0x484,0x487,0x487,0x2c00,0x2c2e,0x2c30,0x2c5e,0xa66f,0xa66f,0x1e000,0x1e006,0x1e008,0x1e018,0x1e01b,0x1e021,0x1e023,0x1e024,0x1e026,0x1e02a,]), + NotoFont.fromFlatRanges('Noto Sans Gothic', 'http://fonts.gstatic.com/s/notosansgothic/v15/TuGKUUVzXI5FBtUq5a8bj6wRbzxTFMX40kFQRx0.ttf', [0x20,0x20,0xa0,0xa0,0x304,0x305,0x308,0x308,0x331,0x331,0x10330,0x1034a,]), + NotoFont.fromFlatRanges('Noto Sans Grantha', 'http://fonts.gstatic.com/s/notosansgrantha/v15/3y976akwcCjmsU8NDyrKo3IQfQ4o-r8cFeulHc6N.ttf', [0x20,0x20,0xa0,0xa0,0x951,0x952,0x964,0x965,0xbaa,0xbaa,0xbb5,0xbb5,0xbe6,0xbf2,0x1cd0,0x1cd0,0x1cd2,0x1cd3,0x1cf2,0x1cf4,0x1cf8,0x1cf9,0x200c,0x200d,0x20f0,0x20f0,0x25cc,0x25cc,0x11300,0x11303,0x11305,0x1130c,0x1130f,0x11310,0x11313,0x11328,0x1132a,0x11330,0x11332,0x11333,0x11335,0x11339,0x1133b,0x11344,0x11347,0x11348,0x1134b,0x1134d,0x11350,0x11350,0x11357,0x11357,0x1135d,0x11363,0x11366,0x1136c,0x11370,0x11374,]), + NotoFont.fromFlatRanges('Noto Sans Gujarati', 'http://fonts.gstatic.com/s/notosansgujarati/v19/wlpWgx_HC1ti5ViekvcxnhMlCVo3f5pv17ivlzsUB14gg1TMR2Gw4VceEl7MA_ypFwPM_OdiEH0s.ttf', [0x20,0x7e,0xa0,0xa3,0xa5,0xa5,0xa7,0xab,0xad,0xb0,0xb4,0xb4,0xb6,0xb8,0xba,0xbb,0xbf,0x107,0x10a,0x113,0x116,0x11b,0x11e,0x123,0x126,0x127,0x12a,0x12b,0x12e,0x133,0x136,0x137,0x139,0x13e,0x141,0x148,0x14a,0x14d,0x150,0x15b,0x15e,0x161,0x164,0x165,0x16a,0x17e,0x1cd,0x1ce,0x218,0x21b,0x237,0x237,0x2c6,0x2c7,0x2c9,0x2c9,0x2d8,0x2dd,0x300,0x304,0x306,0x308,0x30a,0x30c,0x312,0x312,0x326,0x328,0x951,0x952,0x964,0x965,0xa81,0xa83,0xa85,0xa8d,0xa8f,0xa91,0xa93,0xaa8,0xaaa,0xab0,0xab2,0xab3,0xab5,0xab9,0xabc,0xac5,0xac7,0xac9,0xacb,0xacd,0xad0,0xad0,0xae0,0xae3,0xae6,0xaf1,0xaf9,0xaff,0x1e80,0x1e85,0x1e9e,0x1e9e,0x1ef2,0x1ef3,0x200b,0x200d,0x2010,0x2010,0x2013,0x2014,0x2018,0x201a,0x201c,0x201e,0x2022,0x2022,0x2026,0x2026,0x2039,0x203a,0x20ac,0x20ac,0x20b9,0x20b9,0x2122,0x2122,0x2212,0x2212,0x25cc,0x25cc,0xa830,0xa839,]), + NotoFont.fromFlatRanges('Noto Sans Gunjala Gondi', 'http://fonts.gstatic.com/s/notosansgunjalagondi/v15/bWto7e7KfBziStx7lIzKPrcSMwcEnCv6DW7n5hcVXYMTK4q1.ttf', [0x20,0x21,0x25,0x25,0x27,0x2f,0x3a,0x3a,0x3c,0x3f,0xa0,0xa0,0xd7,0xd7,0xf7,0xf7,0x200c,0x200d,0x2018,0x2019,0x201c,0x201d,0x2026,0x2026,0x2212,0x2212,0x25cc,0x25cc,0x11d60,0x11d65,0x11d67,0x11d68,0x11d6a,0x11d8e,0x11d90,0x11d91,0x11d93,0x11d98,0x11da0,0x11da9,]), + NotoFont.fromFlatRanges('Noto Sans Gurmukhi', 'http://fonts.gstatic.com/s/notosansgurmukhi/v18/w8g9H3EvQP81sInb43inmyN9zZ7hb7ATbSWo4q8dJ74a3cVrYFQ_bogT0-gPeG1OenbxZ_trdp7h.ttf', [0x20,0x23,0x25,0x25,0x27,0x3f,0x5b,0x5f,0x7b,0x7e,0xa0,0xa0,0xad,0xad,0xd7,0xd7,0xf7,0xf7,0x951,0x952,0x964,0x965,0xa01,0xa03,0xa05,0xa0a,0xa0f,0xa10,0xa13,0xa28,0xa2a,0xa30,0xa32,0xa33,0xa35,0xa36,0xa38,0xa39,0xa3c,0xa3c,0xa3e,0xa42,0xa47,0xa48,0xa4b,0xa4d,0xa51,0xa51,0xa59,0xa5c,0xa5e,0xa5e,0xa66,0xa76,0x200b,0x200d,0x2010,0x2010,0x2013,0x2014,0x2018,0x2019,0x201c,0x201d,0x2026,0x2026,0x20b9,0x20b9,0x2212,0x2212,0x25cc,0x25cc,0x262c,0x262c,0xa830,0xa839,]), + NotoFont.fromFlatRanges('Noto Sans HK', 'http://fonts.gstatic.com/s/notosanshk/v21/nKKQ-GM_FYFRJvXzVXaAPe9hMnB3Eu7mOQ.otf', [0x20,0x7e,0xa0,0x103,0x110,0x113,0x11a,0x11b,0x128,0x12b,0x143,0x144,0x147,0x148,0x14c,0x14f,0x152,0x153,0x168,0x16d,0x192,0x192,0x1a0,0x1a1,0x1af,0x1b0,0x1cd,0x1dc,0x1f8,0x1f9,0x251,0x251,0x261,0x261,0x2bb,0x2bb,0x2c7,0x2c7,0x2c9,0x2cb,0x2d9,0x2d9,0x2ea,0x2eb,0x300,0x301,0x304,0x304,0x307,0x307,0x30c,0x30c,0x391,0x3a1,0x3a3,0x3a9,0x3b1,0x3c9,0x401,0x401,0x410,0x44f,0x451,0x451,0x1e3e,0x1e3f,0x1ea0,0x1ef9,0x2002,0x2003,0x2010,0x2016,0x2018,0x201a,0x201c,0x201e,0x2020,0x2022,0x2025,0x2027,0x2030,0x2030,0x2032,0x2033,0x2035,0x2035,0x2039,0x203c,0x2042,0x2042,0x2047,0x2049,0x2051,0x2051,0x2074,0x2074,0x20a9,0x20a9,0x20ab,0x20ac,0x20dd,0x20de,0x2100,0x2100,0x2103,0x2103,0x2105,0x2105,0x2109,0x210a,0x210f,0x210f,0x2113,0x2113,0x2116,0x2116,0x2121,0x2122,0x2126,0x2127,0x212b,0x212b,0x212e,0x212e,0x2135,0x2135,0x213b,0x213b,0x2160,0x216b,0x2170,0x217b,0x2190,0x2199,0x21b8,0x21b9,0x21c4,0x21c6,0x21cb,0x21cc,0x21d0,0x21d0,0x21d2,0x21d2,0x21d4,0x21d4,0x21e6,0x21e9,0x21f5,0x21f5,0x2200,0x2200,0x2202,0x2203,0x2205,0x220b,0x220f,0x220f,0x2211,0x2213,0x2215,0x2215,0x221a,0x221a,0x221d,0x2220,0x2223,0x2223,0x2225,0x222e,0x2234,0x2237,0x223d,0x223d,0x2243,0x2243,0x2245,0x2245,0x2248,0x2248,0x224c,0x224c,0x2252,0x2252,0x2260,0x2262,0x2264,0x2267,0x226a,0x226b,0x226e,0x226f,0x2272,0x2273,0x2276,0x2277,0x2282,0x2287,0x228a,0x228b,0x2295,0x2299,0x22a0,0x22a0,0x22a5,0x22a5,0x22bf,0x22bf,0x22da,0x22db,0x22ef,0x22ef,0x2305,0x2307,0x2312,0x2312,0x2318,0x2318,0x2329,0x232a,0x23b0,0x23b1,0x23be,0x23cc,0x23ce,0x23ce,0x23da,0x23db,0x2423,0x2423,0x2460,0x25ab,0x25b1,0x25b3,0x25b6,0x25b7,0x25bc,0x25bd,0x25c0,0x25c1,0x25c6,0x25cc,0x25ce,0x25d3,0x25e2,0x25e6,0x25ef,0x25ef,0x2600,0x2603,0x2605,0x2606,0x2609,0x2609,0x260e,0x260f,0x2616,0x2617,0x261c,0x261f,0x262f,0x262f,0x2640,0x2642,0x2660,0x266f,0x2672,0x267d,0x26a0,0x26a0,0x26bd,0x26be,0x2702,0x2702,0x2713,0x2713,0x271a,0x271a,0x273d,0x273d,0x273f,0x2740,0x2756,0x2756,0x2776,0x2793,0x27a1,0x27a1,0x2934,0x2935,0x29bf,0x29bf,0x29fa,0x29fb,0x2b05,0x2b07,0x2b1a,0x2b1a,0x2b95,0x2b95,0x2e3a,0x2e3b,0x2e80,0x2e99,0x2e9b,0x2ef3,0x2f00,0x2fd5,0x2ff0,0x2ffb,0x3000,0x303f,0x3041,0x3096,0x3099,0x30ff,0x3105,0x312f,0x3131,0x3163,0x3165,0x318e,0x3190,0x31bb,0x31c0,0x31e3,0x31f0,0x321e,0x3220,0x332b,0x332d,0x33ff,0x3435,0x3435,0x3440,0x3440,0x344a,0x344a,0x344c,0x344c,0x3464,0x3464,0x3473,0x3473,0x347a,0x347a,0x347d,0x347e,0x3493,0x3493,0x3496,0x3496,0x34a5,0x34a5,0x34af,0x34af,0x34bc,0x34bc,0x34c1,0x34c1,0x34c8,0x34c8,0x34df,0x34df,0x34e4,0x34e4,0x34e6,0x34e6,0x34fb,0x34fb,0x3506,0x3506,0x353e,0x353e,0x3551,0x3551,0x3553,0x3553,0x3559,0x3559,0x3561,0x3561,0x356d,0x356d,0x3570,0x3570,0x3572,0x3572,0x3577,0x3578,0x3584,0x3584,0x3597,0x3598,0x35a1,0x35a1,0x35a5,0x35a5,0x35ad,0x35ad,0x35bf,0x35bf,0x35c1,0x35c1,0x35c5,0x35c5,0x35c7,0x35c7,0x35ca,0x35ca,0x35ce,0x35ce,0x35d2,0x35d2,0x35d6,0x35d6,0x35db,0x35db,0x35dd,0x35dd,0x35f1,0x35f3,0x35fb,0x35fb,0x35fe,0x35fe,0x3609,0x3609,0x3618,0x3618,0x361a,0x361a,0x3623,0x3623,0x3625,0x3625,0x362d,0x362d,0x3635,0x3635,0x3639,0x3639,0x363e,0x363e,0x3647,0x3649,0x364e,0x364e,0x365f,0x365f,0x3661,0x3661,0x367a,0x367a,0x3681,0x3681,0x369a,0x369a,0x36a5,0x36a5,0x36aa,0x36aa,0x36ac,0x36ac,0x36b0,0x36b1,0x36b5,0x36b5,0x36b9,0x36b9,0x36bc,0x36bc,0x36c1,0x36c1,0x36c3,0x36c5,0x36c7,0x36c8,0x36d3,0x36d4,0x36d6,0x36d6,0x36dd,0x36dd,0x36e1,0x36e2,0x36e5,0x36e6,0x36f5,0x36f5,0x3701,0x3701,0x3703,0x3703,0x3708,0x3708,0x370a,0x370a,0x370d,0x370d,0x371c,0x371c,0x3722,0x3723,0x3725,0x3725,0x372c,0x372d,0x3730,0x3730,0x3732,0x3733,0x373a,0x373a,0x3740,0x3740,0x3743,0x3743,0x3762,0x3762,0x376f,0x376f,0x3797,0x3797,0x37a0,0x37a0,0x37b9,0x37b9,0x37be,0x37be,0x37d6,0x37d6,0x37f2,0x37f2,0x37f8,0x37f8,0x37fb,0x37fb,0x380f,0x380f,0x3819,0x3819,0x3820,0x3820,0x382d,0x382d,0x3836,0x3836,0x3838,0x3838,0x3863,0x3863,0x3875,0x3875,0x38a0,0x38a0,0x38c3,0x38c3,0x38cc,0x38cc,0x38d1,0x38d1,0x38d4,0x38d4,0x38fa,0x38fa,0x3908,0x3908,0x3914,0x3914,0x3927,0x3927,0x3932,0x3932,0x393f,0x393f,0x394d,0x394d,0x3963,0x3963,0x3978,0x3978,0x3980,0x3980,0x3989,0x398a,0x3992,0x3992,0x3999,0x3999,0x399b,0x399b,0x39a1,0x39a1,0x39a4,0x39a4,0x39b8,0x39b8,0x39dc,0x39dc,0x39e2,0x39e2,0x39e5,0x39e5,0x39ec,0x39ec,0x39f8,0x39f8,0x39fb,0x39fb,0x39fe,0x39fe,0x3a01,0x3a01,0x3a03,0x3a03,0x3a06,0x3a06,0x3a17,0x3a18,0x3a29,0x3a2a,0x3a34,0x3a34,0x3a4b,0x3a4b,0x3a52,0x3a52,0x3a57,0x3a57,0x3a5c,0x3a5c,0x3a5e,0x3a5e,0x3a66,0x3a67,0x3a97,0x3a97,0x3aab,0x3aab,0x3abd,0x3abd,0x3ade,0x3ade,0x3ae0,0x3ae0,0x3af0,0x3af0,0x3af2,0x3af2,0x3af5,0x3af5,0x3afb,0x3afb,0x3b0e,0x3b0e,0x3b19,0x3b19,0x3b22,0x3b22,0x3b2b,0x3b2b,0x3b39,0x3b39,0x3b42,0x3b42,0x3b58,0x3b58,0x3b60,0x3b60,0x3b71,0x3b72,0x3b7b,0x3b7c,0x3b80,0x3b80,0x3b95,0x3b96,0x3b99,0x3b99,0x3ba1,0x3ba1,0x3bbc,0x3bbc,0x3bbe,0x3bbe,0x3bc2,0x3bc2,0x3bc4,0x3bc4,0x3bd7,0x3bd7,0x3bdd,0x3bdd,0x3bec,0x3bec,0x3bf2,0x3bf4,0x3c0d,0x3c0d,0x3c11,0x3c11,0x3c15,0x3c15,0x3c18,0x3c18,0x3c54,0x3c54,0x3c8b,0x3c8b,0x3ccb,0x3ccb,0x3ccd,0x3ccd,0x3cd1,0x3cd1,0x3cd6,0x3cd6,0x3cdc,0x3cdc,0x3ceb,0x3ceb,0x3cef,0x3cef,0x3d12,0x3d13,0x3d1d,0x3d1d,0x3d32,0x3d32,0x3d3b,0x3d3b,0x3d46,0x3d46,0x3d4c,0x3d4c,0x3d4e,0x3d4e,0x3d51,0x3d51,0x3d5f,0x3d5f,0x3d62,0x3d62,0x3d69,0x3d6a,0x3d6f,0x3d6f,0x3d75,0x3d75,0x3d7d,0x3d7d,0x3d85,0x3d85,0x3d88,0x3d88,0x3d8a,0x3d8a,0x3d8f,0x3d8f,0x3d91,0x3d91,0x3da5,0x3da5,0x3dad,0x3dad,0x3db4,0x3db4,0x3dbf,0x3dbf,0x3dc6,0x3dc7,0x3dc9,0x3dc9,0x3dcc,0x3dcd,0x3dd3,0x3dd3,0x3ddb,0x3ddb,0x3de7,0x3de8,0x3deb,0x3deb,0x3df3,0x3df4,0x3df7,0x3df7,0x3dfc,0x3dfd,0x3e06,0x3e06,0x3e40,0x3e40,0x3e43,0x3e43,0x3e48,0x3e48,0x3e55,0x3e55,0x3e74,0x3e74,0x3ea8,0x3eaa,0x3ead,0x3ead,0x3eb1,0x3eb1,0x3eb8,0x3eb8,0x3ebf,0x3ebf,0x3ec2,0x3ec2,0x3ec7,0x3ec7,0x3eca,0x3eca,0x3ecc,0x3ecc,0x3ed0,0x3ed1,0x3ed6,0x3ed7,0x3eda,0x3edb,0x3ede,0x3ede,0x3ee1,0x3ee2,0x3ee7,0x3ee7,0x3ee9,0x3ee9,0x3eeb,0x3eec,0x3ef0,0x3ef0,0x3ef3,0x3ef4,0x3efa,0x3efa,0x3efc,0x3efc,0x3eff,0x3f00,0x3f04,0x3f04,0x3f06,0x3f07,0x3f0e,0x3f0e,0x3f53,0x3f53,0x3f58,0x3f59,0x3f63,0x3f63,0x3f7c,0x3f7c,0x3f93,0x3f93,0x3fc0,0x3fc0,0x3fc8,0x3fc8,0x3fd7,0x3fd7,0x3fdc,0x3fdc,0x3fe5,0x3fe5,0x3fed,0x3fed,0x3ff9,0x3ffa,0x4004,0x4004,0x4009,0x4009,0x401d,0x401d,0x4039,0x4039,0x4045,0x4045,0x4053,0x4053,0x4057,0x4057,0x4062,0x4062,0x4065,0x4065,0x406a,0x406a,0x406f,0x406f,0x4071,0x4071,0x40a8,0x40a8,0x40b4,0x40b4,0x40bb,0x40bb,0x40bf,0x40bf,0x40c8,0x40c8,0x40d8,0x40d8,0x40df,0x40df,0x40f8,0x40f8,0x40fa,0x40fa,0x4102,0x4104,0x4109,0x4109,0x410e,0x410e,0x4131,0x4132,0x4167,0x4167,0x416c,0x416c,0x416e,0x416e,0x417c,0x417c,0x417f,0x417f,0x4181,0x4181,0x4190,0x4190,0x41b2,0x41b2,0x41c4,0x41c4,0x41ca,0x41ca,0x41cf,0x41cf,0x41db,0x41db,0x41ed,0x41ed,0x41ef,0x41ef,0x41f9,0x41f9,0x4211,0x4211,0x4223,0x4223,0x4240,0x4240,0x4260,0x4260,0x426a,0x426a,0x4276,0x4276,0x427a,0x427a,0x428c,0x428c,0x4294,0x4294,0x42a2,0x42a2,0x42b5,0x42b5,0x42b9,0x42b9,0x42bc,0x42bc,0x42f4,0x42f4,0x42fb,0x42fc,0x430a,0x430a,0x432b,0x432b,0x436e,0x436e,0x4397,0x4397,0x439a,0x439a,0x43ba,0x43ba,0x43c1,0x43c1,0x43d9,0x43d9,0x43df,0x43df,0x43ed,0x43ed,0x43f0,0x43f0,0x43f2,0x43f2,0x4401,0x4402,0x4413,0x4413,0x4425,0x4425,0x442d,0x442d,0x447a,0x447a,0x448f,0x448f,0x4491,0x4491,0x449f,0x44a0,0x44a2,0x44a2,0x44b0,0x44b0,0x44b7,0x44b7,0x44bd,0x44bd,0x44c0,0x44c0,0x44c3,0x44c3,0x44c5,0x44c5,0x44ce,0x44ce,0x44dd,0x44df,0x44e1,0x44e1,0x44e4,0x44e4,0x44e9,0x44ec,0x44f4,0x44f4,0x4503,0x4504,0x4509,0x4509,0x450b,0x450b,0x4516,0x4516,0x451b,0x451b,0x451d,0x451d,0x4527,0x4527,0x452e,0x452e,0x4533,0x4533,0x4536,0x4536,0x453b,0x453b,0x453d,0x453d,0x453f,0x453f,0x4543,0x4543,0x4551,0x4552,0x4555,0x4555,0x4558,0x4558,0x455c,0x455c,0x4561,0x4562,0x456a,0x456a,0x456d,0x456d,0x4577,0x4578,0x4585,0x4585,0x45a6,0x45a6,0x45b3,0x45b3,0x45da,0x45da,0x45e9,0x45ea,0x4603,0x4603,0x4606,0x4606,0x460f,0x460f,0x4615,0x4615,0x4617,0x4617,0x465b,0x465b,0x467a,0x467a,0x4680,0x4680,0x46a1,0x46a1,0x46ae,0x46ae,0x46bb,0x46bb,0x46cf,0x46d0,0x46f5,0x46f5,0x46f7,0x46f7,0x4713,0x4713,0x4718,0x4718,0x4736,0x4736,0x4744,0x4744,0x474e,0x474f,0x477c,0x477c,0x4798,0x4798,0x47a6,0x47a6,0x47d5,0x47d5,0x47ed,0x47ed,0x47f4,0x47f4,0x4800,0x4800,0x480b,0x480b,0x4837,0x4837,0x485d,0x485d,0x4871,0x4871,0x489b,0x489b,0x48ad,0x48ae,0x48d0,0x48d0,0x48dd,0x48dd,0x48ed,0x48ed,0x48f3,0x48f3,0x48fa,0x48fa,0x4906,0x4906,0x4911,0x4911,0x491e,0x491e,0x4925,0x4925,0x492a,0x492a,0x492d,0x492d,0x492f,0x4930,0x4935,0x4935,0x493c,0x493c,0x493e,0x493e,0x4945,0x4945,0x4951,0x4951,0x4953,0x4953,0x4965,0x4965,0x496a,0x496a,0x4972,0x4972,0x4989,0x4989,0x49a1,0x49a1,0x49a7,0x49a7,0x49df,0x49df,0x49e5,0x49e5,0x49e7,0x49e7,0x4a0f,0x4a0f,0x4a1d,0x4a1d,0x4a24,0x4a24,0x4a35,0x4a35,0x4a96,0x4a96,0x4aa4,0x4aa4,0x4ab4,0x4ab4,0x4ab8,0x4ab8,0x4ad1,0x4ad1,0x4ae4,0x4ae4,0x4aff,0x4aff,0x4b10,0x4b10,0x4b19,0x4b19,0x4b20,0x4b20,0x4b2c,0x4b2c,0x4b37,0x4b37,0x4b6f,0x4b70,0x4b72,0x4b72,0x4b7b,0x4b7b,0x4b7e,0x4b7e,0x4b8e,0x4b8e,0x4b90,0x4b90,0x4b93,0x4b93,0x4b96,0x4b97,0x4b9d,0x4b9d,0x4bbd,0x4bbe,0x4bc0,0x4bc0,0x4c04,0x4c04,0x4c07,0x4c07,0x4c0e,0x4c0e,0x4c32,0x4c32,0x4c3b,0x4c3b,0x4c3e,0x4c3e,0x4c40,0x4c40,0x4c47,0x4c47,0x4c57,0x4c57,0x4c5b,0x4c5b,0x4c6d,0x4c6d,0x4c77,0x4c77,0x4c7b,0x4c7b,0x4c7d,0x4c7d,0x4c81,0x4c81,0x4c85,0x4c85,0x4ca4,0x4ca4,0x4cae,0x4cae,0x4cb0,0x4cb0,0x4cb7,0x4cb7,0x4ccd,0x4ccd,0x4ce1,0x4ce2,0x4ced,0x4ced,0x4d07,0x4d07,0x4d09,0x4d09,0x4d10,0x4d10,0x4d34,0x4d34,0x4d76,0x4d77,0x4d89,0x4d89,0x4d91,0x4d91,0x4d9c,0x4d9c,0x4e00,0x4e01,0x4e03,0x4e04,0x4e07,0x4e11,0x4e14,0x4e16,0x4e18,0x4e1a,0x4e1c,0x4e1c,0x4e1e,0x4e1f,0x4e21,0x4e22,0x4e24,0x4e24,0x4e26,0x4e26,0x4e28,0x4e28,0x4e2a,0x4e33,0x4e36,0x4e39,0x4e3b,0x4e3d,0x4e3f,0x4e3f,0x4e42,0x4e43,0x4e45,0x4e45,0x4e47,0x4e49,0x4e4b,0x4e4b,0x4e4d,0x4e4f,0x4e52,0x4e53,0x4e56,0x4e56,0x4e58,0x4e5f,0x4e69,0x4e6a,0x4e73,0x4e73,0x4e78,0x4e78,0x4e7e,0x4e89,0x4e8b,0x4e8e,0x4e91,0x4e95,0x4e98,0x4e9b,0x4e9e,0x4ea6,0x4ea8,0x4ea8,0x4eab,0x4eae,0x4eb3,0x4eb3,0x4eb6,0x4eb7,0x4eb9,0x4ebc,0x4ebf,0x4ec4,0x4ec6,0x4ecb,0x4ecd,0x4ece,0x4ed4,0x4eda,0x4edc,0x4edf,0x4ee1,0x4ee1,0x4ee3,0x4ee5,0x4ee8,0x4eeb,0x4eee,0x4eee,0x4ef0,0x4ef8,0x4efb,0x4efb,0x4efd,0x4efd,0x4eff,0x4f05,0x4f08,0x4f0b,0x4f0d,0x4f15,0x4f17,0x4f1a,0x4f1d,0x4f1d,0x4f22,0x4f22,0x4f28,0x4f29,0x4f2c,0x4f2d,0x4f2f,0x4f30,0x4f32,0x4f34,0x4f36,0x4f3f,0x4f41,0x4f43,0x4f45,0x4f49,0x4f4b,0x4f64,0x4f67,0x4f67,0x4f69,0x4f6c,0x4f6e,0x4f70,0x4f72,0x4f8b,0x4f8d,0x4f8d,0x4f8f,0x4f92,0x4f94,0x4f98,0x4f9a,0x4f9e,0x4fa2,0x4fa2,0x4fa8,0x4fa8,0x4fab,0x4fab,0x4fae,0x4fb0,0x4fb2,0x4fb7,0x4fb9,0x4fbb,0x4fbd,0x4fbd,0x4fbf,0x4fc5,0x4fc7,0x4fd1,0x4fd3,0x4fd4,0x4fd6,0x4fe1,0x4fe4,0x4fe5,0x4fec,0x4fec,0x4fee,0x4ffa,0x4ffd,0x4ffe,0x5000,0x5000,0x5003,0x5003,0x5005,0x5009,0x500b,0x500f,0x5011,0x501c,0x501e,0x5023,0x5025,0x5031,0x5033,0x5035,0x5037,0x5037,0x503b,0x503c,0x5040,0x5041,0x5043,0x5043,0x5045,0x504f,0x5051,0x5051,0x5053,0x5053,0x5055,0x5058,0x505a,0x5066,0x5068,0x5070,0x5072,0x5077,0x507a,0x507a,0x507d,0x507d,0x5080,0x5083,0x5085,0x5085,0x5087,0x5088,0x508b,0x508e,0x5090,0x5092,0x5094,0x5096,0x5098,0x509e,0x50a2,0x50a3,0x50a6,0x50a6,0x50ac,0x50b8,0x50ba,0x50bf,0x50c1,0x50c2,0x50c4,0x50cb,0x50cd,0x50d1,0x50d3,0x50d7,0x50d9,0x50db,0x50dd,0x50dd,0x50df,0x50e1,0x50e3,0x50ea,0x50ec,0x50f1,0x50f3,0x50f6,0x50f8,0x50f9,0x50fb,0x510e,0x5110,0x5115,0x5117,0x5118,0x511a,0x511a,0x511c,0x511c,0x511f,0x5122,0x5124,0x5126,0x5129,0x512b,0x512d,0x512e,0x5130,0x5135,0x5137,0x513d,0x513f,0x5141,0x5143,0x5149,0x514b,0x514d,0x5151,0x5152,0x5154,0x5157,0x5159,0x5163,0x5165,0x5165,0x5167,0x516e,0x5171,0x5171,0x5174,0x5179,0x517c,0x517c,0x5180,0x5180,0x5182,0x5182,0x5186,0x518a,0x518d,0x518d,0x518f,0x518f,0x5191,0x5198,0x519a,0x519a,0x519c,0x519c,0x519e,0x519e,0x51a0,0x51a0,0x51a2,0x51a2,0x51a4,0x51a5,0x51a7,0x51a8,0x51aa,0x51ac,0x51ae,0x51ae,0x51b0,0x51b9,0x51bc,0x51be,0x51c3,0x51d4,0x51d7,0x51d8,0x51db,0x51e2,0x51e4,0x51e4,0x51ed,0x51ed,0x51f0,0x51f1,0x51f3,0x51f6,0x51f8,0x51fa,0x51fc,0x51fe,0x5200,0x5203,0x5205,0x520c,0x520e,0x520e,0x5210,0x5213,0x5216,0x5217,0x521c,0x5221,0x5224,0x522a,0x522e,0x522e,0x5230,0x5238,0x523a,0x523c,0x5241,0x5241,0x5243,0x5244,0x5246,0x5247,0x5249,0x524f,0x5252,0x5252,0x5254,0x5257,0x5259,0x5262,0x5268,0x526f,0x5272,0x5275,0x5277,0x527d,0x527f,0x5284,0x5287,0x528d,0x528f,0x5291,0x5293,0x5294,0x5296,0x529b,0x529f,0x52a1,0x52a3,0x52a4,0x52a6,0x52a6,0x52a8,0x52ae,0x52b5,0x52b5,0x52b9,0x52b9,0x52bb,0x52bc,0x52be,0x52be,0x52c0,0x52c3,0x52c5,0x52c5,0x52c7,0x52c7,0x52c9,0x52c9,0x52cc,0x52cd,0x52d0,0x52d3,0x52d5,0x52d9,0x52db,0x52db,0x52dd,0x52e4,0x52e6,0x52e6,0x52e9,0x52e9,0x52eb,0x52eb,0x52ef,0x52f1,0x52f3,0x52f5,0x52f7,0x52fc,0x52fe,0x52ff,0x5301,0x5301,0x5305,0x5306,0x5308,0x530b,0x530d,0x5312,0x5315,0x5317,0x5319,0x531a,0x531c,0x531d,0x531f,0x5324,0x5327,0x5327,0x532a,0x532a,0x532c,0x532d,0x532f,0x5334,0x5337,0x5339,0x533b,0x5345,0x5347,0x534a,0x534c,0x534e,0x5351,0x5354,0x5357,0x5357,0x535a,0x535a,0x535c,0x5361,0x5363,0x5364,0x5366,0x5367,0x5369,0x5369,0x536c,0x5375,0x5377,0x5379,0x537b,0x537f,0x5382,0x5382,0x5384,0x5384,0x538a,0x538a,0x538e,0x538f,0x5392,0x5394,0x5396,0x539a,0x539c,0x53a0,0x53a2,0x53a2,0x53a4,0x53ae,0x53b0,0x53b0,0x53b2,0x53b2,0x53b4,0x53b4,0x53b6,0x53b6,0x53b9,0x53b9,0x53bb,0x53bb,0x53c1,0x53c3,0x53c5,0x53c5,0x53c8,0x53cd,0x53d0,0x53d2,0x53d4,0x53d4,0x53d6,0x53db,0x53df,0x53e6,0x53e8,0x53f3,0x53f5,0x53f8,0x53fb,0x53fc,0x53fe,0x53fe,0x5401,0x5401,0x5403,0x5404,0x5406,0x5414,0x5416,0x5416,0x5418,0x5421,0x5423,0x5439,0x543b,0x5443,0x5445,0x5448,0x544a,0x544f,0x5454,0x5454,0x5460,0x546d,0x546f,0x5478,0x547a,0x5482,0x5484,0x5488,0x548b,0x5498,0x549a,0x549a,0x549c,0x549c,0x549e,0x549e,0x54a0,0x54b4,0x54b6,0x54c9,0x54cb,0x54d0,0x54d6,0x54d6,0x54da,0x54da,0x54de,0x54de,0x54e0,0x54eb,0x54ed,0x54ef,0x54f1,0x54f3,0x54f7,0x54f8,0x54fa,0x54fd,0x54ff,0x54ff,0x5501,0x5514,0x5517,0x5518,0x551a,0x551a,0x551e,0x551e,0x5523,0x5523,0x5525,0x5528,0x552a,0x5539,0x553b,0x553c,0x553e,0x5541,0x5543,0x554b,0x554d,0x5553,0x5555,0x5557,0x555c,0x555f,0x5561,0x5566,0x5569,0x556b,0x5571,0x5573,0x5575,0x5577,0x5579,0x5579,0x557b,0x5584,0x5586,0x5595,0x5598,0x559a,0x559c,0x559d,0x559f,0x559f,0x55a1,0x55ae,0x55b0,0x55b5,0x55b9,0x55bc,0x55bf,0x55df,0x55e1,0x55ea,0x55ec,0x55ec,0x55ee,0x55f2,0x55f5,0x55f7,0x55f9,0x5602,0x5604,0x5606,0x5608,0x5609,0x560c,0x5617,0x561b,0x5623,0x5625,0x5625,0x5627,0x5627,0x5629,0x562a,0x562c,0x5630,0x5632,0x563b,0x563d,0x5643,0x5645,0x5646,0x5648,0x564a,0x564c,0x5650,0x5652,0x5654,0x5657,0x565a,0x565d,0x565e,0x5660,0x5666,0x5668,0x5674,0x5676,0x567c,0x567e,0x5687,0x5689,0x5690,0x5692,0x5693,0x5695,0x5695,0x5697,0x569a,0x569c,0x569f,0x56a1,0x56a1,0x56a4,0x56a8,0x56aa,0x56af,0x56b1,0x56b7,0x56b9,0x56b9,0x56bc,0x56c3,0x56c5,0x56c6,0x56c8,0x56cd,0x56d1,0x56d1,0x56d3,0x56d4,0x56d6,0x56d7,0x56da,0x56db,0x56dd,0x56e2,0x56e4,0x56e5,0x56e7,0x56e7,0x56ea,0x56eb,0x56ed,0x56f1,0x56f7,0x56f7,0x56f9,0x56fb,0x56fd,0x56fd,0x56ff,0x5704,0x5707,0x570d,0x5712,0x5716,0x5718,0x5718,0x571a,0x5720,0x5722,0x5723,0x5728,0x572a,0x572c,0x5730,0x5732,0x5734,0x573b,0x573b,0x573d,0x5743,0x5745,0x5747,0x5749,0x5752,0x5754,0x5754,0x5757,0x5757,0x575b,0x575b,0x575f,0x575f,0x5761,0x5762,0x5764,0x5764,0x5766,0x576b,0x576d,0x576d,0x576f,0x5777,0x577a,0x5780,0x5782,0x5783,0x5788,0x5788,0x578a,0x578d,0x578f,0x5790,0x5793,0x5795,0x5797,0x57a5,0x57a7,0x57a7,0x57aa,0x57aa,0x57ae,0x57ae,0x57b3,0x57b6,0x57b8,0x57bf,0x57c1,0x57c4,0x57c6,0x57c8,0x57cb,0x57cc,0x57ce,0x57d0,0x57d2,0x57d2,0x57d4,0x57d5,0x57d7,0x57d7,0x57dc,0x57e7,0x57e9,0x57e9,0x57ec,0x57fe,0x5800,0x580e,0x5810,0x5810,0x5812,0x5812,0x5814,0x5814,0x5818,0x5819,0x581b,0x581e,0x5820,0x582a,0x582c,0x583b,0x583d,0x583d,0x583f,0x5840,0x5844,0x5844,0x5847,0x584f,0x5851,0x5855,0x5857,0x585f,0x5862,0x5865,0x5868,0x5869,0x586b,0x586d,0x586f,0x586f,0x5871,0x5876,0x5879,0x5883,0x5885,0x588b,0x588e,0x5894,0x5896,0x5896,0x5898,0x589a,0x589c,0x58a1,0x58a3,0x58a3,0x58a5,0x58ac,0x58ae,0x58b1,0x58b3,0x58b3,0x58b5,0x58b6,0x58ba,0x58bf,0x58c1,0x58c2,0x58c5,0x58c9,0x58cb,0x58cb,0x58ce,0x58d6,0x58d8,0x58e0,0x58e2,0x58e4,0x58e7,0x58e9,0x58eb,0x58ec,0x58ef,0x58f0,0x58f2,0x58f4,0x58f9,0x58ff,0x5902,0x5907,0x590a,0x590a,0x590c,0x590f,0x5911,0x5912,0x5914,0x5917,0x5919,0x591a,0x591c,0x591d,0x591f,0x5920,0x5922,0x5922,0x5924,0x5925,0x5927,0x5927,0x5929,0x592f,0x5931,0x5932,0x5934,0x5934,0x5937,0x5938,0x593c,0x593c,0x593e,0x593e,0x5940,0x5940,0x5944,0x5945,0x5947,0x594a,0x594e,0x5951,0x5953,0x5955,0x5957,0x5958,0x595a,0x595a,0x595c,0x595c,0x5960,0x5962,0x5965,0x5965,0x5967,0x5967,0x5969,0x596e,0x5970,0x5979,0x597b,0x5985,0x5989,0x598a,0x598d,0x5990,0x5992,0x5994,0x5996,0x599a,0x599d,0x59a8,0x59ac,0x59ac,0x59ae,0x59c1,0x59c3,0x59d4,0x59d6,0x59d6,0x59d8,0x59de,0x59e0,0x59e1,0x59e3,0x59e6,0x59e8,0x5a03,0x5a09,0x5a0d,0x5a0f,0x5a0f,0x5a11,0x5a13,0x5a15,0x5a1c,0x5a1e,0x5a21,0x5a23,0x5a25,0x5a27,0x5a27,0x5a29,0x5a2e,0x5a33,0x5a33,0x5a35,0x5a39,0x5a3c,0x5a3e,0x5a40,0x5a4a,0x5a4c,0x5a4d,0x5a50,0x5a6e,0x5a70,0x5a71,0x5a77,0x5a7f,0x5a81,0x5a84,0x5a86,0x5a86,0x5a88,0x5a88,0x5a8a,0x5a8c,0x5a8e,0x5a97,0x5a99,0x5aa2,0x5aa4,0x5aa7,0x5aa9,0x5aac,0x5aae,0x5ac4,0x5ac6,0x5acf,0x5ad1,0x5ad1,0x5ad3,0x5ad3,0x5ad5,0x5ae6,0x5ae8,0x5aee,0x5af0,0x5af0,0x5af2,0x5afb,0x5afd,0x5aff,0x5b01,0x5b03,0x5b05,0x5b05,0x5b07,0x5b09,0x5b0b,0x5b0d,0x5b0f,0x5b11,0x5b13,0x5b17,0x5b19,0x5b1b,0x5b1d,0x5b21,0x5b23,0x5b28,0x5b2a,0x5b30,0x5b32,0x5b32,0x5b34,0x5b34,0x5b38,0x5b38,0x5b3c,0x5b41,0x5b43,0x5b48,0x5b4a,0x5b51,0x5b53,0x5b58,0x5b5a,0x5b5d,0x5b5f,0x5b5f,0x5b62,0x5b66,0x5b68,0x5b69,0x5b6b,0x5b6e,0x5b70,0x5b78,0x5b7a,0x5b7d,0x5b7f,0x5b85,0x5b87,0x5b89,0x5b8b,0x5b8c,0x5b8e,0x5b90,0x5b92,0x5b93,0x5b95,0x5b9f,0x5ba2,0x5ba8,0x5baa,0x5baa,0x5bac,0x5bae,0x5bb0,0x5bb0,0x5bb3,0x5bb9,0x5bbf,0x5bc7,0x5bca,0x5bce,0x5bd0,0x5bd9,0x5bdb,0x5bdb,0x5bde,0x5bec,0x5bee,0x5bf3,0x5bf5,0x5bf6,0x5bf8,0x5bf8,0x5bfa,0x5bfa,0x5bff,0x5bff,0x5c01,0x5c01,0x5c03,0x5c05,0x5c07,0x5c16,0x5c1a,0x5c1a,0x5c1c,0x5c1c,0x5c1e,0x5c20,0x5c22,0x5c25,0x5c28,0x5c28,0x5c2a,0x5c2a,0x5c2c,0x5c2c,0x5c30,0x5c31,0x5c33,0x5c33,0x5c37,0x5c3c,0x5c3e,0x5c41,0x5c44,0x5c51,0x5c53,0x5c56,0x5c58,0x5c59,0x5c5c,0x5c5e,0x5c60,0x5c60,0x5c62,0x5c65,0x5c67,0x5c6a,0x5c6c,0x5c6f,0x5c71,0x5c71,0x5c73,0x5c74,0x5c78,0x5c7c,0x5c7e,0x5c7e,0x5c83,0x5c83,0x5c85,0x5c86,0x5c88,0x5c8d,0x5c8f,0x5c95,0x5c99,0x5c9a,0x5c9c,0x5cb1,0x5cb3,0x5cb3,0x5cb5,0x5cb8,0x5cba,0x5cba,0x5cc1,0x5cc2,0x5cc6,0x5ccc,0x5cce,0x5cdb,0x5cde,0x5cdf,0x5ce5,0x5ce5,0x5ce8,0x5cea,0x5cec,0x5cf1,0x5cf4,0x5cf9,0x5cfb,0x5cfd,0x5cff,0x5d01,0x5d06,0x5d07,0x5d0b,0x5d12,0x5d14,0x5d1b,0x5d1d,0x5d20,0x5d22,0x5d29,0x5d2c,0x5d2c,0x5d2e,0x5d3a,0x5d3c,0x5d43,0x5d45,0x5d4c,0x5d4e,0x5d4e,0x5d50,0x5d52,0x5d55,0x5d57,0x5d59,0x5d59,0x5d5b,0x5d5b,0x5d5e,0x5d5e,0x5d62,0x5d63,0x5d65,0x5d65,0x5d67,0x5d69,0x5d6b,0x5d6c,0x5d6f,0x5d72,0x5d74,0x5d74,0x5d77,0x5d82,0x5d84,0x5d8b,0x5d8d,0x5d8e,0x5d92,0x5d95,0x5d97,0x5d97,0x5d99,0x5d9a,0x5d9c,0x5da2,0x5da4,0x5da4,0x5da7,0x5db2,0x5db4,0x5dba,0x5dbc,0x5dbd,0x5dc0,0x5dc3,0x5dc6,0x5dc7,0x5dc9,0x5dc9,0x5dcb,0x5dcb,0x5dcd,0x5dcd,0x5dcf,0x5dcf,0x5dd1,0x5dd2,0x5dd4,0x5dd8,0x5ddb,0x5ddb,0x5ddd,0x5de2,0x5de5,0x5de8,0x5deb,0x5deb,0x5dee,0x5dee,0x5df0,0x5df5,0x5df7,0x5df7,0x5df9,0x5df9,0x5dfd,0x5dff,0x5e02,0x5e04,0x5e06,0x5e06,0x5e09,0x5e0c,0x5e0e,0x5e0e,0x5e11,0x5e12,0x5e14,0x5e1b,0x5e1d,0x5e1d,0x5e1f,0x5e25,0x5e28,0x5e29,0x5e2b,0x5e2b,0x5e2d,0x5e2e,0x5e33,0x5e34,0x5e36,0x5e38,0x5e3d,0x5e3e,0x5e40,0x5e45,0x5e48,0x5e48,0x5e4a,0x5e4f,0x5e53,0x5e55,0x5e57,0x5e59,0x5e5b,0x5e63,0x5e66,0x5e70,0x5e72,0x5e76,0x5e78,0x5e80,0x5e82,0x5e84,0x5e86,0x5e8d,0x5e8f,0x5e8f,0x5e92,0x5e92,0x5e95,0x5e97,0x5e99,0x5e9c,0x5ea0,0x5ea0,0x5ea2,0x5ea8,0x5eaa,0x5eae,0x5eb0,0x5eb9,0x5ebd,0x5ebe,0x5ec1,0x5ec2,0x5ec4,0x5ece,0x5ed0,0x5ee3,0x5ee5,0x5ee9,0x5eec,0x5eec,0x5eee,0x5eef,0x5ef1,0x5ef4,0x5ef6,0x5efc,0x5efe,0x5eff,0x5f01,0x5f02,0x5f04,0x5f05,0x5f07,0x5f08,0x5f0a,0x5f0f,0x5f12,0x5f15,0x5f17,0x5f18,0x5f1a,0x5f1b,0x5f1d,0x5f1d,0x5f1f,0x5f1f,0x5f22,0x5f29,0x5f2d,0x5f2e,0x5f30,0x5f31,0x5f33,0x5f33,0x5f35,0x5f38,0x5f3a,0x5f3c,0x5f40,0x5f40,0x5f43,0x5f46,0x5f48,0x5f51,0x5f54,0x5f54,0x5f56,0x5f59,0x5f5c,0x5f5e,0x5f61,0x5f65,0x5f67,0x5f67,0x5f69,0x5f6d,0x5f6f,0x5f74,0x5f76,0x5f79,0x5f7b,0x5f83,0x5f85,0x5f8c,0x5f90,0x5f92,0x5f96,0x5f99,0x5f9b,0x5f9c,0x5f9e,0x5fa1,0x5fa4,0x5faf,0x5fb1,0x5fb2,0x5fb5,0x5fb7,0x5fb9,0x5fc5,0x5fc9,0x5fc9,0x5fcc,0x5fcd,0x5fcf,0x5fd2,0x5fd4,0x5fd9,0x5fdb,0x5fdb,0x5fdd,0x5fe1,0x5fe3,0x5fe5,0x5fe8,0x5fe8,0x5fea,0x5feb,0x5fed,0x5fef,0x5ff1,0x5ff1,0x5ff3,0x5ff5,0x5ff7,0x5ff8,0x5ffa,0x5ffb,0x5ffd,0x5ffd,0x5fff,0x6000,0x6009,0x6017,0x6019,0x601e,0x6020,0x602f,0x6031,0x6035,0x6037,0x6037,0x6039,0x6039,0x603b,0x603b,0x6040,0x6047,0x6049,0x604d,0x6050,0x6050,0x6052,0x6055,0x6058,0x605b,0x605d,0x605f,0x6062,0x6070,0x6072,0x6072,0x6075,0x6075,0x6077,0x6077,0x607e,0x6081,0x6083,0x608a,0x608c,0x608e,0x6090,0x6090,0x6092,0x6092,0x6094,0x6097,0x609a,0x60a0,0x60a2,0x60a4,0x60a6,0x60a8,0x60b0,0x60c1,0x60c3,0x60cf,0x60d1,0x60d1,0x60d3,0x60d5,0x60d7,0x60e4,0x60e6,0x60e9,0x60f0,0x6101,0x6103,0x6110,0x6112,0x6116,0x6118,0x611d,0x611f,0x6120,0x6122,0x6123,0x6127,0x6129,0x612b,0x612c,0x612e,0x6130,0x6132,0x6132,0x6134,0x6134,0x6136,0x6137,0x613b,0x613b,0x613d,0x6142,0x6144,0x6150,0x6152,0x6156,0x6158,0x6168,0x616a,0x616c,0x616e,0x6177,0x6179,0x617a,0x617c,0x617e,0x6180,0x6183,0x6187,0x6187,0x6189,0x618e,0x6190,0x6196,0x6198,0x619d,0x619f,0x619f,0x61a1,0x61a2,0x61a4,0x61a4,0x61a7,0x61ba,0x61bc,0x61bc,0x61be,0x61c3,0x61c5,0x61cd,0x61cf,0x61d0,0x61d3,0x61d3,0x61d6,0x61d6,0x61d8,0x61d8,0x61da,0x61da,0x61de,0x61e0,0x61e2,0x61eb,0x61ed,0x61ee,0x61f0,0x61f2,0x61f5,0x6201,0x6203,0x6204,0x6207,0x620a,0x620c,0x620e,0x6210,0x6212,0x6214,0x6216,0x6219,0x621b,0x621f,0x6225,0x6227,0x6227,0x6229,0x622e,0x6230,0x6230,0x6232,0x6234,0x6236,0x6237,0x6239,0x623a,0x623d,0x6243,0x6246,0x624e,0x6250,0x6254,0x6258,0x625c,0x625e,0x625e,0x6260,0x6266,0x6268,0x6268,0x626d,0x6274,0x6276,0x6277,0x6279,0x628a,0x628c,0x628c,0x628e,0x6298,0x629d,0x629d,0x62a4,0x62a4,0x62a6,0x62a6,0x62a8,0x62b1,0x62b3,0x62b6,0x62b8,0x62b9,0x62bb,0x62bf,0x62c1,0x62dc,0x62df,0x62df,0x62e5,0x62e5,0x62eb,0x6303,0x6307,0x6309,0x630b,0x6311,0x6313,0x6316,0x6318,0x6318,0x6328,0x632f,0x6331,0x633e,0x6340,0x6351,0x6354,0x635a,0x635d,0x635d,0x6364,0x6365,0x6367,0x6369,0x636b,0x6372,0x6375,0x637d,0x637f,0x6385,0x6387,0x6392,0x6394,0x6394,0x6396,0x6399,0x639b,0x63a5,0x63a7,0x63b1,0x63b9,0x63b9,0x63bd,0x63be,0x63c0,0x63d3,0x63d5,0x63eb,0x63ed,0x63f6,0x63f8,0x63f9,0x63fb,0x63fc,0x63fe,0x63fe,0x6406,0x6407,0x6409,0x6410,0x6412,0x6418,0x641a,0x641c,0x641e,0x6428,0x642a,0x6430,0x6432,0x643b,0x643d,0x6441,0x6443,0x6443,0x644b,0x644b,0x644d,0x644e,0x6450,0x6454,0x6458,0x6461,0x6465,0x6469,0x646b,0x647d,0x647f,0x647f,0x6482,0x6482,0x6485,0x6485,0x6487,0x648d,0x648f,0x6493,0x6495,0x649a,0x649c,0x64a0,0x64a2,0x64a6,0x64a9,0x64a9,0x64ab,0x64b4,0x64b6,0x64b6,0x64bb,0x64c5,0x64c7,0x64c7,0x64c9,0x64cb,0x64cd,0x64d0,0x64d2,0x64d4,0x64d6,0x64db,0x64dd,0x64dd,0x64e0,0x64ed,0x64ef,0x64f4,0x64f7,0x64f8,0x64fa,0x6501,0x6503,0x6504,0x6506,0x6507,0x6509,0x650a,0x650c,0x6511,0x6513,0x6519,0x651b,0x6526,0x6529,0x6530,0x6532,0x6539,0x653b,0x653b,0x653d,0x653f,0x6541,0x6541,0x6543,0x6543,0x6545,0x6546,0x6548,0x654a,0x654d,0x654d,0x654f,0x654f,0x6551,0x6551,0x6553,0x655a,0x655c,0x655f,0x6562,0x6568,0x656a,0x656d,0x656f,0x656f,0x6572,0x657c,0x657f,0x6589,0x658b,0x658c,0x6590,0x6592,0x6594,0x6597,0x6599,0x6599,0x659b,0x65a2,0x65a4,0x65a5,0x65a7,0x65a8,0x65aa,0x65ac,0x65ae,0x65b0,0x65b2,0x65b3,0x65b5,0x65b9,0x65bb,0x65bf,0x65c1,0x65c6,0x65cb,0x65d4,0x65d6,0x65d7,0x65da,0x65db,0x65dd,0x65e3,0x65e5,0x65e6,0x65e8,0x65e9,0x65ec,0x65f5,0x65fa,0x65fd,0x65ff,0x6600,0x6602,0x6615,0x6618,0x6618,0x661c,0x6628,0x662b,0x662b,0x662d,0x6636,0x6639,0x663a,0x6641,0x6645,0x6647,0x664d,0x664f,0x664f,0x6651,0x6653,0x6657,0x6657,0x6659,0x6668,0x666a,0x666c,0x666e,0x6674,0x6676,0x667e,0x6680,0x6680,0x6684,0x668e,0x6690,0x6692,0x6694,0x669a,0x669d,0x669d,0x669f,0x66a2,0x66a4,0x66a4,0x66a8,0x66ab,0x66ad,0x66bb,0x66bd,0x66c0,0x66c4,0x66c4,0x66c6,0x66cf,0x66d2,0x66d2,0x66d6,0x66d6,0x66d8,0x66de,0x66e0,0x66e0,0x66e3,0x66e4,0x66e6,0x66e9,0x66eb,0x66ee,0x66f0,0x66f4,0x66f6,0x66f9,0x66fc,0x66fc,0x66fe,0x6705,0x6708,0x6710,0x6712,0x6719,0x671b,0x671b,0x671d,0x6723,0x6725,0x6728,0x672a,0x672e,0x6731,0x6731,0x6733,0x6736,0x6738,0x673f,0x6744,0x6749,0x674b,0x6751,0x6753,0x6753,0x6755,0x6757,0x6759,0x675a,0x675c,0x6762,0x6767,0x6767,0x676a,0x677f,0x6781,0x6787,0x6789,0x6789,0x678b,0x6795,0x6797,0x679a,0x679c,0x679d,0x679f,0x67a0,0x67a4,0x67a4,0x67ac,0x67ac,0x67ae,0x67bb,0x67bf,0x67c6,0x67c8,0x67d4,0x67d6,0x67df,0x67e2,0x67e7,0x67e9,0x67fa,0x67fc,0x67fc,0x67fe,0x6804,0x680d,0x680d,0x6810,0x6810,0x6812,0x6814,0x6816,0x6818,0x681a,0x6822,0x6825,0x6826,0x6828,0x682b,0x682d,0x682f,0x6831,0x683e,0x6840,0x6851,0x6853,0x6856,0x685d,0x685d,0x6865,0x6865,0x686b,0x686b,0x686d,0x686f,0x6871,0x6872,0x6874,0x6879,0x687b,0x688c,0x688f,0x6894,0x6896,0x6898,0x689b,0x689d,0x689f,0x68a4,0x68a6,0x68b6,0x68b9,0x68b9,0x68bd,0x68bd,0x68c1,0x68c1,0x68c3,0x68ce,0x68d0,0x68d8,0x68da,0x68da,0x68dc,0x68e1,0x68e3,0x68e4,0x68e6,0x68ec,0x68ee,0x68fd,0x6900,0x6915,0x6917,0x691b,0x6925,0x6925,0x692a,0x692a,0x692c,0x692c,0x692f,0x6930,0x6932,0x6939,0x693b,0x6946,0x6948,0x694c,0x694e,0x694f,0x6951,0x697b,0x6980,0x6980,0x6982,0x6983,0x6985,0x6986,0x698a,0x698a,0x698d,0x698e,0x6990,0x6991,0x6993,0x699c,0x699e,0x69b7,0x69b9,0x69b9,0x69bb,0x69c4,0x69c6,0x69c6,0x69c9,0x69d1,0x69d3,0x69d6,0x69d9,0x69d9,0x69e1,0x69e2,0x69e4,0x69e9,0x69eb,0x69ee,0x69f1,0x69f4,0x69f6,0x6a0d,0x6a0f,0x6a0f,0x6a11,0x6a11,0x6a13,0x6a21,0x6a23,0x6a23,0x6a25,0x6a29,0x6a2b,0x6a2d,0x6a32,0x6a35,0x6a38,0x6a41,0x6a43,0x6a49,0x6a4b,0x6a5b,0x6a5d,0x6a6b,0x6a6d,0x6a6d,0x6a6f,0x6a6f,0x6a71,0x6a71,0x6a74,0x6a74,0x6a76,0x6a76,0x6a7a,0x6a7a,0x6a7e,0x6a85,0x6a87,0x6a87,0x6a89,0x6a8a,0x6a8c,0x6a97,0x6a99,0x6aa8,0x6aab,0x6aaf,0x6ab1,0x6abb,0x6abd,0x6abe,0x6ac2,0x6ac3,0x6ac5,0x6acd,0x6acf,0x6ad1,0x6ad3,0x6ad4,0x6ad8,0x6ae1,0x6ae5,0x6ae5,0x6ae7,0x6ae8,0x6aea,0x6aec,0x6aee,0x6af1,0x6af3,0x6af3,0x6af6,0x6af6,0x6af8,0x6afc,0x6b00,0x6b00,0x6b02,0x6b05,0x6b08,0x6b0b,0x6b0f,0x6b13,0x6b16,0x6b1a,0x6b1d,0x6b1e,0x6b20,0x6b21,0x6b23,0x6b23,0x6b25,0x6b25,0x6b28,0x6b28,0x6b2c,0x6b2d,0x6b2f,0x6b2f,0x6b31,0x6b3f,0x6b41,0x6b43,0x6b45,0x6b4e,0x6b50,0x6b52,0x6b54,0x6b57,0x6b59,0x6b59,0x6b5b,0x6b5c,0x6b5e,0x6b67,0x6b6a,0x6b6a,0x6b6d,0x6b6d,0x6b6f,0x6b6f,0x6b72,0x6b72,0x6b74,0x6b74,0x6b76,0x6b7b,0x6b7e,0x6b84,0x6b86,0x6b86,0x6b88,0x6b8a,0x6b8c,0x6b8f,0x6b91,0x6b91,0x6b94,0x6b99,0x6b9b,0x6b9b,0x6b9e,0x6ba0,0x6ba2,0x6ba7,0x6baa,0x6bab,0x6bad,0x6bb0,0x6bb2,0x6bb3,0x6bb5,0x6bb7,0x6bba,0x6bba,0x6bbc,0x6bbd,0x6bbf,0x6bc1,0x6bc3,0x6bcd,0x6bcf,0x6bd0,0x6bd2,0x6bd4,0x6bd6,0x6bd8,0x6bda,0x6bdc,0x6bde,0x6bde,0x6be0,0x6be4,0x6be6,0x6be8,0x6bea,0x6bec,0x6bef,0x6bf0,0x6bf2,0x6bf3,0x6bf7,0x6c06,0x6c08,0x6c09,0x6c0b,0x6c0d,0x6c0f,0x6c11,0x6c13,0x6c16,0x6c18,0x6c1d,0x6c1f,0x6c21,0x6c23,0x6c28,0x6c2a,0x6c2c,0x6c2e,0x6c3b,0x6c3d,0x6c43,0x6c46,0x6c46,0x6c49,0x6c50,0x6c52,0x6c52,0x6c54,0x6c55,0x6c57,0x6c61,0x6c65,0x6c6b,0x6c6d,0x6c76,0x6c78,0x6c7b,0x6c7d,0x6c90,0x6c92,0x6c96,0x6c98,0x6c9d,0x6c9f,0x6c9f,0x6ca2,0x6ca2,0x6caa,0x6cb4,0x6cb6,0x6cc7,0x6cc9,0x6cd7,0x6cd9,0x6ce3,0x6ce5,0x6ce5,0x6ce7,0x6cf3,0x6cf5,0x6cf5,0x6cf9,0x6cf9,0x6cff,0x6d12,0x6d16,0x6d1b,0x6d1d,0x6d20,0x6d22,0x6d22,0x6d24,0x6d42,0x6d4e,0x6d4e,0x6d57,0x6d5c,0x6d5e,0x6d6a,0x6d6c,0x6d72,0x6d74,0x6d98,0x6d9a,0x6d9a,0x6da4,0x6da5,0x6daa,0x6dac,0x6dae,0x6daf,0x6db1,0x6db5,0x6db7,0x6dc0,0x6dc2,0x6dc2,0x6dc4,0x6dcd,0x6dcf,0x6de6,0x6de8,0x6df7,0x6df9,0x6dfe,0x6e00,0x6e00,0x6e02,0x6e05,0x6e0a,0x6e0a,0x6e0f,0x6e0f,0x6e15,0x6e15,0x6e18,0x6e1d,0x6e1f,0x6e36,0x6e38,0x6e41,0x6e43,0x6e47,0x6e49,0x6e4b,0x6e4d,0x6e69,0x6e6b,0x6e6b,0x6e6e,0x6e6f,0x6e71,0x6e74,0x6e76,0x6e79,0x6e7c,0x6e7c,0x6e86,0x6e86,0x6e88,0x6e89,0x6e8b,0x6e8b,0x6e8d,0x6e90,0x6e92,0x6e94,0x6e96,0x6ea7,0x6eaa,0x6eab,0x6eae,0x6ed6,0x6ed8,0x6edd,0x6ee2,0x6ee2,0x6ee8,0x6ee9,0x6eeb,0x6eef,0x6ef1,0x6ef2,0x6ef4,0x6f0f,0x6f12,0x6f1a,0x6f1c,0x6f1c,0x6f1e,0x6f27,0x6f29,0x6f41,0x6f43,0x6f44,0x6f4e,0x6f58,0x6f5a,0x6f64,0x6f66,0x6f67,0x6f69,0x6f70,0x6f72,0x6f74,0x6f76,0x6f82,0x6f84,0x6f8e,0x6f90,0x6f90,0x6f92,0x6f97,0x6f9d,0x6fb6,0x6fb8,0x6fc4,0x6fc6,0x6fcf,0x6fd3,0x6fd5,0x6fd8,0x6fe4,0x6fe6,0x6fe9,0x6feb,0x6ff2,0x6ff4,0x6ff4,0x6ff6,0x6ff8,0x6ffa,0x6ffc,0x6ffe,0x7001,0x7003,0x7007,0x7009,0x700f,0x7011,0x7011,0x7014,0x7024,0x7026,0x702c,0x702f,0x7035,0x7037,0x703c,0x703e,0x7046,0x7048,0x704d,0x7050,0x7052,0x7054,0x7058,0x705a,0x706c,0x706e,0x7071,0x7074,0x707a,0x707c,0x707f,0x7081,0x7086,0x7089,0x708b,0x708e,0x708f,0x7091,0x7096,0x7098,0x709a,0x709f,0x70a1,0x70a3,0x70a7,0x70a9,0x70a9,0x70ab,0x70b1,0x70b3,0x70b5,0x70b7,0x70be,0x70c0,0x70c0,0x70c4,0x70c8,0x70ca,0x70da,0x70dc,0x70e2,0x70e4,0x70e4,0x70ef,0x70f1,0x70f3,0x7100,0x7102,0x7102,0x7104,0x7106,0x7109,0x710e,0x7110,0x7110,0x7113,0x7113,0x7117,0x7117,0x7119,0x7123,0x7125,0x7126,0x7128,0x7129,0x712b,0x712c,0x712e,0x7136,0x713a,0x713b,0x713e,0x713e,0x7140,0x7147,0x7149,0x7154,0x7156,0x715a,0x715c,0x716c,0x716e,0x716e,0x7170,0x7178,0x717a,0x717e,0x7180,0x7182,0x7184,0x718a,0x718c,0x718c,0x718e,0x7192,0x7194,0x7194,0x7196,0x71a5,0x71a7,0x71aa,0x71ac,0x71ad,0x71af,0x71b5,0x71b7,0x71ba,0x71bc,0x71cb,0x71ce,0x71d2,0x71d4,0x71d6,0x71d8,0x71dd,0x71df,0x71e2,0x71e4,0x71e8,0x71eb,0x71ee,0x71f0,0x71f2,0x71f4,0x71f6,0x71f8,0x71f9,0x71fb,0x7203,0x7205,0x7207,0x7209,0x720a,0x720c,0x7210,0x7213,0x7217,0x7219,0x721b,0x721d,0x721f,0x7222,0x722e,0x7230,0x7230,0x7235,0x7236,0x7238,0x723b,0x723d,0x7242,0x7244,0x7244,0x7246,0x724c,0x724f,0x7250,0x7252,0x7253,0x7255,0x7263,0x7266,0x7267,0x7269,0x726a,0x726c,0x726c,0x726e,0x7270,0x7272,0x7274,0x7276,0x7279,0x727b,0x7282,0x7284,0x7289,0x728b,0x7298,0x729a,0x729b,0x729d,0x729f,0x72a1,0x72aa,0x72ac,0x72b0,0x72b2,0x72b2,0x72b4,0x72b5,0x72ba,0x72ba,0x72bd,0x72bd,0x72bf,0x72c6,0x72c9,0x72ce,0x72d0,0x72d2,0x72d4,0x72d4,0x72d6,0x72da,0x72dc,0x72dc,0x72df,0x72e4,0x72e6,0x72e6,0x72e8,0x72eb,0x72f3,0x72f4,0x72f6,0x7302,0x7304,0x7304,0x7307,0x7308,0x730a,0x730c,0x730f,0x7313,0x7316,0x7319,0x731b,0x731e,0x7322,0x7323,0x7325,0x732e,0x7330,0x733c,0x733e,0x7345,0x7348,0x734a,0x734c,0x7352,0x7357,0x735b,0x735d,0x7362,0x7365,0x736c,0x736e,0x7378,0x737a,0x738c,0x738e,0x738f,0x7392,0x7398,0x739c,0x73a2,0x73a4,0x73ad,0x73b2,0x73bc,0x73be,0x73c0,0x73c2,0x73d0,0x73d2,0x73de,0x73e0,0x73eb,0x73ed,0x73ef,0x73f3,0x740d,0x7411,0x7412,0x7414,0x7417,0x7419,0x7426,0x7428,0x743a,0x743c,0x743c,0x743f,0x7457,0x7459,0x7465,0x7467,0x7476,0x7479,0x747a,0x747c,0x7483,0x7485,0x748d,0x7490,0x7490,0x7492,0x7492,0x7494,0x7495,0x7497,0x74a1,0x74a3,0x74ab,0x74ad,0x74ad,0x74af,0x74b2,0x74b4,0x74bb,0x74bd,0x74c3,0x74c5,0x74c6,0x74c8,0x74c8,0x74ca,0x74cc,0x74cf,0x74d0,0x74d3,0x74e9,0x74ec,0x74ec,0x74ee,0x74ee,0x74f0,0x74f2,0x74f4,0x74f8,0x74fb,0x74fb,0x74fd,0x7500,0x7502,0x7505,0x7507,0x7508,0x750b,0x751a,0x751c,0x751f,0x7521,0x7522,0x7525,0x7526,0x7528,0x7535,0x7537,0x753b,0x753d,0x7540,0x7542,0x7542,0x7546,0x7548,0x754a,0x754f,0x7551,0x7551,0x7553,0x7555,0x7559,0x755d,0x755f,0x7560,0x7562,0x7567,0x756a,0x7570,0x7572,0x7572,0x7576,0x757a,0x757d,0x7580,0x7583,0x7584,0x7586,0x7587,0x758a,0x7592,0x7594,0x7595,0x7598,0x759a,0x759d,0x759e,0x75a2,0x75a5,0x75a7,0x75a7,0x75aa,0x75ab,0x75b0,0x75b6,0x75b8,0x75c5,0x75c7,0x75c8,0x75ca,0x75d2,0x75d4,0x75d5,0x75d7,0x75e4,0x75e6,0x75e7,0x75ed,0x75ed,0x75ef,0x7603,0x7607,0x760d,0x760f,0x7611,0x7613,0x7616,0x7619,0x7629,0x762c,0x762d,0x762f,0x7635,0x7638,0x7638,0x763a,0x763d,0x7640,0x7640,0x7642,0x7643,0x7646,0x7649,0x764c,0x7654,0x7656,0x765a,0x765c,0x765c,0x765f,0x7662,0x7664,0x7667,0x7669,0x766a,0x766c,0x7676,0x7678,0x767f,0x7681,0x7682,0x7684,0x7684,0x7686,0x768b,0x768e,0x7690,0x7692,0x7693,0x7695,0x7696,0x7699,0x769e,0x76a1,0x76a1,0x76a4,0x76a6,0x76aa,0x76ab,0x76ad,0x76b0,0x76b4,0x76b5,0x76b7,0x76b8,0x76ba,0x76bb,0x76bd,0x76bf,0x76c2,0x76c6,0x76c8,0x76ca,0x76cc,0x76ce,0x76d2,0x76d4,0x76d6,0x76d6,0x76d9,0x76df,0x76e1,0x76e1,0x76e3,0x76e7,0x76e9,0x76ea,0x76ec,0x76f5,0x76f7,0x76fc,0x76fe,0x76fe,0x7701,0x7701,0x7703,0x7705,0x7707,0x770c,0x770e,0x7713,0x7715,0x7715,0x7719,0x771b,0x771d,0x7720,0x7722,0x7729,0x772b,0x772b,0x772d,0x772d,0x772f,0x772f,0x7731,0x773e,0x7740,0x7740,0x7743,0x7747,0x774a,0x774f,0x7752,0x7752,0x7754,0x7756,0x7758,0x775c,0x775e,0x7763,0x7765,0x776f,0x7772,0x7772,0x7777,0x7785,0x7787,0x7789,0x778b,0x778f,0x7791,0x7791,0x7793,0x7793,0x7795,0x7795,0x7797,0x77a3,0x77a5,0x77a5,0x77a7,0x77a8,0x77aa,0x77ad,0x77af,0x77b7,0x77b9,0x77bf,0x77c2,0x77c5,0x77c7,0x77c7,0x77c9,0x77d0,0x77d3,0x77d5,0x77d7,0x77de,0x77e0,0x77e0,0x77e2,0x77e3,0x77e5,0x77e9,0x77ec,0x77f4,0x77f7,0x77fe,0x7802,0x7803,0x7805,0x7806,0x7808,0x7809,0x780c,0x7814,0x7818,0x7818,0x781c,0x7823,0x7825,0x7835,0x7837,0x7839,0x783c,0x783d,0x7842,0x7845,0x7847,0x784e,0x7850,0x7854,0x785c,0x785e,0x7860,0x7860,0x7862,0x7862,0x7864,0x7866,0x7868,0x7871,0x7879,0x787c,0x787e,0x7881,0x7883,0x7889,0x788c,0x788f,0x7891,0x7891,0x7893,0x789a,0x789e,0x78a5,0x78a7,0x78ad,0x78af,0x78b4,0x78b6,0x78b6,0x78b8,0x78bc,0x78be,0x78be,0x78c1,0x78c1,0x78c3,0x78c5,0x78c7,0x78d5,0x78d7,0x78d8,0x78da,0x78db,0x78dd,0x78e5,0x78e7,0x78ea,0x78ec,0x78f5,0x78f7,0x78f7,0x78f9,0x78ff,0x7901,0x7902,0x7904,0x7906,0x7909,0x7909,0x790c,0x790c,0x790e,0x790e,0x7910,0x7914,0x7917,0x7917,0x7919,0x7919,0x791b,0x791e,0x7921,0x7921,0x7923,0x792f,0x7931,0x7936,0x7938,0x7942,0x7944,0x794c,0x794f,0x7965,0x7967,0x796b,0x796d,0x796d,0x7970,0x7974,0x7979,0x797a,0x797c,0x7983,0x7986,0x7988,0x798a,0x798b,0x798d,0x799d,0x799f,0x79a2,0x79a4,0x79ae,0x79b0,0x79b4,0x79b6,0x79bb,0x79bd,0x79c1,0x79c4,0x79c6,0x79c8,0x79d2,0x79d4,0x79d6,0x79d8,0x79d8,0x79dc,0x79e0,0x79e2,0x79e4,0x79e6,0x79e7,0x79e9,0x79ee,0x79f1,0x79f1,0x79f4,0x79f4,0x79f6,0x79f8,0x79fa,0x79fb,0x7a00,0x7a00,0x7a02,0x7a06,0x7a08,0x7a08,0x7a0a,0x7a0e,0x7a10,0x7a15,0x7a17,0x7a1c,0x7a1e,0x7a20,0x7a22,0x7a22,0x7a26,0x7a26,0x7a28,0x7a28,0x7a2a,0x7a32,0x7a37,0x7a37,0x7a39,0x7a40,0x7a43,0x7a4e,0x7a54,0x7a54,0x7a56,0x7a58,0x7a5a,0x7a5c,0x7a5f,0x7a62,0x7a65,0x7a65,0x7a67,0x7a69,0x7a6b,0x7a6e,0x7a70,0x7a72,0x7a74,0x7a76,0x7a78,0x7a7b,0x7a7d,0x7a81,0x7a83,0x7a8c,0x7a8f,0x7a99,0x7a9e,0x7aa0,0x7aa2,0x7aa3,0x7aa8,0x7aac,0x7aae,0x7ab8,0x7aba,0x7abc,0x7abe,0x7ac5,0x7ac7,0x7acb,0x7acf,0x7acf,0x7ad1,0x7ad1,0x7ad3,0x7ad3,0x7ad8,0x7add,0x7adf,0x7ae0,0x7ae2,0x7ae7,0x7ae9,0x7aeb,0x7aed,0x7aef,0x7af6,0x7af7,0x7af9,0x7b01,0x7b04,0x7b06,0x7b08,0x7b0c,0x7b0e,0x7b14,0x7b18,0x7b1b,0x7b1d,0x7b20,0x7b22,0x7b35,0x7b38,0x7b39,0x7b3b,0x7b3b,0x7b40,0x7b40,0x7b42,0x7b52,0x7b54,0x7b56,0x7b58,0x7b58,0x7b60,0x7b67,0x7b69,0x7b69,0x7b6c,0x7b78,0x7b7b,0x7b7b,0x7b82,0x7b82,0x7b84,0x7b85,0x7b87,0x7b88,0x7b8a,0x7b92,0x7b94,0x7b9d,0x7ba0,0x7ba4,0x7bac,0x7baf,0x7bb1,0x7bb2,0x7bb4,0x7bb5,0x7bb7,0x7bb9,0x7bbe,0x7bbe,0x7bc0,0x7bc1,0x7bc4,0x7bc7,0x7bc9,0x7bcc,0x7bce,0x7bd0,0x7bd4,0x7bd5,0x7bd8,0x7bec,0x7bf0,0x7bf4,0x7bf7,0x7c03,0x7c05,0x7c07,0x7c09,0x7c12,0x7c15,0x7c15,0x7c19,0x7c19,0x7c1b,0x7c23,0x7c25,0x7c2d,0x7c30,0x7c30,0x7c33,0x7c33,0x7c35,0x7c35,0x7c37,0x7c39,0x7c3b,0x7c40,0x7c42,0x7c45,0x7c47,0x7c4a,0x7c4c,0x7c4d,0x7c50,0x7c51,0x7c53,0x7c54,0x7c56,0x7c57,0x7c59,0x7c5d,0x7c5f,0x7c60,0x7c63,0x7c67,0x7c69,0x7c70,0x7c72,0x7c75,0x7c78,0x7c81,0x7c83,0x7c86,0x7c88,0x7c8a,0x7c8c,0x7c8e,0x7c91,0x7c92,0x7c94,0x7c98,0x7c9c,0x7c9c,0x7c9e,0x7c9f,0x7ca1,0x7ca3,0x7ca5,0x7ca8,0x7cac,0x7cac,0x7cae,0x7caf,0x7cb1,0x7cb5,0x7cb8,0x7cbf,0x7cc2,0x7cc3,0x7cc5,0x7cc5,0x7cc7,0x7cce,0x7cd0,0x7cd7,0x7cd9,0x7cda,0x7cdc,0x7ce0,0x7ce2,0x7ce2,0x7ce6,0x7ce8,0x7cea,0x7cea,0x7cec,0x7cf9,0x7cfb,0x7cfe,0x7d00,0x7d22,0x7d25,0x7d25,0x7d28,0x7d29,0x7d2b,0x7d2c,0x7d2e,0x7d33,0x7d35,0x7d36,0x7d38,0x7d47,0x7d4a,0x7d4a,0x7d4d,0x7d56,0x7d58,0x7d58,0x7d5a,0x7d5f,0x7d61,0x7d63,0x7d66,0x7d6b,0x7d6d,0x7d73,0x7d79,0x7d7d,0x7d7f,0x7d81,0x7d83,0x7d86,0x7d88,0x7d89,0x7d8b,0x7d8f,0x7d91,0x7d97,0x7d9c,0x7da4,0x7da6,0x7db5,0x7db7,0x7dc2,0x7dc4,0x7dc7,0x7dc9,0x7dd0,0x7dd2,0x7dd4,0x7dd7,0x7de1,0x7de3,0x7dea,0x7dec,0x7dec,0x7dee,0x7df7,0x7df9,0x7dfe,0x7e03,0x7e03,0x7e07,0x7e17,0x7e1a,0x7e25,0x7e27,0x7e27,0x7e29,0x7e2b,0x7e2d,0x7e49,0x7e4c,0x7e4c,0x7e50,0x7e5c,0x7e5e,0x7e63,0x7e65,0x7e65,0x7e67,0x7e70,0x7e72,0x7e82,0x7e86,0x7e88,0x7e8a,0x7e8f,0x7e91,0x7e9c,0x7e9f,0x7e9f,0x7ea4,0x7ea4,0x7eac,0x7eac,0x7eba,0x7eba,0x7ec7,0x7ec7,0x7ecf,0x7ecf,0x7edf,0x7edf,0x7f06,0x7f06,0x7f36,0x7f3a,0x7f3d,0x7f41,0x7f43,0x7f45,0x7f47,0x7f55,0x7f58,0x7f58,0x7f5b,0x7f61,0x7f63,0x7f63,0x7f65,0x7f6e,0x7f70,0x7f73,0x7f75,0x7f7f,0x7f83,0x7f83,0x7f85,0x7f8f,0x7f91,0x7f97,0x7f9a,0x7f9e,0x7fa0,0x7fa9,0x7fac,0x7fc3,0x7fc5,0x7fc5,0x7fc7,0x7fc7,0x7fc9,0x7fd2,0x7fd4,0x7fd5,0x7fd7,0x7fd7,0x7fdb,0x7fe3,0x7fe5,0x7ff5,0x7ff7,0x8008,0x800b,0x8012,0x8014,0x8019,0x801b,0x8021,0x8024,0x8026,0x8028,0x802a,0x802c,0x802c,0x802e,0x8031,0x8033,0x8037,0x8039,0x8039,0x803b,0x803f,0x8043,0x8043,0x8046,0x8048,0x804a,0x804a,0x804f,0x8052,0x8054,0x8054,0x8056,0x8056,0x8058,0x8058,0x805a,0x805e,0x8061,0x8064,0x8066,0x8067,0x806c,0x806c,0x806f,0x8073,0x8075,0x8079,0x807d,0x8080,0x8082,0x8082,0x8084,0x8087,0x8089,0x808c,0x808f,0x8090,0x8092,0x8093,0x8095,0x8096,0x8098,0x809d,0x809f,0x809f,0x80a1,0x80a3,0x80a5,0x80a5,0x80a7,0x80a7,0x80a9,0x80ab,0x80ad,0x80af,0x80b1,0x80b2,0x80b4,0x80b8,0x80ba,0x80ba,0x80bc,0x80bd,0x80c2,0x80ca,0x80cc,0x80d1,0x80d4,0x80de,0x80e0,0x80e1,0x80e3,0x80e6,0x80e9,0x80e9,0x80ec,0x80ed,0x80ef,0x80f6,0x80f8,0x80fe,0x8100,0x8103,0x8105,0x810a,0x810c,0x810c,0x810e,0x810e,0x8112,0x8112,0x8114,0x811b,0x811d,0x811f,0x8121,0x8125,0x8127,0x8127,0x8129,0x812d,0x812f,0x8132,0x8134,0x8134,0x8137,0x8137,0x8139,0x813a,0x813d,0x813e,0x8142,0x8144,0x8146,0x8148,0x814a,0x8156,0x8159,0x815c,0x815e,0x815e,0x8160,0x8162,0x8164,0x8167,0x8169,0x8169,0x816b,0x8174,0x8176,0x817a,0x817c,0x817d,0x817f,0x8180,0x8182,0x8184,0x8186,0x818d,0x818f,0x818f,0x8193,0x8193,0x8195,0x8195,0x8197,0x81a0,0x81a2,0x81a3,0x81a5,0x81ac,0x81ae,0x81ae,0x81b0,0x81b7,0x81b9,0x81ca,0x81cc,0x81cd,0x81cf,0x81d2,0x81d5,0x81d5,0x81d7,0x81db,0x81dd,0x81ea,0x81ec,0x81ef,0x81f2,0x81f4,0x81f6,0x81fc,0x81fe,0x8202,0x8204,0x8205,0x8207,0x820d,0x8210,0x8212,0x8214,0x8216,0x8218,0x8218,0x821a,0x8222,0x8225,0x8226,0x8228,0x822d,0x822f,0x822f,0x8232,0x823a,0x823c,0x8240,0x8242,0x8242,0x8244,0x8245,0x8247,0x8247,0x8249,0x8249,0x824b,0x824b,0x824e,0x825c,0x825e,0x825f,0x8261,0x8266,0x8268,0x8269,0x826b,0x826f,0x8271,0x8272,0x8274,0x8280,0x8283,0x8285,0x8287,0x8287,0x828a,0x828b,0x828d,0x8294,0x8298,0x829b,0x829d,0x82b1,0x82b3,0x82c0,0x82c2,0x82c4,0x82ca,0x82ca,0x82cf,0x82d9,0x82db,0x82dc,0x82de,0x82e8,0x82ea,0x8309,0x830b,0x830d,0x8316,0x831e,0x8320,0x8320,0x8322,0x8322,0x8324,0x832d,0x832f,0x832f,0x8331,0x833d,0x833f,0x8345,0x8347,0x8354,0x8356,0x8357,0x8362,0x8363,0x8366,0x8366,0x836f,0x836f,0x8373,0x8378,0x837a,0x837f,0x8381,0x8381,0x8383,0x8383,0x8385,0x839e,0x83a0,0x83a0,0x83a2,0x83ac,0x83ae,0x83b0,0x83b9,0x83b9,0x83bd,0x83cf,0x83d1,0x83d1,0x83d3,0x83d9,0x83db,0x83e5,0x83e7,0x83f6,0x83f8,0x83ff,0x8401,0x8401,0x8403,0x8407,0x8409,0x8414,0x8416,0x8416,0x8418,0x8418,0x841b,0x841c,0x8420,0x8421,0x8423,0x8424,0x8426,0x8426,0x8429,0x8429,0x842b,0x8440,0x8442,0x844e,0x8450,0x8469,0x846b,0x847a,0x847d,0x8480,0x8482,0x8482,0x8484,0x8484,0x8486,0x8486,0x8488,0x8488,0x848d,0x8494,0x8496,0x84a4,0x84a7,0x84b2,0x84b4,0x84b4,0x84b6,0x84b6,0x84b8,0x84c2,0x84c4,0x84c7,0x84c9,0x84d4,0x84d6,0x84d7,0x84da,0x84db,0x84de,0x84de,0x84e1,0x84e2,0x84e4,0x84e5,0x84e7,0x84ec,0x84ee,0x84f4,0x84f6,0x8500,0x8502,0x851a,0x851c,0x8521,0x8523,0x8531,0x8533,0x8534,0x8538,0x8538,0x853b,0x853b,0x853d,0x853e,0x8540,0x854e,0x8551,0x855b,0x855d,0x8571,0x8573,0x8573,0x8575,0x857c,0x857e,0x857e,0x8580,0x8591,0x8593,0x85a4,0x85a6,0x85aa,0x85af,0x85b1,0x85b3,0x85ba,0x85bd,0x85c9,0x85cb,0x85cb,0x85cd,0x85d2,0x85d5,0x85da,0x85dc,0x85e6,0x85e8,0x85f2,0x85f4,0x85f4,0x85f6,0x8602,0x8604,0x8607,0x8609,0x860d,0x860f,0x8611,0x8613,0x8614,0x8616,0x861c,0x861e,0x862a,0x862c,0x862f,0x8631,0x8636,0x8638,0x863c,0x863e,0x8640,0x8642,0x8643,0x8645,0x8648,0x864b,0x864e,0x8650,0x8650,0x8652,0x8656,0x8659,0x8659,0x865b,0x865c,0x865e,0x865f,0x8661,0x8665,0x8667,0x8674,0x8677,0x8677,0x8679,0x867c,0x867e,0x867e,0x8685,0x8687,0x868a,0x868e,0x8690,0x869a,0x869c,0x869e,0x86a0,0x86a5,0x86a7,0x86aa,0x86ad,0x86ad,0x86af,0x86c9,0x86cb,0x86cc,0x86d0,0x86d1,0x86d3,0x86d4,0x86d6,0x86df,0x86e2,0x86e4,0x86e6,0x86e6,0x86e8,0x86ed,0x86ef,0x86ef,0x86f5,0x86fb,0x86fe,0x86fe,0x8700,0x870e,0x8711,0x8713,0x8715,0x8715,0x8718,0x871c,0x871e,0x871e,0x8720,0x872a,0x872c,0x872e,0x8730,0x8735,0x8737,0x8738,0x873a,0x873c,0x873e,0x8743,0x8746,0x8746,0x874c,0x8771,0x8773,0x877b,0x877d,0x877d,0x8781,0x8789,0x878b,0x878d,0x878f,0x8794,0x8796,0x8798,0x879a,0x879f,0x87a2,0x87a5,0x87a9,0x87c6,0x87c8,0x87cc,0x87ce,0x87ce,0x87d1,0x87d4,0x87d6,0x87e8,0x87ea,0x87ef,0x87f2,0x87f7,0x87f9,0x87fc,0x87fe,0x8806,0x8808,0x880d,0x880f,0x8811,0x8813,0x8819,0x881b,0x881d,0x881f,0x8833,0x8835,0x8839,0x883b,0x8846,0x8848,0x8848,0x884a,0x884f,0x8852,0x8853,0x8855,0x8857,0x8859,0x885b,0x885d,0x885e,0x8860,0x8865,0x8867,0x886b,0x886d,0x8872,0x8874,0x8877,0x8879,0x8879,0x887c,0x8884,0x8887,0x8889,0x888b,0x8893,0x8895,0x88a2,0x88a4,0x88a4,0x88a7,0x88a8,0x88aa,0x88ac,0x88ae,0x88ae,0x88b1,0x88b2,0x88b4,0x88ba,0x88bc,0x88c2,0x88c5,0x88c5,0x88c7,0x88c7,0x88c9,0x88d0,0x88d2,0x88d2,0x88d4,0x88df,0x88e1,0x88e1,0x88e6,0x88e8,0x88eb,0x88ec,0x88ee,0x8902,0x8905,0x8907,0x8909,0x890c,0x890e,0x890e,0x8910,0x891a,0x891e,0x891f,0x8921,0x8927,0x8929,0x8933,0x8935,0x8938,0x893b,0x893e,0x8941,0x8944,0x8946,0x8947,0x8949,0x8949,0x894b,0x894d,0x894f,0x8954,0x8956,0x8966,0x8969,0x896f,0x8971,0x8974,0x8976,0x8977,0x8979,0x897c,0x897e,0x8983,0x8985,0x898b,0x898f,0x898f,0x8991,0x8991,0x8993,0x8998,0x899b,0x899f,0x89a1,0x89a7,0x89a9,0x89aa,0x89ac,0x89af,0x89b2,0x89b2,0x89b6,0x89b7,0x89b9,0x89ba,0x89bc,0x89c1,0x89c6,0x89c6,0x89d2,0x89d6,0x89d9,0x89dd,0x89df,0x89e9,0x89eb,0x89ed,0x89f0,0x89f4,0x89f6,0x89f8,0x89fa,0x89fc,0x89fe,0x8a00,0x8a02,0x8a04,0x8a07,0x8a08,0x8a0a,0x8a0a,0x8a0c,0x8a0c,0x8a0e,0x8a13,0x8a15,0x8a18,0x8a1b,0x8a1f,0x8a22,0x8a23,0x8a25,0x8a25,0x8a27,0x8a27,0x8a29,0x8a2d,0x8a30,0x8a31,0x8a34,0x8a34,0x8a36,0x8a36,0x8a38,0x8a41,0x8a44,0x8a46,0x8a48,0x8a4a,0x8a4c,0x8a52,0x8a54,0x8a59,0x8a5b,0x8a5b,0x8a5e,0x8a5e,0x8a60,0x8a63,0x8a66,0x8a69,0x8a6b,0x8a6e,0x8a70,0x8a77,0x8a79,0x8a7c,0x8a7e,0x8a7f,0x8a81,0x8a87,0x8a8b,0x8a8d,0x8a8f,0x8a96,0x8a98,0x8a9a,0x8a9c,0x8a9c,0x8a9e,0x8a9e,0x8aa0,0x8aa1,0x8aa3,0x8aac,0x8aaf,0x8ab0,0x8ab2,0x8ab2,0x8ab4,0x8ab4,0x8ab6,0x8ab6,0x8ab8,0x8ac0,0x8ac2,0x8ac9,0x8acb,0x8acd,0x8acf,0x8acf,0x8ad1,0x8ae2,0x8ae4,0x8ae4,0x8ae6,0x8ae8,0x8aea,0x8aeb,0x8aed,0x8afc,0x8afe,0x8b02,0x8b04,0x8b08,0x8b0a,0x8b20,0x8b22,0x8b28,0x8b2a,0x8b31,0x8b33,0x8b33,0x8b35,0x8b37,0x8b39,0x8b43,0x8b45,0x8b5a,0x8b5c,0x8b60,0x8b62,0x8b63,0x8b65,0x8b6d,0x8b6f,0x8b70,0x8b74,0x8b74,0x8b77,0x8b7b,0x8b7d,0x8b86,0x8b88,0x8b88,0x8b8a,0x8b8c,0x8b8e,0x8b90,0x8b92,0x8b96,0x8b98,0x8b9c,0x8b9e,0x8ba0,0x8bbe,0x8bbe,0x8be2,0x8be2,0x8c37,0x8c37,0x8c39,0x8c39,0x8c3b,0x8c3f,0x8c41,0x8c43,0x8c45,0x8c51,0x8c54,0x8c57,0x8c5a,0x8c5a,0x8c5c,0x8c5d,0x8c5f,0x8c5f,0x8c61,0x8c62,0x8c64,0x8c66,0x8c68,0x8c6d,0x8c6f,0x8c73,0x8c75,0x8c7b,0x8c7d,0x8c7d,0x8c80,0x8c82,0x8c84,0x8c86,0x8c89,0x8c8a,0x8c8c,0x8c8d,0x8c8f,0x8c95,0x8c97,0x8ca5,0x8ca7,0x8cad,0x8caf,0x8cb0,0x8cb2,0x8cc5,0x8cc7,0x8cc8,0x8cca,0x8cca,0x8ccc,0x8ccd,0x8ccf,0x8ccf,0x8cd1,0x8cd7,0x8cd9,0x8cee,0x8cf0,0x8cf5,0x8cf7,0x8cfe,0x8d00,0x8d00,0x8d02,0x8d0d,0x8d0f,0x8d19,0x8d1b,0x8d1d,0x8d64,0x8d64,0x8d66,0x8d69,0x8d6b,0x8d70,0x8d72,0x8d74,0x8d76,0x8d7b,0x8d7d,0x8d7d,0x8d80,0x8d82,0x8d84,0x8d85,0x8d89,0x8d8a,0x8d8c,0x8d96,0x8d99,0x8d99,0x8d9b,0x8d9c,0x8d9f,0x8da1,0x8da3,0x8da3,0x8da5,0x8daf,0x8db2,0x8db7,0x8db9,0x8dba,0x8dbc,0x8dbc,0x8dbe,0x8dc3,0x8dc5,0x8dc8,0x8dcb,0x8dd1,0x8dd3,0x8ddd,0x8ddf,0x8de4,0x8de6,0x8dec,0x8dee,0x8df4,0x8dfa,0x8dfa,0x8dfc,0x8e07,0x8e09,0x8e0a,0x8e0d,0x8e2b,0x8e2d,0x8e2e,0x8e30,0x8e31,0x8e33,0x8e36,0x8e38,0x8e3a,0x8e3c,0x8e42,0x8e44,0x8e50,0x8e53,0x8e57,0x8e59,0x8e6a,0x8e6c,0x8e6d,0x8e6f,0x8e6f,0x8e71,0x8e78,0x8e7a,0x8e7c,0x8e7e,0x8e7e,0x8e80,0x8e82,0x8e84,0x8e8e,0x8e90,0x8e98,0x8e9a,0x8e9a,0x8e9d,0x8ea1,0x8ea3,0x8ead,0x8eb0,0x8eb0,0x8eb2,0x8eb2,0x8eb6,0x8eb6,0x8eb9,0x8eba,0x8ebc,0x8ebd,0x8ec0,0x8ec0,0x8ec2,0x8ec3,0x8ec9,0x8ecf,0x8ed1,0x8ed4,0x8ed7,0x8ed8,0x8eda,0x8ee2,0x8ee4,0x8ee9,0x8eeb,0x8eef,0x8ef1,0x8ef2,0x8ef4,0x8efc,0x8efe,0x8f03,0x8f05,0x8f0b,0x8f0d,0x8f0e,0x8f10,0x8f20,0x8f23,0x8f26,0x8f29,0x8f2a,0x8f2c,0x8f30,0x8f32,0x8f39,0x8f3b,0x8f3c,0x8f3e,0x8f4b,0x8f4d,0x8f64,0x8f66,0x8f67,0x8f6e,0x8f6e,0x8f93,0x8f93,0x8f9b,0x8f9c,0x8f9f,0x8fa0,0x8fa3,0x8fa3,0x8fa5,0x8fa8,0x8fad,0x8fbc,0x8fbe,0x8fbf,0x8fc1,0x8fc2,0x8fc4,0x8fc6,0x8fc9,0x8fd7,0x8fda,0x8fda,0x8fe0,0x8fe6,0x8fe8,0x8fe8,0x8fea,0x8feb,0x8fed,0x8fee,0x8ff0,0x8ff0,0x8ff4,0x9006,0x9008,0x9008,0x900b,0x900d,0x900f,0x9012,0x9014,0x9017,0x9019,0x9024,0x902d,0x902f,0x9031,0x9038,0x903c,0x903f,0x9041,0x9042,0x9044,0x9044,0x9046,0x9047,0x9049,0x9056,0x9058,0x9059,0x905b,0x905e,0x9060,0x9064,0x9067,0x9069,0x906b,0x9070,0x9072,0x9088,0x908a,0x908b,0x908d,0x908d,0x908f,0x9091,0x9094,0x9095,0x9097,0x9099,0x909b,0x909b,0x909e,0x90a3,0x90a5,0x90a8,0x90aa,0x90aa,0x90ae,0x90b6,0x90b8,0x90b8,0x90bb,0x90bb,0x90bd,0x90bf,0x90c1,0x90c1,0x90c3,0x90c5,0x90c7,0x90c8,0x90ca,0x90cb,0x90ce,0x90ce,0x90d4,0x90dd,0x90df,0x90e5,0x90e8,0x90ed,0x90ef,0x90f5,0x90f9,0x9109,0x910b,0x910b,0x910d,0x9112,0x9114,0x9114,0x9116,0x9124,0x9126,0x9136,0x9138,0x913b,0x913e,0x9141,0x9143,0x9153,0x9155,0x915a,0x915c,0x915c,0x915e,0x9165,0x9167,0x916a,0x916c,0x916c,0x916e,0x9170,0x9172,0x917a,0x917c,0x917c,0x9180,0x9187,0x9189,0x9193,0x9196,0x9196,0x9199,0x91a3,0x91a5,0x91a5,0x91a7,0x91b7,0x91b9,0x91be,0x91c0,0x91c7,0x91c9,0x91c9,0x91cb,0x91d1,0x91d3,0x91da,0x91dc,0x91dd,0x91df,0x91df,0x91e2,0x91ee,0x91f1,0x91f1,0x91f3,0x91fa,0x91fd,0x920a,0x920c,0x921a,0x921c,0x921c,0x921e,0x921e,0x9221,0x9221,0x9223,0x9228,0x922a,0x922b,0x922d,0x922e,0x9230,0x923a,0x923c,0x9241,0x9244,0x9246,0x9248,0x9258,0x925a,0x925b,0x925d,0x9267,0x926b,0x9270,0x9272,0x9272,0x9276,0x928f,0x9291,0x9291,0x9293,0x929d,0x92a0,0x92ac,0x92ae,0x92ae,0x92b1,0x92b7,0x92b9,0x92bc,0x92be,0x92d5,0x92d7,0x92d9,0x92db,0x92db,0x92dd,0x92e1,0x92e3,0x92f4,0x92f6,0x9304,0x9306,0x9309,0x930b,0x9310,0x9312,0x9316,0x9318,0x931b,0x931d,0x9331,0x9333,0x9336,0x9338,0x9339,0x933c,0x933c,0x9340,0x9352,0x9354,0x935c,0x935e,0x936e,0x9370,0x9371,0x9373,0x937e,0x9380,0x938a,0x938c,0x9392,0x9394,0x93aa,0x93ac,0x93b5,0x93b7,0x93b8,0x93bb,0x93bb,0x93bd,0x93bd,0x93bf,0x93c0,0x93c2,0x93c4,0x93c6,0x93c8,0x93ca,0x93e4,0x93e6,0x93e8,0x93ec,0x93ec,0x93ee,0x93ee,0x93f0,0x93f1,0x93f3,0x9401,0x9403,0x9404,0x9406,0x9419,0x941b,0x941b,0x941d,0x941d,0x9420,0x9420,0x9424,0x9433,0x9435,0x9440,0x9442,0x944d,0x944f,0x9452,0x9454,0x9455,0x9457,0x9458,0x945b,0x945b,0x945d,0x945e,0x9460,0x9460,0x9462,0x9465,0x9467,0x9479,0x947b,0x9483,0x9485,0x9485,0x949f,0x949f,0x94a2,0x94a2,0x94c1,0x94c1,0x94c3,0x94c3,0x94dc,0x94dc,0x94f6,0x94f6,0x952d,0x952d,0x9547,0x9547,0x9577,0x9578,0x957a,0x957d,0x957f,0x9580,0x9582,0x9583,0x9585,0x9586,0x9588,0x9589,0x958b,0x9594,0x9596,0x9599,0x959b,0x959c,0x959e,0x95ae,0x95b0,0x95b2,0x95b5,0x95b7,0x95b9,0x95c0,0x95c3,0x95c3,0x95c5,0x95cd,0x95d0,0x95d6,0x95da,0x95dc,0x95de,0x95e5,0x95e8,0x95e8,0x95f4,0x95f4,0x961c,0x961e,0x9620,0x9624,0x9628,0x9628,0x962a,0x962a,0x962c,0x9633,0x9638,0x963d,0x963f,0x9645,0x964a,0x9651,0x9653,0x9654,0x9656,0x9656,0x9658,0x9658,0x965b,0x965f,0x9661,0x9664,0x9669,0x966d,0x966f,0x9678,0x967b,0x967e,0x9680,0x9681,0x9683,0x968b,0x968d,0x968f,0x9691,0x9699,0x969b,0x969c,0x969e,0x969e,0x96a1,0x96a5,0x96a7,0x96aa,0x96ac,0x96ac,0x96ae,0x96ae,0x96b0,0x96b1,0x96b3,0x96b4,0x96b6,0x96b6,0x96b8,0x96b9,0x96bb,0x96bd,0x96bf,0x96ce,0x96d2,0x96df,0x96e1,0x96e3,0x96e5,0x96e5,0x96e8,0x96ea,0x96ef,0x96f2,0x96f4,0x96fb,0x96fd,0x96fd,0x96ff,0x9700,0x9702,0x9709,0x970b,0x970b,0x970d,0x9713,0x9716,0x9716,0x9718,0x9719,0x971b,0x972c,0x972e,0x9732,0x9734,0x9736,0x9738,0x973a,0x973d,0x9744,0x9746,0x974b,0x9751,0x9752,0x9755,0x9758,0x975a,0x9762,0x9766,0x9766,0x9768,0x976a,0x976c,0x976e,0x9770,0x9774,0x9776,0x9778,0x977a,0x9785,0x9787,0x978b,0x978d,0x978f,0x9794,0x9794,0x9797,0x97a6,0x97a8,0x97a8,0x97aa,0x97ae,0x97b1,0x97b4,0x97b6,0x97bb,0x97bd,0x97c9,0x97cb,0x97d0,0x97d2,0x97d9,0x97dc,0x97e1,0x97e3,0x97e3,0x97e5,0x97e6,0x97ed,0x97ee,0x97f0,0x97f3,0x97f5,0x97f6,0x97f8,0x97fb,0x97fd,0x9808,0x980a,0x980a,0x980c,0x9818,0x981b,0x9821,0x9823,0x9824,0x9826,0x9829,0x982b,0x982b,0x982d,0x9830,0x9832,0x9835,0x9837,0x9839,0x983b,0x983b,0x9841,0x9841,0x9843,0x9853,0x9856,0x9859,0x985b,0x9860,0x9862,0x986c,0x986f,0x9875,0x98a8,0x98a9,0x98ac,0x98af,0x98b1,0x98b4,0x98b6,0x98c4,0x98c6,0x98cc,0x98ce,0x98ce,0x98db,0x98dc,0x98de,0x98e3,0x98e5,0x98e7,0x98e9,0x98ed,0x98ef,0x98ef,0x98f1,0x98f2,0x98f4,0x98f6,0x98f9,0x98fa,0x98fc,0x98fe,0x9900,0x9900,0x9902,0x9903,0x9905,0x9905,0x9907,0x990a,0x990c,0x990c,0x990e,0x990e,0x9910,0x991c,0x991e,0x991f,0x9921,0x9921,0x9924,0x9925,0x9927,0x9933,0x9935,0x9935,0x9937,0x9943,0x9945,0x9945,0x9947,0x994e,0x9950,0x9959,0x995b,0x995f,0x9961,0x9963,0x9996,0x9999,0x999b,0x999e,0x99a1,0x99a1,0x99a3,0x99a8,0x99aa,0x99b5,0x99b8,0x99bd,0x99c1,0x99c5,0x99c7,0x99c7,0x99c9,0x99c9,0x99cb,0x99dd,0x99df,0x99e7,0x99e9,0x99ea,0x99ec,0x99ee,0x99f0,0x99f1,0x99f4,0x99ff,0x9a01,0x9a07,0x9a09,0x9a11,0x9a14,0x9a16,0x9a19,0x9a27,0x9a29,0x9a32,0x9a34,0x9a46,0x9a48,0x9a4a,0x9a4c,0x9a50,0x9a52,0x9a5c,0x9a5e,0x9a60,0x9a62,0x9a6c,0x9a8f,0x9a8f,0x9aa8,0x9aa8,0x9aab,0x9aab,0x9aad,0x9aad,0x9aaf,0x9ab4,0x9ab6,0x9ac2,0x9ac6,0x9ac7,0x9aca,0x9aca,0x9acd,0x9acd,0x9acf,0x9ad8,0x9adc,0x9adc,0x9adf,0x9ae3,0x9ae6,0x9ae7,0x9aeb,0x9aef,0x9af1,0x9af4,0x9af6,0x9af7,0x9af9,0x9aff,0x9b01,0x9b06,0x9b08,0x9b12,0x9b14,0x9b1a,0x9b1e,0x9b20,0x9b22,0x9b25,0x9b27,0x9b2b,0x9b2d,0x9b2f,0x9b31,0x9b35,0x9b37,0x9b37,0x9b39,0x9b3c,0x9b3e,0x9b46,0x9b48,0x9b48,0x9b4a,0x9b52,0x9b54,0x9b56,0x9b58,0x9b5b,0x9b5f,0x9b61,0x9b64,0x9b64,0x9b66,0x9b69,0x9b6c,0x9b6c,0x9b6f,0x9b71,0x9b74,0x9b77,0x9b7a,0x9b83,0x9b85,0x9b88,0x9b8b,0x9b8b,0x9b8d,0x9b93,0x9b95,0x9b95,0x9b97,0x9b97,0x9b9a,0x9b9b,0x9b9d,0x9ba2,0x9ba4,0x9ba6,0x9ba8,0x9ba8,0x9baa,0x9bab,0x9bad,0x9bb0,0x9bb5,0x9bb6,0x9bb8,0x9bb9,0x9bbd,0x9bbd,0x9bbf,0x9bc1,0x9bc3,0x9bc4,0x9bc6,0x9bca,0x9bcf,0x9bcf,0x9bd3,0x9bd7,0x9bd9,0x9bde,0x9be0,0x9be2,0x9be4,0x9bed,0x9bf0,0x9bf1,0x9bf4,0x9bf4,0x9bf7,0x9bf8,0x9bfd,0x9bfd,0x9bff,0x9bff,0x9c02,0x9c02,0x9c05,0x9c0e,0x9c10,0x9c10,0x9c12,0x9c15,0x9c17,0x9c17,0x9c1b,0x9c1d,0x9c1f,0x9c21,0x9c23,0x9c26,0x9c28,0x9c29,0x9c2b,0x9c2d,0x9c2f,0x9c2f,0x9c31,0x9c37,0x9c39,0x9c41,0x9c44,0x9c50,0x9c52,0x9c59,0x9c5d,0x9c60,0x9c62,0x9c63,0x9c66,0x9c68,0x9c6d,0x9c6e,0x9c71,0x9c75,0x9c77,0x9c7c,0x9ce5,0x9ce7,0x9ce9,0x9cea,0x9ced,0x9ced,0x9cf1,0x9cf7,0x9cf9,0x9cfd,0x9cff,0x9d00,0x9d02,0x9d09,0x9d0c,0x9d0c,0x9d10,0x9d10,0x9d12,0x9d12,0x9d14,0x9d19,0x9d1b,0x9d1b,0x9d1d,0x9d23,0x9d25,0x9d26,0x9d28,0x9d29,0x9d2d,0x9d31,0x9d33,0x9d34,0x9d36,0x9d39,0x9d3b,0x9d3b,0x9d3d,0x9d45,0x9d49,0x9d4c,0x9d4e,0x9d54,0x9d56,0x9d61,0x9d67,0x9d75,0x9d77,0x9d79,0x9d7b,0x9d8c,0x9d90,0x9d90,0x9d92,0x9d94,0x9d96,0x9dad,0x9daf,0x9daf,0x9db1,0x9dc5,0x9dc7,0x9ddf,0x9de1,0x9de6,0x9de8,0x9de9,0x9deb,0x9df0,0x9df2,0x9e07,0x9e09,0x9e15,0x9e17,0x9e1f,0x9e75,0x9e75,0x9e79,0x9e7d,0x9e7f,0x9e8e,0x9e90,0x9ea2,0x9ea4,0x9eb1,0x9eb4,0x9eb7,0x9ebb,0x9ec4,0x9ec6,0x9ec8,0x9ecc,0x9ed1,0x9ed3,0x9ed6,0x9ed8,0x9ed8,0x9eda,0x9ee0,0x9ee2,0x9ee2,0x9ee4,0x9ee8,0x9eeb,0x9eeb,0x9eed,0x9f02,0x9f06,0x9f0a,0x9f0e,0x9f10,0x9f12,0x9f13,0x9f15,0x9f1c,0x9f1e,0x9f1e,0x9f20,0x9f20,0x9f22,0x9f39,0x9f3b,0x9f3b,0x9f3d,0x9f3e,0x9f40,0x9f50,0x9f52,0x9f67,0x9f69,0x9f6c,0x9f6e,0x9f72,0x9f74,0x9f7b,0x9f7e,0x9f7f,0x9f8d,0x9f8e,0x9f90,0x9f92,0x9f94,0x9f99,0x9f9c,0x9f9c,0x9f9f,0x9fa0,0x9fa2,0x9fa2,0x9fa4,0x9fb3,0x9fc7,0x9fcb,0x9fd0,0x9fd0,0xf900,0xf908,0xf90b,0xf90d,0xf915,0xf915,0xf917,0xf917,0xf91a,0xf91a,0xf922,0xf922,0xf92d,0xf92d,0xf934,0xf934,0xf937,0xf937,0xf93a,0xf93a,0xf943,0xf943,0xf947,0xf948,0xf94a,0xf94a,0xf952,0xf952,0xf95e,0xf95e,0xf962,0xf962,0xf965,0xf965,0xf967,0xf967,0xf96d,0xf96d,0xf972,0xf972,0xf976,0xf976,0xf978,0xf979,0xf97b,0xf97b,0xf97e,0xf97e,0xf980,0xf980,0xf986,0xf986,0xf98a,0xf98a,0xf98e,0xf98e,0xf995,0xf995,0xf99c,0xf99d,0xf99f,0xf99f,0xf9b5,0xf9b5,0xf9bb,0xf9bb,0xf9bd,0xf9be,0xf9c5,0xf9c6,0xf9c8,0xf9c8,0xf9d0,0xf9d0,0xf9d8,0xf9d9,0xf9dc,0xf9de,0xf9e0,0xf9e0,0xf9e2,0xf9e4,0xf9e7,0xf9e7,0xf9e9,0xf9e9,0xf9f4,0xf9f5,0xf9fa,0xf9fa,0xf9fd,0xf9fd,0xf9ff,0xf9ff,0xfa02,0xfa02,0xfa05,0xfa08,0xfa0a,0xfa0a,0xfa0c,0xfa0d,0xfa33,0xfa35,0xfa3a,0xfa3a,0xfa49,0xfa49,0xfa4b,0xfa4b,0xfa5d,0xfa5e,0xfb00,0xfb04,0xfe10,0xfe19,0xfe30,0xfe52,0xfe54,0xfe66,0xfe68,0xfe6b,0xff01,0xff9f,0xffa1,0xffbe,0xffc2,0xffc7,0xffca,0xffcf,0xffd2,0xffd7,0xffda,0xffdc,0xffe0,0xffe6,0xffe8,0xffee,0x1f100,0x1f10c,0x1f110,0x1f16c,0x1f170,0x1f1ac,0x1f200,0x1f202,0x1f210,0x1f23b,0x1f240,0x1f248,0x1f250,0x1f251,0x20021,0x20021,0x2003e,0x2003e,0x20046,0x20046,0x2004e,0x2004e,0x20068,0x20068,0x20086,0x20087,0x2008a,0x2008a,0x20094,0x20094,0x200ca,0x200cd,0x200d1,0x200d1,0x200ee,0x200ee,0x2010c,0x2010c,0x2010e,0x2010e,0x20118,0x20118,0x201a4,0x201a4,0x201a9,0x201a9,0x201ab,0x201ab,0x201c1,0x201c1,0x201d4,0x201d4,0x201f2,0x201f2,0x20204,0x20204,0x2020c,0x2020c,0x20214,0x20214,0x20239,0x20239,0x2025b,0x2025b,0x20274,0x20275,0x20299,0x20299,0x2029e,0x2029e,0x202a0,0x202a0,0x202b7,0x202b7,0x202bf,0x202c0,0x202e5,0x202e5,0x2030a,0x2030a,0x20325,0x20325,0x20341,0x20341,0x20345,0x20347,0x2037e,0x20380,0x203a0,0x203a0,0x203a7,0x203a7,0x203b5,0x203b5,0x203c9,0x203c9,0x203cb,0x203cb,0x203f5,0x203f5,0x203fc,0x203fc,0x20413,0x20414,0x2041f,0x2041f,0x20465,0x20465,0x20487,0x20487,0x2048e,0x2048e,0x20491,0x20492,0x204a3,0x204a3,0x204d7,0x204d7,0x204fc,0x204fc,0x204fe,0x204fe,0x20547,0x20547,0x2058e,0x2058e,0x205a5,0x205a5,0x205b3,0x205b3,0x205c3,0x205c3,0x205ca,0x205ca,0x205d0,0x205d0,0x205d5,0x205d5,0x205df,0x205e0,0x205eb,0x205eb,0x20611,0x20611,0x20615,0x20615,0x20619,0x2061a,0x20628,0x20628,0x20630,0x20630,0x20656,0x20656,0x20676,0x20676,0x2070e,0x2070e,0x20731,0x20731,0x20779,0x20779,0x2082c,0x2082c,0x20873,0x20873,0x208d5,0x208d5,0x20916,0x20916,0x20923,0x20923,0x20954,0x20954,0x20979,0x20979,0x209e7,0x209e7,0x20a11,0x20a11,0x20a50,0x20a50,0x20a6f,0x20a6f,0x20a8a,0x20a8a,0x20ab4,0x20ab4,0x20ac2,0x20ac2,0x20acd,0x20acd,0x20b0d,0x20b0d,0x20b8f,0x20b8f,0x20b9f,0x20b9f,0x20ba8,0x20ba9,0x20bbf,0x20bbf,0x20bc6,0x20bc6,0x20bcb,0x20bcb,0x20be2,0x20be2,0x20beb,0x20beb,0x20bfb,0x20bfb,0x20bff,0x20bff,0x20c0b,0x20c0b,0x20c0d,0x20c0d,0x20c20,0x20c20,0x20c34,0x20c34,0x20c3a,0x20c3b,0x20c41,0x20c43,0x20c53,0x20c53,0x20c65,0x20c65,0x20c77,0x20c78,0x20c7c,0x20c7c,0x20c8d,0x20c8d,0x20c96,0x20c96,0x20c9c,0x20c9c,0x20cb5,0x20cb5,0x20cb8,0x20cb8,0x20ccf,0x20ccf,0x20cd3,0x20cd6,0x20cdd,0x20cdd,0x20ced,0x20ced,0x20cff,0x20cff,0x20d15,0x20d15,0x20d28,0x20d28,0x20d31,0x20d32,0x20d46,0x20d49,0x20d4c,0x20d4e,0x20d6f,0x20d6f,0x20d71,0x20d71,0x20d74,0x20d74,0x20d7c,0x20d7c,0x20d7e,0x20d7f,0x20d96,0x20d96,0x20d9c,0x20d9c,0x20da7,0x20da7,0x20db2,0x20db2,0x20dc8,0x20dc8,0x20e04,0x20e04,0x20e09,0x20e0a,0x20e0d,0x20e11,0x20e16,0x20e16,0x20e1d,0x20e1d,0x20e4c,0x20e4c,0x20e6d,0x20e6d,0x20e73,0x20e73,0x20e75,0x20e7b,0x20e8c,0x20e8c,0x20e96,0x20e96,0x20e98,0x20e98,0x20e9d,0x20e9d,0x20ea2,0x20ea2,0x20eaa,0x20eac,0x20eb6,0x20eb6,0x20ed7,0x20ed8,0x20edd,0x20edd,0x20ef8,0x20efb,0x20f1d,0x20f1d,0x20f26,0x20f26,0x20f2d,0x20f2e,0x20f30,0x20f31,0x20f3b,0x20f3b,0x20f4c,0x20f4c,0x20f64,0x20f64,0x20f8d,0x20f8d,0x20f90,0x20f90,0x20fad,0x20fad,0x20fb4,0x20fb6,0x20fbc,0x20fbc,0x20fdf,0x20fdf,0x20fea,0x20fed,0x21014,0x21014,0x2101d,0x2101e,0x2104f,0x2104f,0x2105c,0x2105c,0x2106f,0x2106f,0x21075,0x21078,0x2107b,0x2107b,0x21088,0x21088,0x21096,0x21096,0x2109d,0x2109d,0x210b4,0x210b4,0x210bf,0x210c1,0x210c7,0x210c9,0x210cf,0x210cf,0x210d3,0x210d3,0x210e4,0x210e4,0x210f4,0x210f6,0x2112f,0x2112f,0x2113b,0x2113b,0x2113d,0x2113d,0x21145,0x21145,0x21148,0x21148,0x2114f,0x2114f,0x21180,0x21180,0x21187,0x21187,0x211d9,0x211d9,0x2123c,0x2123c,0x2124f,0x2124f,0x2127c,0x2127c,0x212a8,0x212a9,0x212b0,0x212b0,0x212e3,0x212e3,0x212fe,0x212fe,0x21302,0x21305,0x21336,0x21336,0x2133a,0x2133a,0x21375,0x21376,0x2138e,0x2138e,0x21398,0x21398,0x2139c,0x2139c,0x213c5,0x213c6,0x213ed,0x213ed,0x213fe,0x213fe,0x21413,0x21413,0x21416,0x21416,0x21424,0x21424,0x2143f,0x2143f,0x21452,0x21452,0x21454,0x21455,0x2148a,0x2148a,0x21497,0x21497,0x214b6,0x214b6,0x214e8,0x214e8,0x214fd,0x214fd,0x21577,0x21577,0x21582,0x21582,0x21596,0x21596,0x2160a,0x2160a,0x21613,0x21613,0x21619,0x21619,0x2163e,0x2163e,0x21661,0x21661,0x21692,0x21692,0x216b8,0x216b8,0x216ba,0x216ba,0x216c0,0x216c2,0x216d3,0x216d3,0x216d5,0x216d5,0x216df,0x216df,0x216e6,0x216e8,0x216fa,0x216fc,0x216fe,0x216fe,0x2170d,0x2170d,0x21710,0x21710,0x21726,0x21726,0x2173a,0x2173c,0x21757,0x21757,0x2176c,0x21771,0x21773,0x21774,0x217ab,0x217ab,0x217b0,0x217b5,0x217c3,0x217c3,0x217c7,0x217c7,0x217d9,0x217dc,0x217df,0x217df,0x217ef,0x217ef,0x217f5,0x217f6,0x217f8,0x217fc,0x21820,0x21820,0x21828,0x2182a,0x2182d,0x2182d,0x21839,0x2183b,0x21840,0x21840,0x21845,0x21845,0x21852,0x21852,0x2185e,0x2185e,0x21861,0x21864,0x21877,0x21877,0x2187b,0x2187b,0x21883,0x21885,0x2189e,0x218a2,0x218be,0x218bf,0x218d1,0x218d1,0x218d6,0x218d9,0x218fa,0x218fa,0x21903,0x21905,0x21910,0x21912,0x21915,0x21915,0x2191c,0x2191c,0x21922,0x21922,0x21927,0x21927,0x2193b,0x2193b,0x21944,0x21944,0x21958,0x21958,0x2196a,0x2196a,0x2197c,0x2197c,0x21980,0x21980,0x21983,0x21983,0x21988,0x21988,0x21996,0x21996,0x219db,0x219db,0x219f3,0x219f3,0x21a2d,0x21a2d,0x21a34,0x21a34,0x21a45,0x21a45,0x21a4b,0x21a4b,0x21a63,0x21a63,0x21b44,0x21b44,0x21bc1,0x21bc2,0x21c2a,0x21c2a,0x21c70,0x21c70,0x21ca2,0x21ca2,0x21ca5,0x21ca5,0x21cac,0x21cac,0x21d46,0x21d46,0x21d53,0x21d53,0x21d5e,0x21d5e,0x21d90,0x21d90,0x21db6,0x21db6,0x21dba,0x21dba,0x21dca,0x21dca,0x21dd1,0x21dd1,0x21deb,0x21deb,0x21df9,0x21df9,0x21e1c,0x21e1c,0x21e23,0x21e23,0x21e37,0x21e37,0x21e3d,0x21e3d,0x21e89,0x21e89,0x21ea4,0x21ea4,0x21ea8,0x21ea8,0x21ec8,0x21ec8,0x21ed5,0x21ed5,0x21f0f,0x21f0f,0x21f15,0x21f15,0x21f6a,0x21f6a,0x21f9e,0x21f9e,0x21fa1,0x21fa1,0x21fe8,0x21fe8,0x22045,0x22045,0x22049,0x22049,0x2207e,0x2207e,0x2209a,0x2209a,0x220c7,0x220c7,0x220fc,0x220fc,0x2212a,0x2212a,0x2215b,0x2215b,0x22173,0x22173,0x2217a,0x2217a,0x221a1,0x221a1,0x221c1,0x221c1,0x221c3,0x221c3,0x22208,0x22208,0x2227c,0x2227c,0x22321,0x22321,0x22325,0x22325,0x223bd,0x223bd,0x223d0,0x223d0,0x223d7,0x223d7,0x223fa,0x223fa,0x22465,0x22465,0x22471,0x22471,0x2248b,0x2248b,0x22491,0x22491,0x224b0,0x224b0,0x224bc,0x224bc,0x224c1,0x224c1,0x224c9,0x224c9,0x224cc,0x224cc,0x224ed,0x224ed,0x22513,0x22513,0x2251b,0x2251b,0x22530,0x22530,0x22554,0x22554,0x2258d,0x2258d,0x225af,0x225af,0x225be,0x225be,0x2261b,0x2261c,0x2262b,0x2262b,0x22668,0x22668,0x2267a,0x2267a,0x22696,0x22696,0x22698,0x22698,0x226f4,0x226f6,0x22712,0x22712,0x22714,0x22714,0x2271b,0x2271b,0x2271f,0x2271f,0x2272a,0x2272a,0x22775,0x22775,0x22781,0x22781,0x22796,0x22796,0x227b4,0x227b5,0x227cd,0x227cd,0x22803,0x22803,0x2285f,0x22860,0x22871,0x22871,0x228ad,0x228ad,0x228c1,0x228c1,0x228f7,0x228f7,0x22926,0x22926,0x22939,0x22939,0x2294f,0x2294f,0x22967,0x22967,0x2296b,0x2296b,0x22980,0x22980,0x22993,0x22993,0x22a66,0x22a66,0x22acf,0x22acf,0x22ad5,0x22ad5,0x22ae6,0x22ae6,0x22ae8,0x22ae8,0x22b0e,0x22b0e,0x22b22,0x22b22,0x22b3f,0x22b3f,0x22b43,0x22b43,0x22b6a,0x22b6a,0x22bca,0x22bca,0x22bce,0x22bce,0x22c26,0x22c27,0x22c38,0x22c38,0x22c4c,0x22c4c,0x22c51,0x22c51,0x22c55,0x22c55,0x22c62,0x22c62,0x22c88,0x22c88,0x22c9b,0x22c9b,0x22ca1,0x22ca1,0x22ca9,0x22ca9,0x22cb2,0x22cb2,0x22cb7,0x22cb7,0x22cc2,0x22cc2,0x22cc6,0x22cc6,0x22cc9,0x22cc9,0x22d07,0x22d08,0x22d12,0x22d12,0x22d44,0x22d44,0x22d4c,0x22d4c,0x22d67,0x22d67,0x22d8d,0x22d8d,0x22d95,0x22d95,0x22da0,0x22da0,0x22da3,0x22da4,0x22db7,0x22db7,0x22dee,0x22dee,0x22e0d,0x22e0d,0x22e36,0x22e36,0x22e42,0x22e42,0x22e78,0x22e78,0x22e8b,0x22e8b,0x22eb3,0x22eb3,0x22eef,0x22eef,0x22f74,0x22f74,0x22fcc,0x22fcc,0x22fe3,0x22fe3,0x23033,0x23033,0x23044,0x23044,0x2304b,0x2304b,0x23066,0x23066,0x2307d,0x2307e,0x2308e,0x2308e,0x230b7,0x230b7,0x230bc,0x230bc,0x230da,0x230da,0x23103,0x23103,0x2313d,0x2313d,0x2317d,0x2317d,0x23182,0x23182,0x231a4,0x231a5,0x231b3,0x231b3,0x231c8,0x231c9,0x231ea,0x231ea,0x231f7,0x231f9,0x2320f,0x2320f,0x23225,0x23225,0x2322f,0x2322f,0x23231,0x23234,0x23256,0x23256,0x2325e,0x2325e,0x23262,0x23262,0x23281,0x23281,0x23289,0x2328a,0x232ab,0x232ad,0x232d2,0x232d2,0x232e0,0x232e1,0x23300,0x23300,0x2330a,0x2330a,0x2331f,0x2331f,0x233b4,0x233b4,0x233cc,0x233cc,0x233de,0x233de,0x233e6,0x233e6,0x233f4,0x233f5,0x233f9,0x233fa,0x233fe,0x233fe,0x23400,0x23400,0x2343f,0x2343f,0x23450,0x23450,0x2346f,0x2346f,0x23472,0x23472,0x234e5,0x234e5,0x23519,0x23519,0x23530,0x23530,0x23551,0x23551,0x2355a,0x2355a,0x23567,0x23567,0x23595,0x23595,0x23599,0x23599,0x2359c,0x2359c,0x235bb,0x235bb,0x235cd,0x235cf,0x235f3,0x235f3,0x23600,0x23600,0x23617,0x23617,0x2361a,0x2361a,0x2363c,0x2363c,0x23640,0x23640,0x23659,0x23659,0x2365f,0x2365f,0x23677,0x23677,0x2368e,0x2368e,0x2369e,0x2369e,0x236a6,0x236a6,0x236ad,0x236ad,0x236ba,0x236ba,0x236df,0x236df,0x236ee,0x236ee,0x23703,0x23703,0x23716,0x23716,0x23720,0x23720,0x2372d,0x2372d,0x2372f,0x2372f,0x2373f,0x2373f,0x23766,0x23766,0x23781,0x23781,0x237a2,0x237a2,0x237bc,0x237bc,0x237c2,0x237c2,0x237d5,0x237d7,0x2383a,0x2383a,0x239c2,0x239c2,0x23aa7,0x23aa7,0x23adb,0x23adb,0x23aee,0x23aee,0x23afa,0x23afa,0x23b1a,0x23b1a,0x23b5a,0x23b5a,0x23c63,0x23c63,0x23c99,0x23c9b,0x23cb5,0x23cb5,0x23cb7,0x23cb7,0x23cc7,0x23cc9,0x23cfc,0x23cff,0x23d40,0x23d40,0x23d5b,0x23d5b,0x23d7e,0x23d7e,0x23d8f,0x23d8f,0x23db6,0x23dbd,0x23de3,0x23de3,0x23df8,0x23df8,0x23e06,0x23e06,0x23e11,0x23e11,0x23e2c,0x23e31,0x23e39,0x23e39,0x23e88,0x23e8b,0x23eb9,0x23eb9,0x23ebf,0x23ebf,0x23ed7,0x23ed7,0x23ef7,0x23efc,0x23f35,0x23f35,0x23f41,0x23f41,0x23f4a,0x23f4a,0x23f61,0x23f61,0x23f7f,0x23f82,0x23f8f,0x23f8f,0x23fb4,0x23fb4,0x23fb7,0x23fb7,0x23fc0,0x23fc0,0x23fc5,0x23fc5,0x23feb,0x23ff0,0x24011,0x24011,0x24039,0x2403d,0x24057,0x24057,0x24085,0x24085,0x2408b,0x2408d,0x24091,0x24091,0x240c9,0x240c9,0x240e1,0x240e1,0x240ec,0x240ec,0x24104,0x24104,0x2410f,0x2410f,0x24119,0x24119,0x2413f,0x24140,0x24144,0x24144,0x2414e,0x2414e,0x24155,0x24157,0x2415c,0x2415c,0x2415f,0x2415f,0x24161,0x24161,0x24177,0x24177,0x2417a,0x2417a,0x241a3,0x241a5,0x241ac,0x241ac,0x241b5,0x241b5,0x241cd,0x241cd,0x241e2,0x241e2,0x241fc,0x241fc,0x2421b,0x2421b,0x2424b,0x2424b,0x24256,0x24256,0x24259,0x24259,0x24276,0x24278,0x24284,0x24284,0x24293,0x24293,0x24295,0x24295,0x242a5,0x242a5,0x242bf,0x242bf,0x242c1,0x242c1,0x242c9,0x242ca,0x242ee,0x242ee,0x242fa,0x242fa,0x2430d,0x2430d,0x2431a,0x2431a,0x24334,0x24334,0x24348,0x24348,0x24362,0x24365,0x2438c,0x2438c,0x24396,0x24396,0x2439c,0x2439c,0x243bd,0x243bd,0x243c1,0x243c1,0x243e9,0x243ea,0x243f2,0x243f2,0x243f8,0x243f8,0x24404,0x24404,0x24435,0x24436,0x2445a,0x2445b,0x24473,0x24473,0x24487,0x24488,0x244b9,0x244b9,0x244bc,0x244bc,0x244ce,0x244ce,0x244d3,0x244d3,0x244d6,0x244d6,0x24505,0x24505,0x24521,0x24521,0x24578,0x24578,0x245c8,0x245c8,0x24618,0x24618,0x2462a,0x2462a,0x24665,0x24665,0x24674,0x24674,0x24697,0x24697,0x246d4,0x246d4,0x24706,0x24706,0x24725,0x24725,0x2472f,0x2472f,0x2478f,0x2478f,0x247e0,0x247e0,0x24812,0x24812,0x24823,0x24823,0x24882,0x24882,0x248e9,0x248e9,0x248f0,0x248f3,0x248fb,0x248fb,0x248ff,0x24901,0x2490c,0x2490c,0x24916,0x24917,0x24919,0x24919,0x2492f,0x2492f,0x24933,0x24934,0x2493e,0x24943,0x24962,0x24963,0x24974,0x24976,0x2497b,0x2497b,0x2497f,0x2497f,0x24982,0x24982,0x24988,0x2498f,0x24994,0x24994,0x249a4,0x249a4,0x249a7,0x249a7,0x249a9,0x249a9,0x249ab,0x249ad,0x249b7,0x249bb,0x249c5,0x249c5,0x249d0,0x249d0,0x249da,0x249da,0x249de,0x249df,0x249e3,0x249e3,0x249e5,0x249e5,0x249ec,0x249ed,0x249f6,0x249f9,0x249fb,0x249fb,0x24a0e,0x24a0e,0x24a12,0x24a13,0x24a15,0x24a15,0x24a21,0x24a2a,0x24a3e,0x24a3e,0x24a42,0x24a42,0x24a45,0x24a45,0x24a4a,0x24a4a,0x24a4e,0x24a51,0x24a5d,0x24a5d,0x24a65,0x24a67,0x24a71,0x24a71,0x24a77,0x24a7a,0x24a8c,0x24a8c,0x24a93,0x24a96,0x24aa4,0x24aa7,0x24ab1,0x24ab3,0x24aba,0x24abc,0x24ac0,0x24ac0,0x24ac7,0x24ac7,0x24aca,0x24aca,0x24ad1,0x24ad1,0x24adf,0x24adf,0x24ae2,0x24ae2,0x24ae9,0x24ae9,0x24b0f,0x24b0f,0x24b6e,0x24b6e,0x24bf5,0x24bf5,0x24c09,0x24c09,0x24c9e,0x24c9f,0x24cc9,0x24cc9,0x24cd9,0x24cd9,0x24d06,0x24d06,0x24d13,0x24d13,0x24db8,0x24db8,0x24dea,0x24deb,0x24e3b,0x24e3b,0x24e50,0x24e50,0x24ea5,0x24ea5,0x24ea7,0x24ea7,0x24f0e,0x24f0e,0x24f5c,0x24f5c,0x24f82,0x24f82,0x24f86,0x24f86,0x24f97,0x24f97,0x24f9a,0x24f9a,0x24fa9,0x24fa9,0x24fb8,0x24fb8,0x24fc2,0x24fc2,0x2502c,0x2502c,0x25052,0x25052,0x2509d,0x2509d,0x2512b,0x2512b,0x25148,0x25148,0x2517d,0x2517e,0x251cd,0x251cd,0x251e3,0x251e3,0x251e6,0x251e7,0x25220,0x25221,0x25250,0x25250,0x25299,0x25299,0x252c7,0x252c7,0x252d8,0x252d8,0x2530e,0x2530e,0x25311,0x25311,0x25313,0x25313,0x25419,0x25419,0x25425,0x25425,0x2542f,0x25430,0x25446,0x25446,0x2546c,0x2546c,0x2546e,0x2546e,0x2549a,0x2549a,0x25531,0x25531,0x25535,0x25535,0x2553f,0x2553f,0x2555b,0x2555e,0x25562,0x25562,0x25565,0x25566,0x25581,0x25581,0x25584,0x25584,0x2558f,0x2558f,0x255b9,0x255b9,0x255d5,0x255d5,0x255db,0x255db,0x255e0,0x255e0,0x25605,0x25605,0x25635,0x25635,0x25651,0x25651,0x25683,0x25683,0x25695,0x25695,0x256e3,0x256e3,0x256f6,0x256f6,0x25706,0x25706,0x2571d,0x2571d,0x25725,0x25725,0x2573d,0x2573d,0x25772,0x25772,0x257c7,0x257c7,0x257df,0x257e1,0x25857,0x25857,0x2585d,0x2585d,0x25872,0x25872,0x258c8,0x258c8,0x258de,0x258de,0x258e1,0x258e1,0x25903,0x25903,0x25946,0x25946,0x25956,0x25956,0x259ac,0x259ac,0x259cc,0x259cc,0x25a54,0x25a54,0x25a95,0x25a95,0x25a9c,0x25a9c,0x25aae,0x25aaf,0x25ad7,0x25ad7,0x25ae9,0x25ae9,0x25b74,0x25b74,0x25b89,0x25b89,0x25bb3,0x25bb4,0x25bc6,0x25bc6,0x25be4,0x25be4,0x25be8,0x25be8,0x25c01,0x25c01,0x25c06,0x25c06,0x25c21,0x25c21,0x25c4a,0x25c4a,0x25c65,0x25c65,0x25c91,0x25c91,0x25ca4,0x25ca4,0x25cc0,0x25cc1,0x25cfe,0x25cfe,0x25d20,0x25d20,0x25d30,0x25d30,0x25d43,0x25d43,0x25d99,0x25d99,0x25db9,0x25db9,0x25e0e,0x25e0e,0x25e49,0x25e49,0x25e81,0x25e83,0x25ea6,0x25ea6,0x25ebc,0x25ebc,0x25ed7,0x25ed8,0x25f1a,0x25f1a,0x25f4b,0x25f4b,0x25fe1,0x25fe2,0x26021,0x26021,0x26029,0x26029,0x26048,0x26048,0x26064,0x26064,0x26083,0x26083,0x26097,0x26097,0x260a4,0x260a5,0x26102,0x26102,0x26121,0x26121,0x26159,0x2615c,0x261ad,0x261ae,0x261b2,0x261b2,0x261dd,0x261dd,0x26258,0x26258,0x26261,0x26261,0x2626a,0x2626b,0x262d0,0x262d0,0x26335,0x26335,0x2634b,0x2634c,0x26351,0x26351,0x263be,0x263be,0x263f5,0x263f5,0x263f8,0x263f8,0x26402,0x26402,0x26410,0x26412,0x2644a,0x2644a,0x26469,0x26469,0x26484,0x26484,0x26488,0x26489,0x2648d,0x2648d,0x26498,0x26498,0x26512,0x26512,0x26572,0x26572,0x265a0,0x265a0,0x265ad,0x265ad,0x265bf,0x265bf,0x26612,0x26612,0x26626,0x26626,0x266af,0x266af,0x266b1,0x266b1,0x266b5,0x266b5,0x266da,0x266da,0x266e8,0x266e8,0x266fc,0x266fc,0x26716,0x26716,0x26741,0x26741,0x26799,0x26799,0x267b3,0x267b4,0x267cc,0x267cc,0x2681c,0x2681c,0x26846,0x26846,0x2685e,0x2685e,0x2686e,0x2686e,0x26888,0x26888,0x2688a,0x2688a,0x26893,0x26893,0x268c7,0x268c7,0x2690e,0x2690e,0x26911,0x26911,0x26926,0x26926,0x26939,0x26939,0x26951,0x26951,0x269a8,0x269a8,0x269b5,0x269b5,0x269f2,0x269f2,0x269fa,0x269fa,0x26a2d,0x26a2e,0x26a34,0x26a34,0x26a42,0x26a42,0x26a51,0x26a52,0x26b05,0x26b05,0x26b0a,0x26b0a,0x26b13,0x26b13,0x26b15,0x26b15,0x26b23,0x26b23,0x26b28,0x26b28,0x26b50,0x26b53,0x26b5b,0x26b5b,0x26b75,0x26b75,0x26b82,0x26b82,0x26b96,0x26b97,0x26b9d,0x26b9d,0x26bb3,0x26bb3,0x26bc0,0x26bc0,0x26bf7,0x26bf7,0x26c21,0x26c21,0x26c40,0x26c41,0x26c46,0x26c46,0x26c7e,0x26c82,0x26ca4,0x26ca4,0x26cb7,0x26cb8,0x26cbd,0x26cbd,0x26cc0,0x26cc0,0x26cc3,0x26cc3,0x26cd1,0x26cd1,0x26d22,0x26d2a,0x26d51,0x26d51,0x26d74,0x26d74,0x26da0,0x26da7,0x26dae,0x26dae,0x26ddc,0x26ddc,0x26dea,0x26deb,0x26df0,0x26df0,0x26e00,0x26e00,0x26e05,0x26e05,0x26e07,0x26e07,0x26e12,0x26e12,0x26e42,0x26e45,0x26e6e,0x26e6e,0x26e72,0x26e72,0x26e77,0x26e77,0x26e84,0x26e84,0x26e88,0x26e88,0x26e8b,0x26e8b,0x26e99,0x26e99,0x26ed0,0x26ed7,0x26f26,0x26f26,0x26f73,0x26f74,0x26f9f,0x26f9f,0x26fa1,0x26fa1,0x26fbe,0x26fbe,0x26fde,0x26fdf,0x2700e,0x2700e,0x2704b,0x2704b,0x27052,0x27053,0x27088,0x27088,0x270ad,0x270af,0x270cd,0x270cd,0x270d2,0x270d2,0x270f0,0x270f0,0x270f8,0x270f8,0x27109,0x27109,0x2710c,0x2710d,0x27126,0x27127,0x27164,0x27165,0x27175,0x27175,0x271cd,0x271cd,0x2721b,0x2721b,0x27267,0x27267,0x27280,0x27280,0x27285,0x27285,0x2728b,0x2728b,0x272b2,0x272b2,0x272b6,0x272b6,0x272e6,0x272e6,0x27352,0x27352,0x2739a,0x2739a,0x273ff,0x273ff,0x27422,0x27422,0x27450,0x27450,0x27484,0x27484,0x27486,0x27486,0x27574,0x27574,0x275a3,0x275a3,0x275e0,0x275e0,0x275e4,0x275e4,0x275fd,0x275fe,0x27607,0x27607,0x2760c,0x2760c,0x27632,0x27632,0x27639,0x27639,0x27655,0x27657,0x27694,0x27694,0x2770f,0x2770f,0x27735,0x27736,0x27741,0x27741,0x2775e,0x2775e,0x27784,0x27785,0x277cc,0x277cc,0x27858,0x27858,0x27870,0x27870,0x2789d,0x2789d,0x278b2,0x278b2,0x278c8,0x278c8,0x27924,0x27924,0x27967,0x27967,0x2797a,0x2797a,0x279a0,0x279a0,0x279dd,0x279dd,0x279fd,0x279fd,0x27a0a,0x27a0a,0x27a0e,0x27a0e,0x27a3e,0x27a3e,0x27a53,0x27a53,0x27a59,0x27a59,0x27a79,0x27a79,0x27a84,0x27a84,0x27abd,0x27abe,0x27af4,0x27af4,0x27b06,0x27b06,0x27b0b,0x27b0b,0x27b18,0x27b18,0x27b38,0x27b3a,0x27b48,0x27b48,0x27b65,0x27b65,0x27bef,0x27bef,0x27bf4,0x27bf4,0x27c12,0x27c12,0x27c6c,0x27c6c,0x27cb1,0x27cb1,0x27cc5,0x27cc5,0x27d2f,0x27d2f,0x27d53,0x27d54,0x27d66,0x27d66,0x27d73,0x27d73,0x27d84,0x27d84,0x27d8f,0x27d8f,0x27d98,0x27d98,0x27dbd,0x27dbd,0x27ddc,0x27ddc,0x27e4d,0x27e4d,0x27e4f,0x27e4f,0x27f2e,0x27f2e,0x27fb7,0x27fb7,0x27ff9,0x27ff9,0x28002,0x28002,0x28009,0x28009,0x2801e,0x2801e,0x28023,0x28024,0x28048,0x28048,0x28083,0x28083,0x28090,0x28090,0x280bd,0x280be,0x280e8,0x280e9,0x280f4,0x280f4,0x2812e,0x2812e,0x2814f,0x2814f,0x2815d,0x2815d,0x2816f,0x2816f,0x28189,0x28189,0x281af,0x281af,0x281bc,0x281bc,0x28207,0x28207,0x28218,0x28218,0x2821a,0x2821a,0x28256,0x28256,0x2827c,0x2827c,0x2829b,0x2829b,0x282cd,0x282cd,0x282e2,0x282e2,0x28306,0x28306,0x28318,0x28318,0x2832f,0x2832f,0x2833a,0x2833a,0x28365,0x28365,0x2836d,0x2836d,0x2837d,0x2837d,0x2838a,0x2838a,0x28412,0x28412,0x28468,0x28468,0x2846c,0x2846c,0x28473,0x28473,0x28482,0x28482,0x28501,0x28501,0x2853c,0x2853d,0x2856c,0x2856c,0x285e8,0x285e8,0x285f4,0x285f4,0x28600,0x28600,0x2860b,0x2860b,0x28625,0x28625,0x2863b,0x2863b,0x286aa,0x286ab,0x286b2,0x286b2,0x286bc,0x286bc,0x286d8,0x286d8,0x286e6,0x286e6,0x2870f,0x2870f,0x28713,0x28713,0x28804,0x28804,0x2882b,0x2882b,0x2890d,0x2890d,0x28933,0x28933,0x28948,0x28949,0x28956,0x28956,0x28964,0x28964,0x28968,0x28968,0x2896c,0x2896d,0x2897e,0x2897e,0x28989,0x28989,0x289a8,0x289a8,0x289aa,0x289ab,0x289b8,0x289b8,0x289bc,0x289bc,0x289c0,0x289c0,0x289dc,0x289dc,0x289de,0x289de,0x289e1,0x289e1,0x289e3,0x289e4,0x289e7,0x289e8,0x289f9,0x289fc,0x28a0f,0x28a0f,0x28a16,0x28a16,0x28a25,0x28a25,0x28a29,0x28a29,0x28a32,0x28a32,0x28a36,0x28a36,0x28a44,0x28a4b,0x28a59,0x28a5a,0x28a81,0x28a83,0x28a9a,0x28a9c,0x28ac0,0x28ac0,0x28ac6,0x28ac6,0x28acb,0x28acc,0x28ace,0x28ace,0x28ade,0x28ae3,0x28ae5,0x28ae5,0x28aea,0x28aea,0x28afc,0x28afc,0x28b0c,0x28b0c,0x28b13,0x28b13,0x28b21,0x28b22,0x28b2b,0x28b2d,0x28b2f,0x28b2f,0x28b46,0x28b46,0x28b4c,0x28b4c,0x28b4e,0x28b4e,0x28b50,0x28b50,0x28b63,0x28b66,0x28b6c,0x28b6c,0x28b8f,0x28b8f,0x28b99,0x28b99,0x28b9c,0x28b9d,0x28bb9,0x28bb9,0x28bc2,0x28bc2,0x28bc5,0x28bc5,0x28bd4,0x28bd4,0x28bd7,0x28bd7,0x28bd9,0x28bda,0x28be7,0x28bec,0x28bf5,0x28bf5,0x28bff,0x28bff,0x28c03,0x28c03,0x28c09,0x28c09,0x28c1c,0x28c1d,0x28c23,0x28c23,0x28c26,0x28c26,0x28c2b,0x28c2b,0x28c30,0x28c30,0x28c39,0x28c39,0x28c3b,0x28c3b,0x28cca,0x28cca,0x28ccd,0x28ccd,0x28cd2,0x28cd2,0x28d34,0x28d34,0x28d99,0x28d99,0x28db9,0x28db9,0x28e0f,0x28e0f,0x28e36,0x28e36,0x28e39,0x28e39,0x28e65,0x28e66,0x28e97,0x28e97,0x28eac,0x28eac,0x28eb2,0x28eb3,0x28ed9,0x28ed9,0x28ee7,0x28ee7,0x28fc5,0x28fc5,0x29079,0x29079,0x29088,0x29088,0x2908b,0x2908b,0x29093,0x29093,0x290af,0x290b1,0x290c0,0x290c0,0x290e4,0x290e5,0x290ec,0x290ed,0x2910d,0x2910d,0x29110,0x29110,0x2913c,0x2913c,0x2914d,0x2914d,0x2915b,0x2915b,0x2915e,0x2915e,0x29170,0x29170,0x2919c,0x2919c,0x291a8,0x291a8,0x291d5,0x291d5,0x291eb,0x291eb,0x2941d,0x2941d,0x29420,0x29420,0x29433,0x29433,0x2943f,0x2943f,0x29448,0x29448,0x294d0,0x294d0,0x294d9,0x294da,0x294e5,0x294e5,0x294e7,0x294e7,0x2959e,0x2959e,0x295b0,0x295b0,0x295b8,0x295b8,0x295d7,0x295d7,0x295e9,0x295e9,0x295f4,0x295f4,0x2967f,0x2967f,0x29720,0x29720,0x29732,0x29732,0x297d4,0x297d4,0x29810,0x29810,0x29857,0x29857,0x298a4,0x298a4,0x298d1,0x298d1,0x298ea,0x298ea,0x298f1,0x298f1,0x298fa,0x298fa,0x29903,0x29903,0x29905,0x29905,0x2992f,0x2992f,0x29945,0x29945,0x29947,0x29949,0x2995d,0x2995d,0x2996a,0x2996a,0x2999d,0x2999d,0x299c3,0x299c3,0x299c9,0x299c9,0x29a28,0x29a28,0x29a4d,0x29a4d,0x29b05,0x29b05,0x29b0e,0x29b0e,0x29bd5,0x29bd5,0x29c73,0x29c73,0x29cad,0x29cad,0x29d3e,0x29d3e,0x29d5a,0x29d5a,0x29d7c,0x29d7c,0x29d98,0x29d98,0x29d9b,0x29d9b,0x29df6,0x29df6,0x29e06,0x29e06,0x29e2d,0x29e2d,0x29e68,0x29e68,0x29eac,0x29eac,0x29eb0,0x29eb0,0x29ec3,0x29ec3,0x29ef8,0x29ef8,0x29f23,0x29f23,0x29f30,0x29f30,0x29fb7,0x29fb7,0x29fde,0x29fde,0x2a014,0x2a014,0x2a087,0x2a087,0x2a0b9,0x2a0b9,0x2a0e1,0x2a0e1,0x2a0ed,0x2a0ed,0x2a0f3,0x2a0f3,0x2a0f8,0x2a0f8,0x2a0fe,0x2a0fe,0x2a107,0x2a107,0x2a123,0x2a123,0x2a133,0x2a134,0x2a150,0x2a150,0x2a192,0x2a193,0x2a1ab,0x2a1ab,0x2a1b4,0x2a1b5,0x2a1df,0x2a1df,0x2a1f5,0x2a1f5,0x2a220,0x2a220,0x2a233,0x2a233,0x2a293,0x2a293,0x2a29f,0x2a29f,0x2a2b2,0x2a2b2,0x2a2b4,0x2a2b4,0x2a2b6,0x2a2b6,0x2a2ba,0x2a2ba,0x2a2bd,0x2a2bd,0x2a2df,0x2a2df,0x2a2ff,0x2a2ff,0x2a351,0x2a351,0x2a3a9,0x2a3a9,0x2a3ed,0x2a3ed,0x2a434,0x2a434,0x2a45b,0x2a45b,0x2a5c6,0x2a5c6,0x2a5cb,0x2a5cb,0x2a601,0x2a601,0x2a632,0x2a632,0x2a64a,0x2a64a,0x2a65b,0x2a65b,0x2a6a9,0x2a6a9,0x2adff,0x2adff,0x2d544,0x2d544,0x2f825,0x2f825,0x2f83b,0x2f83b,0x2f840,0x2f840,0x2f878,0x2f878,0x2f894,0x2f894,0x2f8a6,0x2f8a6,0x2f8cd,0x2f8cd,0x2f8db,0x2f8db,0x2f994,0x2f994,0x2f9b2,0x2f9b2,0x2f9bc,0x2f9bc,0x2f9d4,0x2f9d4,0x30ede,0x30ede,0x3106c,0x3106c,]), + NotoFont.fromFlatRanges('Noto Sans Hanunoo', 'http://fonts.gstatic.com/s/notosanshanunoo/v15/f0Xs0fCv8dxkDWlZSoXOj6CphMloFsEsEpgL_ix2.ttf', [0x20,0x20,0xa0,0xa0,0x1720,0x1736,0x200b,0x200d,0x25cc,0x25cc,]), + NotoFont.fromFlatRanges('Noto Sans Hatran', 'http://fonts.gstatic.com/s/notosanshatran/v15/A2BBn4Ne0RgnVF3Lnko-0sOBIfL_mM83r1nwzDs.ttf', [0x20,0x20,0xa0,0xa0,0x200c,0x200c,0x108e0,0x108f2,0x108f4,0x108f5,0x108fb,0x108ff,]), + NotoFont.fromFlatRanges('Noto Sans Hebrew', 'http://fonts.gstatic.com/s/notosanshebrew/v38/or3HQ7v33eiDljA1IufXTtVf7V6RvEEdhQlk0LlGxCyaeNKYZC0sqk3xXGiXd4qtoiJltutR2g.ttf', [0x20,0x7e,0xa0,0xa3,0xa5,0xa5,0xa7,0xab,0xae,0xb0,0xb4,0xb4,0xb6,0xb8,0xba,0xbb,0xbf,0x107,0x10a,0x113,0x116,0x11b,0x11e,0x123,0x126,0x127,0x12a,0x12b,0x12e,0x133,0x136,0x137,0x139,0x13e,0x141,0x148,0x14a,0x14d,0x150,0x15b,0x15e,0x161,0x164,0x165,0x16a,0x17e,0x1cd,0x1ce,0x218,0x21b,0x237,0x237,0x2c6,0x2c7,0x2c9,0x2c9,0x2d8,0x2dd,0x300,0x304,0x306,0x308,0x30a,0x30c,0x312,0x312,0x326,0x328,0x34f,0x34f,0x591,0x5c7,0x5d0,0x5ea,0x5f0,0x5f4,0x1e80,0x1e85,0x1e9e,0x1e9e,0x1ef2,0x1ef3,0x200c,0x2010,0x2013,0x2014,0x2018,0x201a,0x201c,0x201e,0x2022,0x2022,0x2026,0x2026,0x2039,0x203a,0x20aa,0x20aa,0x20ac,0x20ac,0x2122,0x2122,0x2212,0x2212,0x25cc,0x25cc,0xfb1d,0xfb36,0xfb38,0xfb3c,0xfb3e,0xfb3e,0xfb40,0xfb41,0xfb43,0xfb44,0xfb46,0xfb4f,]), + NotoFont.fromFlatRanges('Noto Sans Imperial Aramaic', 'http://fonts.gstatic.com/s/notosansimperialaramaic/v15/a8IMNpjwKmHXpgXbMIsbTc_kvks91LlLetBr5itQrtdml3YfPNno.ttf', [0x20,0x20,0xa0,0xa0,0x10840,0x10855,0x10857,0x1085f,]), + NotoFont.fromFlatRanges('Noto Sans Indic Siyaq Numbers', 'http://fonts.gstatic.com/s/notosansindicsiyaqnumbers/v15/6xK5dTJFKcWIu4bpRBjRZRpsIYHabOeZ8UZLubTzpXNHKx2WPOpVd5Iu.ttf', [0x20,0x20,0xa0,0xa0,0x627,0x627,0x660,0x669,0x6f0,0x6f9,0x1ec71,0x1ecb4,]), + NotoFont.fromFlatRanges('Noto Sans Inscriptional Pahlavi', 'http://fonts.gstatic.com/s/notosansinscriptionalpahlavi/v15/ll8UK3GaVDuxR-TEqFPIbsR79Xxz9WEKbwsjpz7VklYlC7FCVtqVOAYK0QA.ttf', [0x20,0x20,0xa0,0xa0,0x10b60,0x10b72,0x10b78,0x10b7f,]), + NotoFont.fromFlatRanges('Noto Sans Inscriptional Parthian', 'http://fonts.gstatic.com/s/notosansinscriptionalparthian/v15/k3k7o-IMPvpLmixcA63oYi-yStDkgXuXncL7dzfW3P4TAJ2yklBJ2jNkLlLr.ttf', [0x20,0x20,0xa0,0xa0,0x10b40,0x10b55,0x10b58,0x10b5f,]), + NotoFont.fromFlatRanges('Noto Sans JP', 'http://fonts.gstatic.com/s/notosansjp/v42/-F62fjtqLzI2JPCgQBnw7HFowAIO2lZ9hg.otf', [0x20,0x7e,0xa0,0x103,0x110,0x113,0x11a,0x11b,0x128,0x12b,0x143,0x144,0x147,0x148,0x14c,0x14f,0x152,0x153,0x168,0x16d,0x192,0x192,0x1a0,0x1a1,0x1af,0x1b0,0x1cd,0x1dc,0x1f8,0x1f9,0x251,0x251,0x261,0x261,0x2bb,0x2bb,0x2c7,0x2c7,0x2c9,0x2cb,0x2d9,0x2d9,0x2ea,0x2eb,0x300,0x301,0x304,0x304,0x307,0x307,0x30c,0x30c,0x391,0x3a1,0x3a3,0x3a9,0x3b1,0x3c9,0x401,0x401,0x410,0x44f,0x451,0x451,0x1e3e,0x1e3f,0x1ea0,0x1ef9,0x2002,0x2003,0x2010,0x2016,0x2018,0x201a,0x201c,0x201e,0x2020,0x2022,0x2025,0x2027,0x2030,0x2030,0x2032,0x2033,0x2035,0x2035,0x2039,0x203c,0x2042,0x2042,0x2047,0x2049,0x2051,0x2051,0x2074,0x2074,0x20a9,0x20a9,0x20ab,0x20ac,0x20dd,0x20de,0x2100,0x2100,0x2103,0x2103,0x2105,0x2105,0x2109,0x210a,0x210f,0x210f,0x2113,0x2113,0x2116,0x2116,0x2121,0x2122,0x2126,0x2127,0x212b,0x212b,0x212e,0x212e,0x2135,0x2135,0x213b,0x213b,0x2160,0x216b,0x2170,0x217b,0x2190,0x2199,0x21b8,0x21b9,0x21c4,0x21c6,0x21cb,0x21cc,0x21d0,0x21d0,0x21d2,0x21d2,0x21d4,0x21d4,0x21e6,0x21e9,0x21f5,0x21f5,0x2200,0x2200,0x2202,0x2203,0x2205,0x220b,0x220f,0x220f,0x2211,0x2213,0x2215,0x2215,0x221a,0x221a,0x221d,0x2220,0x2223,0x2223,0x2225,0x222e,0x2234,0x2237,0x223d,0x223d,0x2243,0x2243,0x2245,0x2245,0x2248,0x2248,0x224c,0x224c,0x2252,0x2252,0x2260,0x2262,0x2264,0x2267,0x226a,0x226b,0x226e,0x226f,0x2272,0x2273,0x2276,0x2277,0x2282,0x2287,0x228a,0x228b,0x2295,0x2299,0x22a0,0x22a0,0x22a5,0x22a5,0x22bf,0x22bf,0x22da,0x22db,0x22ef,0x22ef,0x2305,0x2307,0x2312,0x2312,0x2318,0x2318,0x2329,0x232a,0x23b0,0x23b1,0x23be,0x23cc,0x23ce,0x23ce,0x23da,0x23db,0x2423,0x2423,0x2460,0x25ab,0x25b1,0x25b3,0x25b6,0x25b7,0x25bc,0x25bd,0x25c0,0x25c1,0x25c6,0x25cc,0x25ce,0x25d3,0x25e2,0x25e6,0x25ef,0x25ef,0x2600,0x2603,0x2605,0x2606,0x2609,0x2609,0x260e,0x260f,0x2616,0x2617,0x261c,0x261f,0x262f,0x262f,0x2640,0x2642,0x2660,0x266f,0x2672,0x267d,0x26a0,0x26a0,0x26bd,0x26be,0x2702,0x2702,0x2713,0x2713,0x271a,0x271a,0x273d,0x273d,0x273f,0x2740,0x2756,0x2756,0x2776,0x2793,0x27a1,0x27a1,0x2934,0x2935,0x29bf,0x29bf,0x29fa,0x29fb,0x2b05,0x2b07,0x2b1a,0x2b1a,0x2b95,0x2b95,0x2e3a,0x2e3b,0x2e80,0x2e99,0x2e9b,0x2ef3,0x2f00,0x2fd5,0x2ff0,0x2ffb,0x3000,0x303f,0x3041,0x3096,0x3099,0x30ff,0x3105,0x312f,0x3131,0x3163,0x3165,0x318e,0x3190,0x31bb,0x31c0,0x31e3,0x31f0,0x321e,0x3220,0x332b,0x332d,0x33ff,0x3402,0x3402,0x3405,0x3406,0x3427,0x3427,0x342c,0x342c,0x342e,0x342e,0x3468,0x3468,0x346a,0x346a,0x3488,0x3488,0x3492,0x3492,0x34b5,0x34b5,0x34bc,0x34bc,0x34c1,0x34c1,0x34c7,0x34c7,0x34db,0x34db,0x351f,0x351f,0x353e,0x353e,0x355d,0x355e,0x3563,0x3563,0x356e,0x356e,0x35a6,0x35a6,0x35a8,0x35a8,0x35c5,0x35c5,0x35da,0x35da,0x35de,0x35de,0x35f4,0x35f4,0x3605,0x3605,0x3614,0x3614,0x364a,0x364a,0x3691,0x3691,0x3696,0x3696,0x3699,0x3699,0x36cf,0x36cf,0x3761,0x3762,0x376b,0x376c,0x3775,0x3775,0x378d,0x378d,0x37c1,0x37c1,0x37e2,0x37e2,0x37e8,0x37e8,0x37f4,0x37f4,0x37fd,0x37fd,0x3800,0x3800,0x382f,0x382f,0x3836,0x3836,0x3840,0x3840,0x385c,0x385c,0x3861,0x3861,0x38a1,0x38a1,0x38ad,0x38ad,0x38fa,0x38fa,0x3917,0x3917,0x391a,0x391a,0x396f,0x396f,0x39a4,0x39a4,0x39b8,0x39b8,0x3a5c,0x3a5c,0x3a6e,0x3a6e,0x3a73,0x3a73,0x3a85,0x3a85,0x3ac4,0x3ac4,0x3acb,0x3acb,0x3ad6,0x3ad7,0x3aea,0x3aea,0x3af3,0x3af3,0x3b0e,0x3b0e,0x3b1a,0x3b1a,0x3b1c,0x3b1c,0x3b22,0x3b22,0x3b35,0x3b35,0x3b6d,0x3b6d,0x3b77,0x3b77,0x3b87,0x3b88,0x3b8d,0x3b8d,0x3ba4,0x3ba4,0x3bb6,0x3bb6,0x3bc3,0x3bc3,0x3bcd,0x3bcd,0x3bf0,0x3bf0,0x3bf3,0x3bf3,0x3c0f,0x3c0f,0x3c26,0x3c26,0x3cc3,0x3cc3,0x3cd2,0x3cd2,0x3d11,0x3d11,0x3d1e,0x3d1e,0x3d31,0x3d31,0x3d4e,0x3d4e,0x3d64,0x3d64,0x3d9a,0x3d9a,0x3dc0,0x3dc0,0x3dcc,0x3dcc,0x3dd4,0x3dd4,0x3e05,0x3e05,0x3e3f,0x3e40,0x3e60,0x3e60,0x3e66,0x3e66,0x3e68,0x3e68,0x3e83,0x3e83,0x3e8a,0x3e8a,0x3e94,0x3e94,0x3eda,0x3eda,0x3f57,0x3f57,0x3f72,0x3f72,0x3f75,0x3f75,0x3f77,0x3f77,0x3fae,0x3fae,0x3fb1,0x3fb1,0x3fc9,0x3fc9,0x3fd7,0x3fd7,0x3fdc,0x3fdc,0x4039,0x4039,0x4058,0x4058,0x4093,0x4093,0x4103,0x4103,0x4105,0x4105,0x4148,0x4148,0x414f,0x414f,0x4163,0x4163,0x41b4,0x41b4,0x41bf,0x41bf,0x41e6,0x41e6,0x41ee,0x41ee,0x41f3,0x41f3,0x4207,0x4207,0x420e,0x420e,0x4264,0x4264,0x4293,0x4293,0x42c6,0x42c6,0x42d6,0x42d6,0x42dd,0x42dd,0x4302,0x4302,0x432b,0x432b,0x4343,0x4343,0x43ee,0x43ee,0x43f0,0x43f0,0x4408,0x4408,0x440c,0x440c,0x4417,0x4417,0x441c,0x441c,0x4422,0x4422,0x4453,0x4453,0x445b,0x445b,0x4476,0x4476,0x447a,0x447a,0x4491,0x4491,0x44b3,0x44b3,0x44be,0x44be,0x44d4,0x44d4,0x4508,0x4508,0x450d,0x450d,0x4525,0x4525,0x4543,0x4543,0x457a,0x457a,0x459d,0x459d,0x45b8,0x45b8,0x45be,0x45be,0x45e5,0x45e5,0x45ea,0x45ea,0x460f,0x4610,0x4641,0x4641,0x4665,0x4665,0x46a1,0x46a1,0x46ae,0x46af,0x470c,0x470c,0x471f,0x471f,0x4764,0x4764,0x47e6,0x47e6,0x47fd,0x47fd,0x4816,0x4816,0x481e,0x481e,0x4844,0x4844,0x484e,0x484e,0x48b5,0x48b5,0x49b0,0x49b0,0x49e7,0x49e7,0x49fa,0x49fa,0x4a04,0x4a04,0x4a29,0x4a29,0x4abc,0x4abc,0x4b38,0x4b38,0x4b3b,0x4b3b,0x4b7e,0x4b7e,0x4bc2,0x4bc2,0x4bca,0x4bca,0x4bd2,0x4bd2,0x4be8,0x4be8,0x4c17,0x4c17,0x4c20,0x4c20,0x4c38,0x4c38,0x4cc4,0x4cc4,0x4cd1,0x4cd1,0x4ce1,0x4ce1,0x4d07,0x4d07,0x4d77,0x4d77,0x4e00,0x4e05,0x4e07,0x4e12,0x4e14,0x4e19,0x4e1e,0x4e1f,0x4e21,0x4e21,0x4e23,0x4e24,0x4e26,0x4e26,0x4e28,0x4e32,0x4e35,0x4e39,0x4e3b,0x4e3c,0x4e3f,0x4e45,0x4e47,0x4e48,0x4e4b,0x4e4b,0x4e4d,0x4e4f,0x4e51,0x4e51,0x4e55,0x4e5f,0x4e62,0x4e63,0x4e68,0x4e69,0x4e71,0x4e71,0x4e73,0x4e75,0x4e79,0x4e79,0x4e7e,0x4e80,0x4e82,0x4e82,0x4e85,0x4e86,0x4e88,0x4e8e,0x4e91,0x4e92,0x4e94,0x4e99,0x4e9b,0x4ea2,0x4ea4,0x4ea6,0x4ea8,0x4ea8,0x4eab,0x4eb0,0x4eb3,0x4eb3,0x4eb6,0x4eb6,0x4eb9,0x4ebc,0x4ec0,0x4ec4,0x4ec6,0x4ec8,0x4eca,0x4ecb,0x4ecd,0x4ed0,0x4ed4,0x4edb,0x4edd,0x4ee5,0x4ee8,0x4ee8,0x4eeb,0x4eeb,0x4eed,0x4ef3,0x4ef5,0x4ef7,0x4efb,0x4f03,0x4f08,0x4f12,0x4f15,0x4f17,0x4f19,0x4f1a,0x4f1c,0x4f1d,0x4f2b,0x4f2b,0x4f2e,0x4f31,0x4f33,0x4f3e,0x4f40,0x4f40,0x4f42,0x4f43,0x4f46,0x4f49,0x4f4b,0x4f60,0x4f63,0x4f64,0x4f69,0x4f6a,0x4f6c,0x4f6c,0x4f6e,0x4f71,0x4f73,0x4f73,0x4f75,0x4f7f,0x4f81,0x4f86,0x4f88,0x4f94,0x4f96,0x4f9b,0x4f9d,0x4fa1,0x4fab,0x4fab,0x4fad,0x4faf,0x4fb2,0x4fb2,0x4fb5,0x4fb7,0x4fb9,0x4fb9,0x4fbb,0x4fc6,0x4fc8,0x4fd4,0x4fd7,0x4fd8,0x4fda,0x4fdd,0x4fdf,0x4fe6,0x4fee,0x4ff3,0x4ff5,0x4ff6,0x4ff8,0x4ff8,0x4ffa,0x4ffa,0x4ffc,0x5002,0x5004,0x5007,0x5009,0x5014,0x5016,0x501f,0x5021,0x502e,0x5030,0x5030,0x5032,0x5033,0x5035,0x5036,0x5039,0x5039,0x503b,0x503b,0x5040,0x5043,0x5045,0x504a,0x504c,0x504c,0x504e,0x5053,0x5055,0x5057,0x5059,0x505a,0x505c,0x505c,0x505f,0x5060,0x5062,0x5063,0x5065,0x5067,0x506a,0x506a,0x506c,0x506d,0x5070,0x5072,0x5074,0x5078,0x507d,0x507d,0x5080,0x5081,0x5083,0x5086,0x5088,0x5088,0x508a,0x508a,0x508d,0x5096,0x5098,0x509c,0x509e,0x50a3,0x50aa,0x50aa,0x50ac,0x50ad,0x50af,0x50b5,0x50b7,0x50b7,0x50b9,0x50bb,0x50bd,0x50be,0x50c0,0x50c0,0x50c2,0x50c5,0x50c7,0x50c7,0x50c9,0x50ca,0x50cc,0x50d1,0x50d3,0x50d6,0x50d8,0x50da,0x50dc,0x50df,0x50e1,0x50e9,0x50ed,0x50f6,0x50f9,0x50fb,0x50fe,0x50fe,0x5100,0x5104,0x5106,0x5109,0x510b,0x510e,0x5110,0x5110,0x5112,0x5112,0x5114,0x511f,0x5121,0x5121,0x5123,0x5123,0x5127,0x5128,0x512a,0x512a,0x512c,0x512d,0x512f,0x512f,0x5131,0x5135,0x5137,0x513c,0x513f,0x5150,0x5152,0x5155,0x5157,0x5158,0x515a,0x515a,0x515c,0x515c,0x515f,0x5160,0x5162,0x5162,0x5164,0x516e,0x5171,0x5171,0x5173,0x5179,0x517b,0x517c,0x517e,0x517e,0x5180,0x5180,0x5182,0x5186,0x5189,0x5193,0x5195,0x5199,0x519d,0x519d,0x51a0,0x51a6,0x51a8,0x51ad,0x51b0,0x51b8,0x51ba,0x51ba,0x51bc,0x51bf,0x51c2,0x51c6,0x51c8,0x51cd,0x51cf,0x51cf,0x51d1,0x51d6,0x51d8,0x51d8,0x51db,0x51e2,0x51e5,0x51e7,0x51e9,0x51ea,0x51ec,0x51ee,0x51f0,0x51fa,0x51fd,0x51fe,0x5200,0x5208,0x520a,0x520b,0x520e,0x520e,0x5211,0x5218,0x521d,0x521d,0x5222,0x5222,0x5224,0x522b,0x522e,0x522e,0x5230,0x5233,0x5235,0x523c,0x5243,0x5245,0x5247,0x5247,0x5249,0x524d,0x524f,0x524f,0x5254,0x5258,0x525a,0x5261,0x5263,0x5266,0x5269,0x526a,0x526c,0x526c,0x526e,0x5275,0x5277,0x5279,0x527d,0x527d,0x527f,0x5280,0x5282,0x5285,0x5287,0x528a,0x528c,0x528d,0x5291,0x5298,0x529a,0x529c,0x529f,0x52a0,0x52a3,0x52a7,0x52a9,0x52ad,0x52af,0x52b1,0x52b4,0x52be,0x52c0,0x52c1,0x52c3,0x52ca,0x52cc,0x52cd,0x52cf,0x52d2,0x52d4,0x52d9,0x52db,0x52ea,0x52ec,0x52ec,0x52f0,0x52fb,0x52fe,0x5303,0x5305,0x5308,0x530a,0x530d,0x530f,0x5311,0x5313,0x5313,0x5315,0x5321,0x5323,0x5325,0x5327,0x532d,0x532f,0x5333,0x5335,0x5335,0x5338,0x5343,0x5345,0x534d,0x5351,0x5354,0x5357,0x535c,0x535e,0x535e,0x5360,0x5361,0x5363,0x5367,0x5369,0x5369,0x536c,0x5375,0x5377,0x537b,0x537d,0x537f,0x5382,0x5384,0x5387,0x5389,0x538e,0x538e,0x5393,0x5394,0x5396,0x5396,0x5398,0x539a,0x539d,0x539d,0x539f,0x53a1,0x53a4,0x53a6,0x53a8,0x53ab,0x53ad,0x53b0,0x53b2,0x53b8,0x53ba,0x53bb,0x53bd,0x53bd,0x53c0,0x53c5,0x53c8,0x53cf,0x53d2,0x53d7,0x53d9,0x53db,0x53dd,0x53f8,0x53fa,0x53fa,0x5401,0x5404,0x5408,0x5413,0x541a,0x541b,0x541d,0x5421,0x5424,0x5424,0x5426,0x542f,0x5431,0x5431,0x5433,0x5436,0x5438,0x5439,0x543b,0x5440,0x5442,0x5444,0x5446,0x544a,0x544c,0x544f,0x5451,0x5451,0x5455,0x5455,0x545e,0x545f,0x5462,0x5462,0x5464,0x5464,0x5466,0x546e,0x5470,0x5471,0x5473,0x5477,0x547b,0x547d,0x547f,0x5481,0x5483,0x5486,0x5488,0x5492,0x5495,0x5496,0x549c,0x549c,0x549f,0x54a2,0x54a4,0x54af,0x54b1,0x54b3,0x54b7,0x54c4,0x54c6,0x54ca,0x54cd,0x54ce,0x54d8,0x54d8,0x54e0,0x54e2,0x54e5,0x54e6,0x54e8,0x54ea,0x54ec,0x54ef,0x54f1,0x54f3,0x54f6,0x54f6,0x54fa,0x54fa,0x54fc,0x5501,0x5504,0x5509,0x550c,0x5510,0x5514,0x5516,0x5527,0x5527,0x552a,0x552b,0x552e,0x552f,0x5531,0x5533,0x5535,0x5536,0x5538,0x5539,0x553b,0x553e,0x5540,0x5541,0x5544,0x5547,0x5549,0x554a,0x554c,0x554d,0x554f,0x5551,0x5553,0x5553,0x5556,0x5558,0x555a,0x555e,0x5560,0x5561,0x5563,0x5564,0x5566,0x5566,0x557b,0x5584,0x5586,0x558b,0x558e,0x558f,0x5591,0x5594,0x5597,0x559a,0x559c,0x559f,0x55a3,0x55a4,0x55a7,0x55ae,0x55b0,0x55b0,0x55b2,0x55b2,0x55b6,0x55b6,0x55bf,0x55bf,0x55c1,0x55c1,0x55c3,0x55c7,0x55c9,0x55c9,0x55cb,0x55cc,0x55ce,0x55ce,0x55d1,0x55d4,0x55d7,0x55d8,0x55da,0x55df,0x55e2,0x55e4,0x55e9,0x55e9,0x55ec,0x55ec,0x55ee,0x55ee,0x55f1,0x55f1,0x55f6,0x55f9,0x55fd,0x55ff,0x5605,0x560a,0x560d,0x5612,0x5614,0x5614,0x5616,0x5619,0x561b,0x561b,0x5620,0x5620,0x5628,0x5629,0x562c,0x562c,0x562f,0x5639,0x563b,0x563d,0x563f,0x5644,0x5646,0x5647,0x5649,0x5649,0x564b,0x5650,0x5653,0x5654,0x565b,0x565b,0x565e,0x565e,0x5660,0x5664,0x5666,0x5666,0x5668,0x566d,0x566f,0x566f,0x5671,0x5672,0x5674,0x5676,0x5678,0x5678,0x567a,0x567a,0x5680,0x5680,0x5684,0x5688,0x568a,0x568c,0x568f,0x568f,0x5694,0x5695,0x5699,0x569a,0x569d,0x56a0,0x56a2,0x56a2,0x56a5,0x56a9,0x56ab,0x56ae,0x56b1,0x56b4,0x56b6,0x56b7,0x56bc,0x56bc,0x56be,0x56be,0x56c0,0x56c3,0x56c5,0x56c5,0x56c8,0x56d1,0x56d3,0x56d3,0x56d7,0x56e1,0x56e3,0x56e8,0x56eb,0x56eb,0x56ed,0x56ee,0x56f0,0x56f3,0x56f6,0x56f7,0x56f9,0x56fa,0x56fd,0x56fd,0x56ff,0x5704,0x5707,0x570d,0x570f,0x570f,0x5711,0x5713,0x5715,0x5716,0x5718,0x5718,0x571a,0x571d,0x571f,0x572a,0x572c,0x5730,0x5733,0x5734,0x5737,0x5738,0x573b,0x573b,0x573d,0x5740,0x5742,0x5742,0x5745,0x5747,0x574a,0x574a,0x574c,0x5752,0x5759,0x5759,0x575f,0x575f,0x5761,0x5762,0x5764,0x576b,0x576d,0x5771,0x5773,0x5775,0x5777,0x5777,0x5779,0x577c,0x577e,0x577f,0x5781,0x5783,0x5788,0x5789,0x578b,0x578c,0x5793,0x5795,0x5797,0x5797,0x5799,0x579a,0x579c,0x57a4,0x57a7,0x57aa,0x57ac,0x57ac,0x57ae,0x57ae,0x57b0,0x57b0,0x57b3,0x57b3,0x57b8,0x57b8,0x57bd,0x57bd,0x57c0,0x57c0,0x57c3,0x57c3,0x57c6,0x57c8,0x57cb,0x57cc,0x57ce,0x57cf,0x57d2,0x57d7,0x57dc,0x57e1,0x57e3,0x57e4,0x57e6,0x57e7,0x57e9,0x57e9,0x57ed,0x57ed,0x57f0,0x57f0,0x57f4,0x5800,0x5802,0x5806,0x5808,0x580d,0x5815,0x5815,0x5819,0x5819,0x581b,0x581b,0x581d,0x5821,0x5824,0x5824,0x5826,0x5827,0x582a,0x582a,0x582d,0x582d,0x582f,0x5832,0x5834,0x5835,0x5839,0x583a,0x583d,0x583d,0x583f,0x5841,0x5849,0x584d,0x584f,0x5852,0x5854,0x5855,0x5857,0x585a,0x585e,0x585f,0x5861,0x5862,0x5864,0x5864,0x5867,0x5869,0x586b,0x586b,0x586d,0x586d,0x5870,0x5870,0x5872,0x5872,0x5875,0x5875,0x5878,0x5879,0x587c,0x587c,0x587e,0x5881,0x5883,0x5883,0x5885,0x5885,0x5887,0x588d,0x588f,0x5890,0x5893,0x5894,0x5896,0x5898,0x589c,0x58a2,0x58a6,0x58a6,0x58a8,0x58ab,0x58ae,0x58ae,0x58b1,0x58b3,0x58b8,0x58bc,0x58be,0x58be,0x58c1,0x58c5,0x58c7,0x58c8,0x58ca,0x58ca,0x58cc,0x58ce,0x58d0,0x58da,0x58dc,0x58e2,0x58e4,0x58e5,0x58e9,0x58e9,0x58eb,0x58ec,0x58ee,0x58f4,0x58f7,0x58f7,0x58f9,0x58fd,0x5902,0x5902,0x5905,0x5906,0x5909,0x590d,0x590f,0x5910,0x5912,0x5916,0x5918,0x591d,0x591f,0x591f,0x5921,0x5925,0x5927,0x5933,0x5935,0x5939,0x593d,0x593f,0x5943,0x5944,0x5946,0x5949,0x594e,0x5955,0x5957,0x595b,0x595d,0x5963,0x5965,0x5965,0x5967,0x596f,0x5972,0x5976,0x5978,0x5979,0x597b,0x597d,0x5981,0x5984,0x598a,0x598e,0x5992,0x5993,0x5995,0x5997,0x5999,0x5999,0x599b,0x599b,0x599d,0x599d,0x599f,0x599f,0x59a3,0x59a5,0x59a7,0x59a8,0x59ac,0x59b0,0x59b2,0x59b3,0x59b7,0x59b7,0x59b9,0x59bc,0x59be,0x59be,0x59c1,0x59c1,0x59c3,0x59c4,0x59c6,0x59c6,0x59c8,0x59cb,0x59cd,0x59cd,0x59d0,0x59d4,0x59d9,0x59da,0x59dc,0x59df,0x59e3,0x59e8,0x59ea,0x59ec,0x59ee,0x59ef,0x59f1,0x59f2,0x59f4,0x59f4,0x59f6,0x59f8,0x59fb,0x59fb,0x59ff,0x5a01,0x5a03,0x5a04,0x5a09,0x5a09,0x5a0c,0x5a0e,0x5a11,0x5a13,0x5a17,0x5a18,0x5a1a,0x5a1c,0x5a1e,0x5a20,0x5a23,0x5a25,0x5a27,0x5a2a,0x5a2d,0x5a2d,0x5a2f,0x5a30,0x5a35,0x5a36,0x5a3c,0x5a3c,0x5a40,0x5a41,0x5a44,0x5a49,0x5a4c,0x5a4c,0x5a50,0x5a50,0x5a55,0x5a55,0x5a5a,0x5a5a,0x5a5e,0x5a5e,0x5a62,0x5a63,0x5a65,0x5a67,0x5a6a,0x5a6a,0x5a6c,0x5a6d,0x5a77,0x5a77,0x5a7a,0x5a7b,0x5a7e,0x5a7f,0x5a84,0x5a84,0x5a8b,0x5a8b,0x5a90,0x5a90,0x5a92,0x5a93,0x5a96,0x5a96,0x5a99,0x5a9c,0x5a9e,0x5aa0,0x5aa2,0x5aa2,0x5aa7,0x5aa7,0x5aac,0x5aac,0x5ab1,0x5ab3,0x5ab5,0x5ab5,0x5ab8,0x5ab8,0x5aba,0x5abf,0x5ac1,0x5ac2,0x5ac4,0x5ac4,0x5ac6,0x5ac6,0x5ac8,0x5ac9,0x5acb,0x5acc,0x5acf,0x5ad0,0x5ad6,0x5ad7,0x5ada,0x5ada,0x5adc,0x5adc,0x5ae0,0x5ae1,0x5ae3,0x5ae3,0x5ae5,0x5ae6,0x5ae9,0x5aea,0x5aee,0x5aee,0x5af0,0x5af0,0x5af5,0x5af6,0x5afa,0x5afb,0x5afd,0x5afd,0x5b00,0x5b01,0x5b08,0x5b09,0x5b0b,0x5b0c,0x5b16,0x5b17,0x5b19,0x5b19,0x5b1b,0x5b1b,0x5b1d,0x5b1d,0x5b21,0x5b22,0x5b25,0x5b25,0x5b2a,0x5b2a,0x5b2c,0x5b2d,0x5b30,0x5b30,0x5b32,0x5b32,0x5b34,0x5b34,0x5b36,0x5b36,0x5b38,0x5b38,0x5b3e,0x5b3e,0x5b40,0x5b41,0x5b43,0x5b43,0x5b45,0x5b45,0x5b4b,0x5b4c,0x5b50,0x5b52,0x5b54,0x5b58,0x5b5a,0x5b5f,0x5b63,0x5b66,0x5b68,0x5b69,0x5b6b,0x5b6b,0x5b6e,0x5b71,0x5b73,0x5b73,0x5b75,0x5b76,0x5b78,0x5b78,0x5b7a,0x5b7a,0x5b7c,0x5b91,0x5b93,0x5b9d,0x5b9f,0x5b9f,0x5ba2,0x5ba6,0x5ba8,0x5ba9,0x5bac,0x5bba,0x5bbc,0x5bbc,0x5bbf,0x5bc7,0x5bc9,0x5bc9,0x5bcc,0x5bd0,0x5bd2,0x5bd4,0x5bd6,0x5bdb,0x5bdd,0x5be2,0x5be4,0x5be9,0x5beb,0x5bec,0x5bee,0x5bf1,0x5bf3,0x5bf6,0x5bf8,0x5bf8,0x5bfa,0x5bfa,0x5bfd,0x5bff,0x5c01,0x5c0f,0x5c11,0x5c14,0x5c16,0x5c17,0x5c19,0x5c1a,0x5c1e,0x5c20,0x5c22,0x5c24,0x5c26,0x5c26,0x5c28,0x5c2e,0x5c30,0x5c32,0x5c35,0x5c36,0x5c38,0x5c41,0x5c45,0x5c46,0x5c48,0x5c48,0x5c4a,0x5c4b,0x5c4d,0x5c51,0x5c53,0x5c53,0x5c55,0x5c55,0x5c59,0x5c5c,0x5c5e,0x5c65,0x5c67,0x5c69,0x5c6c,0x5c71,0x5c74,0x5c76,0x5c79,0x5c7d,0x5c87,0x5c88,0x5c8a,0x5c8a,0x5c8c,0x5c8c,0x5c8f,0x5c92,0x5c94,0x5c94,0x5c9d,0x5c9d,0x5c9f,0x5ca3,0x5ca6,0x5cad,0x5cb1,0x5cb8,0x5cba,0x5cbc,0x5cbe,0x5cbe,0x5cc5,0x5cc5,0x5cc7,0x5cc7,0x5cc9,0x5cc9,0x5ccb,0x5ccb,0x5cd0,0x5cd0,0x5cd2,0x5cd2,0x5cd7,0x5cd7,0x5cd9,0x5cd9,0x5cdd,0x5cdd,0x5ce0,0x5ce1,0x5ce6,0x5ce6,0x5ce8,0x5cea,0x5ced,0x5cf2,0x5cf4,0x5cf6,0x5cfa,0x5cfb,0x5cfd,0x5cfd,0x5d01,0x5d01,0x5d06,0x5d07,0x5d0b,0x5d0b,0x5d0d,0x5d0e,0x5d10,0x5d12,0x5d14,0x5d1b,0x5d1d,0x5d1d,0x5d1f,0x5d20,0x5d22,0x5d24,0x5d26,0x5d27,0x5d29,0x5d29,0x5d2b,0x5d2b,0x5d31,0x5d31,0x5d34,0x5d34,0x5d39,0x5d39,0x5d3d,0x5d3d,0x5d3f,0x5d3f,0x5d42,0x5d43,0x5d46,0x5d48,0x5d4a,0x5d4c,0x5d4e,0x5d4e,0x5d50,0x5d53,0x5d55,0x5d55,0x5d59,0x5d59,0x5d5c,0x5d5c,0x5d5f,0x5d62,0x5d64,0x5d64,0x5d69,0x5d6a,0x5d6c,0x5d6d,0x5d6f,0x5d70,0x5d73,0x5d73,0x5d76,0x5d76,0x5d79,0x5d7a,0x5d7e,0x5d7f,0x5d81,0x5d84,0x5d87,0x5d88,0x5d8a,0x5d8c,0x5d90,0x5d90,0x5d92,0x5d95,0x5d97,0x5d97,0x5d99,0x5d99,0x5d9b,0x5d9b,0x5d9d,0x5d9d,0x5d9f,0x5da0,0x5da2,0x5da2,0x5da4,0x5da4,0x5da7,0x5da7,0x5dab,0x5dac,0x5dae,0x5dae,0x5db0,0x5db0,0x5db2,0x5db2,0x5db4,0x5db4,0x5db7,0x5dba,0x5dbc,0x5dbd,0x5dc3,0x5dc3,0x5dc7,0x5dc7,0x5dc9,0x5dc9,0x5dcb,0x5dce,0x5dd0,0x5dd3,0x5dd6,0x5dd9,0x5ddb,0x5ddb,0x5ddd,0x5dde,0x5de0,0x5de9,0x5deb,0x5deb,0x5dee,0x5dee,0x5df1,0x5df5,0x5df7,0x5df9,0x5dfb,0x5dfb,0x5dfd,0x5e00,0x5e02,0x5e03,0x5e06,0x5e07,0x5e0b,0x5e0d,0x5e11,0x5e12,0x5e14,0x5e16,0x5e18,0x5e1b,0x5e1d,0x5e1d,0x5e1f,0x5e20,0x5e25,0x5e25,0x5e28,0x5e28,0x5e2b,0x5e2b,0x5e2d,0x5e30,0x5e32,0x5e33,0x5e35,0x5e38,0x5e3d,0x5e3e,0x5e40,0x5e40,0x5e43,0x5e45,0x5e47,0x5e47,0x5e49,0x5e49,0x5e4b,0x5e4c,0x5e4e,0x5e4e,0x5e50,0x5e51,0x5e54,0x5e58,0x5e5b,0x5e5c,0x5e5e,0x5e5f,0x5e61,0x5e64,0x5e68,0x5e68,0x5e6a,0x5e6e,0x5e70,0x5e70,0x5e72,0x5e81,0x5e83,0x5e84,0x5e87,0x5e87,0x5e8a,0x5e8b,0x5e8e,0x5e8f,0x5e95,0x5e97,0x5e99,0x5e9a,0x5e9c,0x5e9c,0x5ea0,0x5ea0,0x5ea2,0x5ea2,0x5ea4,0x5ea8,0x5eaa,0x5ead,0x5eb1,0x5eb1,0x5eb3,0x5eb3,0x5eb5,0x5eb9,0x5ebd,0x5ebf,0x5ec1,0x5ec3,0x5ec6,0x5ec6,0x5ec8,0x5ecc,0x5ece,0x5ed6,0x5ed9,0x5ee3,0x5ee5,0x5ee5,0x5ee8,0x5ee9,0x5eeb,0x5eec,0x5ef0,0x5ef1,0x5ef3,0x5ef4,0x5ef6,0x5f04,0x5f06,0x5f11,0x5f13,0x5f19,0x5f1b,0x5f1f,0x5f21,0x5f29,0x5f2b,0x5f31,0x5f34,0x5f38,0x5f3a,0x5f41,0x5f44,0x5f45,0x5f47,0x5f48,0x5f4a,0x5f4a,0x5f4c,0x5f4e,0x5f50,0x5f51,0x5f53,0x5f54,0x5f56,0x5f59,0x5f5b,0x5f5d,0x5f60,0x5f67,0x5f69,0x5f6d,0x5f6f,0x5f75,0x5f77,0x5f7a,0x5f7c,0x5f85,0x5f87,0x5f8d,0x5f8f,0x5f93,0x5f96,0x5f99,0x5f9c,0x5f9e,0x5fa0,0x5fa2,0x5fa4,0x5fa4,0x5fa7,0x5fb1,0x5fb3,0x5fb5,0x5fb7,0x5fb9,0x5fbc,0x5fbd,0x5fc3,0x5fc5,0x5fc7,0x5fc9,0x5fcb,0x5fcd,0x5fd0,0x5fd4,0x5fd6,0x5fd9,0x5fdc,0x5fde,0x5fe0,0x5fe2,0x5fe4,0x5fe4,0x5fe8,0x5ff3,0x5ff5,0x5ff6,0x5ff8,0x5ff8,0x5ffa,0x5ffd,0x5fff,0x5fff,0x6007,0x6007,0x600a,0x600a,0x600d,0x6010,0x6012,0x601d,0x601f,0x6022,0x6024,0x602b,0x602d,0x602d,0x602f,0x602f,0x6031,0x6031,0x6033,0x6033,0x6035,0x6035,0x603a,0x603a,0x6040,0x6043,0x6046,0x604d,0x6050,0x6052,0x6054,0x6057,0x6059,0x605a,0x605d,0x605d,0x605f,0x6065,0x6067,0x606d,0x606f,0x6071,0x6075,0x6075,0x6077,0x6077,0x607e,0x607f,0x6081,0x6086,0x6088,0x608e,0x6091,0x6098,0x609a,0x609b,0x609d,0x60a0,0x60a2,0x60aa,0x60b0,0x60b8,0x60bb,0x60be,0x60c2,0x60c2,0x60c4,0x60cb,0x60ce,0x60cf,0x60d1,0x60d1,0x60d3,0x60d5,0x60d8,0x60e3,0x60e5,0x60e5,0x60e7,0x60e8,0x60ee,0x60ee,0x60f0,0x60fd,0x6100,0x6103,0x6106,0x610a,0x610c,0x6117,0x6119,0x611c,0x611e,0x6122,0x6127,0x6128,0x612a,0x612c,0x6130,0x6131,0x6134,0x6137,0x6139,0x613a,0x613c,0x613f,0x6141,0x6142,0x6144,0x614e,0x6153,0x6153,0x6155,0x6155,0x6158,0x615a,0x615d,0x6160,0x6162,0x6165,0x6167,0x6168,0x616b,0x616c,0x616e,0x6178,0x617b,0x6184,0x6187,0x6187,0x618a,0x618b,0x618d,0x618e,0x6190,0x6194,0x6196,0x619a,0x619c,0x619d,0x619f,0x61a0,0x61a4,0x61a5,0x61a7,0x61ae,0x61b2,0x61b2,0x61b6,0x61b6,0x61b8,0x61ba,0x61bc,0x61bc,0x61be,0x61be,0x61c0,0x61c3,0x61c6,0x61d0,0x61d5,0x61d5,0x61dc,0x61df,0x61e1,0x61e3,0x61e5,0x61e9,0x61ec,0x61ed,0x61ef,0x61ef,0x61f2,0x61f2,0x61f4,0x61f8,0x61fa,0x61fa,0x61fc,0x6201,0x6203,0x6204,0x6207,0x620a,0x620c,0x620e,0x6210,0x6216,0x621a,0x6223,0x6226,0x6227,0x6229,0x622b,0x622e,0x6234,0x6236,0x6236,0x6238,0x6239,0x623b,0x623b,0x623d,0x6244,0x6246,0x6249,0x624b,0x624e,0x6250,0x6256,0x6258,0x6258,0x625a,0x625c,0x625e,0x625e,0x6260,0x6261,0x6263,0x6264,0x6268,0x6268,0x626d,0x626f,0x6271,0x6271,0x6273,0x6273,0x6276,0x6276,0x6279,0x6280,0x6282,0x6285,0x6289,0x628a,0x628d,0x6299,0x629b,0x629c,0x629e,0x629e,0x62a6,0x62a6,0x62a8,0x62a8,0x62ab,0x62ac,0x62b1,0x62b1,0x62b3,0x62b3,0x62b5,0x62b7,0x62b9,0x62bf,0x62c2,0x62c2,0x62c4,0x62ca,0x62cc,0x62dd,0x62e0,0x62e1,0x62ea,0x62ea,0x62ec,0x62ef,0x62f1,0x62f7,0x62fc,0x62ff,0x6301,0x6304,0x6307,0x630d,0x6310,0x6311,0x6313,0x6313,0x6316,0x6316,0x6318,0x6319,0x631b,0x631b,0x631f,0x631f,0x6327,0x632b,0x632d,0x632d,0x632f,0x632f,0x6332,0x6332,0x6335,0x6336,0x6339,0x633f,0x6341,0x6344,0x6346,0x6346,0x6349,0x6350,0x6352,0x6355,0x6357,0x6359,0x635b,0x635c,0x6365,0x6369,0x636b,0x636e,0x6371,0x6372,0x6374,0x6378,0x637a,0x637d,0x637f,0x6380,0x6382,0x6384,0x6387,0x638a,0x638c,0x638c,0x638e,0x6390,0x6392,0x6392,0x6394,0x6396,0x6398,0x639b,0x639e,0x63af,0x63b2,0x63b2,0x63b4,0x63b5,0x63bb,0x63bb,0x63bd,0x63be,0x63c0,0x63c1,0x63c3,0x63c6,0x63c8,0x63c9,0x63ce,0x63d6,0x63da,0x63dc,0x63e0,0x63e1,0x63e3,0x63e3,0x63e5,0x63e5,0x63e9,0x63ee,0x63f2,0x63fa,0x6406,0x6406,0x6409,0x640a,0x640d,0x640d,0x640f,0x6410,0x6412,0x6414,0x6416,0x6418,0x641c,0x641c,0x641e,0x641e,0x6420,0x6420,0x6422,0x6422,0x6424,0x6426,0x6428,0x642a,0x642c,0x642d,0x642f,0x6430,0x6434,0x6436,0x643a,0x643a,0x643d,0x643f,0x6442,0x6442,0x644b,0x644b,0x644e,0x644f,0x6451,0x6454,0x6458,0x6458,0x645a,0x645d,0x645f,0x6461,0x6463,0x6463,0x6467,0x6467,0x6469,0x6469,0x646d,0x646d,0x646f,0x646f,0x6473,0x6474,0x6476,0x6476,0x6478,0x647b,0x647d,0x647d,0x6483,0x6483,0x6485,0x6485,0x6487,0x6488,0x648f,0x6493,0x6495,0x6495,0x6498,0x649b,0x649d,0x649f,0x64a1,0x64a1,0x64a3,0x64a6,0x64a8,0x64a9,0x64ab,0x64ae,0x64b0,0x64b0,0x64b2,0x64b3,0x64b9,0x64b9,0x64bb,0x64bf,0x64c1,0x64c2,0x64c4,0x64c5,0x64c7,0x64c7,0x64c9,0x64ce,0x64d0,0x64d2,0x64d4,0x64d5,0x64d7,0x64d8,0x64da,0x64da,0x64e0,0x64e7,0x64e9,0x64ea,0x64ec,0x64ed,0x64ef,0x64f2,0x64f4,0x64f7,0x64fa,0x64fb,0x64fd,0x6501,0x6504,0x6505,0x6508,0x650a,0x650f,0x650f,0x6513,0x6514,0x6516,0x6516,0x6518,0x6519,0x651b,0x651f,0x6522,0x6524,0x6526,0x6526,0x6529,0x652c,0x652e,0x652f,0x6531,0x6532,0x6534,0x653f,0x6543,0x6545,0x6547,0x6549,0x654d,0x6552,0x6554,0x6559,0x655d,0x6560,0x6562,0x6563,0x6566,0x6567,0x656b,0x656c,0x6570,0x6570,0x6572,0x6572,0x6574,0x6575,0x6577,0x6578,0x657a,0x657a,0x657d,0x657d,0x6581,0x6585,0x6587,0x658a,0x658c,0x658c,0x658e,0x658e,0x6590,0x6592,0x6595,0x6595,0x6597,0x6599,0x659b,0x659d,0x659f,0x65a1,0x65a3,0x65a7,0x65ab,0x65b0,0x65b2,0x65b5,0x65b7,0x65b9,0x65bc,0x65bf,0x65c1,0x65c6,0x65c8,0x65c9,0x65cb,0x65cc,0x65ce,0x65d0,0x65d2,0x65d2,0x65d4,0x65d4,0x65d6,0x65d9,0x65db,0x65db,0x65df,0x65e3,0x65e5,0x65e9,0x65ec,0x65ed,0x65f0,0x65f2,0x65f4,0x65f5,0x65f9,0x65fc,0x65fe,0x6600,0x6602,0x6604,0x6606,0x660a,0x660c,0x660f,0x6611,0x6616,0x661c,0x6631,0x6633,0x6637,0x6639,0x663c,0x663f,0x6646,0x6648,0x664c,0x664e,0x664f,0x6651,0x6652,0x6657,0x6670,0x6673,0x667c,0x667e,0x6681,0x6683,0x6684,0x6687,0x6689,0x668b,0x668e,0x6690,0x6692,0x6696,0x669d,0x669f,0x66a0,0x66a2,0x66a2,0x66a4,0x66a4,0x66a6,0x66a6,0x66ab,0x66ab,0x66ad,0x66ae,0x66b1,0x66b5,0x66b8,0x66b9,0x66bb,0x66bc,0x66be,0x66c4,0x66c6,0x66c9,0x66cc,0x66cc,0x66ce,0x66cf,0x66d4,0x66d4,0x66d6,0x66d6,0x66d9,0x66dd,0x66df,0x66e0,0x66e6,0x66e6,0x66e8,0x66e9,0x66eb,0x66ec,0x66ee,0x66ee,0x66f0,0x66f0,0x66f2,0x66f5,0x66f7,0x6701,0x6703,0x6703,0x6705,0x6705,0x6707,0x6709,0x670b,0x6710,0x6712,0x6717,0x6719,0x6719,0x671b,0x6720,0x6722,0x6722,0x6725,0x6728,0x672a,0x672e,0x6731,0x6731,0x6733,0x6738,0x673a,0x673a,0x673d,0x673f,0x6741,0x6741,0x6743,0x6743,0x6745,0x6749,0x674c,0x6751,0x6753,0x6756,0x6759,0x6759,0x675c,0x6766,0x676a,0x676a,0x676c,0x6777,0x677b,0x677c,0x677e,0x6781,0x6784,0x6785,0x6787,0x6787,0x6789,0x6789,0x678b,0x678c,0x678e,0x6793,0x6795,0x679d,0x67a0,0x67a2,0x67a4,0x67a4,0x67a6,0x67a6,0x67a9,0x67a9,0x67af,0x67b9,0x67bb,0x67be,0x67c0,0x67c6,0x67c8,0x67ca,0x67ce,0x67d4,0x67d7,0x67de,0x67e1,0x67e2,0x67e4,0x67e4,0x67e6,0x67e7,0x67e9,0x67e9,0x67ec,0x67ec,0x67ee,0x67f7,0x67f9,0x67fc,0x67fe,0x67ff,0x6801,0x6805,0x6810,0x6810,0x6813,0x6814,0x6816,0x6819,0x681d,0x681f,0x6821,0x6822,0x6827,0x682d,0x682f,0x6834,0x6838,0x6839,0x683b,0x6846,0x6848,0x684a,0x684c,0x684e,0x6850,0x6855,0x6857,0x6859,0x685b,0x685d,0x685f,0x685f,0x6863,0x6863,0x6867,0x6867,0x686b,0x686b,0x686e,0x6872,0x6874,0x6877,0x6879,0x687c,0x687e,0x687f,0x6881,0x6886,0x6888,0x6888,0x688d,0x6890,0x6893,0x6894,0x6896,0x689d,0x689f,0x68a3,0x68a5,0x68ab,0x68ad,0x68b6,0x68b9,0x68bc,0x68c3,0x68c6,0x68c8,0x68cd,0x68cf,0x68da,0x68dc,0x68dd,0x68df,0x68e1,0x68e3,0x68e5,0x68e7,0x68e8,0x68ea,0x68f2,0x68f5,0x68f7,0x68f9,0x68fd,0x6900,0x6901,0x6903,0x6913,0x6916,0x6917,0x6919,0x691c,0x6921,0x6923,0x6925,0x6926,0x6928,0x6928,0x692a,0x692a,0x6930,0x6931,0x6933,0x6936,0x6938,0x6939,0x693b,0x693b,0x693d,0x693d,0x693f,0x693f,0x6942,0x6942,0x6945,0x6946,0x6949,0x694a,0x694e,0x694e,0x6953,0x6955,0x6957,0x6957,0x6959,0x695e,0x6960,0x6966,0x6968,0x6975,0x6977,0x6982,0x6986,0x6986,0x698a,0x698a,0x698d,0x698e,0x6991,0x6992,0x6994,0x6996,0x6998,0x6998,0x699b,0x699c,0x69a0,0x69a1,0x69a5,0x69a8,0x69ab,0x69ab,0x69ad,0x69b2,0x69b4,0x69b4,0x69b7,0x69b8,0x69ba,0x69bc,0x69be,0x69c1,0x69c3,0x69c3,0x69c5,0x69c5,0x69c7,0x69c8,0x69ca,0x69d1,0x69d3,0x69d3,0x69d6,0x69d9,0x69dd,0x69de,0x69e2,0x69e3,0x69e5,0x69e5,0x69e7,0x69eb,0x69ed,0x69ef,0x69f1,0x69f6,0x69f9,0x69f9,0x69fb,0x69fb,0x69fd,0x6a03,0x6a05,0x6a05,0x6a0a,0x6a0c,0x6a0f,0x6a0f,0x6a11,0x6a15,0x6a17,0x6a17,0x6a19,0x6a1b,0x6a1d,0x6a24,0x6a28,0x6a2b,0x6a2e,0x6a2e,0x6a30,0x6a30,0x6a32,0x6a3b,0x6a3d,0x6a3f,0x6a44,0x6a4b,0x6a4e,0x6a4e,0x6a50,0x6a52,0x6a54,0x6a56,0x6a58,0x6a59,0x6a5b,0x6a5b,0x6a5f,0x6a5f,0x6a61,0x6a62,0x6a64,0x6a64,0x6a66,0x6a67,0x6a6a,0x6a6b,0x6a71,0x6a73,0x6a78,0x6a78,0x6a7a,0x6a7a,0x6a7e,0x6a81,0x6a83,0x6a84,0x6a86,0x6a87,0x6a89,0x6a89,0x6a8b,0x6a8b,0x6a8d,0x6a8e,0x6a90,0x6a91,0x6a94,0x6a94,0x6a97,0x6a97,0x6a9b,0x6aa3,0x6aa5,0x6aa5,0x6aaa,0x6aac,0x6aae,0x6ab1,0x6ab3,0x6ab4,0x6ab8,0x6ab8,0x6abb,0x6abb,0x6abd,0x6abf,0x6ac1,0x6ac3,0x6ac6,0x6ac6,0x6ac8,0x6ac9,0x6acc,0x6acc,0x6ad0,0x6ad1,0x6ad3,0x6ad6,0x6ada,0x6adf,0x6ae2,0x6ae2,0x6ae4,0x6ae4,0x6ae7,0x6ae8,0x6aea,0x6aea,0x6aec,0x6aec,0x6af0,0x6af3,0x6af8,0x6af8,0x6afa,0x6afd,0x6b02,0x6b07,0x6b09,0x6b0b,0x6b0f,0x6b12,0x6b16,0x6b17,0x6b1b,0x6b1b,0x6b1d,0x6b21,0x6b23,0x6b24,0x6b27,0x6b28,0x6b2b,0x6b2c,0x6b2f,0x6b2f,0x6b32,0x6b32,0x6b35,0x6b3b,0x6b3d,0x6b3f,0x6b43,0x6b43,0x6b46,0x6b47,0x6b49,0x6b4a,0x6b4c,0x6b4e,0x6b50,0x6b50,0x6b52,0x6b54,0x6b56,0x6b56,0x6b58,0x6b59,0x6b5b,0x6b5b,0x6b5d,0x6b5d,0x6b5f,0x6b67,0x6b69,0x6b6c,0x6b6e,0x6b70,0x6b72,0x6b75,0x6b77,0x6b7b,0x6b7d,0x6b86,0x6b89,0x6b8b,0x6b8d,0x6b8d,0x6b95,0x6b98,0x6b9b,0x6b9b,0x6b9e,0x6ba0,0x6ba2,0x6ba4,0x6ba8,0x6bb5,0x6bb7,0x6bc0,0x6bc3,0x6bc9,0x6bcb,0x6bcf,0x6bd2,0x6bd4,0x6bd6,0x6bd8,0x6bda,0x6bdb,0x6bdf,0x6bdf,0x6be1,0x6be1,0x6be3,0x6be3,0x6be6,0x6be7,0x6beb,0x6bec,0x6bee,0x6bef,0x6bf1,0x6bf1,0x6bf3,0x6bf3,0x6bf7,0x6bf7,0x6bf9,0x6bf9,0x6bff,0x6bff,0x6c02,0x6c02,0x6c04,0x6c05,0x6c08,0x6c0a,0x6c0d,0x6c14,0x6c17,0x6c17,0x6c19,0x6c19,0x6c1b,0x6c1b,0x6c1f,0x6c1f,0x6c23,0x6c24,0x6c26,0x6c28,0x6c2c,0x6c2c,0x6c2e,0x6c2e,0x6c33,0x6c38,0x6c3a,0x6c3b,0x6c3e,0x6c42,0x6c4a,0x6c4b,0x6c4d,0x6c50,0x6c52,0x6c52,0x6c54,0x6c55,0x6c57,0x6c57,0x6c59,0x6c60,0x6c62,0x6c62,0x6c67,0x6c68,0x6c6a,0x6c6b,0x6c6d,0x6c6d,0x6c6f,0x6c70,0x6c72,0x6c74,0x6c76,0x6c76,0x6c78,0x6c7b,0x6c7d,0x6c7e,0x6c81,0x6c89,0x6c8c,0x6c8d,0x6c90,0x6c90,0x6c92,0x6c9c,0x6c9f,0x6c9f,0x6ca1,0x6ca2,0x6caa,0x6cae,0x6cb0,0x6cb4,0x6cb8,0x6cbf,0x6cc1,0x6cc2,0x6cc4,0x6cc6,0x6cc9,0x6cca,0x6ccc,0x6ccd,0x6ccf,0x6cd7,0x6cd9,0x6cdd,0x6ce0,0x6ce3,0x6ce5,0x6ce5,0x6ce7,0x6cf4,0x6cfb,0x6cfb,0x6d00,0x6d01,0x6d04,0x6d04,0x6d07,0x6d07,0x6d0a,0x6d0c,0x6d0e,0x6d0f,0x6d11,0x6d13,0x6d17,0x6d17,0x6d19,0x6d1b,0x6d1e,0x6d1f,0x6d24,0x6d2b,0x6d2e,0x6d2f,0x6d31,0x6d36,0x6d38,0x6d39,0x6d3b,0x6d3f,0x6d41,0x6d41,0x6d44,0x6d45,0x6d57,0x6d5c,0x6d5e,0x6d61,0x6d63,0x6d67,0x6d69,0x6d6a,0x6d6c,0x6d6c,0x6d6e,0x6d70,0x6d74,0x6d74,0x6d77,0x6d79,0x6d7c,0x6d7c,0x6d80,0x6d82,0x6d85,0x6d85,0x6d87,0x6d8a,0x6d8c,0x6d8e,0x6d91,0x6d99,0x6d9b,0x6d9c,0x6daa,0x6dac,0x6dae,0x6daf,0x6db2,0x6db2,0x6db4,0x6db5,0x6db7,0x6db9,0x6dbc,0x6dbd,0x6dbf,0x6dc0,0x6dc2,0x6dc2,0x6dc4,0x6dc8,0x6dca,0x6dcc,0x6dce,0x6dd2,0x6dd5,0x6dd6,0x6dd8,0x6ddb,0x6ddd,0x6de2,0x6de4,0x6de6,0x6de8,0x6dec,0x6dee,0x6dfc,0x6e00,0x6e00,0x6e04,0x6e05,0x6e07,0x6e0b,0x6e13,0x6e13,0x6e15,0x6e15,0x6e17,0x6e17,0x6e19,0x6e1b,0x6e1d,0x6e27,0x6e29,0x6e29,0x6e2b,0x6e2f,0x6e32,0x6e32,0x6e34,0x6e34,0x6e36,0x6e36,0x6e38,0x6e3c,0x6e3e,0x6e3e,0x6e42,0x6e45,0x6e48,0x6e4f,0x6e51,0x6e54,0x6e56,0x6e58,0x6e5b,0x6e5f,0x6e62,0x6e63,0x6e67,0x6e68,0x6e6b,0x6e6b,0x6e6e,0x6e6f,0x6e72,0x6e73,0x6e76,0x6e76,0x6e7b,0x6e7b,0x6e7d,0x6e80,0x6e82,0x6e82,0x6e89,0x6e89,0x6e8c,0x6e8d,0x6e8f,0x6e90,0x6e93,0x6e93,0x6e96,0x6e96,0x6e98,0x6e99,0x6e9c,0x6e9d,0x6e9f,0x6ea0,0x6ea2,0x6ea2,0x6ea5,0x6ea5,0x6ea7,0x6ea7,0x6eaa,0x6eab,0x6ead,0x6eaf,0x6eb1,0x6eb4,0x6eb6,0x6eb7,0x6eba,0x6ebd,0x6ebf,0x6ec5,0x6ec7,0x6ecf,0x6ed1,0x6ed1,0x6ed3,0x6ed5,0x6ed9,0x6edb,0x6edd,0x6ede,0x6ee6,0x6ee6,0x6eeb,0x6eef,0x6ef2,0x6ef2,0x6ef4,0x6ef4,0x6ef7,0x6ef9,0x6efb,0x6efb,0x6efd,0x6eff,0x6f01,0x6f02,0x6f04,0x6f04,0x6f06,0x6f06,0x6f08,0x6f0a,0x6f0c,0x6f0d,0x6f0f,0x6f11,0x6f13,0x6f16,0x6f18,0x6f18,0x6f1a,0x6f1b,0x6f20,0x6f20,0x6f22,0x6f23,0x6f25,0x6f26,0x6f29,0x6f2d,0x6f2f,0x6f33,0x6f35,0x6f36,0x6f38,0x6f38,0x6f3b,0x6f3c,0x6f3e,0x6f3f,0x6f41,0x6f41,0x6f45,0x6f45,0x6f4f,0x6f4f,0x6f51,0x6f54,0x6f57,0x6f62,0x6f64,0x6f64,0x6f66,0x6f66,0x6f68,0x6f68,0x6f6c,0x6f70,0x6f74,0x6f74,0x6f78,0x6f78,0x6f7a,0x6f7a,0x6f7c,0x6f7e,0x6f80,0x6f84,0x6f86,0x6f88,0x6f8b,0x6f8e,0x6f90,0x6f94,0x6f96,0x6f98,0x6f9a,0x6f9a,0x6f9d,0x6f9d,0x6f9f,0x6fa1,0x6fa3,0x6fa8,0x6faa,0x6faa,0x6fae,0x6fb1,0x6fb3,0x6fb3,0x6fb5,0x6fb7,0x6fb9,0x6fb9,0x6fbc,0x6fbc,0x6fbe,0x6fbe,0x6fc0,0x6fc3,0x6fc5,0x6fca,0x6fd4,0x6fd5,0x6fd8,0x6fd8,0x6fda,0x6fdb,0x6fde,0x6fe1,0x6fe4,0x6fe4,0x6fe8,0x6fe9,0x6feb,0x6fec,0x6fee,0x6ff1,0x6ff3,0x6ff3,0x6ff5,0x6ff6,0x6ff9,0x6ffa,0x6ffc,0x6ffe,0x7000,0x7001,0x7005,0x7007,0x7009,0x700b,0x700d,0x700d,0x700f,0x700f,0x7011,0x7011,0x7015,0x7015,0x7017,0x7018,0x701a,0x701b,0x701d,0x7020,0x7023,0x7023,0x7026,0x7028,0x702c,0x702c,0x702f,0x7030,0x7032,0x7032,0x7034,0x7034,0x7037,0x7037,0x7039,0x703a,0x703c,0x703c,0x703e,0x703e,0x7043,0x7044,0x7047,0x704c,0x704e,0x704e,0x7051,0x7051,0x7054,0x7055,0x7058,0x7058,0x705d,0x705e,0x7063,0x7065,0x7069,0x7069,0x706b,0x706c,0x706e,0x7070,0x7075,0x7076,0x7078,0x7078,0x707c,0x707e,0x7081,0x7081,0x7085,0x7086,0x7089,0x708a,0x708e,0x708e,0x7092,0x7092,0x7094,0x7099,0x709b,0x709b,0x709f,0x709f,0x70a4,0x70a4,0x70ab,0x70b1,0x70b3,0x70b4,0x70b7,0x70bb,0x70c8,0x70c8,0x70ca,0x70cb,0x70cf,0x70cf,0x70d1,0x70d1,0x70d3,0x70d6,0x70d8,0x70d9,0x70dc,0x70dd,0x70df,0x70df,0x70e4,0x70e4,0x70ec,0x70ec,0x70f1,0x70f1,0x70f9,0x70fa,0x70fd,0x70fd,0x7103,0x7109,0x710b,0x710c,0x710f,0x710f,0x7114,0x7114,0x7119,0x711a,0x711c,0x711c,0x711e,0x711e,0x7120,0x7121,0x7126,0x7126,0x712b,0x712b,0x712d,0x7131,0x7136,0x7136,0x7138,0x7138,0x713c,0x713c,0x7141,0x7141,0x7145,0x7147,0x7149,0x714c,0x714e,0x714e,0x7150,0x7153,0x7155,0x7157,0x7159,0x715a,0x715c,0x715c,0x715e,0x715e,0x7160,0x7160,0x7162,0x7162,0x7164,0x7169,0x716c,0x716c,0x716e,0x716e,0x7179,0x7179,0x717d,0x717d,0x7180,0x7180,0x7184,0x7185,0x7187,0x7188,0x718a,0x718a,0x718c,0x718c,0x718f,0x718f,0x7192,0x7192,0x7194,0x7196,0x7199,0x719b,0x719f,0x71a0,0x71a2,0x71a2,0x71a8,0x71a8,0x71ac,0x71ac,0x71ae,0x71b3,0x71b9,0x71ba,0x71be,0x71c1,0x71c3,0x71c4,0x71c8,0x71c9,0x71cb,0x71cc,0x71ce,0x71ce,0x71d0,0x71d0,0x71d2,0x71d7,0x71d9,0x71da,0x71dc,0x71dc,0x71df,0x71e0,0x71e5,0x71e7,0x71ec,0x71ee,0x71f4,0x71f5,0x71f8,0x71f9,0x71fb,0x71fc,0x71fe,0x7200,0x7206,0x7209,0x720d,0x720d,0x7210,0x7210,0x7213,0x7213,0x7215,0x7215,0x7217,0x7217,0x721a,0x721b,0x721d,0x721d,0x721f,0x721f,0x7224,0x7224,0x7228,0x7228,0x722a,0x722d,0x722f,0x7230,0x7232,0x7232,0x7234,0x7236,0x7238,0x7243,0x7245,0x7248,0x724b,0x724c,0x724e,0x7250,0x7252,0x7253,0x7255,0x7263,0x7267,0x7269,0x726b,0x726b,0x726e,0x726f,0x7271,0x7272,0x7274,0x7274,0x7277,0x7279,0x727b,0x7282,0x7284,0x7284,0x7287,0x7287,0x7289,0x7289,0x728d,0x728e,0x7292,0x7293,0x7296,0x7296,0x729b,0x729b,0x72a0,0x72a0,0x72a2,0x72a2,0x72a7,0x72a8,0x72ac,0x72b2,0x72b4,0x72b4,0x72b6,0x72b6,0x72b9,0x72b9,0x72be,0x72be,0x72c0,0x72c4,0x72c6,0x72c7,0x72c9,0x72c9,0x72cc,0x72cc,0x72ce,0x72ce,0x72d0,0x72d0,0x72d2,0x72d2,0x72d5,0x72d9,0x72db,0x72db,0x72df,0x72e2,0x72e5,0x72e5,0x72e9,0x72e9,0x72ec,0x72ed,0x72f3,0x72f4,0x72f7,0x72fe,0x7302,0x7302,0x7304,0x7305,0x7307,0x7307,0x730a,0x730b,0x730d,0x730d,0x7312,0x7313,0x7316,0x7319,0x731b,0x731f,0x7322,0x7322,0x7324,0x7325,0x7327,0x732c,0x732e,0x732f,0x7331,0x7337,0x7339,0x733b,0x733d,0x733f,0x7343,0x7345,0x734d,0x7350,0x7352,0x7352,0x7356,0x7358,0x735d,0x7360,0x7363,0x7363,0x7366,0x736c,0x736e,0x7372,0x7375,0x7375,0x7377,0x737c,0x7380,0x7381,0x7383,0x7387,0x7389,0x738b,0x738e,0x738e,0x7390,0x7390,0x7393,0x7398,0x739c,0x739c,0x739e,0x73a0,0x73a2,0x73a2,0x73a5,0x73a6,0x73a8,0x73ab,0x73ad,0x73ad,0x73b2,0x73b3,0x73b5,0x73b5,0x73b7,0x73b7,0x73b9,0x73bd,0x73bf,0x73c0,0x73c2,0x73c2,0x73c5,0x73c6,0x73c8,0x73cf,0x73d2,0x73d3,0x73d6,0x73d6,0x73d9,0x73d9,0x73dd,0x73de,0x73e0,0x73e1,0x73e3,0x73e7,0x73e9,0x73ea,0x73ed,0x73ee,0x73f1,0x73f1,0x73f4,0x73f5,0x73f7,0x73fb,0x73fd,0x7401,0x7403,0x7407,0x7409,0x740a,0x7411,0x7411,0x7413,0x7413,0x741a,0x741b,0x7421,0x7422,0x7424,0x7426,0x7428,0x7436,0x7439,0x743a,0x743f,0x7441,0x7443,0x7444,0x7446,0x7447,0x744b,0x744b,0x744d,0x744d,0x7451,0x7453,0x7455,0x7455,0x7457,0x7457,0x7459,0x7460,0x7462,0x7464,0x7466,0x746b,0x746d,0x7473,0x7476,0x7476,0x747e,0x747e,0x7480,0x7481,0x7483,0x7483,0x7485,0x7489,0x748b,0x748b,0x748f,0x7492,0x7497,0x749a,0x749c,0x749c,0x749e,0x74a3,0x74a5,0x74ab,0x74ae,0x74b2,0x74b5,0x74b5,0x74b9,0x74bb,0x74bd,0x74bd,0x74bf,0x74bf,0x74c8,0x74ca,0x74cc,0x74cc,0x74cf,0x74d0,0x74d3,0x74d4,0x74d6,0x74d6,0x74d8,0x74d8,0x74da,0x74dc,0x74de,0x74e0,0x74e2,0x74e4,0x74e6,0x74eb,0x74ee,0x74f2,0x74f4,0x74f4,0x74f6,0x74f8,0x74fa,0x74fc,0x74ff,0x74ff,0x7501,0x7501,0x7503,0x7506,0x750c,0x750e,0x7511,0x7513,0x7515,0x7518,0x751a,0x751a,0x751c,0x751c,0x751e,0x752c,0x752f,0x7533,0x7536,0x7540,0x7543,0x7544,0x7546,0x7552,0x7554,0x7554,0x7557,0x7557,0x7559,0x7562,0x7564,0x7567,0x7569,0x756d,0x756f,0x757f,0x7581,0x7582,0x7585,0x7587,0x7589,0x758c,0x758e,0x7595,0x7599,0x759a,0x759c,0x759d,0x75a2,0x75a5,0x75ab,0x75ab,0x75b0,0x75b5,0x75b7,0x75ba,0x75bc,0x75c7,0x75ca,0x75ca,0x75cc,0x75cf,0x75d2,0x75d5,0x75d7,0x75d9,0x75db,0x75e4,0x75e7,0x75e7,0x75e9,0x75e9,0x75ec,0x75ec,0x75ee,0x75f4,0x75f9,0x75fa,0x75fc,0x75fc,0x75fe,0x7604,0x7607,0x760d,0x760f,0x760f,0x7612,0x7613,0x7615,0x7616,0x7618,0x7619,0x761b,0x7629,0x762d,0x762d,0x7630,0x7630,0x7632,0x7635,0x7638,0x763c,0x7640,0x764c,0x764e,0x764e,0x7652,0x7652,0x7655,0x7656,0x7658,0x7659,0x765c,0x765c,0x765f,0x765f,0x7661,0x7662,0x7664,0x7665,0x7667,0x766a,0x766c,0x7672,0x7674,0x7674,0x7676,0x7676,0x7678,0x7678,0x767a,0x767e,0x7680,0x7688,0x768b,0x768e,0x7690,0x7690,0x7693,0x7693,0x7695,0x7696,0x7699,0x76a8,0x76aa,0x76aa,0x76ad,0x76b0,0x76b4,0x76b4,0x76b6,0x76ba,0x76bd,0x76bd,0x76bf,0x76bf,0x76c1,0x76c3,0x76c5,0x76c6,0x76c8,0x76ce,0x76d2,0x76d2,0x76d4,0x76d4,0x76d6,0x76d7,0x76d9,0x76d9,0x76db,0x76dc,0x76de,0x76e1,0x76e3,0x76e8,0x76ea,0x76ec,0x76ee,0x76ee,0x76f0,0x76f2,0x76f4,0x76f4,0x76f6,0x76f6,0x76f8,0x76f9,0x76fb,0x76fc,0x76fe,0x76fe,0x7700,0x7701,0x7704,0x7704,0x7706,0x770c,0x770e,0x770e,0x7712,0x7712,0x7714,0x7715,0x7717,0x7717,0x7719,0x771c,0x771e,0x7720,0x7722,0x7722,0x7724,0x7726,0x7728,0x7729,0x772d,0x772f,0x7734,0x773a,0x773c,0x773e,0x7740,0x7740,0x7742,0x7742,0x7745,0x7747,0x774a,0x774a,0x774d,0x774f,0x7752,0x7752,0x7756,0x7758,0x775a,0x775c,0x775e,0x7768,0x776a,0x776c,0x7770,0x7770,0x7772,0x7774,0x7779,0x777a,0x777c,0x7780,0x7784,0x7784,0x778b,0x778e,0x7791,0x7791,0x7794,0x7796,0x779a,0x779a,0x779e,0x77a0,0x77a2,0x77a2,0x77a4,0x77a5,0x77a7,0x77a7,0x77a9,0x77aa,0x77ac,0x77b1,0x77b3,0x77b3,0x77b5,0x77b7,0x77b9,0x77b9,0x77bb,0x77bf,0x77c3,0x77c3,0x77c7,0x77c7,0x77c9,0x77c9,0x77cd,0x77cd,0x77d1,0x77d2,0x77d5,0x77d5,0x77d7,0x77d7,0x77d9,0x77dc,0x77de,0x77e0,0x77e2,0x77e7,0x77e9,0x77ea,0x77ec,0x77f1,0x77f3,0x77f4,0x77f8,0x77f8,0x77fb,0x77fc,0x7802,0x7802,0x7805,0x7806,0x7809,0x7809,0x780c,0x780e,0x7811,0x7812,0x7814,0x7815,0x7819,0x7819,0x781d,0x781d,0x7820,0x7823,0x7825,0x7827,0x782c,0x782e,0x7830,0x7830,0x7832,0x7832,0x7834,0x7835,0x7837,0x7837,0x783a,0x783a,0x783f,0x783f,0x7843,0x7845,0x7847,0x7848,0x784c,0x784c,0x784e,0x784f,0x7851,0x7852,0x785c,0x785e,0x7860,0x7861,0x7863,0x7864,0x7868,0x7868,0x786a,0x786c,0x786e,0x786f,0x7872,0x7872,0x7874,0x7874,0x787a,0x787a,0x787c,0x787c,0x787e,0x787e,0x7881,0x7881,0x7886,0x7887,0x788a,0x788a,0x788c,0x788f,0x7891,0x7891,0x7893,0x7895,0x7897,0x7898,0x789a,0x789a,0x789d,0x789f,0x78a1,0x78a1,0x78a3,0x78a4,0x78a7,0x78aa,0x78ac,0x78ad,0x78af,0x78b3,0x78b5,0x78b5,0x78ba,0x78bf,0x78c1,0x78c1,0x78c5,0x78cc,0x78ce,0x78ce,0x78d0,0x78d6,0x78da,0x78db,0x78df,0x78e1,0x78e4,0x78e4,0x78e6,0x78e8,0x78ea,0x78ea,0x78ec,0x78ec,0x78ef,0x78ef,0x78f2,0x78f4,0x78f6,0x78f7,0x78f9,0x78fb,0x78fd,0x7901,0x7906,0x7907,0x790c,0x790c,0x790e,0x790e,0x7910,0x7912,0x7919,0x791c,0x791e,0x7920,0x7925,0x792e,0x7930,0x7931,0x7934,0x7935,0x793a,0x7942,0x7944,0x794b,0x794f,0x7951,0x7953,0x7958,0x795a,0x7960,0x7962,0x7962,0x7965,0x7965,0x7967,0x7969,0x796b,0x796b,0x796d,0x796d,0x7972,0x7972,0x7977,0x7977,0x7979,0x797c,0x797e,0x7981,0x7984,0x7985,0x798a,0x798f,0x7991,0x7991,0x7993,0x7996,0x7998,0x7998,0x799b,0x799d,0x79a1,0x79a1,0x79a6,0x79ab,0x79ae,0x79b1,0x79b3,0x79b4,0x79b8,0x79bb,0x79bd,0x79c2,0x79c4,0x79c4,0x79c7,0x79cd,0x79cf,0x79cf,0x79d1,0x79d2,0x79d4,0x79d6,0x79d8,0x79d8,0x79da,0x79da,0x79dd,0x79e7,0x79e9,0x79ed,0x79f0,0x79f1,0x79f8,0x79f8,0x79fb,0x79fc,0x7a00,0x7a00,0x7a02,0x7a03,0x7a05,0x7a05,0x7a07,0x7a0e,0x7a11,0x7a11,0x7a14,0x7a15,0x7a17,0x7a1c,0x7a1e,0x7a21,0x7a27,0x7a27,0x7a2b,0x7a2b,0x7a2d,0x7a32,0x7a34,0x7a35,0x7a37,0x7a40,0x7a42,0x7a49,0x7a4c,0x7a50,0x7a55,0x7a57,0x7a59,0x7a59,0x7a5c,0x7a5d,0x7a5f,0x7a63,0x7a65,0x7a65,0x7a67,0x7a67,0x7a69,0x7a6b,0x7a6d,0x7a6d,0x7a70,0x7a70,0x7a74,0x7a76,0x7a78,0x7a7a,0x7a7d,0x7a86,0x7a88,0x7a88,0x7a8a,0x7a8b,0x7a90,0x7a98,0x7a9e,0x7aa0,0x7aa3,0x7aa3,0x7aa9,0x7aaa,0x7aac,0x7aac,0x7aae,0x7ab0,0x7ab3,0x7ab3,0x7ab5,0x7ab6,0x7ab9,0x7abf,0x7ac3,0x7acf,0x7ad1,0x7ad3,0x7ad5,0x7ad5,0x7ad9,0x7add,0x7adf,0x7ae3,0x7ae5,0x7aed,0x7aef,0x7af1,0x7af4,0x7af4,0x7af6,0x7af6,0x7af8,0x7afb,0x7afd,0x7aff,0x7b02,0x7b02,0x7b04,0x7b04,0x7b06,0x7b08,0x7b0a,0x7b0b,0x7b0f,0x7b0f,0x7b11,0x7b12,0x7b14,0x7b14,0x7b18,0x7b19,0x7b1b,0x7b1b,0x7b1e,0x7b20,0x7b23,0x7b23,0x7b25,0x7b31,0x7b33,0x7b36,0x7b39,0x7b39,0x7b3b,0x7b3b,0x7b3d,0x7b3d,0x7b3f,0x7b41,0x7b45,0x7b49,0x7b4b,0x7b56,0x7b5d,0x7b5d,0x7b60,0x7b60,0x7b64,0x7b67,0x7b69,0x7b6a,0x7b6c,0x7b75,0x7b77,0x7b77,0x7b79,0x7b7a,0x7b7f,0x7b7f,0x7b84,0x7b84,0x7b86,0x7b87,0x7b89,0x7b89,0x7b8b,0x7b8b,0x7b8d,0x7b92,0x7b94,0x7ba1,0x7ba5,0x7ba5,0x7baa,0x7baa,0x7bac,0x7bad,0x7baf,0x7bb2,0x7bb4,0x7bb6,0x7bb8,0x7bb8,0x7bba,0x7bbd,0x7bc0,0x7bc2,0x7bc4,0x7bcc,0x7bcf,0x7bcf,0x7bd4,0x7bd4,0x7bd6,0x7bd7,0x7bd9,0x7bdb,0x7bdd,0x7bdd,0x7be0,0x7be0,0x7be4,0x7be6,0x7be8,0x7bea,0x7bed,0x7bed,0x7bf0,0x7bf0,0x7bf2,0x7bfa,0x7bfc,0x7bfc,0x7bfe,0x7bfe,0x7c00,0x7c04,0x7c06,0x7c07,0x7c09,0x7c09,0x7c0b,0x7c0f,0x7c11,0x7c14,0x7c17,0x7c17,0x7c19,0x7c19,0x7c1b,0x7c1b,0x7c1e,0x7c21,0x7c23,0x7c23,0x7c25,0x7c28,0x7c2a,0x7c2c,0x7c2f,0x7c2f,0x7c31,0x7c31,0x7c33,0x7c34,0x7c36,0x7c3a,0x7c3d,0x7c40,0x7c42,0x7c43,0x7c45,0x7c46,0x7c4a,0x7c4a,0x7c4c,0x7c4d,0x7c4f,0x7c61,0x7c63,0x7c65,0x7c67,0x7c67,0x7c69,0x7c69,0x7c6c,0x7c70,0x7c72,0x7c73,0x7c75,0x7c75,0x7c79,0x7c79,0x7c7b,0x7c7e,0x7c81,0x7c83,0x7c86,0x7c87,0x7c89,0x7c89,0x7c8b,0x7c8b,0x7c8d,0x7c8d,0x7c8f,0x7c90,0x7c92,0x7c92,0x7c94,0x7c95,0x7c97,0x7c98,0x7c9b,0x7c9b,0x7c9e,0x7ca2,0x7ca4,0x7ca8,0x7cab,0x7cab,0x7cad,0x7cae,0x7cb0,0x7cb3,0x7cb6,0x7cb7,0x7cb9,0x7cc0,0x7cc2,0x7cc2,0x7cc4,0x7cc5,0x7cc7,0x7cca,0x7ccd,0x7ccf,0x7cd2,0x7cda,0x7cdc,0x7ce0,0x7ce2,0x7ce2,0x7ce6,0x7ce7,0x7ce9,0x7ce9,0x7ceb,0x7ceb,0x7cef,0x7cef,0x7cf2,0x7cf2,0x7cf4,0x7cf6,0x7cf8,0x7cfb,0x7cfe,0x7cfe,0x7d00,0x7d00,0x7d02,0x7d0b,0x7d0d,0x7d0d,0x7d0f,0x7d1e,0x7d20,0x7d23,0x7d26,0x7d26,0x7d2a,0x7d33,0x7d35,0x7d35,0x7d39,0x7d3a,0x7d3c,0x7d48,0x7d4b,0x7d51,0x7d53,0x7d53,0x7d55,0x7d57,0x7d59,0x7d5e,0x7d61,0x7d63,0x7d65,0x7d68,0x7d6a,0x7d6a,0x7d6e,0x7d6e,0x7d70,0x7d73,0x7d75,0x7d76,0x7d78,0x7d7b,0x7d7d,0x7d7d,0x7d7f,0x7d7f,0x7d81,0x7d83,0x7d85,0x7d86,0x7d88,0x7d89,0x7d8b,0x7d8d,0x7d8f,0x7d8f,0x7d91,0x7d91,0x7d93,0x7d93,0x7d96,0x7d97,0x7d99,0x7da0,0x7da2,0x7da3,0x7da6,0x7da7,0x7daa,0x7dbb,0x7dbd,0x7dc0,0x7dc2,0x7dc7,0x7dca,0x7dd2,0x7dd5,0x7dda,0x7ddc,0x7dde,0x7de0,0x7de6,0x7de8,0x7ded,0x7def,0x7def,0x7df1,0x7df2,0x7df4,0x7df6,0x7df9,0x7dfb,0x7e00,0x7e01,0x7e04,0x7e05,0x7e08,0x7e0b,0x7e10,0x7e12,0x7e15,0x7e15,0x7e17,0x7e17,0x7e1b,0x7e23,0x7e26,0x7e28,0x7e2b,0x7e2f,0x7e31,0x7e33,0x7e35,0x7e37,0x7e39,0x7e3b,0x7e3d,0x7e3f,0x7e41,0x7e41,0x7e43,0x7e48,0x7e4a,0x7e4b,0x7e4d,0x7e4e,0x7e50,0x7e50,0x7e52,0x7e52,0x7e54,0x7e56,0x7e58,0x7e5a,0x7e5d,0x7e5f,0x7e61,0x7e62,0x7e65,0x7e67,0x7e69,0x7e6b,0x7e6d,0x7e70,0x7e73,0x7e73,0x7e75,0x7e75,0x7e78,0x7e79,0x7e7b,0x7e7f,0x7e81,0x7e83,0x7e86,0x7e8a,0x7e8c,0x7e96,0x7e98,0x7e98,0x7e9a,0x7e9f,0x7f36,0x7f36,0x7f38,0x7f38,0x7f3a,0x7f3f,0x7f43,0x7f45,0x7f47,0x7f47,0x7f4c,0x7f55,0x7f58,0x7f58,0x7f5b,0x7f5d,0x7f5f,0x7f61,0x7f63,0x7f6b,0x7f6d,0x7f6e,0x7f70,0x7f72,0x7f75,0x7f75,0x7f77,0x7f79,0x7f7d,0x7f80,0x7f82,0x7f83,0x7f85,0x7f88,0x7f8a,0x7f91,0x7f94,0x7f94,0x7f96,0x7f97,0x7f9a,0x7f9a,0x7f9c,0x7f9e,0x7fa1,0x7fa4,0x7fa6,0x7fa6,0x7fa8,0x7faa,0x7fad,0x7faf,0x7fb2,0x7fb2,0x7fb4,0x7fb4,0x7fb6,0x7fb6,0x7fb8,0x7fb9,0x7fbc,0x7fbd,0x7fbf,0x7fc1,0x7fc3,0x7fc3,0x7fc5,0x7fc6,0x7fc8,0x7fc8,0x7fca,0x7fca,0x7fcc,0x7fcc,0x7fce,0x7fcf,0x7fd2,0x7fd2,0x7fd4,0x7fd5,0x7fdb,0x7fdb,0x7fdf,0x7fe1,0x7fe3,0x7fe3,0x7fe5,0x7fe6,0x7fe8,0x7fe9,0x7feb,0x7fec,0x7fee,0x7ff0,0x7ff2,0x7ff3,0x7ff9,0x8008,0x800a,0x8019,0x801c,0x8021,0x8024,0x8024,0x8026,0x8026,0x8028,0x8028,0x802c,0x802c,0x802e,0x802e,0x8030,0x8030,0x8033,0x8037,0x8039,0x8040,0x8043,0x8044,0x8046,0x8046,0x804a,0x804a,0x8052,0x8052,0x8056,0x8056,0x8058,0x8058,0x805a,0x805a,0x805e,0x8062,0x8064,0x8064,0x8066,0x8066,0x8068,0x8068,0x806d,0x806d,0x806f,0x8077,0x8079,0x8079,0x807b,0x807b,0x807d,0x8081,0x8084,0x8089,0x808b,0x808c,0x808e,0x808e,0x8093,0x8093,0x8096,0x8096,0x8098,0x809e,0x80a1,0x80a2,0x80a4,0x80a7,0x80a9,0x80ad,0x80af,0x80af,0x80b1,0x80b2,0x80b4,0x80b4,0x80b8,0x80ba,0x80c3,0x80c6,0x80c8,0x80c8,0x80ca,0x80ca,0x80cc,0x80cf,0x80d2,0x80d2,0x80d4,0x80db,0x80dd,0x80de,0x80e0,0x80e1,0x80e4,0x80e6,0x80ed,0x80fe,0x8101,0x8103,0x8105,0x810b,0x810d,0x810d,0x8116,0x8118,0x811a,0x811c,0x811e,0x811e,0x8120,0x8120,0x8123,0x8124,0x8127,0x8127,0x8129,0x8129,0x812b,0x812c,0x812f,0x8131,0x8133,0x8133,0x8135,0x8135,0x8139,0x813a,0x813c,0x813e,0x8141,0x8141,0x8145,0x8147,0x814a,0x814c,0x814e,0x814e,0x8150,0x8155,0x8157,0x8157,0x815f,0x8161,0x8165,0x8169,0x816b,0x816b,0x816d,0x8171,0x8173,0x8174,0x8177,0x817a,0x817f,0x8186,0x8188,0x8188,0x818a,0x818b,0x818e,0x8190,0x8193,0x8193,0x8195,0x8196,0x8198,0x8198,0x819a,0x819e,0x81a0,0x81a0,0x81a2,0x81a4,0x81a8,0x81a9,0x81ae,0x81ae,0x81b0,0x81b0,0x81b2,0x81b5,0x81b8,0x81b8,0x81ba,0x81bb,0x81bd,0x81c3,0x81c5,0x81c6,0x81c8,0x81cb,0x81cd,0x81cf,0x81d1,0x81d1,0x81d3,0x81d3,0x81d5,0x81db,0x81dd,0x81e1,0x81e3,0x81e5,0x81e7,0x81e8,0x81ea,0x81ed,0x81ef,0x81f6,0x81f8,0x8205,0x8207,0x8210,0x8212,0x8214,0x8216,0x821f,0x8221,0x8222,0x8228,0x822c,0x822e,0x822e,0x8232,0x823a,0x823c,0x823c,0x8240,0x8240,0x8243,0x8247,0x8249,0x8249,0x824b,0x824b,0x824e,0x824f,0x8251,0x8251,0x8256,0x825a,0x825c,0x825d,0x825f,0x8260,0x8262,0x8264,0x8266,0x8268,0x826a,0x826b,0x826d,0x826f,0x8271,0x8272,0x8274,0x8274,0x8276,0x8279,0x827b,0x827b,0x827d,0x8281,0x8283,0x8284,0x8287,0x8287,0x8289,0x828b,0x828d,0x828e,0x8291,0x8294,0x8296,0x8296,0x8298,0x829b,0x829d,0x829d,0x829f,0x82a1,0x82a3,0x82b4,0x82b7,0x82bf,0x82c5,0x82c6,0x82d0,0x82d5,0x82d7,0x82d7,0x82d9,0x82dc,0x82de,0x82e8,0x82ea,0x82eb,0x82ed,0x82ed,0x82ef,0x82ef,0x82f1,0x82f1,0x82f3,0x82f4,0x82f6,0x82f7,0x82f9,0x82fb,0x82fd,0x82fe,0x8300,0x830c,0x830e,0x830e,0x8316,0x8318,0x831b,0x831f,0x8321,0x8323,0x8328,0x8328,0x832b,0x833a,0x833c,0x833d,0x8340,0x8340,0x8342,0x8347,0x8349,0x834a,0x834d,0x8358,0x835a,0x835a,0x8362,0x8363,0x8370,0x8370,0x8373,0x8373,0x8375,0x8375,0x8377,0x8378,0x837b,0x837d,0x837f,0x8380,0x8382,0x8382,0x8384,0x8387,0x8389,0x838a,0x838d,0x838e,0x8392,0x8396,0x8398,0x83a0,0x83a2,0x83a2,0x83a6,0x83ad,0x83b1,0x83b1,0x83b5,0x83b5,0x83bd,0x83c1,0x83c5,0x83c5,0x83c7,0x83c7,0x83c9,0x83ca,0x83cc,0x83cc,0x83ce,0x83d1,0x83d3,0x83d4,0x83d6,0x83d6,0x83d8,0x83d8,0x83dc,0x83dd,0x83df,0x83e1,0x83e5,0x83e5,0x83e8,0x83eb,0x83ef,0x83f2,0x83f4,0x83f4,0x83f6,0x83f9,0x83fb,0x83fd,0x8401,0x8401,0x8403,0x8404,0x8406,0x8407,0x840a,0x840f,0x8411,0x8411,0x8413,0x8413,0x8415,0x8415,0x8417,0x8417,0x8419,0x8419,0x8420,0x8420,0x8422,0x8422,0x8429,0x842a,0x842c,0x842c,0x842f,0x842f,0x8431,0x8431,0x8435,0x8435,0x8438,0x8439,0x843c,0x843d,0x8445,0x844a,0x844d,0x844f,0x8451,0x8452,0x8456,0x845c,0x845f,0x8467,0x8469,0x8471,0x8473,0x847a,0x847c,0x847d,0x8481,0x8482,0x8484,0x8485,0x848b,0x848b,0x8490,0x8490,0x8492,0x8495,0x8497,0x8497,0x8499,0x8499,0x849c,0x849c,0x849e,0x849f,0x84a1,0x84a1,0x84a6,0x84a6,0x84a8,0x84aa,0x84ad,0x84ad,0x84af,0x84af,0x84b1,0x84b2,0x84b4,0x84b4,0x84b8,0x84c2,0x84c4,0x84c4,0x84c6,0x84d1,0x84d3,0x84d3,0x84d6,0x84d6,0x84d9,0x84da,0x84dc,0x84dc,0x84e7,0x84e7,0x84ea,0x84ea,0x84ec,0x84ec,0x84ee,0x84f2,0x84f4,0x84f4,0x84f7,0x84f7,0x84fa,0x84fd,0x84ff,0x8500,0x8502,0x8503,0x8506,0x8507,0x850c,0x850c,0x850e,0x850e,0x8510,0x8511,0x8513,0x8515,0x8517,0x8518,0x851a,0x851c,0x851e,0x851f,0x8521,0x8527,0x852a,0x852d,0x852f,0x852f,0x8532,0x8536,0x853d,0x8541,0x8543,0x8543,0x8546,0x8546,0x8548,0x854b,0x854e,0x8553,0x8555,0x855a,0x855c,0x8564,0x8568,0x856b,0x856d,0x856d,0x856f,0x856f,0x8577,0x8577,0x8579,0x857b,0x857d,0x8581,0x8584,0x858c,0x858f,0x8591,0x8593,0x8594,0x8597,0x8599,0x859b,0x859d,0x859f,0x85a0,0x85a2,0x85a2,0x85a4,0x85b0,0x85b4,0x85b4,0x85b6,0x85ba,0x85bc,0x85bf,0x85c1,0x85c2,0x85c7,0x85c7,0x85c9,0x85cb,0x85cd,0x85d0,0x85d5,0x85d5,0x85d8,0x85da,0x85dc,0x85dd,0x85df,0x85e1,0x85e4,0x85e6,0x85e8,0x85ea,0x85ed,0x85ed,0x85f3,0x85f4,0x85f6,0x85f7,0x85f9,0x85fc,0x85fe,0x8600,0x8602,0x8602,0x8604,0x8607,0x860a,0x860b,0x860d,0x860e,0x8610,0x8613,0x8616,0x861b,0x861e,0x861e,0x8621,0x8622,0x8624,0x8624,0x8627,0x8627,0x8629,0x8629,0x862d,0x862d,0x862f,0x8630,0x8636,0x8636,0x8638,0x863a,0x863c,0x863d,0x863f,0x8642,0x8646,0x8646,0x864d,0x864e,0x8650,0x8650,0x8652,0x8664,0x8667,0x8667,0x8669,0x8669,0x866b,0x866c,0x866f,0x866f,0x8671,0x8671,0x8675,0x8677,0x8679,0x867b,0x867d,0x867d,0x8687,0x868d,0x8691,0x8691,0x8693,0x8693,0x8695,0x8696,0x8698,0x8698,0x869a,0x869a,0x869c,0x869d,0x86a1,0x86a1,0x86a3,0x86a4,0x86a6,0x86ab,0x86ad,0x86ad,0x86af,0x86b1,0x86b3,0x86b9,0x86bf,0x86c1,0x86c3,0x86c7,0x86c9,0x86c9,0x86cb,0x86cb,0x86cd,0x86ce,0x86d1,0x86d2,0x86d4,0x86d5,0x86d7,0x86d7,0x86d9,0x86dc,0x86de,0x86e0,0x86e3,0x86e7,0x86e9,0x86e9,0x86ec,0x86ef,0x86f8,0x86fe,0x8700,0x8700,0x8702,0x870b,0x870d,0x8714,0x8718,0x871a,0x871c,0x871c,0x871e,0x871f,0x8721,0x8723,0x8725,0x8725,0x8728,0x8729,0x872e,0x872f,0x8731,0x8732,0x8734,0x8734,0x8737,0x8737,0x8739,0x8740,0x8743,0x8743,0x8745,0x8745,0x8749,0x8749,0x874b,0x874e,0x8751,0x8751,0x8753,0x8753,0x8755,0x8755,0x8757,0x8759,0x875d,0x875d,0x875f,0x8761,0x8763,0x8766,0x8768,0x8768,0x876a,0x876a,0x876e,0x876f,0x8771,0x8772,0x8774,0x8774,0x8776,0x8776,0x8778,0x8778,0x877b,0x877c,0x877f,0x877f,0x8782,0x8789,0x878b,0x878e,0x8790,0x8790,0x8793,0x8793,0x8795,0x8795,0x8797,0x8799,0x879e,0x87a0,0x87a2,0x87a3,0x87a7,0x87a7,0x87ab,0x87af,0x87b1,0x87b1,0x87b3,0x87b3,0x87b5,0x87b5,0x87ba,0x87bb,0x87bd,0x87c1,0x87c4,0x87c4,0x87c6,0x87cb,0x87ce,0x87ce,0x87d0,0x87d0,0x87d2,0x87d2,0x87d5,0x87d6,0x87d9,0x87da,0x87dc,0x87dc,0x87df,0x87e0,0x87e2,0x87e6,0x87ea,0x87ed,0x87ef,0x87ef,0x87f1,0x87f3,0x87f5,0x87fb,0x87fe,0x87ff,0x8801,0x8801,0x8803,0x8803,0x8805,0x8807,0x8809,0x880b,0x880d,0x8816,0x8818,0x881c,0x881e,0x881f,0x8821,0x8823,0x8827,0x8828,0x882d,0x882e,0x8830,0x8832,0x8835,0x8836,0x8839,0x883c,0x8840,0x8846,0x8848,0x884e,0x8851,0x8853,0x8855,0x8864,0x8868,0x8869,0x886b,0x886b,0x886e,0x8872,0x8875,0x8875,0x8877,0x8877,0x8879,0x8879,0x887b,0x887b,0x887d,0x8882,0x8888,0x8888,0x888b,0x888b,0x888d,0x888d,0x8892,0x8892,0x8896,0x889c,0x889e,0x88a0,0x88a2,0x88a2,0x88a4,0x88a4,0x88a8,0x88a8,0x88aa,0x88ab,0x88ae,0x88ae,0x88b0,0x88b1,0x88b4,0x88b5,0x88b7,0x88b7,0x88ba,0x88ba,0x88bc,0x88c6,0x88ca,0x88cf,0x88d1,0x88d5,0x88d8,0x88d9,0x88db,0x88e1,0x88e7,0x88e8,0x88ef,0x88f5,0x88f7,0x88f9,0x88fc,0x88fe,0x8901,0x8902,0x8904,0x8904,0x8906,0x8907,0x890a,0x890a,0x890c,0x8910,0x8912,0x8913,0x8915,0x8916,0x8918,0x891a,0x891c,0x891e,0x8920,0x8920,0x8925,0x8928,0x892a,0x892b,0x8930,0x8932,0x8935,0x893b,0x893e,0x893e,0x8940,0x8946,0x8949,0x8949,0x894c,0x894d,0x894f,0x894f,0x8952,0x8952,0x8956,0x8957,0x895a,0x895c,0x895e,0x8964,0x8966,0x8966,0x896a,0x896b,0x896d,0x8970,0x8972,0x8975,0x8977,0x8977,0x897a,0x8981,0x8983,0x8983,0x8986,0x898b,0x898d,0x898d,0x898f,0x8990,0x8993,0x8998,0x899a,0x899c,0x899f,0x89a1,0x89a5,0x89a7,0x89a9,0x89aa,0x89ac,0x89ac,0x89af,0x89b0,0x89b2,0x89b7,0x89ba,0x89ba,0x89bc,0x89bd,0x89bf,0x89c1,0x89d2,0x89d2,0x89d4,0x89d8,0x89da,0x89da,0x89dc,0x89dd,0x89e3,0x89e3,0x89e5,0x89e7,0x89e9,0x89e9,0x89eb,0x89eb,0x89ed,0x89ed,0x89f1,0x89f1,0x89f3,0x89f4,0x89f6,0x89f6,0x89f8,0x89f9,0x89fd,0x89fd,0x89ff,0x8a05,0x8a07,0x8a08,0x8a0a,0x8a0a,0x8a0c,0x8a0c,0x8a0e,0x8a18,0x8a1b,0x8a1b,0x8a1d,0x8a26,0x8a2a,0x8a2d,0x8a2f,0x8a2f,0x8a31,0x8a31,0x8a33,0x8a37,0x8a3a,0x8a3e,0x8a40,0x8a41,0x8a43,0x8a43,0x8a45,0x8a49,0x8a4d,0x8a4e,0x8a50,0x8a58,0x8a5b,0x8a5e,0x8a60,0x8a63,0x8a65,0x8a67,0x8a69,0x8a69,0x8a6b,0x8a6e,0x8a70,0x8a73,0x8a75,0x8a77,0x8a79,0x8a7c,0x8a7e,0x8a80,0x8a82,0x8a87,0x8a89,0x8a89,0x8a8b,0x8a8d,0x8a8f,0x8a93,0x8a95,0x8a9a,0x8a9e,0x8aa1,0x8aa3,0x8aaa,0x8aac,0x8ab0,0x8ab2,0x8ab3,0x8ab6,0x8ab7,0x8ab9,0x8ab9,0x8abb,0x8abc,0x8abe,0x8abf,0x8ac2,0x8ac4,0x8ac6,0x8acd,0x8acf,0x8ad7,0x8ada,0x8ae2,0x8ae4,0x8ae4,0x8ae6,0x8ae7,0x8aeb,0x8aee,0x8af0,0x8af1,0x8af3,0x8af8,0x8afa,0x8afa,0x8afc,0x8afc,0x8afe,0x8b02,0x8b04,0x8b07,0x8b0a,0x8b11,0x8b14,0x8b14,0x8b16,0x8b17,0x8b19,0x8b21,0x8b26,0x8b26,0x8b28,0x8b28,0x8b2b,0x8b2d,0x8b30,0x8b30,0x8b33,0x8b33,0x8b37,0x8b37,0x8b39,0x8b39,0x8b3c,0x8b3c,0x8b3e,0x8b3e,0x8b41,0x8b46,0x8b48,0x8b49,0x8b4c,0x8b4f,0x8b51,0x8b54,0x8b56,0x8b56,0x8b58,0x8b5c,0x8b5e,0x8b5f,0x8b63,0x8b63,0x8b66,0x8b66,0x8b69,0x8b69,0x8b6b,0x8b6d,0x8b6f,0x8b72,0x8b74,0x8b74,0x8b76,0x8b79,0x8b7c,0x8b81,0x8b83,0x8b85,0x8b8a,0x8b90,0x8b92,0x8b96,0x8b99,0x8b9a,0x8b9c,0x8ba0,0x8c37,0x8c3a,0x8c3d,0x8c3f,0x8c41,0x8c41,0x8c45,0x8c4c,0x8c4e,0x8c51,0x8c53,0x8c55,0x8c57,0x8c5b,0x8c5d,0x8c5d,0x8c61,0x8c64,0x8c66,0x8c66,0x8c68,0x8c6d,0x8c73,0x8c73,0x8c75,0x8c76,0x8c78,0x8c7c,0x8c7e,0x8c7e,0x8c82,0x8c82,0x8c85,0x8c87,0x8c89,0x8c8e,0x8c90,0x8c90,0x8c92,0x8c94,0x8c98,0x8c99,0x8c9b,0x8ca2,0x8ca4,0x8ca4,0x8ca7,0x8cb0,0x8cb2,0x8cb4,0x8cb6,0x8cbd,0x8cbf,0x8ccb,0x8ccd,0x8ccf,0x8cd1,0x8cd3,0x8cd5,0x8cd6,0x8cd9,0x8cde,0x8ce0,0x8ce4,0x8ce6,0x8ce6,0x8ce8,0x8ce8,0x8cea,0x8cea,0x8cec,0x8ced,0x8cef,0x8cf2,0x8cf4,0x8cf5,0x8cf7,0x8cf8,0x8cfa,0x8cff,0x8d01,0x8d01,0x8d03,0x8d05,0x8d07,0x8d0b,0x8d0d,0x8d10,0x8d12,0x8d14,0x8d16,0x8d17,0x8d1b,0x8d1d,0x8d64,0x8d67,0x8d69,0x8d69,0x8d6b,0x8d6e,0x8d70,0x8d71,0x8d73,0x8d74,0x8d76,0x8d77,0x8d7f,0x8d7f,0x8d81,0x8d82,0x8d84,0x8d85,0x8d88,0x8d88,0x8d8a,0x8d8a,0x8d8d,0x8d8d,0x8d90,0x8d91,0x8d95,0x8d95,0x8d99,0x8d99,0x8d9e,0x8da0,0x8da3,0x8da3,0x8da6,0x8da6,0x8da8,0x8da8,0x8dab,0x8dac,0x8daf,0x8daf,0x8db2,0x8db3,0x8db5,0x8db5,0x8db7,0x8db7,0x8db9,0x8dbc,0x8dbe,0x8dbe,0x8dc0,0x8dc0,0x8dc2,0x8dc2,0x8dc5,0x8dc8,0x8dca,0x8dcc,0x8dce,0x8dcf,0x8dd1,0x8dd1,0x8dd4,0x8dd7,0x8dd9,0x8ddb,0x8ddd,0x8ddd,0x8ddf,0x8ddf,0x8de1,0x8de1,0x8de3,0x8de5,0x8de7,0x8de8,0x8dea,0x8dec,0x8def,0x8df5,0x8dfc,0x8dfd,0x8dff,0x8dff,0x8e01,0x8e01,0x8e04,0x8e06,0x8e08,0x8e0c,0x8e0f,0x8e11,0x8e14,0x8e14,0x8e16,0x8e16,0x8e1d,0x8e23,0x8e26,0x8e27,0x8e2a,0x8e2a,0x8e30,0x8e31,0x8e33,0x8e39,0x8e3d,0x8e3d,0x8e40,0x8e42,0x8e44,0x8e44,0x8e47,0x8e50,0x8e54,0x8e55,0x8e59,0x8e59,0x8e5b,0x8e64,0x8e69,0x8e69,0x8e6c,0x8e6d,0x8e6f,0x8e72,0x8e74,0x8e77,0x8e79,0x8e7c,0x8e81,0x8e85,0x8e87,0x8e87,0x8e89,0x8e8b,0x8e8d,0x8e8d,0x8e90,0x8e95,0x8e98,0x8e9b,0x8e9d,0x8e9e,0x8ea1,0x8ea2,0x8ea7,0x8ea7,0x8ea9,0x8eb1,0x8eb3,0x8eb3,0x8eb5,0x8eb6,0x8eba,0x8ebb,0x8ebe,0x8ebe,0x8ec0,0x8ec1,0x8ec3,0x8ec8,0x8eca,0x8ecd,0x8ecf,0x8ecf,0x8ed1,0x8ed2,0x8ed4,0x8ed4,0x8edb,0x8edc,0x8edf,0x8edf,0x8ee2,0x8ee3,0x8ee8,0x8ee8,0x8eeb,0x8eeb,0x8eed,0x8eee,0x8ef0,0x8ef1,0x8ef7,0x8efe,0x8f00,0x8f00,0x8f02,0x8f03,0x8f05,0x8f05,0x8f07,0x8f0a,0x8f0c,0x8f0c,0x8f0f,0x8f10,0x8f12,0x8f19,0x8f1b,0x8f21,0x8f23,0x8f23,0x8f25,0x8f2f,0x8f33,0x8f3b,0x8f3e,0x8f47,0x8f49,0x8f4a,0x8f4c,0x8f4f,0x8f51,0x8f55,0x8f57,0x8f58,0x8f5c,0x8f5f,0x8f61,0x8f66,0x8f9b,0x8fa8,0x8fad,0x8fb2,0x8fb4,0x8fb8,0x8fba,0x8fbc,0x8fbe,0x8fc2,0x8fc4,0x8fc6,0x8fc8,0x8fc8,0x8fca,0x8fcb,0x8fcd,0x8fce,0x8fd0,0x8fd5,0x8fda,0x8fda,0x8fe0,0x8fe0,0x8fe2,0x8fe6,0x8fe8,0x8feb,0x8fed,0x8ff1,0x8ff4,0x8ffb,0x8ffd,0x8ffe,0x9000,0x9006,0x9008,0x9008,0x900b,0x9011,0x9013,0x901b,0x901d,0x9023,0x9027,0x902a,0x902c,0x902f,0x9031,0x9039,0x903c,0x903c,0x903e,0x903f,0x9041,0x9045,0x9047,0x9047,0x9049,0x9056,0x9058,0x9059,0x905b,0x905e,0x9060,0x9063,0x9065,0x9069,0x906c,0x9070,0x9072,0x9072,0x9074,0x907a,0x907c,0x907d,0x907f,0x9085,0x9087,0x908c,0x908e,0x9091,0x9095,0x9095,0x9097,0x9099,0x909b,0x909b,0x90a0,0x90a3,0x90a5,0x90a6,0x90a8,0x90a8,0x90aa,0x90aa,0x90af,0x90b6,0x90b8,0x90b8,0x90bd,0x90be,0x90c1,0x90c1,0x90c3,0x90c5,0x90c7,0x90ca,0x90cc,0x90cc,0x90ce,0x90ce,0x90d2,0x90d2,0x90d5,0x90d5,0x90d7,0x90d9,0x90db,0x90df,0x90e1,0x90e2,0x90e4,0x90e5,0x90e8,0x90e8,0x90eb,0x90eb,0x90ed,0x90ed,0x90ef,0x90f0,0x90f2,0x90f2,0x90f4,0x90f7,0x90fd,0x9100,0x9102,0x9102,0x9104,0x9106,0x9108,0x9108,0x910d,0x910d,0x9110,0x9110,0x9112,0x9112,0x9114,0x911a,0x911c,0x911c,0x911e,0x911e,0x9120,0x9120,0x9122,0x9123,0x9125,0x9125,0x9127,0x9127,0x9129,0x9129,0x912d,0x9132,0x9134,0x9134,0x9136,0x9137,0x9139,0x913a,0x913c,0x913d,0x9143,0x9143,0x9146,0x914f,0x9152,0x9154,0x9156,0x915b,0x9161,0x9165,0x9167,0x9167,0x9169,0x916a,0x916c,0x916d,0x9172,0x9175,0x9177,0x917b,0x9181,0x9183,0x9185,0x9187,0x9189,0x918b,0x918d,0x918e,0x9190,0x9195,0x9197,0x9198,0x919c,0x919c,0x919e,0x919e,0x91a1,0x91a2,0x91a4,0x91a4,0x91a6,0x91a6,0x91a8,0x91a8,0x91aa,0x91b6,0x91b8,0x91b8,0x91ba,0x91bd,0x91bf,0x91c9,0x91cb,0x91d1,0x91d3,0x91d4,0x91d6,0x91df,0x91e1,0x91e1,0x91e3,0x91e7,0x91e9,0x91ea,0x91ec,0x91f1,0x91f5,0x91f7,0x91f9,0x91f9,0x91fb,0x91fd,0x91ff,0x9201,0x9204,0x9207,0x9209,0x920a,0x920c,0x920e,0x9210,0x9218,0x921c,0x921e,0x9223,0x9226,0x9228,0x9229,0x922c,0x922c,0x922e,0x9230,0x9233,0x923a,0x923c,0x923c,0x923e,0x9240,0x9242,0x924b,0x924d,0x9251,0x9256,0x925e,0x9260,0x9262,0x9264,0x9269,0x926e,0x9271,0x9275,0x9279,0x927b,0x9280,0x9283,0x9283,0x9285,0x9285,0x9288,0x928a,0x928d,0x928e,0x9291,0x9293,0x9295,0x929c,0x929f,0x92a0,0x92a4,0x92a5,0x92a7,0x92a8,0x92ab,0x92ab,0x92ad,0x92ad,0x92af,0x92af,0x92b2,0x92b3,0x92b6,0x92bd,0x92bf,0x92c3,0x92c5,0x92c8,0x92cb,0x92d0,0x92d2,0x92d3,0x92d5,0x92d5,0x92d7,0x92d9,0x92dc,0x92dd,0x92df,0x92e1,0x92e3,0x92e5,0x92e7,0x92ea,0x92ec,0x92ee,0x92f0,0x92f0,0x92f2,0x92f3,0x92f7,0x92fc,0x92ff,0x9300,0x9302,0x9302,0x9304,0x9304,0x9306,0x9306,0x9308,0x9308,0x930d,0x930d,0x930f,0x9311,0x9314,0x9315,0x9318,0x931a,0x931c,0x932c,0x932e,0x932f,0x9332,0x9337,0x933a,0x933b,0x9344,0x9344,0x9347,0x934b,0x934d,0x934d,0x9350,0x9352,0x9354,0x9358,0x935a,0x935c,0x935e,0x935e,0x9360,0x9360,0x9364,0x9365,0x9367,0x9367,0x9369,0x9371,0x9373,0x9376,0x937a,0x937a,0x937c,0x9382,0x9388,0x9388,0x938a,0x938d,0x938f,0x938f,0x9392,0x9392,0x9394,0x9398,0x939a,0x939b,0x939e,0x939e,0x93a1,0x93a1,0x93a3,0x93a4,0x93a6,0x93a9,0x93ab,0x93ae,0x93b0,0x93b0,0x93b4,0x93b6,0x93b9,0x93bb,0x93c1,0x93c1,0x93c3,0x93cd,0x93d0,0x93d1,0x93d3,0x93d3,0x93d6,0x93d9,0x93dc,0x93df,0x93e1,0x93e2,0x93e4,0x93e8,0x93f1,0x93f1,0x93f5,0x93f5,0x93f7,0x93fb,0x93fd,0x93fd,0x9401,0x9404,0x9407,0x9409,0x940d,0x9410,0x9413,0x941a,0x941f,0x941f,0x9421,0x9421,0x942b,0x942b,0x942e,0x942f,0x9431,0x9436,0x9438,0x9438,0x943a,0x943b,0x943d,0x943d,0x943f,0x943f,0x9441,0x9441,0x9443,0x9445,0x9448,0x9448,0x944a,0x944a,0x944c,0x944c,0x9451,0x9453,0x9455,0x9455,0x9459,0x945c,0x945e,0x9463,0x9468,0x9468,0x946a,0x946b,0x946d,0x9472,0x9475,0x9475,0x9477,0x9477,0x947c,0x947f,0x9481,0x9481,0x9483,0x9485,0x9577,0x9579,0x957e,0x9580,0x9582,0x9584,0x9586,0x958f,0x9591,0x9594,0x9596,0x9596,0x9598,0x9599,0x959d,0x95a9,0x95ab,0x95ad,0x95b1,0x95b2,0x95b4,0x95b4,0x95b6,0x95b6,0x95b9,0x95bf,0x95c3,0x95c3,0x95c6,0x95cd,0x95d0,0x95d6,0x95d8,0x95da,0x95dc,0x95e2,0x95e4,0x95e6,0x95e8,0x95e8,0x961c,0x961e,0x9621,0x9622,0x9624,0x9626,0x9628,0x9628,0x962a,0x962a,0x962c,0x962c,0x962e,0x962f,0x9631,0x9634,0x9637,0x963d,0x963f,0x9642,0x9644,0x9644,0x964b,0x964d,0x964f,0x9650,0x9652,0x9652,0x9654,0x9654,0x9656,0x9658,0x965b,0x965f,0x9661,0x9666,0x966a,0x966a,0x966c,0x966c,0x966e,0x966e,0x9670,0x9670,0x9672,0x9678,0x967a,0x967f,0x9681,0x9686,0x9688,0x968b,0x968d,0x968f,0x9691,0x9691,0x9694,0x969d,0x969f,0x96a0,0x96a3,0x96aa,0x96ae,0x96b4,0x96b6,0x96bd,0x96c0,0x96c1,0x96c4,0x96c7,0x96c9,0x96ce,0x96d1,0x96d2,0x96d5,0x96d6,0x96d8,0x96df,0x96e2,0x96e3,0x96e8,0x96eb,0x96ef,0x96f2,0x96f6,0x96f7,0x96f9,0x96fb,0x9700,0x9700,0x9702,0x970a,0x970d,0x970f,0x9711,0x9711,0x9713,0x9714,0x9716,0x9716,0x9719,0x971e,0x9721,0x9724,0x9727,0x9728,0x972a,0x972a,0x9730,0x9733,0x9736,0x9736,0x9738,0x9739,0x973b,0x973b,0x973d,0x973e,0x9741,0x9744,0x9746,0x974a,0x974d,0x974f,0x9751,0x9752,0x9755,0x975c,0x975e,0x975e,0x9760,0x9764,0x9766,0x976b,0x976d,0x976e,0x9771,0x9771,0x9773,0x9774,0x9776,0x977d,0x977f,0x9781,0x9784,0x9786,0x9789,0x9789,0x978b,0x978b,0x978d,0x978d,0x978f,0x9790,0x9795,0x979a,0x979c,0x979c,0x979e,0x97a0,0x97a2,0x97a3,0x97a6,0x97a6,0x97a8,0x97a8,0x97ab,0x97ae,0x97b1,0x97b6,0x97b8,0x97ba,0x97bc,0x97bc,0x97be,0x97bf,0x97c1,0x97c1,0x97c3,0x97ce,0x97d0,0x97d1,0x97d3,0x97d4,0x97d7,0x97d9,0x97db,0x97de,0x97e0,0x97e1,0x97e4,0x97e4,0x97e6,0x97e6,0x97ed,0x97ef,0x97f1,0x97f8,0x97fa,0x97fb,0x97ff,0x97ff,0x9801,0x9808,0x980a,0x980a,0x980c,0x9814,0x9816,0x981a,0x981c,0x981c,0x981e,0x981e,0x9820,0x9821,0x9823,0x9826,0x982b,0x9830,0x9832,0x9835,0x9837,0x9839,0x983b,0x983e,0x9844,0x9844,0x9846,0x9847,0x984a,0x984f,0x9851,0x985b,0x985e,0x985e,0x9862,0x9863,0x9865,0x9867,0x986a,0x986c,0x986f,0x9871,0x9873,0x9875,0x98a8,0x98a8,0x98aa,0x98ab,0x98ad,0x98b1,0x98b4,0x98b4,0x98b6,0x98b8,0x98ba,0x98bc,0x98bf,0x98bf,0x98c2,0x98c8,0x98cb,0x98cc,0x98ce,0x98ce,0x98db,0x98dc,0x98de,0x98e3,0x98e5,0x98e7,0x98e9,0x98eb,0x98ed,0x98f4,0x98f6,0x98f6,0x98fc,0x98fe,0x9902,0x9903,0x9905,0x9905,0x9907,0x990a,0x990c,0x990c,0x9910,0x9918,0x991a,0x9922,0x9924,0x9924,0x9926,0x9928,0x992b,0x992c,0x992e,0x992e,0x9931,0x9935,0x9939,0x993e,0x9940,0x9942,0x9945,0x9949,0x994b,0x994e,0x9950,0x9952,0x9954,0x9955,0x9957,0x9959,0x995b,0x995c,0x995e,0x9960,0x9963,0x9963,0x9996,0x9999,0x999b,0x999b,0x999d,0x999f,0x99a3,0x99a3,0x99a5,0x99a6,0x99a8,0x99a8,0x99ac,0x99ae,0x99b0,0x99b5,0x99b9,0x99ba,0x99bc,0x99bd,0x99bf,0x99bf,0x99c1,0x99c1,0x99c3,0x99c6,0x99c8,0x99c9,0x99d0,0x99d5,0x99d8,0x99df,0x99e1,0x99e2,0x99e7,0x99e7,0x99ea,0x99ee,0x99f0,0x99f2,0x99f4,0x99f5,0x99f8,0x99f9,0x99fb,0x99ff,0x9a01,0x9a05,0x9a08,0x9a08,0x9a0a,0x9a0c,0x9a0e,0x9a13,0x9a16,0x9a16,0x9a19,0x9a1a,0x9a1e,0x9a1e,0x9a20,0x9a20,0x9a22,0x9a24,0x9a27,0x9a28,0x9a2b,0x9a2b,0x9a2d,0x9a2e,0x9a30,0x9a31,0x9a33,0x9a33,0x9a35,0x9a38,0x9a3e,0x9a3e,0x9a40,0x9a45,0x9a47,0x9a47,0x9a4a,0x9a4e,0x9a51,0x9a52,0x9a54,0x9a58,0x9a5a,0x9a5b,0x9a5d,0x9a5d,0x9a5f,0x9a5f,0x9a62,0x9a62,0x9a64,0x9a65,0x9a69,0x9a6c,0x9aa8,0x9aa8,0x9aaa,0x9aaa,0x9aac,0x9ab0,0x9ab2,0x9ab2,0x9ab4,0x9ab9,0x9abb,0x9ac1,0x9ac3,0x9ac4,0x9ac6,0x9ac6,0x9ac8,0x9ac8,0x9ace,0x9ad9,0x9adb,0x9adc,0x9ade,0x9ae0,0x9ae2,0x9ae7,0x9ae9,0x9aef,0x9af1,0x9af5,0x9af7,0x9af7,0x9af9,0x9afb,0x9afd,0x9afd,0x9aff,0x9b06,0x9b08,0x9b09,0x9b0b,0x9b0e,0x9b10,0x9b10,0x9b12,0x9b12,0x9b16,0x9b16,0x9b18,0x9b1d,0x9b1f,0x9b20,0x9b22,0x9b23,0x9b25,0x9b2f,0x9b31,0x9b35,0x9b37,0x9b37,0x9b39,0x9b3d,0x9b41,0x9b45,0x9b48,0x9b48,0x9b4b,0x9b4f,0x9b51,0x9b51,0x9b54,0x9b58,0x9b5a,0x9b5b,0x9b5e,0x9b5e,0x9b61,0x9b61,0x9b63,0x9b63,0x9b65,0x9b66,0x9b68,0x9b68,0x9b6a,0x9b6f,0x9b72,0x9b79,0x9b7f,0x9b80,0x9b83,0x9b87,0x9b89,0x9b8b,0x9b8d,0x9b94,0x9b96,0x9b97,0x9b9a,0x9b9a,0x9b9d,0x9ba0,0x9ba6,0x9bae,0x9bb0,0x9bb2,0x9bb4,0x9bb4,0x9bb7,0x9bb9,0x9bbb,0x9bbc,0x9bbe,0x9bc1,0x9bc6,0x9bca,0x9bce,0x9bd2,0x9bd4,0x9bd4,0x9bd6,0x9bd8,0x9bdb,0x9bdb,0x9bdd,0x9bdd,0x9bdf,0x9bdf,0x9be1,0x9be5,0x9be7,0x9be8,0x9bea,0x9beb,0x9bee,0x9bf3,0x9bf5,0x9bf5,0x9bf7,0x9bfa,0x9bfd,0x9bfd,0x9bff,0x9c00,0x9c02,0x9c02,0x9c04,0x9c04,0x9c06,0x9c06,0x9c08,0x9c0d,0x9c0f,0x9c16,0x9c18,0x9c1e,0x9c21,0x9c2a,0x9c2d,0x9c32,0x9c35,0x9c37,0x9c39,0x9c3b,0x9c3d,0x9c3e,0x9c41,0x9c41,0x9c43,0x9c4a,0x9c4e,0x9c50,0x9c52,0x9c54,0x9c56,0x9c58,0x9c5a,0x9c61,0x9c63,0x9c63,0x9c65,0x9c65,0x9c67,0x9c6b,0x9c6d,0x9c6e,0x9c70,0x9c70,0x9c72,0x9c72,0x9c75,0x9c78,0x9c7a,0x9c7c,0x9ce5,0x9ce7,0x9ce9,0x9ce9,0x9ceb,0x9cec,0x9cf0,0x9cf0,0x9cf2,0x9cf4,0x9cf6,0x9cf7,0x9cf9,0x9cf9,0x9d02,0x9d03,0x9d06,0x9d09,0x9d0b,0x9d0b,0x9d0e,0x9d0e,0x9d11,0x9d12,0x9d15,0x9d15,0x9d17,0x9d18,0x9d1b,0x9d1f,0x9d23,0x9d23,0x9d26,0x9d26,0x9d28,0x9d28,0x9d2a,0x9d2c,0x9d2f,0x9d30,0x9d32,0x9d34,0x9d3a,0x9d3f,0x9d41,0x9d48,0x9d4a,0x9d4a,0x9d50,0x9d54,0x9d59,0x9d59,0x9d5c,0x9d65,0x9d69,0x9d6c,0x9d6f,0x9d70,0x9d72,0x9d73,0x9d76,0x9d77,0x9d7a,0x9d7c,0x9d7e,0x9d7e,0x9d83,0x9d84,0x9d86,0x9d87,0x9d89,0x9d8a,0x9d8d,0x9d8f,0x9d92,0x9d93,0x9d95,0x9d9a,0x9da1,0x9da1,0x9da4,0x9da4,0x9da9,0x9dac,0x9dae,0x9daf,0x9db1,0x9db2,0x9db4,0x9db5,0x9db8,0x9dbd,0x9dbf,0x9dc4,0x9dc6,0x9dc7,0x9dc9,0x9dca,0x9dcf,0x9dcf,0x9dd3,0x9dd7,0x9dd9,0x9dda,0x9dde,0x9de0,0x9de3,0x9de3,0x9de5,0x9de7,0x9de9,0x9de9,0x9deb,0x9deb,0x9ded,0x9df0,0x9df2,0x9df4,0x9df8,0x9dfa,0x9dfd,0x9dfe,0x9e02,0x9e02,0x9e07,0x9e07,0x9e0a,0x9e0a,0x9e0d,0x9e0e,0x9e10,0x9e12,0x9e15,0x9e16,0x9e19,0x9e1f,0x9e75,0x9e75,0x9e78,0x9e7d,0x9e7f,0x9e85,0x9e87,0x9e88,0x9e8b,0x9e8c,0x9e8e,0x9e8f,0x9e91,0x9e93,0x9e95,0x9e98,0x9e9b,0x9e9b,0x9e9d,0x9e9f,0x9ea4,0x9ea6,0x9ea8,0x9eaa,0x9eac,0x9eb0,0x9eb3,0x9eb5,0x9eb8,0x9ebf,0x9ec3,0x9ec4,0x9ec6,0x9ec6,0x9ec8,0x9ec8,0x9ecb,0x9ed2,0x9ed4,0x9ed5,0x9ed8,0x9ed9,0x9edb,0x9ee0,0x9ee4,0x9ee5,0x9ee7,0x9ee8,0x9eec,0x9ef2,0x9ef4,0x9ef9,0x9efb,0x9eff,0x9f02,0x9f03,0x9f07,0x9f09,0x9f0e,0x9f17,0x9f19,0x9f1b,0x9f1f,0x9f22,0x9f26,0x9f26,0x9f2a,0x9f2c,0x9f2f,0x9f2f,0x9f31,0x9f32,0x9f34,0x9f34,0x9f37,0x9f37,0x9f39,0x9f3f,0x9f41,0x9f41,0x9f43,0x9f47,0x9f4a,0x9f4b,0x9f4e,0x9f50,0x9f52,0x9f58,0x9f5a,0x9f5a,0x9f5d,0x9f63,0x9f66,0x9f6a,0x9f6c,0x9f73,0x9f75,0x9f77,0x9f7a,0x9f7a,0x9f7d,0x9f7d,0x9f7f,0x9f7f,0x9f8d,0x9f8d,0x9f8f,0x9f92,0x9f94,0x9f97,0x9f99,0x9f99,0x9f9c,0x9fa3,0x9fa5,0x9fa5,0x9fb4,0x9fb4,0x9fbc,0x9fc2,0x9fc4,0x9fc4,0x9fc6,0x9fc6,0x9fcc,0x9fcc,0xf900,0xf959,0xf95b,0xf9f2,0xf9f4,0xfa0b,0xfa0e,0xfa6d,0xfb00,0xfb04,0xfe10,0xfe19,0xfe30,0xfe52,0xfe54,0xfe66,0xfe68,0xfe6b,0xff01,0xff9f,0xffa1,0xffbe,0xffc2,0xffc7,0xffca,0xffcf,0xffd2,0xffd7,0xffda,0xffdc,0xffe0,0xffe6,0xffe8,0xffee,0x1f100,0x1f10c,0x1f110,0x1f16c,0x1f170,0x1f1ac,0x1f200,0x1f202,0x1f210,0x1f23b,0x1f240,0x1f248,0x1f250,0x1f251,0x2000b,0x2000b,0x20089,0x2008a,0x200a2,0x200a2,0x200a4,0x200a4,0x200b0,0x200b0,0x200f5,0x200f5,0x20158,0x20158,0x201a2,0x201a2,0x20213,0x20213,0x2032b,0x2032b,0x20371,0x20371,0x20381,0x20381,0x203f9,0x203f9,0x2044a,0x2044a,0x20509,0x20509,0x2053f,0x2053f,0x205b1,0x205b1,0x205d6,0x205d6,0x20611,0x20611,0x20628,0x20628,0x206ec,0x206ec,0x2074f,0x2074f,0x207c8,0x207c8,0x20807,0x20807,0x2083a,0x2083a,0x208b9,0x208b9,0x2090e,0x2090e,0x2097c,0x2097c,0x20984,0x20984,0x2099d,0x2099d,0x20a64,0x20a64,0x20ad3,0x20ad3,0x20b1d,0x20b1d,0x20b9f,0x20b9f,0x20bb7,0x20bb7,0x20d45,0x20d45,0x20d58,0x20d58,0x20de1,0x20de1,0x20e64,0x20e64,0x20e6d,0x20e6d,0x20e95,0x20e95,0x20f5f,0x20f5f,0x21201,0x21201,0x2123d,0x2123d,0x21255,0x21255,0x21274,0x21274,0x2127b,0x2127b,0x212d7,0x212d7,0x212e4,0x212e4,0x212fd,0x212fd,0x2131b,0x2131b,0x21336,0x21336,0x21344,0x21344,0x213c4,0x213c4,0x2146d,0x2146e,0x215d7,0x215d7,0x21647,0x21647,0x216b4,0x216b4,0x21706,0x21706,0x21742,0x21742,0x218bd,0x218bd,0x219c3,0x219c3,0x21a1a,0x21a1a,0x21c56,0x21c56,0x21d2d,0x21d2d,0x21d45,0x21d45,0x21d62,0x21d62,0x21d78,0x21d78,0x21d92,0x21d92,0x21d9c,0x21d9c,0x21da1,0x21da1,0x21db7,0x21db7,0x21de0,0x21de0,0x21e33,0x21e34,0x21f1e,0x21f1e,0x21f76,0x21f76,0x21ffa,0x21ffa,0x2217b,0x2217b,0x22218,0x22218,0x2231e,0x2231e,0x223ad,0x223ad,0x22609,0x22609,0x226f3,0x226f3,0x2285b,0x2285b,0x228ab,0x228ab,0x2298f,0x2298f,0x22ab8,0x22ab8,0x22b46,0x22b46,0x22b4f,0x22b50,0x22ba6,0x22ba6,0x22c1d,0x22c1d,0x22c24,0x22c24,0x22de1,0x22de1,0x22e42,0x22e42,0x22feb,0x22feb,0x231b6,0x231b6,0x231c3,0x231c4,0x231f5,0x231f5,0x23372,0x23372,0x233cc,0x233cc,0x233d0,0x233d0,0x233d2,0x233d3,0x233d5,0x233d5,0x233da,0x233da,0x233df,0x233df,0x233e4,0x233e4,0x233fe,0x233fe,0x2344a,0x2344b,0x23451,0x23451,0x23465,0x23465,0x234e4,0x234e4,0x2355a,0x2355a,0x23594,0x23594,0x235c4,0x235c4,0x23638,0x2363a,0x23647,0x23647,0x2370c,0x2370c,0x2371c,0x2371c,0x2373f,0x2373f,0x23763,0x23764,0x237e7,0x237e7,0x237f1,0x237f1,0x237ff,0x237ff,0x23824,0x23824,0x2383d,0x2383d,0x23a98,0x23a98,0x23c7f,0x23c7f,0x23cbe,0x23cbe,0x23cfe,0x23cfe,0x23d00,0x23d00,0x23d0e,0x23d0e,0x23d40,0x23d40,0x23dd3,0x23dd3,0x23df9,0x23dfa,0x23f7e,0x23f7e,0x2404b,0x2404b,0x24096,0x24096,0x24103,0x24103,0x241c6,0x241c6,0x241fe,0x241fe,0x242ee,0x242ee,0x243bc,0x243bc,0x243d0,0x243d0,0x24629,0x24629,0x246a5,0x246a5,0x247f1,0x247f1,0x24896,0x24896,0x248e9,0x248e9,0x24a4d,0x24a4d,0x24b56,0x24b56,0x24b6f,0x24b6f,0x24c16,0x24c16,0x24d14,0x24d14,0x24e04,0x24e04,0x24e0e,0x24e0e,0x24e37,0x24e37,0x24e6a,0x24e6a,0x24e8b,0x24e8b,0x24ff2,0x24ff2,0x2504a,0x2504a,0x25055,0x25055,0x25122,0x25122,0x251a9,0x251a9,0x251cd,0x251cd,0x251e5,0x251e5,0x2521e,0x2521e,0x2524c,0x2524c,0x2542e,0x2542e,0x2548e,0x2548e,0x254d9,0x254d9,0x2550e,0x2550e,0x255a7,0x255a7,0x2567f,0x2567f,0x25771,0x25771,0x257a9,0x257a9,0x257b4,0x257b4,0x25874,0x25874,0x259c4,0x259c4,0x259cc,0x259cc,0x259d4,0x259d4,0x25ad7,0x25ad7,0x25ae3,0x25ae4,0x25af1,0x25af1,0x25bb2,0x25bb2,0x25c4b,0x25c4b,0x25c64,0x25c64,0x25da1,0x25da1,0x25e2e,0x25e2e,0x25e56,0x25e56,0x25e62,0x25e62,0x25e65,0x25e65,0x25ec2,0x25ec2,0x25ed8,0x25ed8,0x25ee8,0x25ee8,0x25f23,0x25f23,0x25f5c,0x25f5c,0x25fd4,0x25fd4,0x25fe0,0x25fe0,0x25ffb,0x25ffb,0x2600c,0x2600c,0x26017,0x26017,0x26060,0x26060,0x260ed,0x260ed,0x26222,0x26222,0x2626a,0x2626a,0x26270,0x26270,0x26286,0x26286,0x2634c,0x2634c,0x26402,0x26402,0x2667e,0x2667e,0x266b0,0x266b0,0x2671d,0x2671d,0x268dd,0x268dd,0x268ea,0x268ea,0x26951,0x26951,0x2696f,0x2696f,0x26999,0x26999,0x269dd,0x269dd,0x26a1e,0x26a1e,0x26a58,0x26a58,0x26a8c,0x26a8c,0x26ab7,0x26ab7,0x26aff,0x26aff,0x26c29,0x26c29,0x26c73,0x26c73,0x26c9e,0x26c9e,0x26cdd,0x26cdd,0x26e40,0x26e40,0x26e65,0x26e65,0x26f94,0x26f94,0x26ff6,0x26ff8,0x270f4,0x270f4,0x2710d,0x2710d,0x27139,0x27139,0x273da,0x273db,0x273fe,0x273fe,0x27410,0x27410,0x27449,0x27449,0x27614,0x27615,0x27631,0x27631,0x27684,0x27684,0x27693,0x27693,0x2770e,0x2770e,0x27723,0x27723,0x27752,0x27752,0x278b2,0x278b2,0x27985,0x27985,0x279b4,0x279b4,0x27a84,0x27a84,0x27bb3,0x27bb3,0x27bbe,0x27bbe,0x27bc7,0x27bc7,0x27c3c,0x27c3c,0x27cb8,0x27cb8,0x27d73,0x27d73,0x27da0,0x27da0,0x27e10,0x27e10,0x27eaf,0x27eaf,0x27fb7,0x27fb7,0x2808a,0x2808a,0x280bb,0x280bb,0x28277,0x28277,0x28282,0x28282,0x282f3,0x282f3,0x283cd,0x283cd,0x2840c,0x2840c,0x28455,0x28455,0x284dc,0x284dc,0x2856b,0x2856b,0x285c8,0x285c9,0x286d7,0x286d7,0x286fa,0x286fa,0x28946,0x28946,0x28949,0x28949,0x2896b,0x2896b,0x28987,0x28988,0x289ba,0x289bb,0x28a1e,0x28a1e,0x28a29,0x28a29,0x28a43,0x28a43,0x28a71,0x28a71,0x28a99,0x28a99,0x28acd,0x28acd,0x28add,0x28add,0x28ae4,0x28ae4,0x28bc1,0x28bc1,0x28bef,0x28bef,0x28cdd,0x28cdd,0x28d10,0x28d10,0x28d71,0x28d71,0x28dfb,0x28dfb,0x28e0f,0x28e0f,0x28e17,0x28e17,0x28e1f,0x28e1f,0x28e36,0x28e36,0x28e89,0x28e89,0x28eeb,0x28eeb,0x28ef6,0x28ef6,0x28f32,0x28f32,0x28ff8,0x28ff8,0x292a0,0x292a0,0x292b1,0x292b1,0x29490,0x29490,0x295cf,0x295cf,0x2967f,0x2967f,0x296f0,0x296f0,0x29719,0x29719,0x29750,0x29750,0x29810,0x29810,0x298c6,0x298c6,0x29a72,0x29a72,0x29d4b,0x29d4b,0x29ddb,0x29ddb,0x29e15,0x29e15,0x29e3d,0x29e3d,0x29e49,0x29e49,0x29e8a,0x29e8a,0x29ec4,0x29ec4,0x29edb,0x29edb,0x29ee9,0x29ee9,0x29fce,0x29fce,0x29fd7,0x29fd7,0x2a01a,0x2a01a,0x2a02f,0x2a02f,0x2a082,0x2a082,0x2a0f9,0x2a0f9,0x2a190,0x2a190,0x2a2b2,0x2a2b2,0x2a38c,0x2a38c,0x2a437,0x2a437,0x2a5f1,0x2a5f1,0x2a602,0x2a602,0x2a61a,0x2a61a,0x2a6b2,0x2a6b2,0x2a9e6,0x2a9e6,0x2b746,0x2b746,0x2b751,0x2b751,0x2b753,0x2b753,0x2b75a,0x2b75a,0x2b75c,0x2b75c,0x2b765,0x2b765,0x2b776,0x2b777,0x2b77c,0x2b77c,0x2b782,0x2b782,0x2b789,0x2b789,0x2b78b,0x2b78b,0x2b78e,0x2b78e,0x2b794,0x2b794,0x2b7ac,0x2b7ac,0x2b7af,0x2b7af,0x2b7bd,0x2b7bd,0x2b7c9,0x2b7c9,0x2b7cf,0x2b7cf,0x2b7d2,0x2b7d2,0x2b7d8,0x2b7d8,0x2b7f0,0x2b7f0,0x2b80d,0x2b80d,0x2b817,0x2b817,0x2b81a,0x2b81a,0x2d544,0x2d544,0x2e278,0x2e278,0x2e569,0x2e569,0x2e6ea,0x2e6ea,0x2f804,0x2f804,0x2f80f,0x2f80f,0x2f815,0x2f815,0x2f818,0x2f818,0x2f81a,0x2f81a,0x2f822,0x2f822,0x2f828,0x2f828,0x2f82c,0x2f82c,0x2f833,0x2f833,0x2f83f,0x2f83f,0x2f846,0x2f846,0x2f852,0x2f852,0x2f862,0x2f862,0x2f86d,0x2f86d,0x2f873,0x2f873,0x2f877,0x2f877,0x2f884,0x2f884,0x2f899,0x2f89a,0x2f8a6,0x2f8a6,0x2f8ac,0x2f8ac,0x2f8b2,0x2f8b2,0x2f8b6,0x2f8b6,0x2f8d3,0x2f8d3,0x2f8db,0x2f8dc,0x2f8e1,0x2f8e1,0x2f8e5,0x2f8e5,0x2f8ea,0x2f8ea,0x2f8ed,0x2f8ed,0x2f8fc,0x2f8fc,0x2f903,0x2f903,0x2f90b,0x2f90b,0x2f90f,0x2f90f,0x2f91a,0x2f91a,0x2f920,0x2f921,0x2f945,0x2f945,0x2f947,0x2f947,0x2f96c,0x2f96c,0x2f995,0x2f995,0x2f9d0,0x2f9d0,0x2f9de,0x2f9df,0x2f9f4,0x2f9f4,0x30ede,0x30ede,0x3106c,0x3106c,]), + NotoFont.fromFlatRanges('Noto Sans Javanese', 'http://fonts.gstatic.com/s/notosansjavanese/v15/2V0AKJkDAIA6Hp4zoSScDjV0Y-eoHAHJ8r88Rp29eA.ttf', [0x20,0x20,0xa0,0xa0,0x200b,0x200d,0x25cc,0x25cc,0xa980,0xa9cd,0xa9cf,0xa9d9,0xa9de,0xa9df,]), + NotoFont.fromFlatRanges('Noto Sans KR', 'http://fonts.gstatic.com/s/notosanskr/v27/PbykFmXiEBPT4ITbgNA5Cgm20HTs4JMMuA.otf', [0x20,0x7e,0xa0,0x103,0x110,0x113,0x11a,0x11b,0x128,0x12b,0x143,0x144,0x147,0x148,0x14c,0x14f,0x152,0x153,0x168,0x16d,0x192,0x192,0x1a0,0x1a1,0x1af,0x1b0,0x1cd,0x1dc,0x1f8,0x1f9,0x251,0x251,0x261,0x261,0x2bb,0x2bb,0x2c7,0x2c7,0x2c9,0x2cb,0x2d9,0x2d9,0x2ea,0x2eb,0x300,0x301,0x304,0x304,0x307,0x307,0x30c,0x30c,0x391,0x3a1,0x3a3,0x3a9,0x3b1,0x3c9,0x401,0x401,0x410,0x44f,0x451,0x451,0x1100,0x11ff,0x1e3e,0x1e3f,0x1ea0,0x1ef9,0x2002,0x2003,0x2010,0x2016,0x2018,0x201a,0x201c,0x201e,0x2020,0x2022,0x2025,0x2027,0x2030,0x2030,0x2032,0x2033,0x2035,0x2035,0x2039,0x203c,0x2042,0x2042,0x2047,0x2049,0x2051,0x2051,0x2074,0x2074,0x20a9,0x20a9,0x20ab,0x20ac,0x20dd,0x20de,0x2100,0x2100,0x2103,0x2103,0x2105,0x2105,0x2109,0x210a,0x210f,0x210f,0x2113,0x2113,0x2116,0x2116,0x2121,0x2122,0x2126,0x2127,0x212b,0x212b,0x212e,0x212e,0x2135,0x2135,0x213b,0x213b,0x2160,0x216b,0x2170,0x217b,0x2190,0x2199,0x21b8,0x21b9,0x21c4,0x21c6,0x21cb,0x21cc,0x21d0,0x21d0,0x21d2,0x21d2,0x21d4,0x21d4,0x21e6,0x21e9,0x21f5,0x21f5,0x2200,0x2200,0x2202,0x2203,0x2205,0x220b,0x220f,0x220f,0x2211,0x2213,0x2215,0x2215,0x221a,0x221a,0x221d,0x2220,0x2223,0x2223,0x2225,0x222e,0x2234,0x2237,0x223d,0x223d,0x2243,0x2243,0x2245,0x2245,0x2248,0x2248,0x224c,0x224c,0x2252,0x2252,0x2260,0x2262,0x2264,0x2267,0x226a,0x226b,0x226e,0x226f,0x2272,0x2273,0x2276,0x2277,0x2282,0x2287,0x228a,0x228b,0x2295,0x2299,0x22a0,0x22a0,0x22a5,0x22a5,0x22bf,0x22bf,0x22da,0x22db,0x22ef,0x22ef,0x2305,0x2307,0x2312,0x2312,0x2318,0x2318,0x2329,0x232a,0x23b0,0x23b1,0x23be,0x23cc,0x23ce,0x23ce,0x23da,0x23db,0x2423,0x2423,0x2460,0x25ab,0x25b1,0x25b3,0x25b6,0x25b7,0x25bc,0x25bd,0x25c0,0x25c1,0x25c6,0x25cc,0x25ce,0x25d3,0x25e2,0x25e6,0x25ef,0x25ef,0x2600,0x2603,0x2605,0x2606,0x2609,0x2609,0x260e,0x260f,0x2616,0x2617,0x261c,0x261f,0x262f,0x262f,0x2640,0x2642,0x2660,0x266f,0x2672,0x267d,0x26a0,0x26a0,0x26bd,0x26be,0x2702,0x2702,0x2713,0x2713,0x271a,0x271a,0x273d,0x273d,0x273f,0x2740,0x2756,0x2756,0x2776,0x2793,0x27a1,0x27a1,0x2934,0x2935,0x29bf,0x29bf,0x29fa,0x29fb,0x2b05,0x2b07,0x2b1a,0x2b1a,0x2b95,0x2b95,0x2e3a,0x2e3b,0x2e80,0x2e99,0x2e9b,0x2ef3,0x2f00,0x2fd5,0x2ff0,0x2ffb,0x3000,0x303f,0x3041,0x3096,0x3099,0x30ff,0x3105,0x312f,0x3131,0x318e,0x3190,0x31bb,0x31c0,0x31e3,0x31f0,0x321e,0x3220,0x332b,0x332d,0x33ff,0x349a,0x349a,0x34d7,0x34d7,0x3515,0x3515,0x3521,0x3521,0x353e,0x353e,0x35ff,0x35ff,0x366f,0x366f,0x36c3,0x36c5,0x36e6,0x36e6,0x3723,0x3723,0x372f,0x372f,0x373a,0x373a,0x37bc,0x37bc,0x380c,0x380c,0x3818,0x3818,0x3883,0x3883,0x38ba,0x38ba,0x38e7,0x38e7,0x38fd,0x38fd,0x3960,0x3960,0x3965,0x3965,0x3983,0x3983,0x3990,0x3990,0x39a5,0x39a5,0x39b6,0x39b6,0x3a39,0x3a39,0x3aa4,0x3aa4,0x3adc,0x3adc,0x3af6,0x3af6,0x3b03,0x3b03,0x3b23,0x3b23,0x3b79,0x3b79,0x3bf3,0x3bf3,0x3c14,0x3c14,0x3c24,0x3c24,0x3c2d,0x3c2d,0x3cbd,0x3cbe,0x3cfc,0x3cfc,0x3d17,0x3d17,0x3d5f,0x3d5f,0x3dbc,0x3dbc,0x3dc2,0x3dc2,0x3ec4,0x3ec4,0x3eed,0x3eed,0x3efd,0x3efd,0x3f04,0x3f04,0x402f,0x402f,0x4034,0x4034,0x4062,0x4062,0x40a9,0x40a9,0x40c9,0x40c9,0x4137,0x4137,0x41ac,0x41ac,0x4259,0x4259,0x43bb,0x43bb,0x43c7,0x43c7,0x43e7,0x43e7,0x43ea,0x43ea,0x4450,0x4450,0x4512,0x4512,0x45f2,0x45f2,0x4618,0x4618,0x46b7,0x46b7,0x46be,0x46be,0x46d4,0x46d4,0x46d8,0x46d8,0x46dd,0x46dd,0x472d,0x472d,0x476c,0x476c,0x477d,0x477d,0x479f,0x479f,0x4863,0x4863,0x4883,0x4883,0x4896,0x4896,0x48a6,0x48a6,0x4925,0x4925,0x499e,0x499e,0x49a5,0x49a5,0x49cb,0x49cb,0x4a12,0x4a12,0x4a2d,0x4a2d,0x4ab8,0x4ab8,0x4adf,0x4adf,0x4ae8,0x4ae8,0x4afb,0x4afb,0x4b53,0x4b53,0x4b71,0x4b71,0x4cdf,0x4ce0,0x4d1b,0x4d1b,0x4e00,0x4e01,0x4e03,0x4e03,0x4e07,0x4e0b,0x4e0d,0x4e0e,0x4e11,0x4e11,0x4e14,0x4e16,0x4e18,0x4e19,0x4e1e,0x4e1f,0x4e24,0x4e24,0x4e26,0x4e26,0x4e28,0x4e28,0x4e2b,0x4e2d,0x4e30,0x4e32,0x4e36,0x4e36,0x4e38,0x4e39,0x4e3b,0x4e3b,0x4e3f,0x4e3f,0x4e42,0x4e43,0x4e45,0x4e45,0x4e4b,0x4e4b,0x4e4d,0x4e4f,0x4e56,0x4e5b,0x4e5d,0x4e5f,0x4e67,0x4e67,0x4e6b,0x4e6d,0x4e71,0x4e71,0x4e73,0x4e73,0x4e76,0x4e77,0x4e7a,0x4e7c,0x4e7e,0x4e7e,0x4e80,0x4e80,0x4e82,0x4e82,0x4e85,0x4e86,0x4e88,0x4e89,0x4e8b,0x4e8c,0x4e8e,0x4e92,0x4e94,0x4e95,0x4e98,0x4e99,0x4e9b,0x4e9c,0x4e9e,0x4ea2,0x4ea4,0x4ea6,0x4ea8,0x4ea8,0x4eab,0x4eae,0x4eb0,0x4eb0,0x4eb3,0x4eb4,0x4eb6,0x4eb6,0x4eb9,0x4ebb,0x4ec0,0x4ec1,0x4ec4,0x4ec4,0x4ec6,0x4ec7,0x4eca,0x4ecb,0x4ecd,0x4ecd,0x4ed4,0x4ed9,0x4edd,0x4edf,0x4ee1,0x4ee1,0x4ee3,0x4ee5,0x4eee,0x4eee,0x4ef0,0x4ef0,0x4ef2,0x4ef3,0x4ef5,0x4ef7,0x4efb,0x4efb,0x4efd,0x4efd,0x4eff,0x4f01,0x4f09,0x4f0b,0x4f0d,0x4f11,0x4f1a,0x4f1a,0x4f1d,0x4f1d,0x4f2f,0x4f30,0x4f34,0x4f34,0x4f36,0x4f36,0x4f38,0x4f38,0x4f3a,0x4f3a,0x4f3c,0x4f3e,0x4f42,0x4f43,0x4f46,0x4f49,0x4f4b,0x4f4b,0x4f4d,0x4f51,0x4f53,0x4f57,0x4f59,0x4f5f,0x4f69,0x4f6a,0x4f6f,0x4f70,0x4f73,0x4f74,0x4f76,0x4f76,0x4f78,0x4f81,0x4f83,0x4f84,0x4f86,0x4f86,0x4f88,0x4f8b,0x4f8d,0x4f92,0x4f94,0x4f94,0x4f96,0x4f98,0x4f9a,0x4f9d,0x4fae,0x4faf,0x4fb2,0x4fb2,0x4fb5,0x4fb6,0x4fb9,0x4fb9,0x4fbb,0x4fbb,0x4fbf,0x4fbf,0x4fc1,0x4fc5,0x4fc9,0x4fca,0x4fcc,0x4fd4,0x4fd7,0x4fdb,0x4fdd,0x4fe1,0x4fe3,0x4fe3,0x4fee,0x4ff1,0x4ff3,0x4ff6,0x4ff8,0x4ff8,0x4ffa,0x4ffa,0x4ffe,0x4ffe,0x5000,0x5000,0x5002,0x5002,0x5005,0x5007,0x5009,0x5009,0x500b,0x500b,0x500d,0x500d,0x500f,0x500f,0x5011,0x5014,0x5016,0x5016,0x5018,0x501a,0x501c,0x501c,0x501e,0x501f,0x5021,0x502e,0x5030,0x5030,0x503b,0x503b,0x5043,0x5044,0x5047,0x504a,0x504e,0x504f,0x5053,0x5053,0x5055,0x5056,0x5058,0x505a,0x505c,0x505c,0x5060,0x5060,0x5062,0x5062,0x5065,0x5066,0x506a,0x506a,0x5070,0x5070,0x5072,0x5072,0x5074,0x5076,0x5078,0x5078,0x5080,0x5080,0x5083,0x5083,0x5085,0x5085,0x508b,0x508b,0x508d,0x508d,0x5091,0x5092,0x5094,0x5094,0x5096,0x5096,0x5098,0x509b,0x509d,0x509e,0x50a2,0x50a2,0x50ac,0x50ae,0x50b2,0x50b5,0x50b7,0x50b7,0x50bd,0x50bf,0x50c2,0x50c2,0x50c4,0x50c5,0x50c9,0x50ca,0x50cf,0x50cf,0x50d1,0x50d1,0x50d4,0x50d6,0x50da,0x50db,0x50de,0x50de,0x50e2,0x50e2,0x50e5,0x50e7,0x50e9,0x50e9,0x50ec,0x50ee,0x50f5,0x50f5,0x50f9,0x50f9,0x50fb,0x50fb,0x50fe,0x5104,0x5106,0x5107,0x5109,0x5109,0x510b,0x510c,0x5110,0x5110,0x5112,0x5115,0x5117,0x5118,0x511a,0x511c,0x511f,0x511f,0x5121,0x5122,0x5124,0x5125,0x5127,0x5127,0x512a,0x512b,0x5131,0x5133,0x5135,0x5135,0x5137,0x513c,0x513f,0x5141,0x5143,0x5149,0x514b,0x514e,0x5150,0x5150,0x5152,0x5152,0x5154,0x5157,0x515a,0x515a,0x515c,0x515c,0x5162,0x5162,0x5165,0x5165,0x5167,0x516e,0x5171,0x5171,0x5175,0x5178,0x517c,0x517c,0x5180,0x5180,0x5182,0x5182,0x5186,0x5186,0x5189,0x518a,0x518c,0x518d,0x518f,0x518f,0x5191,0x5193,0x5195,0x5199,0x519e,0x519e,0x51a0,0x51a0,0x51a2,0x51a5,0x51aa,0x51ac,0x51b0,0x51b2,0x51b6,0x51b7,0x51bd,0x51be,0x51c4,0x51c6,0x51c9,0x51cd,0x51d2,0x51d2,0x51d4,0x51d4,0x51d6,0x51d6,0x51db,0x51de,0x51e0,0x51e1,0x51e9,0x51e9,0x51ed,0x51ed,0x51f0,0x51f1,0x51f3,0x51f6,0x51f8,0x51fa,0x51fd,0x51fd,0x5200,0x5203,0x5206,0x5208,0x520a,0x520a,0x520e,0x520e,0x5211,0x5211,0x5213,0x5213,0x5216,0x5217,0x521d,0x521d,0x5224,0x5227,0x5229,0x522a,0x522e,0x522e,0x5230,0x5233,0x5236,0x523b,0x5243,0x5244,0x5246,0x5247,0x5249,0x524d,0x5254,0x5257,0x525a,0x525b,0x525d,0x525f,0x5261,0x5261,0x5269,0x526a,0x526f,0x526f,0x5272,0x5272,0x5274,0x5275,0x5277,0x5277,0x527a,0x527a,0x527d,0x527d,0x527f,0x527f,0x5282,0x5283,0x5287,0x5289,0x528d,0x528d,0x5291,0x5293,0x5297,0x5298,0x529b,0x529b,0x529f,0x52a0,0x52a3,0x52a4,0x52a7,0x52a7,0x52a9,0x52ae,0x52b9,0x52b9,0x52be,0x52be,0x52c1,0x52c1,0x52c3,0x52c3,0x52c5,0x52c5,0x52c7,0x52c7,0x52c9,0x52c9,0x52cc,0x52cd,0x52d2,0x52d2,0x52d5,0x52d6,0x52d8,0x52d9,0x52db,0x52db,0x52dd,0x52e4,0x52e6,0x52e6,0x52ed,0x52ed,0x52f2,0x52f3,0x52f5,0x52f5,0x52f8,0x52fb,0x52fe,0x5303,0x5305,0x5305,0x5308,0x5308,0x530a,0x530a,0x530c,0x530d,0x530f,0x5310,0x5315,0x5317,0x5319,0x531a,0x5320,0x5321,0x5323,0x5323,0x5327,0x5327,0x532a,0x532a,0x532f,0x532f,0x5331,0x5331,0x5336,0x5336,0x5338,0x533b,0x533d,0x5341,0x5343,0x5345,0x5347,0x534a,0x534d,0x534d,0x5351,0x5354,0x5357,0x5357,0x535a,0x535a,0x535c,0x535c,0x535e,0x535e,0x5360,0x5361,0x5364,0x5364,0x5366,0x5366,0x5368,0x5369,0x536c,0x536c,0x536e,0x5375,0x5377,0x537b,0x537d,0x537f,0x5382,0x5382,0x5384,0x5384,0x538e,0x538e,0x5393,0x5393,0x5396,0x5396,0x5398,0x5398,0x539a,0x539a,0x539d,0x539d,0x539f,0x53a0,0x53a5,0x53a6,0x53aa,0x53aa,0x53ad,0x53ae,0x53b2,0x53b3,0x53b6,0x53b6,0x53b9,0x53b9,0x53bb,0x53bb,0x53c2,0x53c3,0x53c5,0x53c5,0x53c8,0x53cd,0x53d4,0x53d4,0x53d6,0x53d7,0x53d9,0x53d9,0x53db,0x53db,0x53df,0x53df,0x53e1,0x53e6,0x53e8,0x53f8,0x5401,0x5401,0x5403,0x5404,0x5408,0x5411,0x541b,0x541b,0x541d,0x541d,0x541f,0x5420,0x5426,0x5426,0x5429,0x5429,0x542b,0x542c,0x542e,0x542e,0x5431,0x5431,0x5433,0x5433,0x5436,0x5436,0x5438,0x5439,0x543b,0x543e,0x5440,0x5440,0x5442,0x5442,0x5446,0x5446,0x5448,0x5448,0x544a,0x544a,0x544e,0x544e,0x5451,0x5451,0x545d,0x545d,0x545f,0x545f,0x5462,0x5462,0x5464,0x5464,0x5466,0x5466,0x5468,0x5468,0x546a,0x546b,0x5470,0x5471,0x5473,0x5473,0x5475,0x5476,0x547b,0x547d,0x547f,0x5480,0x5484,0x5484,0x5486,0x5487,0x548b,0x5490,0x5496,0x5496,0x54a0,0x54a0,0x54a2,0x54a2,0x54a4,0x54a5,0x54a8,0x54a8,0x54ab,0x54ac,0x54af,0x54af,0x54b2,0x54b3,0x54b8,0x54b8,0x54bb,0x54bd,0x54bf,0x54c4,0x54c6,0x54c9,0x54e1,0x54e1,0x54e5,0x54e6,0x54e8,0x54e9,0x54ed,0x54ee,0x54f1,0x54f2,0x54fa,0x54fa,0x54fd,0x54fd,0x54ff,0x54ff,0x5504,0x5504,0x5506,0x5507,0x5509,0x5509,0x550e,0x5510,0x5514,0x5514,0x551c,0x551c,0x552b,0x552b,0x552e,0x552f,0x5531,0x5531,0x5533,0x5533,0x5535,0x5535,0x5539,0x5539,0x553c,0x553c,0x553e,0x553e,0x5540,0x5540,0x5542,0x5542,0x5544,0x5544,0x5546,0x5546,0x554a,0x554a,0x554f,0x554f,0x5553,0x5553,0x5556,0x5557,0x555c,0x555c,0x555e,0x555e,0x5563,0x5563,0x557b,0x5581,0x5583,0x5584,0x5586,0x5587,0x5589,0x558b,0x5591,0x5591,0x5593,0x5594,0x5598,0x559a,0x559c,0x559f,0x55a3,0x55a4,0x55a7,0x55ac,0x55ae,0x55ae,0x55b0,0x55b0,0x55c3,0x55c3,0x55c5,0x55c5,0x55c7,0x55c7,0x55c9,0x55c9,0x55d1,0x55d1,0x55d4,0x55d4,0x55da,0x55dc,0x55df,0x55e0,0x55e2,0x55e4,0x55f7,0x55f7,0x55fd,0x55ff,0x5604,0x5604,0x5606,0x5606,0x5608,0x5609,0x560c,0x5610,0x5612,0x5612,0x5614,0x5614,0x5616,0x5617,0x5629,0x5629,0x562c,0x562c,0x562f,0x562f,0x5632,0x5632,0x5634,0x5634,0x5636,0x5639,0x563b,0x563b,0x563f,0x563f,0x5641,0x5642,0x5649,0x5649,0x564b,0x564b,0x564d,0x564f,0x5653,0x5653,0x5664,0x5665,0x5668,0x566d,0x566f,0x566f,0x5672,0x5672,0x5674,0x5674,0x5676,0x5676,0x5678,0x5678,0x567a,0x567a,0x5680,0x5680,0x5684,0x5684,0x5686,0x5687,0x568f,0x568f,0x5699,0x569a,0x56a5,0x56a5,0x56a7,0x56a7,0x56ac,0x56ac,0x56ae,0x56ae,0x56b3,0x56b4,0x56b6,0x56b6,0x56bc,0x56bc,0x56c0,0x56c3,0x56c8,0x56ca,0x56cd,0x56cd,0x56d1,0x56d1,0x56d7,0x56d7,0x56da,0x56db,0x56de,0x56e0,0x56e3,0x56e3,0x56e6,0x56e7,0x56eb,0x56eb,0x56ed,0x56ee,0x56f0,0x56f0,0x56f3,0x56f3,0x56f7,0x56f7,0x56f9,0x56fa,0x56fd,0x56fd,0x56ff,0x56ff,0x5701,0x5704,0x5707,0x570b,0x570d,0x570d,0x5712,0x5713,0x5716,0x5716,0x5718,0x5718,0x571c,0x571c,0x571f,0x571f,0x5725,0x5725,0x5728,0x572a,0x572c,0x572e,0x5730,0x5730,0x573b,0x573b,0x573e,0x573e,0x5740,0x5742,0x5747,0x5747,0x574a,0x574a,0x574c,0x5751,0x5761,0x5761,0x5764,0x5764,0x5766,0x576a,0x576e,0x5771,0x5773,0x5773,0x5775,0x5775,0x5777,0x5778,0x577b,0x577c,0x5782,0x5782,0x5788,0x5788,0x578b,0x578c,0x5793,0x5793,0x5795,0x5795,0x579e,0x579e,0x57a0,0x57a0,0x57a2,0x57a4,0x57b8,0x57b8,0x57bd,0x57bd,0x57c3,0x57c3,0x57c6,0x57c9,0x57cb,0x57cb,0x57ce,0x57cf,0x57d1,0x57d2,0x57dc,0x57dc,0x57df,0x57e0,0x57e4,0x57e4,0x57e9,0x57e9,0x57ed,0x57ee,0x57f0,0x57f0,0x57f3,0x57f4,0x57f6,0x57f7,0x57f9,0x57fd,0x5800,0x5800,0x5802,0x5806,0x5808,0x580b,0x5817,0x5817,0x5819,0x5819,0x581d,0x581e,0x5820,0x5821,0x5823,0x5824,0x5826,0x5827,0x582a,0x582a,0x582d,0x582d,0x582f,0x5831,0x5834,0x5835,0x583a,0x583a,0x5840,0x5840,0x5849,0x584d,0x584f,0x5852,0x5854,0x5854,0x5857,0x585a,0x585e,0x585e,0x5861,0x5862,0x5864,0x5864,0x5869,0x5869,0x5875,0x5875,0x5879,0x5879,0x587c,0x587e,0x5880,0x5881,0x5883,0x5883,0x5885,0x5885,0x5889,0x588a,0x588c,0x588d,0x5890,0x5890,0x5893,0x5893,0x589c,0x589f,0x58a1,0x58a1,0x58a3,0x58a3,0x58a8,0x58a9,0x58ab,0x58ab,0x58ae,0x58ae,0x58b0,0x58b1,0x58b3,0x58b3,0x58ba,0x58bb,0x58be,0x58be,0x58c1,0x58c1,0x58c3,0x58c3,0x58c5,0x58c5,0x58c7,0x58c7,0x58ce,0x58ce,0x58d1,0x58d1,0x58d3,0x58d5,0x58d8,0x58da,0x58dc,0x58df,0x58e1,0x58e1,0x58e4,0x58e4,0x58eb,0x58ec,0x58ee,0x58f0,0x58f2,0x58f2,0x58f9,0x58fb,0x58fd,0x58fd,0x5902,0x5902,0x5906,0x5906,0x5908,0x5908,0x590a,0x590a,0x590f,0x5910,0x5914,0x5916,0x5919,0x591c,0x5922,0x5922,0x5924,0x5925,0x5927,0x5927,0x5929,0x592f,0x5931,0x5932,0x5937,0x5938,0x593d,0x593e,0x5944,0x5944,0x5947,0x5949,0x594c,0x594c,0x594e,0x5951,0x5953,0x5955,0x5957,0x5958,0x595a,0x595a,0x595c,0x595c,0x5960,0x5960,0x5962,0x5962,0x5967,0x5967,0x5969,0x596e,0x5972,0x5974,0x5976,0x5976,0x5978,0x5978,0x597d,0x597d,0x5982,0x5984,0x598a,0x598a,0x598c,0x598d,0x5991,0x5993,0x5996,0x5997,0x5999,0x5999,0x599d,0x599d,0x59a3,0x59a5,0x59a7,0x59a8,0x59ac,0x59ac,0x59af,0x59af,0x59b2,0x59b2,0x59b5,0x59b6,0x59b8,0x59b9,0x59bb,0x59bb,0x59be,0x59bf,0x59c1,0x59c1,0x59c3,0x59c3,0x59c6,0x59c6,0x59c8,0x59cb,0x59cd,0x59cd,0x59d0,0x59d4,0x59d9,0x59da,0x59dc,0x59de,0x59e2,0x59e6,0x59e8,0x59e8,0x59ea,0x59ec,0x59ee,0x59ee,0x59f0,0x59f0,0x59f2,0x59f2,0x59f7,0x59fc,0x59ff,0x59ff,0x5a01,0x5a01,0x5a03,0x5a03,0x5a09,0x5a0a,0x5a0d,0x5a0d,0x5a11,0x5a11,0x5a13,0x5a13,0x5a18,0x5a19,0x5a1b,0x5a1c,0x5a1f,0x5a20,0x5a23,0x5a23,0x5a25,0x5a25,0x5a27,0x5a27,0x5a29,0x5a29,0x5a2b,0x5a2b,0x5a2d,0x5a2d,0x5a35,0x5a36,0x5a3c,0x5a3c,0x5a3f,0x5a41,0x5a46,0x5a47,0x5a49,0x5a49,0x5a4b,0x5a4c,0x5a50,0x5a51,0x5a5a,0x5a5a,0x5a60,0x5a60,0x5a62,0x5a63,0x5a66,0x5a67,0x5a69,0x5a6a,0x5a6d,0x5a6d,0x5a72,0x5a72,0x5a77,0x5a77,0x5a7f,0x5a7f,0x5a84,0x5a84,0x5a8d,0x5a8d,0x5a90,0x5a90,0x5a92,0x5a93,0x5a95,0x5a95,0x5a9a,0x5a9b,0x5a9e,0x5a9f,0x5aa2,0x5aa2,0x5aa4,0x5aa4,0x5aa7,0x5aa7,0x5aaa,0x5aaa,0x5ab3,0x5ab3,0x5ab5,0x5ab5,0x5aba,0x5abf,0x5ac1,0x5ac2,0x5ac4,0x5ac4,0x5ac8,0x5ac9,0x5acb,0x5acc,0x5ad5,0x5ad7,0x5ad9,0x5adb,0x5add,0x5add,0x5ae0,0x5ae6,0x5ae9,0x5ae9,0x5aeb,0x5aeb,0x5aed,0x5aef,0x5af6,0x5af6,0x5afa,0x5afb,0x5afd,0x5afd,0x5b00,0x5b00,0x5b05,0x5b05,0x5b08,0x5b09,0x5b0b,0x5b0c,0x5b16,0x5b16,0x5b19,0x5b19,0x5b1b,0x5b1b,0x5b25,0x5b25,0x5b28,0x5b28,0x5b2a,0x5b2a,0x5b2d,0x5b2d,0x5b30,0x5b30,0x5b32,0x5b32,0x5b34,0x5b34,0x5b3e,0x5b40,0x5b43,0x5b43,0x5b45,0x5b45,0x5b4c,0x5b4c,0x5b50,0x5b51,0x5b54,0x5b58,0x5b5a,0x5b5d,0x5b5f,0x5b5f,0x5b61,0x5b61,0x5b63,0x5b66,0x5b69,0x5b69,0x5b6b,0x5b6b,0x5b70,0x5b71,0x5b75,0x5b76,0x5b78,0x5b78,0x5b7a,0x5b7a,0x5b7c,0x5b7c,0x5b7f,0x5b82,0x5b85,0x5b85,0x5b87,0x5b8c,0x5b8f,0x5b8f,0x5b93,0x5b93,0x5b95,0x5b9d,0x5b9f,0x5b9f,0x5ba2,0x5ba6,0x5bac,0x5bac,0x5bae,0x5bae,0x5bb0,0x5bb0,0x5bb3,0x5bb6,0x5bb8,0x5bb9,0x5bbf,0x5bc0,0x5bc2,0x5bc7,0x5bcc,0x5bcc,0x5bd0,0x5bd0,0x5bd2,0x5bd4,0x5bd6,0x5bd8,0x5bdb,0x5bdb,0x5bde,0x5bdf,0x5be1,0x5be2,0x5be4,0x5be9,0x5beb,0x5bf0,0x5bf5,0x5bf6,0x5bf8,0x5bfa,0x5bff,0x5bff,0x5c01,0x5c01,0x5c04,0x5c0f,0x5c11,0x5c11,0x5c14,0x5c14,0x5c16,0x5c16,0x5c19,0x5c19,0x5c1f,0x5c20,0x5c22,0x5c24,0x5c28,0x5c28,0x5c2b,0x5c2b,0x5c31,0x5c31,0x5c38,0x5c41,0x5c45,0x5c48,0x5c4b,0x5c4b,0x5c4d,0x5c4e,0x5c50,0x5c51,0x5c55,0x5c55,0x5c5b,0x5c5b,0x5c60,0x5c60,0x5c62,0x5c62,0x5c64,0x5c65,0x5c68,0x5c68,0x5c6c,0x5c6c,0x5c6f,0x5c6f,0x5c71,0x5c71,0x5c73,0x5c73,0x5c79,0x5c7a,0x5c88,0x5c88,0x5c8a,0x5c8a,0x5c8c,0x5c8c,0x5c8f,0x5c92,0x5c94,0x5c94,0x5c9d,0x5c9d,0x5ca1,0x5ca1,0x5ca3,0x5ca3,0x5ca5,0x5cad,0x5cb1,0x5cb1,0x5cb3,0x5cb3,0x5cb5,0x5cb5,0x5cb7,0x5cb8,0x5cba,0x5cba,0x5cbe,0x5cbe,0x5cc0,0x5cc0,0x5ccb,0x5ccb,0x5cd2,0x5cd2,0x5cd9,0x5cd9,0x5ce0,0x5ce0,0x5ce8,0x5ce9,0x5ced,0x5ced,0x5cef,0x5cf1,0x5cf4,0x5cf4,0x5cf6,0x5cf6,0x5cfb,0x5cfb,0x5cfd,0x5cfd,0x5d06,0x5d07,0x5d0d,0x5d0e,0x5d10,0x5d11,0x5d14,0x5d19,0x5d1b,0x5d1b,0x5d1f,0x5d1f,0x5d22,0x5d22,0x5d24,0x5d24,0x5d26,0x5d27,0x5d29,0x5d29,0x5d34,0x5d34,0x5d3d,0x5d3d,0x5d41,0x5d42,0x5d44,0x5d44,0x5d4b,0x5d4c,0x5d4e,0x5d4e,0x5d50,0x5d50,0x5d53,0x5d54,0x5d69,0x5d69,0x5d6c,0x5d6c,0x5d6f,0x5d6f,0x5d71,0x5d71,0x5d81,0x5d82,0x5d84,0x5d84,0x5d86,0x5d87,0x5d8b,0x5d8b,0x5d92,0x5d92,0x5d94,0x5d95,0x5d99,0x5d99,0x5d9d,0x5d9d,0x5da0,0x5da0,0x5da2,0x5da2,0x5da7,0x5da7,0x5daa,0x5dab,0x5dae,0x5dae,0x5db0,0x5db0,0x5db7,0x5db8,0x5dba,0x5dba,0x5dbc,0x5dbe,0x5dc9,0x5dc9,0x5dcb,0x5dcb,0x5dcd,0x5dcd,0x5dd1,0x5dd3,0x5dd6,0x5dd6,0x5dda,0x5ddb,0x5ddd,0x5dde,0x5de0,0x5de2,0x5de5,0x5de8,0x5deb,0x5deb,0x5dee,0x5dee,0x5df1,0x5df5,0x5df7,0x5df9,0x5dfb,0x5dfb,0x5dfd,0x5dfe,0x5e02,0x5e03,0x5e06,0x5e06,0x5e09,0x5e09,0x5e0c,0x5e0c,0x5e11,0x5e11,0x5e15,0x5e16,0x5e19,0x5e1b,0x5e1d,0x5e1d,0x5e20,0x5e20,0x5e25,0x5e25,0x5e28,0x5e28,0x5e2b,0x5e2b,0x5e2d,0x5e2d,0x5e33,0x5e33,0x5e36,0x5e38,0x5e3d,0x5e3d,0x5e3f,0x5e40,0x5e43,0x5e45,0x5e47,0x5e47,0x5e4c,0x5e4c,0x5e4e,0x5e4e,0x5e54,0x5e55,0x5e58,0x5e58,0x5e5e,0x5e5f,0x5e61,0x5e63,0x5e68,0x5e68,0x5e6a,0x5e6c,0x5e70,0x5e74,0x5e76,0x5e80,0x5e83,0x5e84,0x5e87,0x5e87,0x5e8a,0x5e8b,0x5e8f,0x5e8f,0x5e95,0x5e97,0x5e9a,0x5e9a,0x5e9c,0x5e9c,0x5ea0,0x5ea0,0x5ea5,0x5ea8,0x5eab,0x5eab,0x5ead,0x5ead,0x5eb3,0x5eb3,0x5eb5,0x5eb8,0x5ebd,0x5ebe,0x5ec1,0x5ec2,0x5ec8,0x5ecb,0x5ecf,0x5ed1,0x5ed3,0x5ed3,0x5ed5,0x5ed6,0x5ed9,0x5edb,0x5edd,0x5ee3,0x5ee5,0x5ee5,0x5ee7,0x5ee9,0x5eec,0x5eec,0x5ef1,0x5ef1,0x5ef3,0x5ef4,0x5ef6,0x5ef7,0x5efa,0x5efb,0x5efe,0x5eff,0x5f01,0x5f01,0x5f03,0x5f04,0x5f07,0x5f08,0x5f0a,0x5f0b,0x5f0f,0x5f0f,0x5f11,0x5f15,0x5f17,0x5f18,0x5f1b,0x5f1b,0x5f1f,0x5f1f,0x5f22,0x5f22,0x5f25,0x5f27,0x5f29,0x5f29,0x5f2d,0x5f2d,0x5f31,0x5f31,0x5f34,0x5f35,0x5f37,0x5f37,0x5f3a,0x5f3a,0x5f3c,0x5f3c,0x5f3e,0x5f3e,0x5f40,0x5f40,0x5f46,0x5f46,0x5f48,0x5f48,0x5f4a,0x5f4a,0x5f4c,0x5f4c,0x5f4e,0x5f4e,0x5f50,0x5f51,0x5f53,0x5f54,0x5f56,0x5f59,0x5f5b,0x5f5b,0x5f5d,0x5f5d,0x5f61,0x5f62,0x5f64,0x5f67,0x5f69,0x5f6d,0x5f70,0x5f71,0x5f73,0x5f73,0x5f77,0x5f77,0x5f79,0x5f79,0x5f7c,0x5f7c,0x5f7f,0x5f82,0x5f85,0x5f85,0x5f87,0x5f8c,0x5f90,0x5f92,0x5f97,0x5f99,0x5f9c,0x5f9c,0x5f9e,0x5f9e,0x5fa0,0x5fa1,0x5fa3,0x5fa3,0x5fa7,0x5faa,0x5fac,0x5faf,0x5fb3,0x5fb3,0x5fb5,0x5fb5,0x5fb7,0x5fb7,0x5fb9,0x5fb9,0x5fbc,0x5fbd,0x5fc3,0x5fc5,0x5fc8,0x5fc9,0x5fcc,0x5fce,0x5fd0,0x5fd0,0x5fd2,0x5fd3,0x5fd5,0x5fd9,0x5fdc,0x5fe1,0x5fe4,0x5fe4,0x5fe8,0x5fe8,0x5feb,0x5feb,0x5fed,0x5fef,0x5ff1,0x5ff1,0x5ff5,0x5ff5,0x5ff8,0x5ff8,0x5ffb,0x5ffd,0x5fff,0x5fff,0x600a,0x600a,0x600d,0x600d,0x600f,0x600f,0x6012,0x6012,0x6014,0x6017,0x6019,0x6019,0x601b,0x601d,0x6020,0x6021,0x6025,0x602a,0x602f,0x6030,0x6033,0x6033,0x6041,0x6043,0x6046,0x6048,0x604a,0x604b,0x604d,0x604d,0x6050,0x6050,0x6052,0x6052,0x6055,0x6055,0x6059,0x605a,0x605d,0x605d,0x605f,0x6060,0x6062,0x6065,0x6068,0x606d,0x606f,0x6070,0x6075,0x6075,0x6081,0x6081,0x6083,0x6086,0x6089,0x608d,0x608f,0x608f,0x6092,0x6092,0x6094,0x6097,0x609a,0x609b,0x609f,0x60a0,0x60a2,0x60a4,0x60a7,0x60a7,0x60aa,0x60aa,0x60b0,0x60b6,0x60b8,0x60b8,0x60bb,0x60be,0x60c4,0x60c7,0x60c9,0x60c9,0x60cb,0x60cb,0x60cf,0x60cf,0x60d1,0x60d1,0x60d3,0x60d3,0x60d5,0x60d5,0x60d7,0x60e2,0x60f0,0x60f4,0x60f6,0x60fc,0x6100,0x6101,0x6103,0x6103,0x6106,0x6106,0x6108,0x6109,0x610d,0x610f,0x6114,0x6115,0x611a,0x611c,0x611e,0x611f,0x6122,0x6122,0x6127,0x6128,0x612b,0x612d,0x6130,0x6130,0x6134,0x6134,0x6137,0x6137,0x613c,0x613c,0x613e,0x613f,0x6142,0x6142,0x6144,0x6144,0x6146,0x6148,0x614a,0x614d,0x614f,0x614f,0x6152,0x6155,0x6158,0x615a,0x615c,0x615d,0x615f,0x6164,0x6167,0x6168,0x616a,0x616b,0x616e,0x616e,0x6170,0x6171,0x6173,0x6177,0x617a,0x617a,0x617c,0x617e,0x6181,0x6183,0x618a,0x618a,0x618d,0x618e,0x6190,0x6194,0x6196,0x6196,0x6198,0x619a,0x61a4,0x61a4,0x61a7,0x61a9,0x61ab,0x61ac,0x61ae,0x61af,0x61b2,0x61b2,0x61b6,0x61b6,0x61b8,0x61b8,0x61ba,0x61be,0x61c3,0x61c3,0x61c6,0x61cc,0x61cf,0x61cf,0x61d5,0x61d5,0x61d7,0x61d7,0x61de,0x61df,0x61e3,0x61e3,0x61e6,0x61e6,0x61f2,0x61f2,0x61f6,0x61f8,0x61fa,0x61fa,0x61fc,0x6200,0x6207,0x6208,0x620a,0x620a,0x620c,0x620e,0x6210,0x6212,0x6214,0x6216,0x6218,0x6218,0x621a,0x621a,0x621e,0x621f,0x6221,0x6222,0x6226,0x6227,0x6229,0x622a,0x622d,0x622e,0x6230,0x6236,0x6239,0x6239,0x623e,0x6241,0x6243,0x6243,0x6247,0x624e,0x6251,0x6253,0x6257,0x6258,0x625b,0x625c,0x625e,0x625e,0x6263,0x6263,0x6268,0x6268,0x626e,0x626e,0x6271,0x6271,0x6273,0x6273,0x6276,0x6276,0x6279,0x627a,0x627c,0x627c,0x627e,0x6280,0x6283,0x6284,0x6286,0x6286,0x6289,0x628a,0x628f,0x628f,0x6291,0x6292,0x6294,0x6298,0x629b,0x629b,0x62a6,0x62a6,0x62a8,0x62a8,0x62ab,0x62ac,0x62ae,0x62ae,0x62b1,0x62b2,0x62b5,0x62b5,0x62b9,0x62b9,0x62bc,0x62bd,0x62c2,0x62c2,0x62c4,0x62cd,0x62cf,0x62d9,0x62db,0x62dc,0x62e1,0x62e1,0x62ec,0x62ef,0x62f1,0x62f1,0x62f3,0x62f3,0x62f5,0x62f7,0x62fd,0x62ff,0x6301,0x6302,0x6307,0x6307,0x6309,0x6309,0x630c,0x630c,0x6310,0x6312,0x6328,0x6328,0x632a,0x632b,0x632f,0x632f,0x6339,0x633b,0x633d,0x633e,0x6342,0x6344,0x6346,0x6346,0x6349,0x6349,0x634c,0x6350,0x6353,0x6353,0x6355,0x6355,0x6357,0x6357,0x635a,0x635a,0x6367,0x6369,0x636b,0x636b,0x636e,0x636e,0x6371,0x6372,0x6376,0x6377,0x637a,0x637b,0x637f,0x6380,0x6383,0x6384,0x6387,0x638a,0x638c,0x638c,0x638e,0x638f,0x6392,0x6392,0x6396,0x6396,0x6398,0x6398,0x639b,0x639c,0x639f,0x63a2,0x63a5,0x63a5,0x63a7,0x63aa,0x63ac,0x63ac,0x63be,0x63be,0x63c0,0x63c0,0x63c3,0x63c4,0x63c6,0x63c6,0x63c9,0x63c9,0x63cf,0x63d0,0x63d2,0x63d2,0x63d6,0x63d6,0x63da,0x63db,0x63df,0x63e1,0x63e3,0x63e3,0x63e9,0x63e9,0x63eb,0x63eb,0x63ed,0x63ee,0x63f2,0x63f2,0x63f4,0x63f7,0x6406,0x6406,0x6409,0x6409,0x640d,0x640d,0x640f,0x640f,0x6412,0x6412,0x6414,0x6414,0x6416,0x6418,0x641c,0x641c,0x6420,0x6420,0x6422,0x6422,0x6424,0x6425,0x6428,0x6428,0x642a,0x642d,0x642f,0x6430,0x6434,0x6434,0x6436,0x6436,0x643a,0x643a,0x643e,0x643e,0x6458,0x6458,0x645b,0x645b,0x645e,0x645e,0x6460,0x6460,0x6467,0x6467,0x6469,0x6469,0x646d,0x646d,0x646f,0x646f,0x6473,0x6473,0x6478,0x647b,0x647d,0x647d,0x6485,0x6485,0x6488,0x6488,0x6490,0x6493,0x6495,0x6495,0x6499,0x649b,0x649d,0x649f,0x64a4,0x64a5,0x64a9,0x64a9,0x64ab,0x64ab,0x64ad,0x64ae,0x64b0,0x64b0,0x64b2,0x64b2,0x64bb,0x64bc,0x64be,0x64bf,0x64c1,0x64c1,0x64c4,0x64c5,0x64c7,0x64c7,0x64c9,0x64ca,0x64cd,0x64ce,0x64d0,0x64d0,0x64d2,0x64d2,0x64d4,0x64d5,0x64d7,0x64d8,0x64da,0x64da,0x64e0,0x64e3,0x64e5,0x64e7,0x64ec,0x64ed,0x64ef,0x64ef,0x64f1,0x64f2,0x64f4,0x64f4,0x64fa,0x64fa,0x64fe,0x64fe,0x6500,0x6500,0x6502,0x6502,0x6504,0x6504,0x6507,0x6507,0x650a,0x650a,0x650f,0x650f,0x6514,0x6514,0x6518,0x6519,0x651d,0x651d,0x6522,0x6524,0x652a,0x652c,0x652f,0x652f,0x6532,0x6532,0x6534,0x6539,0x653b,0x653b,0x653d,0x653f,0x6543,0x6543,0x6545,0x6545,0x6548,0x6549,0x654d,0x654f,0x6551,0x6552,0x6554,0x6559,0x655d,0x655e,0x6562,0x6563,0x6566,0x6566,0x656c,0x656d,0x6572,0x6572,0x6574,0x6575,0x6577,0x6578,0x657e,0x657e,0x6581,0x6583,0x6585,0x6585,0x6587,0x6587,0x6589,0x6589,0x658c,0x658c,0x6590,0x6591,0x6597,0x6597,0x6599,0x6599,0x659b,0x659d,0x659f,0x659f,0x65a1,0x65a1,0x65a4,0x65a5,0x65a7,0x65a7,0x65ab,0x65ad,0x65af,0x65b2,0x65b7,0x65b7,0x65b9,0x65b9,0x65bc,0x65bd,0x65bf,0x65bf,0x65c1,0x65c6,0x65ca,0x65cc,0x65cf,0x65cf,0x65d2,0x65d2,0x65d7,0x65d7,0x65e0,0x65e1,0x65e3,0x65e3,0x65e5,0x65e6,0x65e8,0x65e9,0x65ec,0x65ed,0x65f1,0x65f2,0x65f4,0x65f4,0x65fa,0x65fd,0x65ff,0x6600,0x6602,0x6603,0x6606,0x6607,0x6609,0x660a,0x660c,0x6611,0x6613,0x6615,0x661b,0x661c,0x661e,0x6621,0x6623,0x6625,0x6627,0x6628,0x662b,0x662b,0x662d,0x662d,0x662f,0x6631,0x6634,0x6637,0x663a,0x663b,0x6641,0x6644,0x6648,0x6649,0x664b,0x664c,0x664e,0x6651,0x6659,0x665b,0x665d,0x6662,0x6664,0x6669,0x666b,0x6670,0x6673,0x6674,0x6676,0x667b,0x667d,0x667d,0x667f,0x667f,0x6684,0x6684,0x6687,0x6689,0x668b,0x668c,0x668e,0x668e,0x6690,0x6691,0x6696,0x6698,0x669a,0x669a,0x669d,0x669e,0x66a0,0x66a0,0x66a2,0x66a3,0x66ab,0x66ac,0x66ae,0x66ae,0x66b1,0x66b5,0x66b8,0x66bb,0x66be,0x66c1,0x66c4,0x66c9,0x66d3,0x66d4,0x66d6,0x66d6,0x66d8,0x66de,0x66e0,0x66e0,0x66e3,0x66e3,0x66e6,0x66e6,0x66e8,0x66ea,0x66ec,0x66ec,0x66ee,0x66f0,0x66f2,0x66f4,0x66f7,0x66fa,0x66fc,0x66fc,0x66fe,0x6700,0x6703,0x6705,0x6708,0x670d,0x6710,0x6710,0x6714,0x6715,0x6717,0x6717,0x671b,0x671b,0x671d,0x6720,0x6722,0x6723,0x6726,0x6728,0x672a,0x672e,0x6731,0x6731,0x6733,0x6734,0x6736,0x6736,0x673a,0x673a,0x673d,0x673e,0x6745,0x6746,0x6749,0x6749,0x674b,0x674c,0x674e,0x6751,0x6753,0x6753,0x6756,0x6756,0x675c,0x6760,0x6765,0x6765,0x676a,0x676a,0x676c,0x676d,0x676f,0x6773,0x6775,0x6775,0x6777,0x6777,0x677b,0x677c,0x677e,0x677f,0x6783,0x6783,0x6787,0x6787,0x6789,0x6789,0x678b,0x678c,0x678f,0x6790,0x6792,0x6793,0x6795,0x6795,0x6797,0x679a,0x679c,0x679d,0x67af,0x67b0,0x67b2,0x67b3,0x67b6,0x67b8,0x67be,0x67be,0x67c1,0x67c1,0x67c4,0x67c5,0x67ca,0x67ca,0x67cf,0x67d4,0x67d6,0x67da,0x67dd,0x67df,0x67e2,0x67e2,0x67e9,0x67e9,0x67ec,0x67ec,0x67ef,0x67f1,0x67f3,0x67f6,0x67f9,0x67f9,0x67fb,0x67fb,0x67fe,0x67ff,0x6803,0x6804,0x6810,0x6810,0x6812,0x6813,0x6816,0x6817,0x681d,0x681e,0x6821,0x6822,0x682a,0x682a,0x682e,0x682f,0x6831,0x6832,0x6834,0x6834,0x6838,0x6839,0x683b,0x683d,0x6840,0x6844,0x6846,0x6846,0x6848,0x6849,0x684e,0x684e,0x6850,0x6851,0x6853,0x6854,0x686d,0x686d,0x686f,0x686f,0x6874,0x6874,0x6876,0x6877,0x687e,0x687f,0x6881,0x6881,0x6883,0x6883,0x6885,0x6886,0x688f,0x688f,0x6893,0x6894,0x6897,0x6897,0x689b,0x689b,0x689d,0x689d,0x689f,0x68a3,0x68a7,0x68a8,0x68ad,0x68ad,0x68af,0x68b1,0x68b3,0x68b3,0x68b5,0x68b6,0x68c4,0x68c5,0x68c9,0x68cd,0x68d0,0x68d0,0x68d2,0x68d2,0x68d5,0x68d8,0x68da,0x68da,0x68df,0x68e0,0x68e3,0x68e3,0x68e7,0x68e8,0x68ec,0x68ec,0x68ee,0x68ee,0x68f2,0x68f2,0x68f9,0x68fd,0x6900,0x6901,0x6904,0x6906,0x690b,0x690b,0x690d,0x690f,0x6911,0x6912,0x6919,0x6919,0x691c,0x691c,0x6927,0x6927,0x6930,0x6930,0x6934,0x6934,0x6936,0x6936,0x6939,0x6939,0x693d,0x693d,0x693f,0x693f,0x6942,0x6942,0x694a,0x694a,0x694f,0x694f,0x6953,0x6955,0x6957,0x6957,0x6959,0x695a,0x695d,0x695e,0x6960,0x6963,0x6965,0x6965,0x6968,0x6968,0x696a,0x696f,0x6973,0x6973,0x6975,0x6975,0x6977,0x6979,0x697b,0x697b,0x697d,0x697d,0x698e,0x698e,0x6991,0x6991,0x6994,0x6995,0x6998,0x6998,0x699b,0x699c,0x699f,0x699f,0x69a4,0x69a7,0x69ad,0x69ae,0x69b0,0x69b2,0x69b4,0x69b4,0x69b7,0x69b7,0x69ba,0x69bc,0x69be,0x69c1,0x69c3,0x69c3,0x69c7,0x69c7,0x69ca,0x69d0,0x69d3,0x69d3,0x69d6,0x69d6,0x69e2,0x69e2,0x69e5,0x69ea,0x69ed,0x69ed,0x69f2,0x69f2,0x69f9,0x69f9,0x69fb,0x69fb,0x69fd,0x69fd,0x69ff,0x6a00,0x6a02,0x6a02,0x6a05,0x6a05,0x6a0a,0x6a0b,0x6a11,0x6a14,0x6a17,0x6a17,0x6a19,0x6a19,0x6a1b,0x6a1b,0x6a1e,0x6a1f,0x6a21,0x6a21,0x6a23,0x6a23,0x6a29,0x6a29,0x6a2b,0x6a2b,0x6a35,0x6a35,0x6a38,0x6a3b,0x6a3d,0x6a3d,0x6a43,0x6a45,0x6a47,0x6a4d,0x6a50,0x6a50,0x6a52,0x6a53,0x6a58,0x6a5a,0x6a5f,0x6a5f,0x6a61,0x6a62,0x6a64,0x6a64,0x6a66,0x6a66,0x6a6b,0x6a6b,0x6a72,0x6a72,0x6a75,0x6a75,0x6a7f,0x6a80,0x6a83,0x6a84,0x6a89,0x6a89,0x6a8d,0x6a8e,0x6a90,0x6a90,0x6a94,0x6a94,0x6a97,0x6a97,0x6a9c,0x6a9d,0x6a9f,0x6aa0,0x6aa2,0x6aa3,0x6aae,0x6aae,0x6ab3,0x6ab3,0x6ab6,0x6ab6,0x6abb,0x6abc,0x6abf,0x6abf,0x6ac2,0x6ac3,0x6ad3,0x6ad3,0x6ada,0x6adf,0x6ae8,0x6ae8,0x6aea,0x6aea,0x6aec,0x6aec,0x6af6,0x6af6,0x6afb,0x6afc,0x6b02,0x6b04,0x6b0a,0x6b0a,0x6b0c,0x6b0c,0x6b11,0x6b12,0x6b16,0x6b16,0x6b1e,0x6b1e,0x6b20,0x6b21,0x6b23,0x6b23,0x6b2c,0x6b2c,0x6b32,0x6b32,0x6b37,0x6b3b,0x6b3d,0x6b3f,0x6b43,0x6b43,0x6b46,0x6b47,0x6b49,0x6b4a,0x6b4c,0x6b4c,0x6b4e,0x6b4e,0x6b50,0x6b50,0x6b54,0x6b54,0x6b59,0x6b5b,0x6b5f,0x6b67,0x6b69,0x6b6a,0x6b6f,0x6b6f,0x6b72,0x6b72,0x6b77,0x6b7b,0x6b7f,0x6b80,0x6b82,0x6b84,0x6b86,0x6b86,0x6b89,0x6b8a,0x6b8d,0x6b8d,0x6b91,0x6b91,0x6b96,0x6b96,0x6b98,0x6b98,0x6b9e,0x6b9e,0x6ba2,0x6ba2,0x6ba4,0x6ba4,0x6bab,0x6bab,0x6bad,0x6baf,0x6bb2,0x6bb3,0x6bb5,0x6bb5,0x6bb7,0x6bb7,0x6bba,0x6bba,0x6bbc,0x6bbd,0x6bbf,0x6bc1,0x6bc4,0x6bc6,0x6bcb,0x6bcb,0x6bcd,0x6bcd,0x6bcf,0x6bcf,0x6bd2,0x6bd4,0x6bd6,0x6bd8,0x6bda,0x6bdb,0x6beb,0x6bec,0x6bef,0x6bef,0x6bf3,0x6bf3,0x6bf8,0x6bf8,0x6bff,0x6bff,0x6c05,0x6c05,0x6c08,0x6c08,0x6c0f,0x6c11,0x6c13,0x6c14,0x6c17,0x6c17,0x6c1b,0x6c1b,0x6c23,0x6c24,0x6c33,0x6c38,0x6c3e,0x6c43,0x6c4b,0x6c4b,0x6c4e,0x6c50,0x6c52,0x6c55,0x6c57,0x6c57,0x6c59,0x6c60,0x6c66,0x6c66,0x6c68,0x6c6a,0x6c6d,0x6c6d,0x6c70,0x6c70,0x6c72,0x6c72,0x6c74,0x6c74,0x6c76,0x6c76,0x6c7a,0x6c7a,0x6c7d,0x6c7e,0x6c81,0x6c89,0x6c8c,0x6c8d,0x6c90,0x6c90,0x6c92,0x6c96,0x6c98,0x6c9b,0x6ca2,0x6ca2,0x6cab,0x6cac,0x6cae,0x6cae,0x6cb0,0x6cb1,0x6cb3,0x6cb3,0x6cb6,0x6cb6,0x6cb8,0x6cb9,0x6cbb,0x6cbf,0x6cc1,0x6cc2,0x6cc4,0x6cc6,0x6cc9,0x6cca,0x6ccc,0x6ccc,0x6cd0,0x6cd1,0x6cd3,0x6cd5,0x6cd7,0x6cd7,0x6cd9,0x6cdd,0x6ce0,0x6ce3,0x6ce5,0x6ce5,0x6ce8,0x6ce8,0x6ceb,0x6ceb,0x6cee,0x6cf1,0x6cf3,0x6cf3,0x6cff,0x6cff,0x6d04,0x6d04,0x6d07,0x6d07,0x6d0a,0x6d0c,0x6d11,0x6d12,0x6d14,0x6d14,0x6d17,0x6d17,0x6d19,0x6d19,0x6d1b,0x6d1b,0x6d1e,0x6d1f,0x6d23,0x6d23,0x6d25,0x6d25,0x6d27,0x6d2c,0x6d2e,0x6d2e,0x6d32,0x6d32,0x6d35,0x6d36,0x6d38,0x6d3e,0x6d41,0x6d41,0x6d59,0x6d5a,0x6d5c,0x6d5c,0x6d61,0x6d61,0x6d63,0x6d67,0x6d69,0x6d6a,0x6d6c,0x6d6c,0x6d6e,0x6d6f,0x6d72,0x6d72,0x6d74,0x6d74,0x6d77,0x6d79,0x6d7f,0x6d7f,0x6d82,0x6d82,0x6d85,0x6d85,0x6d87,0x6d89,0x6d8c,0x6d8f,0x6d91,0x6d91,0x6d93,0x6d97,0x6daa,0x6dac,0x6daf,0x6daf,0x6db2,0x6db2,0x6db4,0x6db5,0x6db7,0x6db8,0x6dbc,0x6dbc,0x6dbf,0x6dc0,0x6dc3,0x6dc8,0x6dcb,0x6dcc,0x6dcf,0x6dd2,0x6dd6,0x6dd6,0x6dd8,0x6dda,0x6ddd,0x6dde,0x6de0,0x6de6,0x6de8,0x6de8,0x6dea,0x6dec,0x6dee,0x6dee,0x6df1,0x6df1,0x6df3,0x6df3,0x6df5,0x6dfc,0x6e05,0x6e05,0x6e08,0x6e08,0x6e0a,0x6e0a,0x6e17,0x6e17,0x6e19,0x6e1b,0x6e1d,0x6e1d,0x6e1f,0x6e26,0x6e28,0x6e28,0x6e2b,0x6e2d,0x6e2f,0x6e2f,0x6e32,0x6e32,0x6e34,0x6e34,0x6e36,0x6e38,0x6e3a,0x6e3a,0x6e3c,0x6e3e,0x6e40,0x6e40,0x6e43,0x6e45,0x6e4a,0x6e4a,0x6e4d,0x6e4e,0x6e51,0x6e51,0x6e53,0x6e56,0x6e58,0x6e58,0x6e5b,0x6e5c,0x6e5e,0x6e5f,0x6e63,0x6e63,0x6e67,0x6e67,0x6e6b,0x6e6b,0x6e6e,0x6e6f,0x6e72,0x6e73,0x6e75,0x6e75,0x6e7a,0x6e7a,0x6e8f,0x6e90,0x6e95,0x6e96,0x6e98,0x6e98,0x6e9c,0x6e9d,0x6e9f,0x6e9f,0x6ea2,0x6ea2,0x6ea5,0x6ea5,0x6ea7,0x6ea8,0x6eaa,0x6eab,0x6eaf,0x6eaf,0x6eb1,0x6eb2,0x6eb5,0x6eb7,0x6eba,0x6eba,0x6ebd,0x6ebd,0x6ec2,0x6ec5,0x6ec8,0x6ec9,0x6ecb,0x6ecc,0x6ece,0x6ece,0x6ed1,0x6ed1,0x6ed3,0x6ed5,0x6ed9,0x6ed9,0x6eec,0x6eed,0x6eef,0x6eef,0x6ef2,0x6ef2,0x6ef4,0x6ef5,0x6ef7,0x6ef8,0x6efc,0x6efc,0x6efe,0x6eff,0x6f01,0x6f02,0x6f04,0x6f04,0x6f06,0x6f06,0x6f09,0x6f09,0x6f0c,0x6f0c,0x6f0f,0x6f0f,0x6f11,0x6f11,0x6f13,0x6f15,0x6f19,0x6f1a,0x6f20,0x6f20,0x6f22,0x6f24,0x6f26,0x6f28,0x6f2a,0x6f2d,0x6f30,0x6f33,0x6f38,0x6f38,0x6f3c,0x6f3c,0x6f3e,0x6f3f,0x6f41,0x6f41,0x6f4f,0x6f4f,0x6f51,0x6f52,0x6f54,0x6f54,0x6f57,0x6f5f,0x6f61,0x6f64,0x6f66,0x6f66,0x6f6d,0x6f71,0x6f74,0x6f74,0x6f78,0x6f78,0x6f7a,0x6f7a,0x6f7c,0x6f7e,0x6f81,0x6f82,0x6f84,0x6f84,0x6f86,0x6f89,0x6f8b,0x6f8e,0x6f90,0x6f90,0x6f92,0x6f92,0x6f94,0x6f98,0x6f9f,0x6f9f,0x6fa1,0x6fa1,0x6fa3,0x6fa5,0x6fa7,0x6fa8,0x6faa,0x6faa,0x6fae,0x6faf,0x6fb1,0x6fb1,0x6fb3,0x6fb3,0x6fb6,0x6fb6,0x6fb9,0x6fb9,0x6fbe,0x6fbe,0x6fc0,0x6fc3,0x6fc6,0x6fc7,0x6fc9,0x6fca,0x6fd4,0x6fd5,0x6fd8,0x6fd8,0x6fda,0x6fdb,0x6fde,0x6fe1,0x6fe4,0x6fe6,0x6fe9,0x6fe9,0x6feb,0x6fec,0x6fee,0x6fef,0x6ff1,0x6ff1,0x6ff3,0x6ff4,0x6ff6,0x6ff6,0x6ffa,0x6ffa,0x6ffe,0x6ffe,0x7001,0x7001,0x7005,0x7007,0x7009,0x7009,0x700b,0x700b,0x700f,0x700f,0x7011,0x7013,0x7015,0x7015,0x7018,0x7018,0x701a,0x701f,0x7023,0x7023,0x7026,0x7028,0x702f,0x7030,0x7032,0x7032,0x7037,0x7038,0x703c,0x703c,0x703e,0x703e,0x7044,0x7044,0x7046,0x7046,0x704c,0x704c,0x704e,0x704e,0x7050,0x7051,0x7053,0x7053,0x7058,0x7058,0x705d,0x705e,0x7063,0x7063,0x7066,0x7066,0x7069,0x7069,0x706b,0x706c,0x706f,0x7070,0x7078,0x7078,0x707c,0x707e,0x7081,0x7081,0x7085,0x7086,0x708a,0x708a,0x708e,0x708e,0x7092,0x7092,0x7095,0x7095,0x7098,0x709b,0x70a1,0x70a1,0x70a4,0x70a4,0x70a6,0x70a6,0x70ab,0x70b0,0x70b3,0x70b3,0x70b7,0x70ba,0x70c8,0x70c8,0x70ca,0x70cb,0x70cf,0x70cf,0x70d3,0x70d4,0x70d8,0x70d9,0x70dc,0x70dd,0x70df,0x70df,0x70ef,0x70ef,0x70f1,0x70f1,0x70f9,0x70fa,0x70fd,0x70fd,0x7103,0x7104,0x7106,0x7106,0x7109,0x7109,0x710c,0x710c,0x7119,0x711a,0x711c,0x711c,0x711e,0x711e,0x7120,0x7121,0x7126,0x7126,0x712d,0x7131,0x7136,0x7136,0x7143,0x7143,0x7146,0x7147,0x7149,0x714a,0x714c,0x714c,0x714e,0x714e,0x7150,0x7150,0x7152,0x7153,0x7155,0x7157,0x7159,0x7159,0x715c,0x715e,0x7162,0x7162,0x7164,0x7169,0x716c,0x716c,0x716e,0x716e,0x717d,0x717d,0x7180,0x7180,0x7184,0x7185,0x7187,0x718a,0x718f,0x718f,0x7192,0x7192,0x7194,0x7194,0x7199,0x7199,0x719b,0x719b,0x719f,0x71a2,0x71a4,0x71a4,0x71a8,0x71a9,0x71ac,0x71ac,0x71af,0x71af,0x71b1,0x71b2,0x71b9,0x71ba,0x71be,0x71be,0x71c1,0x71c1,0x71c3,0x71c3,0x71c8,0x71c9,0x71cb,0x71cb,0x71ce,0x71d0,0x71d2,0x71d2,0x71d4,0x71d6,0x71d9,0x71d9,0x71db,0x71db,0x71df,0x71e0,0x71e5,0x71e7,0x71ec,0x71ee,0x71f9,0x71f9,0x71fb,0x7201,0x7206,0x7207,0x720b,0x720d,0x7210,0x7210,0x7214,0x7214,0x7217,0x7217,0x721a,0x721b,0x721f,0x721f,0x7225,0x7225,0x7228,0x7228,0x722a,0x722a,0x722c,0x722d,0x7230,0x7230,0x7232,0x7232,0x7235,0x7236,0x7238,0x723b,0x723d,0x7240,0x7242,0x7242,0x7246,0x7248,0x724b,0x724c,0x7252,0x7254,0x7256,0x7256,0x7258,0x725b,0x725d,0x725d,0x725f,0x725f,0x7261,0x7263,0x7267,0x7267,0x7269,0x7269,0x726f,0x726f,0x7272,0x7272,0x7274,0x7274,0x7278,0x7279,0x727d,0x727d,0x7280,0x7282,0x7287,0x7287,0x728d,0x728d,0x7292,0x7292,0x7296,0x7296,0x72a2,0x72a2,0x72a7,0x72a7,0x72ac,0x72ad,0x72af,0x72af,0x72b3,0x72b5,0x72c0,0x72c0,0x72c2,0x72c2,0x72c4,0x72c4,0x72c9,0x72c9,0x72ce,0x72ce,0x72d0,0x72d0,0x72d2,0x72d2,0x72d7,0x72d7,0x72d9,0x72d9,0x72e1,0x72e2,0x72e5,0x72e5,0x72e8,0x72e9,0x72ec,0x72ec,0x72f4,0x72f4,0x72f7,0x72fd,0x7309,0x730a,0x7313,0x7313,0x7316,0x7319,0x731b,0x731d,0x7322,0x7322,0x7325,0x7325,0x7327,0x732b,0x7331,0x7331,0x7334,0x7334,0x7336,0x7337,0x733e,0x733f,0x7343,0x7345,0x734e,0x734e,0x7350,0x7350,0x7352,0x7352,0x7357,0x7358,0x735c,0x735c,0x7360,0x7360,0x7368,0x736c,0x736f,0x7370,0x7372,0x7372,0x7375,0x7375,0x7377,0x7378,0x737a,0x737c,0x7381,0x7381,0x7384,0x7384,0x7386,0x7389,0x738b,0x738b,0x738e,0x738e,0x7392,0x7392,0x7394,0x7398,0x739e,0x73a0,0x73a6,0x73a7,0x73a9,0x73ab,0x73ad,0x73ad,0x73b2,0x73b4,0x73b7,0x73b7,0x73b9,0x73b9,0x73bb,0x73bd,0x73bf,0x73c0,0x73c2,0x73c2,0x73c6,0x73c6,0x73c8,0x73ca,0x73cc,0x73cd,0x73cf,0x73cf,0x73d2,0x73d2,0x73d6,0x73d9,0x73dd,0x73de,0x73e0,0x73e0,0x73e2,0x73e6,0x73e9,0x73eb,0x73ed,0x73ee,0x73f5,0x73f5,0x73f7,0x73f9,0x73fd,0x73fe,0x7401,0x7401,0x7403,0x7407,0x7409,0x7409,0x7413,0x7413,0x7417,0x7418,0x741b,0x741b,0x741d,0x741d,0x741f,0x7422,0x7424,0x7426,0x7428,0x7428,0x742a,0x742c,0x742e,0x7436,0x7438,0x7438,0x743a,0x743a,0x743f,0x7446,0x7448,0x7449,0x744b,0x744c,0x744e,0x744e,0x7455,0x7455,0x7457,0x7457,0x7459,0x7460,0x7462,0x7465,0x7468,0x746a,0x746d,0x7473,0x747d,0x747e,0x7480,0x7480,0x7482,0x7487,0x7489,0x748c,0x7490,0x7490,0x7498,0x7498,0x749c,0x749f,0x74a1,0x74a1,0x74a3,0x74a3,0x74a5,0x74a5,0x74a7,0x74a8,0x74aa,0x74ab,0x74b0,0x74b2,0x74b5,0x74b6,0x74b8,0x74b9,0x74bc,0x74bd,0x74bf,0x74c0,0x74c6,0x74c6,0x74ca,0x74ca,0x74cd,0x74cd,0x74cf,0x74d0,0x74d3,0x74d4,0x74d8,0x74d8,0x74da,0x74dc,0x74e0,0x74e0,0x74e2,0x74e3,0x74e6,0x74e6,0x74e9,0x74e9,0x74ee,0x74ee,0x74f2,0x74f3,0x74f7,0x74f7,0x7501,0x7501,0x7503,0x7504,0x750c,0x750e,0x7511,0x7511,0x7513,0x7513,0x7515,0x7515,0x7518,0x7518,0x751a,0x751c,0x751e,0x751f,0x7522,0x7526,0x7528,0x7528,0x752b,0x752c,0x7530,0x7533,0x7537,0x7538,0x753a,0x753b,0x753f,0x753f,0x7543,0x7543,0x7547,0x7547,0x754a,0x754c,0x754e,0x754f,0x7551,0x7551,0x7553,0x7554,0x7559,0x755d,0x7560,0x7560,0x7562,0x7562,0x7564,0x7567,0x756a,0x756b,0x756f,0x7570,0x7575,0x7576,0x7578,0x7578,0x757a,0x757a,0x757f,0x757f,0x7586,0x7588,0x758a,0x758b,0x758e,0x758f,0x7591,0x7592,0x7594,0x7594,0x7599,0x759a,0x759d,0x759d,0x75a3,0x75a3,0x75a5,0x75a5,0x75a9,0x75a9,0x75ab,0x75ab,0x75b1,0x75b5,0x75b8,0x75b9,0x75bc,0x75be,0x75c0,0x75c0,0x75c2,0x75c3,0x75c5,0x75c5,0x75c7,0x75c7,0x75ca,0x75ca,0x75cd,0x75ce,0x75d2,0x75d5,0x75d8,0x75d9,0x75db,0x75db,0x75de,0x75de,0x75e2,0x75e4,0x75e7,0x75e7,0x75f0,0x75f0,0x75f2,0x75f4,0x75f9,0x75fa,0x75fc,0x75fc,0x75ff,0x7601,0x7607,0x7609,0x760b,0x760b,0x760d,0x760d,0x7610,0x7610,0x7615,0x7615,0x7619,0x7619,0x761f,0x7622,0x7624,0x7624,0x7626,0x7627,0x762f,0x7631,0x7633,0x7634,0x763b,0x763b,0x7642,0x7643,0x7646,0x7649,0x764c,0x764c,0x764e,0x764e,0x7652,0x7652,0x7655,0x7656,0x7658,0x7658,0x765c,0x765c,0x7661,0x7662,0x7664,0x7665,0x7667,0x7669,0x766c,0x7672,0x7676,0x7676,0x7678,0x7678,0x767a,0x767e,0x7680,0x7681,0x7683,0x7684,0x7686,0x7687,0x768b,0x768b,0x768e,0x768e,0x7690,0x7690,0x7692,0x7693,0x7696,0x7697,0x769a,0x769c,0x769e,0x769e,0x76a4,0x76a4,0x76ac,0x76ac,0x76ae,0x76ae,0x76b4,0x76b4,0x76b6,0x76b6,0x76b8,0x76b8,0x76ba,0x76ba,0x76bf,0x76bf,0x76c2,0x76c3,0x76c6,0x76c6,0x76c8,0x76c8,0x76ca,0x76ca,0x76cc,0x76ce,0x76d2,0x76d4,0x76d6,0x76d6,0x76d9,0x76d9,0x76db,0x76dc,0x76de,0x76df,0x76e1,0x76e1,0x76e3,0x76e5,0x76e7,0x76e7,0x76ea,0x76ea,0x76ec,0x76ec,0x76ee,0x76ee,0x76f1,0x76f2,0x76f4,0x76f4,0x76f8,0x76f9,0x76fb,0x76fc,0x76fe,0x7702,0x7704,0x7704,0x7707,0x770c,0x7710,0x7710,0x771a,0x771b,0x771e,0x7720,0x7725,0x7726,0x7728,0x7729,0x7734,0x7734,0x7737,0x773c,0x773e,0x773e,0x7740,0x7740,0x7743,0x7743,0x7746,0x7747,0x774d,0x774d,0x7752,0x7752,0x775a,0x775b,0x775f,0x7763,0x7765,0x7766,0x7768,0x7768,0x776b,0x776c,0x7777,0x7777,0x7779,0x7779,0x777d,0x777f,0x778b,0x778b,0x778d,0x778e,0x7791,0x7791,0x7796,0x7796,0x7799,0x7799,0x779e,0x779e,0x77a0,0x77a0,0x77a2,0x77a2,0x77a5,0x77a5,0x77aa,0x77aa,0x77ac,0x77ae,0x77b0,0x77b0,0x77b3,0x77b3,0x77b9,0x77b9,0x77bb,0x77bd,0x77bf,0x77bf,0x77c7,0x77c7,0x77c9,0x77c9,0x77cd,0x77cd,0x77d7,0x77d7,0x77d9,0x77dc,0x77de,0x77de,0x77e1,0x77e3,0x77e5,0x77e5,0x77e7,0x77e7,0x77e9,0x77e9,0x77ed,0x77f0,0x77f3,0x77f3,0x77f8,0x77f8,0x77fa,0x77fd,0x7802,0x7802,0x7807,0x7807,0x780c,0x780c,0x780f,0x780f,0x7811,0x7812,0x7814,0x7814,0x7822,0x7822,0x7825,0x7827,0x782c,0x782d,0x7830,0x7830,0x7832,0x7832,0x7834,0x7834,0x7843,0x7843,0x7845,0x7845,0x784f,0x784f,0x785c,0x785d,0x7860,0x7860,0x7867,0x7868,0x786a,0x786c,0x786e,0x786f,0x787c,0x787c,0x7881,0x7881,0x7884,0x7884,0x7887,0x7887,0x788c,0x788f,0x7891,0x7891,0x7893,0x7893,0x7897,0x7897,0x789f,0x789f,0x78a3,0x78a4,0x78a7,0x78a9,0x78ac,0x78ad,0x78ba,0x78bc,0x78be,0x78be,0x78c1,0x78c1,0x78c5,0x78c5,0x78c8,0x78c8,0x78ca,0x78cb,0x78ce,0x78d1,0x78d4,0x78d5,0x78da,0x78da,0x78e0,0x78e0,0x78e7,0x78e8,0x78ea,0x78ea,0x78ec,0x78ec,0x78ef,0x78ef,0x78f4,0x78f5,0x78f7,0x78f7,0x78fa,0x78fd,0x7901,0x7901,0x790c,0x790c,0x790e,0x790f,0x7911,0x7912,0x7916,0x7916,0x7919,0x7919,0x7927,0x7927,0x792a,0x792d,0x7931,0x7931,0x793a,0x793c,0x793e,0x793e,0x7940,0x7941,0x7944,0x794a,0x7950,0x7950,0x7953,0x7958,0x795a,0x7960,0x7962,0x7962,0x7965,0x7965,0x7967,0x7968,0x796d,0x796d,0x7979,0x797a,0x797c,0x797c,0x797f,0x7981,0x798a,0x798b,0x798d,0x798f,0x7991,0x7991,0x7994,0x7994,0x799b,0x799b,0x799d,0x799d,0x79a6,0x79a8,0x79aa,0x79ab,0x79ae,0x79ae,0x79b0,0x79b1,0x79b3,0x79b4,0x79b8,0x79c1,0x79c4,0x79c4,0x79c6,0x79c6,0x79c9,0x79cb,0x79d1,0x79d2,0x79d5,0x79d5,0x79d8,0x79d8,0x79de,0x79df,0x79e2,0x79e4,0x79e6,0x79e7,0x79e9,0x79ec,0x79f5,0x79f5,0x79f8,0x79f8,0x79fb,0x79fb,0x7a00,0x7a02,0x7a05,0x7a05,0x7a08,0x7a08,0x7a0a,0x7a0d,0x7a14,0x7a14,0x7a17,0x7a1a,0x7a1c,0x7a1c,0x7a1e,0x7a20,0x7a22,0x7a22,0x7a27,0x7a27,0x7a2e,0x7a2e,0x7a30,0x7a31,0x7a33,0x7a33,0x7a36,0x7a37,0x7a39,0x7a39,0x7a3b,0x7a3d,0x7a3f,0x7a40,0x7a42,0x7a42,0x7a45,0x7a46,0x7a49,0x7a49,0x7a4c,0x7a4e,0x7a57,0x7a57,0x7a60,0x7a62,0x7a66,0x7a66,0x7a69,0x7a69,0x7a6b,0x7a6b,0x7a70,0x7a70,0x7a74,0x7a76,0x7a79,0x7a7a,0x7a7d,0x7a86,0x7a88,0x7a88,0x7a8a,0x7a8a,0x7a92,0x7a93,0x7a95,0x7a99,0x7a9b,0x7a9b,0x7a9f,0x7aa0,0x7aa3,0x7aa3,0x7aa9,0x7aaa,0x7aac,0x7aac,0x7aae,0x7aaf,0x7ab3,0x7ab3,0x7ab6,0x7ab6,0x7ab9,0x7abb,0x7abe,0x7abf,0x7ac4,0x7ac5,0x7ac7,0x7ac8,0x7aca,0x7acb,0x7ad7,0x7ad7,0x7ad9,0x7ad9,0x7adc,0x7add,0x7adf,0x7ae0,0x7ae2,0x7ae3,0x7ae5,0x7ae6,0x7aea,0x7aea,0x7aed,0x7aed,0x7aef,0x7aef,0x7af4,0x7af4,0x7af6,0x7af6,0x7af8,0x7afa,0x7afd,0x7afd,0x7aff,0x7aff,0x7b06,0x7b06,0x7b08,0x7b08,0x7b0a,0x7b0a,0x7b0c,0x7b0c,0x7b0e,0x7b0f,0x7b11,0x7b12,0x7b18,0x7b19,0x7b1b,0x7b1b,0x7b1e,0x7b1e,0x7b20,0x7b20,0x7b25,0x7b28,0x7b2c,0x7b2d,0x7b2f,0x7b2f,0x7b33,0x7b33,0x7b35,0x7b35,0x7b39,0x7b39,0x7b45,0x7b46,0x7b48,0x7b49,0x7b4b,0x7b4d,0x7b4f,0x7b54,0x7b56,0x7b56,0x7b5f,0x7b60,0x7b65,0x7b67,0x7b69,0x7b69,0x7b6c,0x7b6c,0x7b6e,0x7b6e,0x7b71,0x7b71,0x7b73,0x7b73,0x7b75,0x7b75,0x7b7d,0x7b7d,0x7b87,0x7b87,0x7b8b,0x7b8b,0x7b8d,0x7b8f,0x7b92,0x7b92,0x7b94,0x7b95,0x7b97,0x7b97,0x7b99,0x7b9a,0x7b9c,0x7b9d,0x7ba0,0x7ba1,0x7bad,0x7bad,0x7bb1,0x7bb1,0x7bb4,0x7bb4,0x7bb8,0x7bb8,0x7bbe,0x7bbe,0x7bc0,0x7bc1,0x7bc4,0x7bc4,0x7bc6,0x7bc7,0x7bc9,0x7bcc,0x7bd2,0x7bd2,0x7bd4,0x7bd4,0x7bd9,0x7bd9,0x7bdb,0x7bdb,0x7bdd,0x7bdd,0x7be0,0x7be1,0x7be4,0x7be4,0x7be6,0x7be6,0x7be9,0x7bea,0x7bf3,0x7bf3,0x7bf7,0x7bf7,0x7bfe,0x7bfe,0x7c00,0x7c00,0x7c07,0x7c07,0x7c09,0x7c09,0x7c0b,0x7c0b,0x7c0f,0x7c0f,0x7c12,0x7c12,0x7c1e,0x7c21,0x7c27,0x7c27,0x7c2a,0x7c2b,0x7c37,0x7c38,0x7c3d,0x7c3f,0x7c43,0x7c43,0x7c4c,0x7c4d,0x7c50,0x7c50,0x7c52,0x7c52,0x7c54,0x7c54,0x7c5b,0x7c5c,0x7c5f,0x7c60,0x7c64,0x7c65,0x7c67,0x7c67,0x7c69,0x7c69,0x7c6c,0x7c6c,0x7c72,0x7c73,0x7c7e,0x7c7e,0x7c81,0x7c81,0x7c83,0x7c83,0x7c89,0x7c89,0x7c8d,0x7c8d,0x7c92,0x7c92,0x7c95,0x7c95,0x7c97,0x7c98,0x7c9f,0x7c9f,0x7ca2,0x7ca2,0x7ca4,0x7ca8,0x7cae,0x7cae,0x7cb1,0x7cb3,0x7cb9,0x7cb9,0x7cbc,0x7cbe,0x7cc5,0x7cc6,0x7cca,0x7cca,0x7cd5,0x7cd7,0x7cd9,0x7cda,0x7cdc,0x7ce0,0x7ce2,0x7ce2,0x7ce5,0x7ce5,0x7ce7,0x7ce7,0x7cef,0x7cef,0x7cf1,0x7cf2,0x7cf4,0x7cf6,0x7cf8,0x7cfb,0x7cfe,0x7cfe,0x7d00,0x7d00,0x7d02,0x7d08,0x7d0a,0x7d0b,0x7d0d,0x7d0d,0x7d10,0x7d10,0x7d13,0x7d15,0x7d17,0x7d1c,0x7d20,0x7d22,0x7d2b,0x7d2c,0x7d2e,0x7d33,0x7d35,0x7d35,0x7d38,0x7d3a,0x7d41,0x7d46,0x7d49,0x7d49,0x7d4c,0x7d4d,0x7d50,0x7d51,0x7d55,0x7d56,0x7d59,0x7d59,0x7d5b,0x7d5c,0x7d5e,0x7d5e,0x7d61,0x7d63,0x7d66,0x7d66,0x7d68,0x7d6a,0x7d6e,0x7d6e,0x7d70,0x7d73,0x7d75,0x7d76,0x7d79,0x7d7a,0x7d7f,0x7d7f,0x7d83,0x7d83,0x7d86,0x7d86,0x7d8e,0x7d8f,0x7d93,0x7d93,0x7d98,0x7d98,0x7d9a,0x7d9a,0x7d9c,0x7d9c,0x7da0,0x7da0,0x7da2,0x7da3,0x7da5,0x7da7,0x7da9,0x7da9,0x7dab,0x7dae,0x7db0,0x7db2,0x7db4,0x7db5,0x7db8,0x7db8,0x7dba,0x7dbb,0x7dbd,0x7dbf,0x7dc4,0x7dc4,0x7dc7,0x7dc7,0x7dca,0x7dcd,0x7dcf,0x7dcf,0x7dd6,0x7dd8,0x7dda,0x7dda,0x7ddc,0x7dde,0x7de0,0x7de1,0x7de3,0x7de3,0x7de6,0x7de6,0x7de8,0x7de9,0x7dec,0x7dec,0x7def,0x7def,0x7df4,0x7df4,0x7df6,0x7df6,0x7df9,0x7df9,0x7dfb,0x7dfb,0x7e03,0x7e03,0x7e08,0x7e0b,0x7e10,0x7e11,0x7e15,0x7e15,0x7e17,0x7e18,0x7e1b,0x7e1b,0x7e1d,0x7e23,0x7e2b,0x7e2b,0x7e2e,0x7e2f,0x7e31,0x7e33,0x7e35,0x7e35,0x7e37,0x7e37,0x7e39,0x7e39,0x7e3b,0x7e3b,0x7e3d,0x7e3e,0x7e41,0x7e41,0x7e43,0x7e48,0x7e50,0x7e50,0x7e52,0x7e52,0x7e54,0x7e57,0x7e59,0x7e5a,0x7e5e,0x7e5e,0x7e61,0x7e62,0x7e69,0x7e6b,0x7e6d,0x7e6d,0x7e6f,0x7e70,0x7e76,0x7e76,0x7e79,0x7e79,0x7e7c,0x7e7e,0x7e81,0x7e82,0x7e87,0x7e88,0x7e8a,0x7e8a,0x7e8c,0x7e8d,0x7e8f,0x7e8f,0x7e93,0x7e94,0x7e96,0x7e96,0x7e98,0x7e98,0x7e9b,0x7e9c,0x7e9f,0x7e9f,0x7f36,0x7f38,0x7f3a,0x7f3a,0x7f3e,0x7f3f,0x7f43,0x7f45,0x7f47,0x7f47,0x7f4b,0x7f4e,0x7f50,0x7f55,0x7f58,0x7f58,0x7f5d,0x7f5d,0x7f5f,0x7f61,0x7f63,0x7f63,0x7f66,0x7f66,0x7f68,0x7f68,0x7f6a,0x7f6b,0x7f6e,0x7f6e,0x7f70,0x7f70,0x7f72,0x7f72,0x7f75,0x7f75,0x7f77,0x7f79,0x7f7c,0x7f7e,0x7f82,0x7f82,0x7f85,0x7f88,0x7f8a,0x7f8a,0x7f8c,0x7f8c,0x7f8e,0x7f8e,0x7f94,0x7f94,0x7f96,0x7f98,0x7f9a,0x7f9a,0x7f9d,0x7f9e,0x7fa1,0x7fa1,0x7fa4,0x7fa4,0x7fa8,0x7fa9,0x7fab,0x7fab,0x7faf,0x7faf,0x7fb2,0x7fb2,0x7fb6,0x7fb6,0x7fb8,0x7fb9,0x7fbd,0x7fbd,0x7fbf,0x7fbf,0x7fc1,0x7fc1,0x7fc5,0x7fc5,0x7fca,0x7fca,0x7fcc,0x7fcc,0x7fce,0x7fce,0x7fd2,0x7fd2,0x7fd4,0x7fd6,0x7fdb,0x7fdb,0x7fdf,0x7fe1,0x7fe3,0x7fe4,0x7fe6,0x7fe6,0x7fe9,0x7fe9,0x7feb,0x7fec,0x7fee,0x7fee,0x7ff0,0x7ff0,0x7ff3,0x7ff3,0x7ff9,0x7ffc,0x7ffe,0x7ffe,0x8000,0x8009,0x800c,0x800c,0x8010,0x8012,0x8014,0x8019,0x801e,0x801e,0x8021,0x8021,0x8026,0x8026,0x8028,0x8028,0x802c,0x802d,0x8030,0x8030,0x8033,0x8033,0x8036,0x8036,0x803d,0x803d,0x803f,0x803f,0x8043,0x8043,0x8046,0x8046,0x8048,0x8048,0x804a,0x804a,0x8052,0x8052,0x8055,0x8056,0x8058,0x8058,0x805a,0x805a,0x805e,0x805e,0x8061,0x8061,0x806f,0x8073,0x8075,0x8077,0x807d,0x8080,0x8084,0x8087,0x8089,0x8089,0x808b,0x808c,0x8093,0x8093,0x8096,0x8096,0x8098,0x8098,0x809a,0x809b,0x809d,0x809d,0x80a1,0x80a2,0x80a5,0x80a6,0x80a9,0x80ab,0x80ad,0x80ad,0x80af,0x80af,0x80b1,0x80b2,0x80b4,0x80b5,0x80ba,0x80ba,0x80c3,0x80c4,0x80c6,0x80c6,0x80ca,0x80ca,0x80cc,0x80cc,0x80ce,0x80ce,0x80d5,0x80d6,0x80d9,0x80dc,0x80de,0x80de,0x80e0,0x80e1,0x80e4,0x80e5,0x80ef,0x80ef,0x80f1,0x80f1,0x80f4,0x80f4,0x80f7,0x80f8,0x80fd,0x80fe,0x8102,0x8102,0x8105,0x810a,0x8116,0x8118,0x811a,0x811b,0x8123,0x8124,0x8127,0x8127,0x8129,0x8129,0x812b,0x812b,0x812f,0x8130,0x8139,0x813a,0x813e,0x813e,0x8141,0x8141,0x8146,0x8146,0x814a,0x814b,0x814e,0x814e,0x8150,0x8155,0x8160,0x8160,0x8164,0x8166,0x816b,0x816b,0x816d,0x816d,0x8170,0x8171,0x8174,0x8174,0x8176,0x817a,0x817f,0x8180,0x8182,0x8184,0x8186,0x8186,0x8188,0x8188,0x818a,0x818b,0x818f,0x818f,0x819a,0x819a,0x819c,0x819e,0x81a0,0x81a0,0x81a3,0x81a3,0x81a8,0x81a9,0x81b0,0x81b0,0x81b3,0x81b5,0x81b8,0x81ba,0x81bd,0x81c0,0x81c2,0x81c2,0x81c6,0x81c6,0x81ca,0x81ca,0x81cd,0x81cd,0x81cf,0x81cf,0x81d1,0x81d1,0x81d8,0x81da,0x81dd,0x81dd,0x81df,0x81e0,0x81e3,0x81e3,0x81e5,0x81e5,0x81e7,0x81e8,0x81ea,0x81ea,0x81ec,0x81ed,0x81f3,0x81f4,0x81f6,0x81f6,0x81fa,0x81fc,0x81fe,0x81fe,0x8201,0x8203,0x8205,0x8205,0x8207,0x8208,0x820a,0x820a,0x820c,0x820d,0x8210,0x8210,0x8212,0x8212,0x8216,0x8216,0x8218,0x8218,0x821b,0x821c,0x821e,0x821f,0x8221,0x8221,0x822a,0x822c,0x8233,0x8233,0x8235,0x8239,0x823d,0x823d,0x8240,0x8240,0x8245,0x8245,0x8247,0x8247,0x8251,0x8251,0x8258,0x825a,0x825f,0x825f,0x8264,0x8264,0x8266,0x8266,0x8268,0x8268,0x826a,0x826b,0x826e,0x826f,0x8271,0x8272,0x8274,0x8274,0x8276,0x8279,0x827d,0x827e,0x8283,0x8283,0x828a,0x828b,0x828d,0x828e,0x8290,0x8290,0x8292,0x8292,0x8294,0x8294,0x8298,0x829a,0x829d,0x829d,0x829f,0x829f,0x82a1,0x82a3,0x82a5,0x82b1,0x82b3,0x82b3,0x82b7,0x82b9,0x82bb,0x82bf,0x82c5,0x82c5,0x82d1,0x82d5,0x82d7,0x82d7,0x82db,0x82dc,0x82de,0x82e1,0x82e3,0x82e3,0x82e5,0x82e7,0x82e9,0x82e9,0x82eb,0x82eb,0x82f1,0x82f1,0x82f3,0x82f4,0x82f9,0x82fb,0x82fd,0x8305,0x8308,0x8309,0x8317,0x8317,0x831b,0x831d,0x8323,0x8325,0x8328,0x8328,0x832a,0x832b,0x832f,0x832f,0x8331,0x8336,0x8338,0x8339,0x833c,0x833c,0x8340,0x8340,0x8343,0x8343,0x8347,0x8347,0x8349,0x834a,0x834f,0x8352,0x8363,0x8363,0x8373,0x8373,0x8377,0x8377,0x837a,0x837b,0x8382,0x8382,0x8385,0x8385,0x8389,0x838a,0x838e,0x838e,0x8392,0x8393,0x8396,0x8396,0x8398,0x8398,0x839a,0x839b,0x839d,0x83a0,0x83a2,0x83a2,0x83a8,0x83ab,0x83bd,0x83c2,0x83c5,0x83c5,0x83c9,0x83ca,0x83cc,0x83cc,0x83d1,0x83d1,0x83d3,0x83d4,0x83d6,0x83d6,0x83d8,0x83d8,0x83dc,0x83dc,0x83df,0x83e1,0x83e9,0x83e9,0x83eb,0x83eb,0x83ef,0x83f2,0x83f4,0x83f4,0x83f6,0x83f6,0x83f9,0x83f9,0x83fb,0x83fb,0x83fd,0x83fd,0x8403,0x8404,0x8406,0x8407,0x840a,0x840e,0x8429,0x8429,0x842c,0x842c,0x8431,0x8431,0x8435,0x8435,0x8438,0x8439,0x843c,0x843d,0x8446,0x8446,0x8449,0x844a,0x8451,0x8451,0x8457,0x8457,0x845a,0x845b,0x8461,0x8461,0x8463,0x8463,0x8466,0x8466,0x8469,0x846d,0x846f,0x8471,0x8473,0x8473,0x8475,0x8475,0x8477,0x8477,0x847a,0x847a,0x8482,0x8482,0x8490,0x8491,0x8494,0x8494,0x8499,0x8499,0x849c,0x849c,0x849f,0x849f,0x84a1,0x84a1,0x84a8,0x84a8,0x84ad,0x84ad,0x84af,0x84af,0x84b2,0x84b2,0x84b4,0x84b4,0x84b8,0x84bd,0x84bf,0x84c2,0x84c4,0x84c4,0x84c6,0x84c6,0x84c9,0x84cb,0x84cd,0x84cd,0x84cf,0x84d1,0x84d3,0x84d3,0x84d6,0x84d6,0x84da,0x84da,0x84ec,0x84ef,0x84f1,0x84f1,0x84f4,0x84f4,0x84fa,0x84fa,0x84fc,0x84fd,0x8500,0x8500,0x8506,0x8506,0x850e,0x850e,0x8511,0x8511,0x8513,0x8515,0x8517,0x8518,0x851a,0x851a,0x851e,0x851f,0x8521,0x8521,0x8523,0x8523,0x8525,0x8526,0x852a,0x852a,0x852c,0x852d,0x852f,0x8530,0x853d,0x853d,0x853f,0x853f,0x8541,0x8541,0x8543,0x8543,0x8546,0x8546,0x8549,0x854b,0x854e,0x854e,0x8553,0x8553,0x8555,0x8559,0x855e,0x855e,0x8561,0x8561,0x8563,0x8564,0x8568,0x856b,0x856d,0x856d,0x8578,0x8578,0x857a,0x857a,0x857e,0x857e,0x8580,0x8580,0x8584,0x8584,0x8586,0x8587,0x8589,0x858a,0x858c,0x858c,0x858f,0x858f,0x8591,0x8591,0x8594,0x8594,0x8597,0x8597,0x8599,0x8599,0x859b,0x859b,0x859d,0x859d,0x85a4,0x85a6,0x85a8,0x85ab,0x85af,0x85b0,0x85ba,0x85ba,0x85c1,0x85c1,0x85c7,0x85c7,0x85c9,0x85c9,0x85cd,0x85d0,0x85d5,0x85d5,0x85dc,0x85dd,0x85e4,0x85e5,0x85e9,0x85ea,0x85f7,0x85f7,0x85f9,0x85fb,0x85fd,0x85fd,0x85ff,0x8600,0x8602,0x8602,0x8604,0x8604,0x8606,0x8607,0x860a,0x860b,0x8616,0x8618,0x861a,0x861a,0x861f,0x861f,0x8622,0x8622,0x8627,0x8627,0x8629,0x862a,0x862d,0x862d,0x862f,0x862f,0x863c,0x863c,0x863f,0x863f,0x8641,0x8641,0x864d,0x864e,0x8650,0x8650,0x8653,0x8655,0x865b,0x865c,0x865e,0x865f,0x8667,0x8667,0x866b,0x866c,0x866f,0x866f,0x8671,0x8671,0x8678,0x867b,0x868a,0x868d,0x8693,0x8693,0x8695,0x8695,0x86a3,0x86a4,0x86a8,0x86aa,0x86af,0x86b1,0x86b4,0x86b4,0x86c0,0x86c0,0x86c5,0x86c7,0x86c9,0x86c9,0x86cb,0x86cb,0x86d4,0x86d4,0x86d9,0x86d9,0x86db,0x86db,0x86de,0x86df,0x86e3,0x86e4,0x86e9,0x86e9,0x86ec,0x86ed,0x86f8,0x86f9,0x86fb,0x86fb,0x86fe,0x86fe,0x8700,0x8700,0x8702,0x8703,0x8706,0x8706,0x8708,0x870b,0x8711,0x8711,0x8718,0x8718,0x871a,0x871a,0x871c,0x871d,0x8721,0x8721,0x8725,0x8725,0x8728,0x8729,0x8734,0x8735,0x8737,0x8737,0x873a,0x873b,0x873f,0x8740,0x874c,0x874c,0x874e,0x874e,0x8755,0x8755,0x8757,0x8757,0x8759,0x8759,0x875f,0x8760,0x8764,0x8766,0x8768,0x8768,0x876e,0x876e,0x8774,0x8774,0x8776,0x8776,0x8778,0x8778,0x8782,0x8783,0x878c,0x878d,0x8798,0x8798,0x879e,0x879f,0x87a2,0x87a3,0x87ad,0x87ad,0x87b3,0x87b4,0x87ba,0x87bb,0x87bd,0x87bd,0x87c0,0x87c0,0x87c4,0x87c4,0x87c7,0x87c7,0x87ca,0x87cb,0x87d2,0x87d2,0x87da,0x87db,0x87e0,0x87e0,0x87e3,0x87e3,0x87ec,0x87ec,0x87ef,0x87ef,0x87f2,0x87f2,0x87f7,0x87f7,0x87f9,0x87f9,0x87fb,0x87fb,0x87fe,0x87fe,0x8805,0x8805,0x880d,0x880d,0x8811,0x8811,0x8815,0x8815,0x881f,0x881f,0x8821,0x8823,0x8831,0x8832,0x8836,0x8836,0x8839,0x8839,0x883b,0x883b,0x8840,0x8840,0x8844,0x8844,0x8846,0x8846,0x884a,0x884a,0x884c,0x884e,0x8852,0x8853,0x8857,0x8857,0x8859,0x8859,0x885b,0x885b,0x885d,0x885e,0x8861,0x8864,0x8868,0x8868,0x886b,0x886b,0x886e,0x886e,0x8870,0x8870,0x8872,0x8872,0x8877,0x8877,0x887d,0x887f,0x8881,0x8882,0x8888,0x8888,0x888b,0x888b,0x888d,0x888d,0x8892,0x8892,0x8896,0x8897,0x889b,0x889b,0x889d,0x889e,0x88a2,0x88a2,0x88aa,0x88ab,0x88b4,0x88b4,0x88c0,0x88c2,0x88c5,0x88c5,0x88ca,0x88ca,0x88cd,0x88cd,0x88cf,0x88cf,0x88d2,0x88d2,0x88d4,0x88d5,0x88d8,0x88d9,0x88dc,0x88dd,0x88df,0x88df,0x88e1,0x88e1,0x88e8,0x88e8,0x88ef,0x88ef,0x88f1,0x88f1,0x88f3,0x88f5,0x88f8,0x88f9,0x88fd,0x88fe,0x8904,0x8904,0x8907,0x8907,0x890a,0x890a,0x890c,0x890c,0x8910,0x8913,0x8915,0x8915,0x8918,0x891a,0x8925,0x8925,0x8927,0x8927,0x892a,0x892b,0x892f,0x8930,0x8936,0x8936,0x8938,0x8938,0x893a,0x893b,0x8941,0x8941,0x8944,0x8944,0x894d,0x894d,0x8952,0x8952,0x8956,0x8956,0x8958,0x8958,0x895c,0x895c,0x895e,0x8960,0x8964,0x8964,0x896a,0x896a,0x896d,0x896d,0x896f,0x896f,0x8972,0x8972,0x8974,0x8974,0x897e,0x8981,0x8983,0x8983,0x8986,0x8989,0x898b,0x898b,0x898f,0x898f,0x8993,0x8993,0x8996,0x8998,0x899a,0x899a,0x89a0,0x89a1,0x89a8,0x89aa,0x89ac,0x89ac,0x89af,0x89af,0x89b2,0x89b3,0x89b6,0x89b7,0x89ba,0x89ba,0x89bd,0x89bd,0x89bf,0x89c1,0x89d2,0x89d2,0x89d4,0x89d4,0x89d6,0x89d7,0x89da,0x89da,0x89dc,0x89dd,0x89e3,0x89e3,0x89e5,0x89e7,0x89f0,0x89f1,0x89f3,0x89f4,0x89f6,0x89f6,0x89f8,0x89f8,0x8a00,0x8a00,0x8a02,0x8a03,0x8a07,0x8a0a,0x8a0c,0x8a0c,0x8a0e,0x8a0e,0x8a10,0x8a18,0x8a1b,0x8a1b,0x8a1d,0x8a1d,0x8a1f,0x8a1f,0x8a22,0x8a23,0x8a25,0x8a25,0x8a2a,0x8a2a,0x8a2d,0x8a2d,0x8a31,0x8a31,0x8a33,0x8a34,0x8a36,0x8a36,0x8a3a,0x8a3c,0x8a3e,0x8a3e,0x8a41,0x8a41,0x8a46,0x8a46,0x8a4b,0x8a4b,0x8a50,0x8a51,0x8a54,0x8a58,0x8a5b,0x8a5b,0x8a5e,0x8a5e,0x8a60,0x8a63,0x8a66,0x8a66,0x8a69,0x8a69,0x8a6b,0x8a6e,0x8a70,0x8a73,0x8a75,0x8a75,0x8a79,0x8a79,0x8a7c,0x8a7c,0x8a7f,0x8a7f,0x8a82,0x8a82,0x8a84,0x8a87,0x8a8c,0x8a8d,0x8a91,0x8a91,0x8a93,0x8a93,0x8a95,0x8a95,0x8a98,0x8a98,0x8a9a,0x8a9a,0x8a9e,0x8a9e,0x8aa0,0x8aa8,0x8aaa,0x8aaa,0x8ab0,0x8ab0,0x8ab2,0x8ab2,0x8ab9,0x8ab9,0x8abc,0x8abc,0x8abe,0x8abf,0x8ac2,0x8ac2,0x8ac4,0x8ac4,0x8ac7,0x8ac7,0x8ac9,0x8ac9,0x8acb,0x8acb,0x8acd,0x8acd,0x8acf,0x8acf,0x8ad2,0x8ad2,0x8ad6,0x8ad7,0x8adb,0x8ae1,0x8ae4,0x8ae4,0x8ae6,0x8ae7,0x8aea,0x8aeb,0x8aed,0x8aee,0x8af0,0x8af4,0x8af6,0x8af8,0x8afa,0x8afa,0x8afc,0x8afc,0x8afe,0x8afe,0x8b00,0x8b02,0x8b04,0x8b04,0x8b07,0x8b07,0x8b0c,0x8b0c,0x8b0e,0x8b0e,0x8b10,0x8b11,0x8b14,0x8b14,0x8b16,0x8b17,0x8b19,0x8b1d,0x8b20,0x8b20,0x8b23,0x8b23,0x8b26,0x8b26,0x8b28,0x8b28,0x8b2b,0x8b2c,0x8b33,0x8b33,0x8b37,0x8b37,0x8b39,0x8b39,0x8b3c,0x8b3c,0x8b3e,0x8b3e,0x8b41,0x8b41,0x8b43,0x8b43,0x8b46,0x8b46,0x8b49,0x8b49,0x8b4c,0x8b4c,0x8b4e,0x8b4f,0x8b53,0x8b54,0x8b56,0x8b56,0x8b58,0x8b5a,0x8b5c,0x8b5c,0x8b5e,0x8b5f,0x8b66,0x8b66,0x8b6b,0x8b6c,0x8b6f,0x8b71,0x8b74,0x8b74,0x8b77,0x8b77,0x8b7d,0x8b7d,0x8b7f,0x8b80,0x8b83,0x8b83,0x8b89,0x8b8a,0x8b8c,0x8b8c,0x8b8e,0x8b8e,0x8b90,0x8b90,0x8b92,0x8b93,0x8b96,0x8b96,0x8b9a,0x8b9a,0x8b9c,0x8b9c,0x8b9e,0x8b9e,0x8ba0,0x8ba0,0x8c37,0x8c37,0x8c3f,0x8c3f,0x8c41,0x8c41,0x8c46,0x8c4a,0x8c4c,0x8c4c,0x8c4e,0x8c4e,0x8c50,0x8c50,0x8c55,0x8c56,0x8c5a,0x8c5a,0x8c61,0x8c62,0x8c68,0x8c68,0x8c6a,0x8c6c,0x8c73,0x8c73,0x8c78,0x8c7a,0x8c82,0x8c83,0x8c8a,0x8c8a,0x8c8c,0x8c8d,0x8c93,0x8c94,0x8c98,0x8c98,0x8c9d,0x8ca2,0x8ca7,0x8cac,0x8caf,0x8cb0,0x8cb2,0x8cb4,0x8cb6,0x8cbd,0x8cbf,0x8cc4,0x8cc6,0x8cc8,0x8cca,0x8cca,0x8cd1,0x8cd1,0x8cd3,0x8cd3,0x8cd9,0x8cdc,0x8cde,0x8cde,0x8ce0,0x8ce6,0x8cea,0x8cea,0x8cec,0x8ced,0x8cf0,0x8cf1,0x8cf3,0x8cf4,0x8cfb,0x8cfd,0x8d04,0x8d05,0x8d07,0x8d08,0x8d0a,0x8d0b,0x8d0d,0x8d0d,0x8d0f,0x8d10,0x8d13,0x8d14,0x8d16,0x8d16,0x8d1b,0x8d1b,0x8d1d,0x8d1d,0x8d64,0x8d64,0x8d66,0x8d67,0x8d6b,0x8d6b,0x8d6d,0x8d6e,0x8d70,0x8d70,0x8d73,0x8d74,0x8d76,0x8d77,0x8d81,0x8d81,0x8d85,0x8d85,0x8d8a,0x8d8a,0x8d8e,0x8d8e,0x8d90,0x8d90,0x8d99,0x8d99,0x8da0,0x8da0,0x8da3,0x8da3,0x8da8,0x8da8,0x8dab,0x8dab,0x8db2,0x8db3,0x8dba,0x8dba,0x8dbe,0x8dbe,0x8dc2,0x8dc2,0x8dc6,0x8dc6,0x8dcb,0x8dcc,0x8dce,0x8dcf,0x8dd5,0x8dd7,0x8ddb,0x8ddb,0x8ddd,0x8ddd,0x8ddf,0x8ddf,0x8de1,0x8de1,0x8de3,0x8de3,0x8de8,0x8de8,0x8dea,0x8ded,0x8def,0x8def,0x8df1,0x8df1,0x8df3,0x8df3,0x8dfc,0x8dfc,0x8e06,0x8e06,0x8e08,0x8e0a,0x8e0f,0x8e10,0x8e14,0x8e14,0x8e1d,0x8e1f,0x8e2a,0x8e2a,0x8e30,0x8e30,0x8e34,0x8e36,0x8e3a,0x8e3a,0x8e3d,0x8e3d,0x8e40,0x8e40,0x8e42,0x8e42,0x8e44,0x8e44,0x8e47,0x8e4a,0x8e4c,0x8e4c,0x8e4f,0x8e4f,0x8e55,0x8e55,0x8e59,0x8e59,0x8e5c,0x8e5c,0x8e5f,0x8e60,0x8e63,0x8e64,0x8e72,0x8e72,0x8e74,0x8e74,0x8e76,0x8e76,0x8e7b,0x8e7b,0x8e81,0x8e81,0x8e85,0x8e85,0x8e87,0x8e87,0x8e89,0x8e8b,0x8e8d,0x8e8d,0x8e90,0x8e91,0x8e93,0x8e94,0x8e99,0x8e99,0x8e9e,0x8e9e,0x8ea1,0x8ea1,0x8ea9,0x8eac,0x8eb1,0x8eb1,0x8eb3,0x8eb3,0x8ebe,0x8ebe,0x8ec0,0x8ec0,0x8ec6,0x8ec6,0x8eca,0x8ecd,0x8ed2,0x8ed2,0x8ede,0x8edf,0x8ee2,0x8ee2,0x8ee8,0x8ee8,0x8eeb,0x8eeb,0x8ef8,0x8efc,0x8efe,0x8efe,0x8f03,0x8f03,0x8f05,0x8f05,0x8f07,0x8f09,0x8f12,0x8f15,0x8f1b,0x8f1f,0x8f26,0x8f2a,0x8f2d,0x8f2d,0x8f2f,0x8f30,0x8f33,0x8f33,0x8f38,0x8f39,0x8f3b,0x8f3b,0x8f3e,0x8f40,0x8f42,0x8f42,0x8f44,0x8f46,0x8f49,0x8f49,0x8f4d,0x8f4e,0x8f52,0x8f52,0x8f54,0x8f54,0x8f57,0x8f58,0x8f5d,0x8f5f,0x8f61,0x8f64,0x8f66,0x8f66,0x8f9b,0x8f9c,0x8f9f,0x8f9f,0x8fa2,0x8fa3,0x8fa6,0x8fa6,0x8fa8,0x8fa8,0x8fad,0x8fb2,0x8fb5,0x8fb6,0x8fbb,0x8fbb,0x8fbf,0x8fc0,0x8fc2,0x8fc5,0x8fcd,0x8fce,0x8fd0,0x8fd1,0x8fd3,0x8fd5,0x8fe2,0x8fe2,0x8fe4,0x8fe6,0x8fe8,0x8fe8,0x8fea,0x8fed,0x8ff0,0x8ff0,0x8ff2,0x8ff2,0x8ff4,0x8ff4,0x8ff7,0x8ffa,0x8ffc,0x8ffd,0x8fff,0x9003,0x9005,0x9006,0x9008,0x9008,0x900b,0x9011,0x9014,0x9017,0x9019,0x901a,0x901d,0x9023,0x902e,0x902e,0x9031,0x9032,0x9034,0x9036,0x9038,0x9038,0x903c,0x903c,0x903e,0x903e,0x9041,0x9042,0x9047,0x9047,0x9049,0x904b,0x904d,0x9055,0x9058,0x9059,0x905b,0x905e,0x9060,0x9061,0x9063,0x9063,0x9068,0x9069,0x906c,0x906f,0x9072,0x9072,0x9075,0x9078,0x907a,0x907a,0x907c,0x9085,0x9087,0x9088,0x908a,0x908a,0x908c,0x908d,0x908f,0x9091,0x9095,0x9095,0x9097,0x9099,0x90a0,0x90a0,0x90a2,0x90a3,0x90a6,0x90a6,0x90a8,0x90a8,0x90aa,0x90aa,0x90af,0x90b1,0x90b3,0x90b3,0x90b5,0x90b5,0x90b8,0x90b8,0x90bd,0x90be,0x90c1,0x90c1,0x90c3,0x90c5,0x90ca,0x90ca,0x90ce,0x90ce,0x90dc,0x90de,0x90e1,0x90e2,0x90e8,0x90e8,0x90ea,0x90eb,0x90ed,0x90ed,0x90ef,0x90ef,0x90f3,0x90f5,0x90fd,0x90fd,0x9102,0x9102,0x9112,0x9112,0x9115,0x9117,0x9119,0x9119,0x911e,0x911e,0x9122,0x9123,0x9127,0x9127,0x912d,0x912d,0x9130,0x9132,0x9134,0x9134,0x913d,0x913d,0x9148,0x914e,0x9152,0x9152,0x9156,0x9157,0x9162,0x9165,0x9169,0x916a,0x916c,0x916c,0x9172,0x9172,0x9174,0x9179,0x9183,0x9183,0x9187,0x9187,0x9189,0x9189,0x918b,0x918b,0x918d,0x918d,0x9190,0x9190,0x9192,0x9192,0x919c,0x919c,0x919e,0x919e,0x91a2,0x91a2,0x91aa,0x91ac,0x91ae,0x91af,0x91b1,0x91b2,0x91b4,0x91b5,0x91bc,0x91bc,0x91c0,0x91c1,0x91c3,0x91c3,0x91c5,0x91c7,0x91c9,0x91c9,0x91cb,0x91d1,0x91d7,0x91d8,0x91dc,0x91dd,0x91e3,0x91e7,0x91e9,0x91ea,0x91ed,0x91ed,0x91f5,0x91f5,0x91ff,0x91ff,0x9207,0x9207,0x920d,0x920d,0x9210,0x9212,0x9214,0x9215,0x9217,0x9217,0x921c,0x921c,0x921e,0x921f,0x9226,0x9226,0x9231,0x9231,0x9234,0x9235,0x9237,0x9238,0x923a,0x923a,0x923f,0x9241,0x9244,0x9245,0x9249,0x9249,0x924b,0x924b,0x924d,0x9252,0x9257,0x9257,0x925b,0x925b,0x925e,0x925e,0x9262,0x9262,0x9264,0x9266,0x9277,0x9278,0x927c,0x927c,0x927e,0x927e,0x9280,0x9280,0x9283,0x9283,0x9285,0x9285,0x928b,0x928b,0x9291,0x9291,0x9293,0x9293,0x9295,0x9296,0x9298,0x929c,0x92b3,0x92b4,0x92b6,0x92b7,0x92b9,0x92b9,0x92c6,0x92c6,0x92cc,0x92cc,0x92cf,0x92cf,0x92d1,0x92d2,0x92d5,0x92d5,0x92d7,0x92d7,0x92df,0x92df,0x92e4,0x92e5,0x92ea,0x92ea,0x92f2,0x92f2,0x92f8,0x92fa,0x92fc,0x92fe,0x9300,0x9300,0x9304,0x9304,0x9306,0x9306,0x930f,0x9310,0x9315,0x9315,0x9318,0x931a,0x931e,0x9324,0x9326,0x9328,0x932a,0x932c,0x932e,0x932f,0x9348,0x934b,0x934d,0x934d,0x9351,0x9351,0x9354,0x9354,0x9357,0x9357,0x935b,0x935d,0x9364,0x9365,0x936b,0x936c,0x936e,0x936e,0x9370,0x9370,0x9372,0x9372,0x9375,0x9375,0x937c,0x937c,0x937e,0x937e,0x938a,0x938a,0x938c,0x938c,0x9394,0x9394,0x9396,0x9397,0x939a,0x939b,0x939f,0x93a1,0x93a3,0x93a4,0x93a7,0x93a7,0x93ac,0x93ad,0x93b0,0x93b0,0x93bb,0x93bb,0x93c3,0x93c3,0x93c7,0x93c8,0x93ca,0x93cc,0x93d1,0x93d1,0x93d6,0x93d8,0x93dc,0x93df,0x93e1,0x93e2,0x93e4,0x93e4,0x93e6,0x93e6,0x93e8,0x93e8,0x93f6,0x93f6,0x93f8,0x93f9,0x93fb,0x93fb,0x9403,0x9404,0x940f,0x9410,0x9413,0x9414,0x9418,0x9419,0x9425,0x9425,0x942a,0x942b,0x9435,0x9436,0x9438,0x9438,0x943a,0x943a,0x9442,0x9442,0x9444,0x9444,0x944a,0x944a,0x944c,0x944c,0x9451,0x9452,0x9455,0x9455,0x945b,0x945b,0x945e,0x945e,0x9460,0x9460,0x9462,0x9463,0x946a,0x946b,0x9470,0x9472,0x9475,0x9475,0x9477,0x9477,0x947c,0x947f,0x9485,0x9485,0x9577,0x9578,0x957f,0x9580,0x9583,0x9583,0x9588,0x958b,0x958e,0x958f,0x9591,0x9594,0x9598,0x9598,0x959c,0x959c,0x959f,0x95a0,0x95a2,0x95a5,0x95a8,0x95a9,0x95ab,0x95ad,0x95b1,0x95b1,0x95b6,0x95b6,0x95b9,0x95b9,0x95bb,0x95be,0x95c3,0x95c3,0x95c7,0x95c8,0x95ca,0x95cd,0x95d3,0x95d6,0x95da,0x95da,0x95dc,0x95dc,0x95de,0x95de,0x95e0,0x95e2,0x95e5,0x95e5,0x95e8,0x95e8,0x961c,0x961d,0x9621,0x9621,0x9624,0x9624,0x9627,0x962a,0x962d,0x962f,0x9632,0x9632,0x963b,0x963b,0x963f,0x9640,0x9642,0x9642,0x9644,0x9644,0x964b,0x964d,0x9650,0x9650,0x9654,0x9654,0x9656,0x9656,0x9658,0x9658,0x965b,0x965f,0x9661,0x9664,0x966a,0x966a,0x966c,0x966c,0x9670,0x9670,0x9672,0x9679,0x967c,0x967d,0x9684,0x9686,0x968a,0x968b,0x968d,0x968f,0x9691,0x9691,0x9694,0x9695,0x9697,0x9698,0x969b,0x969c,0x96a3,0x96a4,0x96a7,0x96aa,0x96b0,0x96b1,0x96b3,0x96b4,0x96b6,0x96b9,0x96bb,0x96bc,0x96c0,0x96c1,0x96c4,0x96c7,0x96c9,0x96c9,0x96cb,0x96ce,0x96d5,0x96d6,0x96d9,0x96de,0x96e2,0x96e3,0x96e8,0x96ea,0x96ef,0x96f0,0x96f2,0x96f2,0x96f6,0x96f7,0x96f9,0x96fb,0x9700,0x9700,0x9704,0x9709,0x970c,0x970f,0x9711,0x9711,0x9713,0x9714,0x9716,0x9716,0x9719,0x9719,0x971c,0x971c,0x971e,0x971e,0x9723,0x9723,0x9726,0x9727,0x972a,0x972a,0x9730,0x9730,0x9732,0x9732,0x9738,0x9739,0x973d,0x973d,0x9742,0x9742,0x9744,0x9744,0x9746,0x9746,0x9748,0x9749,0x974c,0x974c,0x9751,0x9752,0x9755,0x9756,0x9758,0x975e,0x9760,0x9762,0x9766,0x9766,0x9768,0x9769,0x976d,0x976d,0x9773,0x9775,0x9777,0x9777,0x977a,0x977a,0x977c,0x977c,0x9780,0x9781,0x9784,0x9785,0x978b,0x978b,0x978d,0x978d,0x978f,0x978f,0x9798,0x9798,0x97a0,0x97a0,0x97a3,0x97a3,0x97a6,0x97a6,0x97a8,0x97a8,0x97ab,0x97ad,0x97b1,0x97b1,0x97b4,0x97b4,0x97b8,0x97b9,0x97c1,0x97c1,0x97c3,0x97c3,0x97c6,0x97c6,0x97cb,0x97cd,0x97d0,0x97d0,0x97d3,0x97d3,0x97d9,0x97d9,0x97dc,0x97de,0x97e0,0x97e1,0x97e6,0x97e6,0x97ed,0x97ee,0x97f1,0x97f3,0x97f5,0x97f6,0x97fa,0x97fb,0x97fe,0x9803,0x9805,0x9806,0x9808,0x9808,0x980a,0x980a,0x980c,0x9813,0x9816,0x9818,0x981e,0x981e,0x9821,0x9821,0x9823,0x9824,0x9826,0x9826,0x982b,0x982b,0x982d,0x982e,0x9830,0x9830,0x9832,0x9832,0x9837,0x9839,0x983b,0x983c,0x983f,0x983f,0x9842,0x9842,0x9846,0x9848,0x984b,0x984e,0x9852,0x9855,0x9858,0x985a,0x985c,0x985c,0x985e,0x985e,0x9865,0x9867,0x986b,0x986b,0x986f,0x9871,0x9873,0x9875,0x98a8,0x98a8,0x98ad,0x98ad,0x98af,0x98af,0x98b1,0x98b2,0x98b6,0x98b6,0x98ba,0x98ba,0x98bc,0x98bc,0x98bf,0x98bf,0x98c2,0x98c2,0x98c4,0x98c4,0x98c6,0x98c7,0x98c9,0x98c9,0x98cb,0x98cb,0x98ce,0x98ce,0x98db,0x98dc,0x98de,0x98e2,0x98e6,0x98e7,0x98ea,0x98eb,0x98ed,0x98ef,0x98f1,0x98f1,0x98f4,0x98f4,0x98fb,0x98fe,0x9903,0x9903,0x9909,0x990a,0x990c,0x990c,0x9910,0x9910,0x9912,0x9915,0x9918,0x9918,0x991a,0x991a,0x991e,0x991e,0x9920,0x9920,0x9926,0x9928,0x992a,0x992a,0x992c,0x992c,0x992e,0x992e,0x9930,0x9931,0x9933,0x9933,0x9939,0x9939,0x993c,0x993d,0x9942,0x9942,0x9945,0x9945,0x9948,0x9949,0x994b,0x994d,0x9950,0x9952,0x9954,0x9955,0x9957,0x9957,0x995c,0x995c,0x995e,0x995e,0x9963,0x9963,0x9996,0x9999,0x999c,0x999d,0x999f,0x999f,0x99a1,0x99a1,0x99a3,0x99a3,0x99a5,0x99a5,0x99a7,0x99a8,0x99aa,0x99aa,0x99ac,0x99ae,0x99b0,0x99b1,0x99b3,0x99b4,0x99b6,0x99b6,0x99b9,0x99b9,0x99c1,0x99c1,0x99c4,0x99c5,0x99c8,0x99c9,0x99cf,0x99d2,0x99d5,0x99d5,0x99d8,0x99d9,0x99db,0x99df,0x99e2,0x99e2,0x99e8,0x99e8,0x99ea,0x99ea,0x99ed,0x99ee,0x99f1,0x99f1,0x99f8,0x99f8,0x99fa,0x99fb,0x99fd,0x99fd,0x99ff,0x99ff,0x9a01,0x9a05,0x9a08,0x9a08,0x9a0b,0x9a0b,0x9a0d,0x9a0f,0x9a11,0x9a11,0x9a16,0x9a16,0x9a18,0x9a19,0x9a1b,0x9a1b,0x9a2b,0x9a2b,0x9a2d,0x9a2d,0x9a30,0x9a30,0x9a35,0x9a38,0x9a3e,0x9a3e,0x9a40,0x9a45,0x9a4a,0x9a4a,0x9a4c,0x9a4f,0x9a52,0x9a52,0x9a55,0x9a55,0x9a57,0x9a58,0x9a5a,0x9a5b,0x9a5f,0x9a5f,0x9a62,0x9a62,0x9a64,0x9a65,0x9a69,0x9a6a,0x9a6c,0x9a6c,0x9aa8,0x9aa8,0x9aaa,0x9aaa,0x9ab0,0x9ab0,0x9ab8,0x9ab9,0x9abc,0x9abc,0x9abf,0x9ac0,0x9ac6,0x9ac6,0x9acf,0x9acf,0x9ad1,0x9ad1,0x9ad3,0x9ad4,0x9ad6,0x9ad8,0x9adf,0x9adf,0x9ae1,0x9ae1,0x9ae3,0x9ae3,0x9ae5,0x9ae6,0x9aeb,0x9aeb,0x9aed,0x9aee,0x9af0,0x9af0,0x9af2,0x9af2,0x9af4,0x9af4,0x9af9,0x9afb,0x9afd,0x9afd,0x9b02,0x9b02,0x9b05,0x9b06,0x9b0a,0x9b0b,0x9b0d,0x9b0d,0x9b10,0x9b10,0x9b12,0x9b12,0x9b16,0x9b16,0x9b18,0x9b1a,0x9b1f,0x9b1f,0x9b22,0x9b23,0x9b25,0x9b25,0x9b27,0x9b2a,0x9b2e,0x9b2f,0x9b31,0x9b32,0x9b3a,0x9b3a,0x9b3c,0x9b3c,0x9b41,0x9b45,0x9b48,0x9b48,0x9b4b,0x9b4b,0x9b4d,0x9b4f,0x9b51,0x9b51,0x9b54,0x9b54,0x9b58,0x9b58,0x9b5a,0x9b5a,0x9b66,0x9b66,0x9b6f,0x9b6f,0x9b74,0x9b74,0x9b80,0x9b80,0x9b83,0x9b83,0x9b8e,0x9b8e,0x9b90,0x9b93,0x9b97,0x9b97,0x9b9f,0x9b9f,0x9ba7,0x9ba8,0x9baa,0x9bab,0x9bad,0x9bae,0x9bb9,0x9bb9,0x9bc1,0x9bc1,0x9bc6,0x9bc6,0x9bc9,0x9bca,0x9bd4,0x9bd4,0x9bd6,0x9bd6,0x9bdb,0x9bdb,0x9be2,0x9be2,0x9be4,0x9be4,0x9be8,0x9be8,0x9bf7,0x9bf7,0x9c08,0x9c08,0x9c0a,0x9c0a,0x9c0c,0x9c0d,0x9c10,0x9c10,0x9c12,0x9c13,0x9c15,0x9c15,0x9c24,0x9c25,0x9c2d,0x9c2f,0x9c31,0x9c32,0x9c35,0x9c35,0x9c39,0x9c3b,0x9c3e,0x9c3e,0x9c47,0x9c47,0x9c49,0x9c49,0x9c4f,0x9c4f,0x9c52,0x9c53,0x9c57,0x9c57,0x9c60,0x9c60,0x9c63,0x9c63,0x9c67,0x9c67,0x9c78,0x9c78,0x9c7b,0x9c7c,0x9ce5,0x9ce7,0x9ce9,0x9ce9,0x9cf3,0x9cf4,0x9cf6,0x9cf6,0x9d03,0x9d03,0x9d06,0x9d09,0x9d0c,0x9d0c,0x9d12,0x9d12,0x9d15,0x9d15,0x9d18,0x9d19,0x9d1b,0x9d1b,0x9d1e,0x9d1f,0x9d23,0x9d23,0x9d25,0x9d26,0x9d28,0x9d28,0x9d2f,0x9d30,0x9d36,0x9d36,0x9d3b,0x9d3b,0x9d41,0x9d42,0x9d44,0x9d44,0x9d51,0x9d51,0x9d53,0x9d54,0x9d5d,0x9d5e,0x9d60,0x9d61,0x9d66,0x9d66,0x9d69,0x9d69,0x9d6c,0x9d6c,0x9d6f,0x9d70,0x9d72,0x9d72,0x9d77,0x9d77,0x9d7b,0x9d7b,0x9d7e,0x9d7e,0x9d84,0x9d84,0x9d89,0x9d8a,0x9d96,0x9d96,0x9d9a,0x9d9a,0x9da1,0x9da1,0x9da4,0x9da4,0x9da9,0x9da9,0x9dac,0x9dac,0x9daf,0x9daf,0x9db4,0x9db5,0x9db8,0x9db9,0x9dbb,0x9dbb,0x9dbf,0x9dbf,0x9dc1,0x9dc2,0x9dc4,0x9dc4,0x9dc7,0x9dc7,0x9dd3,0x9dd3,0x9dd6,0x9dd7,0x9dd9,0x9dd9,0x9de6,0x9de6,0x9de9,0x9deb,0x9df0,0x9df3,0x9df8,0x9dfa,0x9dfd,0x9dfd,0x9dff,0x9dff,0x9e07,0x9e07,0x9e0f,0x9e0f,0x9e15,0x9e15,0x9e1a,0x9e1f,0x9e75,0x9e75,0x9e77,0x9e77,0x9e79,0x9e7b,0x9e7d,0x9e7d,0x9e7f,0x9e80,0x9e82,0x9e82,0x9e84,0x9e84,0x9e8b,0x9e8c,0x9e8f,0x9e8f,0x9e91,0x9e93,0x9e97,0x9e98,0x9e9d,0x9e9f,0x9ea4,0x9ea6,0x9ea9,0x9eaa,0x9eaf,0x9eaf,0x9eb4,0x9eb5,0x9ebb,0x9ebb,0x9ebd,0x9ebf,0x9ec3,0x9ec5,0x9ecc,0x9ed1,0x9ed4,0x9ed4,0x9ed6,0x9ed6,0x9ed8,0x9ed8,0x9eda,0x9ede,0x9ee0,0x9ee0,0x9ee5,0x9ee5,0x9ee8,0x9ee8,0x9eee,0x9eef,0x9ef2,0x9ef2,0x9ef4,0x9ef7,0x9ef9,0x9f00,0x9f02,0x9f02,0x9f04,0x9f04,0x9f07,0x9f0a,0x9f0e,0x9f0e,0x9f10,0x9f10,0x9f13,0x9f14,0x9f17,0x9f17,0x9f19,0x9f19,0x9f20,0x9f20,0x9f22,0x9f22,0x9f2b,0x9f2c,0x9f2f,0x9f2f,0x9f34,0x9f34,0x9f38,0x9f39,0x9f3b,0x9f3b,0x9f3e,0x9f3e,0x9f4a,0x9f4b,0x9f4e,0x9f4e,0x9f50,0x9f50,0x9f52,0x9f52,0x9f54,0x9f55,0x9f57,0x9f57,0x9f5f,0x9f61,0x9f66,0x9f67,0x9f69,0x9f6c,0x9f72,0x9f72,0x9f76,0x9f77,0x9f7f,0x9f7f,0x9f8d,0x9f8e,0x9f90,0x9f92,0x9f94,0x9f95,0x9f99,0x9f99,0x9f9c,0x9f9d,0x9f9f,0x9fa0,0x9fa2,0x9fa2,0x9fa5,0x9fa5,0xa960,0xa97c,0xac00,0xd7a3,0xd7b0,0xd7c6,0xd7cb,0xd7fb,0xf900,0xf95e,0xf960,0xf9a9,0xf9ab,0xfa0b,0xfa12,0xfa12,0xfa15,0xfa17,0xfa19,0xfa1e,0xfa22,0xfa22,0xfa26,0xfa26,0xfa2a,0xfa2c,0xfa2e,0xfa31,0xfa33,0xfa3d,0xfa3f,0xfa3f,0xfa41,0xfa41,0xfa43,0xfa55,0xfa57,0xfa57,0xfa59,0xfa68,0xfa6a,0xfa6a,0xfb00,0xfb04,0xfe10,0xfe19,0xfe30,0xfe52,0xfe54,0xfe66,0xfe68,0xfe6b,0xff01,0xffbe,0xffc2,0xffc7,0xffca,0xffcf,0xffd2,0xffd7,0xffda,0xffdc,0xffe0,0xffe6,0xffe8,0xffee,0x1f100,0x1f10c,0x1f110,0x1f16c,0x1f170,0x1f1ac,0x1f200,0x1f202,0x1f210,0x1f23b,0x1f240,0x1f248,0x1f250,0x1f251,0x200d7,0x200d7,0x2012c,0x2012c,0x205ca,0x205ca,0x20628,0x20628,0x20984,0x20984,0x21155,0x21155,0x2128d,0x2128d,0x21594,0x21594,0x21727,0x21727,0x21f5c,0x21f5c,0x224b0,0x224b0,0x224ed,0x224ed,0x2294f,0x2294f,0x22c6f,0x22c6f,0x22ee0,0x22ee0,0x230fd,0x230fd,0x23343,0x23343,0x2363b,0x2363b,0x23ad9,0x23ad9,0x242f1,0x242f1,0x2439d,0x2439d,0x248e9,0x248e9,0x248f0,0x248f0,0x24a01,0x24a01,0x24a12,0x24a12,0x25055,0x25055,0x2533e,0x2533e,0x253b5,0x253b5,0x253fe,0x253fe,0x25832,0x25832,0x2583a,0x2583a,0x25874,0x25874,0x25978,0x25978,0x25ad7,0x25ad7,0x25b97,0x25b97,0x25e44,0x25e44,0x26057,0x26057,0x265a4,0x265a4,0x267d8,0x267d8,0x26951,0x26951,0x27144,0x27144,0x275ff,0x275ff,0x27625,0x27625,0x278b2,0x278b2,0x27a51,0x27a51,0x27b02,0x27b02,0x27cef,0x27cef,0x27e7d,0x27e7d,0x27f1b,0x27f1b,0x27fb7,0x27fb7,0x283f6,0x283f6,0x284dc,0x284dc,0x28d8a,0x28d8a,0x28da1,0x28da1,0x28e0f,0x28e0f,0x291d5,0x291d5,0x291e3,0x291e3,0x294de,0x294de,0x29509,0x29509,0x2967f,0x2967f,0x29810,0x29810,0x2983b,0x2983b,0x2a2ad,0x2a2ad,0x2a4d0,0x2a4d0,0x2a664,0x2a664,0x2c115,0x2c115,0x2c7d3,0x2c7d3,0x2d544,0x2d544,0x2e569,0x2e569,0x2f815,0x2f815,0x2f818,0x2f818,0x2f81a,0x2f81a,0x2f82c,0x2f82c,0x2f833,0x2f833,0x2f852,0x2f852,0x2f862,0x2f862,0x2f877,0x2f877,0x2f884,0x2f884,0x2f8b2,0x2f8b2,0x2f8ed,0x2f8ed,0x2f8fc,0x2f8fc,0x2f920,0x2f920,0x2f96c,0x2f96c,0x2f9d0,0x2f9d0,0x2f9df,0x2f9df,0x30729,0x30729,0x30ede,0x30ede,]), + NotoFont.fromFlatRanges('Noto Sans Kaithi', 'http://fonts.gstatic.com/s/notosanskaithi/v15/buEtppS9f8_vkXadMBJJu0tWjLwjQi0KdoZIKlo.ttf', [0x20,0x20,0x2d,0x2d,0xa0,0xa0,0x966,0x96f,0x200b,0x200d,0x2010,0x2010,0x25cc,0x25cc,0xa830,0xa839,0x11080,0x110c1,0x110cd,0x110cd,]), + NotoFont.fromFlatRanges('Noto Sans Kannada', 'http://fonts.gstatic.com/s/notosanskannada/v21/8vIs7xs32H97qzQKnzfeXycxXZyUmySvZWItmf1fe6TVmgop9ndpS-BqHEyGrDvNzSIMLsPKrkY.ttf', [0x20,0x7e,0xa0,0xa3,0xa5,0xa5,0xa7,0xab,0xad,0xb0,0xb4,0xb4,0xb6,0xb8,0xba,0xbb,0xbf,0x107,0x10a,0x113,0x116,0x11b,0x11e,0x123,0x126,0x127,0x12a,0x12b,0x12e,0x133,0x136,0x137,0x139,0x13e,0x141,0x148,0x14a,0x14d,0x150,0x15b,0x15e,0x161,0x164,0x165,0x16a,0x17e,0x1cd,0x1ce,0x218,0x21b,0x237,0x237,0x2c6,0x2c7,0x2c9,0x2c9,0x2d8,0x2dd,0x300,0x304,0x306,0x308,0x30a,0x30c,0x312,0x312,0x326,0x328,0x951,0x952,0x964,0x965,0xc80,0xc8c,0xc8e,0xc90,0xc92,0xca8,0xcaa,0xcb3,0xcb5,0xcb9,0xcbc,0xcc4,0xcc6,0xcc8,0xcca,0xccd,0xcd5,0xcd6,0xcde,0xcde,0xce0,0xce3,0xce6,0xcef,0xcf1,0xcf2,0x1cd0,0x1cd0,0x1cd2,0x1cd2,0x1cda,0x1cda,0x1cf2,0x1cf2,0x1cf4,0x1cf5,0x1e80,0x1e85,0x1e9e,0x1e9e,0x1ef2,0x1ef3,0x200b,0x200d,0x2010,0x2010,0x2013,0x2014,0x2018,0x201a,0x201c,0x201e,0x2022,0x2022,0x2026,0x2026,0x2039,0x203a,0x20ac,0x20ac,0x20b9,0x20b9,0x2122,0x2122,0x2212,0x2212,0x25cc,0x25cc,0xa830,0xa835,]), + NotoFont.fromFlatRanges('Noto Sans Kayah Li', 'http://fonts.gstatic.com/s/notosanskayahli/v18/B50nF61OpWTRcGrhOVJJwOMXdca6Yecki3E06x2jVTX3WCc3CZH4EXLuKVM.ttf', [0x20,0x20,0x2d,0x2d,0xa0,0xa0,0x200c,0x200d,0x2010,0x2010,0x25cc,0x25cc,0xa900,0xa92f,]), + NotoFont.fromFlatRanges('Noto Sans Kharoshthi', 'http://fonts.gstatic.com/s/notosanskharoshthi/v15/Fh4qPiLjKS30-P4-pGMMXCCfvkc5Vd7KE5z4rFyx5mR1.ttf', [0x20,0x20,0x2d,0x2d,0xa0,0xa0,0x200b,0x200d,0x2010,0x2010,0x25cc,0x25cc,0x10a00,0x10a03,0x10a05,0x10a06,0x10a0c,0x10a13,0x10a15,0x10a17,0x10a19,0x10a35,0x10a38,0x10a3a,0x10a3f,0x10a48,0x10a50,0x10a58,]), + NotoFont.fromFlatRanges('Noto Sans Khmer', 'http://fonts.gstatic.com/s/notosanskhmer/v18/ijw3s5roRME5LLRxjsRb-gssOenAyendxrgV2c-Zw-9vbVUti_Z_dWgtWYuNAJz4kAbrddiA.ttf', [0x20,0x7e,0xa0,0xa3,0xa5,0xa5,0xa7,0xab,0xad,0xb0,0xb4,0xb4,0xb6,0xb8,0xba,0xbb,0xbf,0x107,0x10a,0x113,0x116,0x11b,0x11e,0x123,0x126,0x127,0x12a,0x12b,0x12e,0x133,0x136,0x137,0x139,0x13e,0x141,0x148,0x14a,0x14d,0x150,0x15b,0x15e,0x161,0x164,0x165,0x16a,0x17e,0x1cd,0x1ce,0x218,0x21b,0x237,0x237,0x2c6,0x2c7,0x2c9,0x2c9,0x2d8,0x2dd,0x300,0x304,0x306,0x308,0x30a,0x30c,0x312,0x312,0x326,0x328,0x1780,0x17dd,0x17e0,0x17e9,0x17f0,0x17f9,0x19e0,0x19ff,0x1e80,0x1e85,0x1e9e,0x1e9e,0x1ef2,0x1ef3,0x200b,0x200d,0x2010,0x2010,0x2013,0x2014,0x2018,0x201a,0x201c,0x201e,0x2022,0x2022,0x2026,0x2026,0x2039,0x203a,0x20ac,0x20ac,0x2122,0x2122,0x2212,0x2212,0x25cc,0x25cc,]), + NotoFont.fromFlatRanges('Noto Sans Khojki', 'http://fonts.gstatic.com/s/notosanskhojki/v15/-nFnOHM29Oofr2wohFbTuPPKVWpmK_d709jy92k.ttf', [0x20,0x20,0xa0,0xa0,0xae6,0xaef,0x200c,0x200d,0x25cc,0x25cc,0xa830,0xa839,0x11200,0x11211,0x11213,0x1123e,]), + NotoFont.fromFlatRanges('Noto Sans Khudawadi', 'http://fonts.gstatic.com/s/notosanskhudawadi/v15/fdNi9t6ZsWBZ2k5ltHN73zZ5hc8HANlHIjRnVVXz9MY.ttf', [0x20,0x20,0xa0,0xa0,0x964,0x965,0x200c,0x200d,0x2013,0x2014,0x25cc,0x25cc,0xa830,0xa839,0x112b0,0x112ea,0x112f0,0x112f9,]), + NotoFont.fromFlatRanges('Noto Sans Lao', 'http://fonts.gstatic.com/s/notosanslao/v24/bx6lNx2Ol_ixgdYWLm9BwxM3NW6BOkuf763Clj73CiQ_J1Djx9pidOt4ccbdf5MK3riB2w.ttf', [0x20,0x7e,0xa0,0xa3,0xa5,0xa5,0xa7,0xab,0xae,0xb0,0xb4,0xb4,0xb6,0xb8,0xba,0xbb,0xbf,0x107,0x10a,0x113,0x116,0x11b,0x11e,0x123,0x126,0x127,0x12a,0x12b,0x12e,0x133,0x136,0x137,0x139,0x13e,0x141,0x148,0x14a,0x14d,0x150,0x15b,0x15e,0x161,0x164,0x165,0x16a,0x17e,0x1cd,0x1ce,0x218,0x21b,0x237,0x237,0x2c6,0x2c7,0x2c9,0x2c9,0x2d8,0x2dd,0x300,0x304,0x306,0x308,0x30a,0x30c,0x312,0x312,0x326,0x328,0xe81,0xe82,0xe84,0xe84,0xe87,0xe88,0xe8a,0xe8a,0xe8d,0xe8d,0xe94,0xe97,0xe99,0xe9f,0xea1,0xea3,0xea5,0xea5,0xea7,0xea7,0xeaa,0xeab,0xead,0xeb9,0xebb,0xebd,0xec0,0xec4,0xec6,0xec6,0xec8,0xecd,0xed0,0xed9,0xedc,0xedf,0x1e80,0x1e85,0x1e9e,0x1e9e,0x1ef2,0x1ef3,0x200b,0x200d,0x2013,0x2014,0x2018,0x201a,0x201c,0x201e,0x2022,0x2022,0x2026,0x2026,0x2039,0x203a,0x20ac,0x20ad,0x2122,0x2122,0x2212,0x2212,0x25cc,0x25cc,]), + NotoFont.fromFlatRanges('Noto Sans Lepcha', 'http://fonts.gstatic.com/s/notosanslepcha/v15/0QI7MWlB_JWgA166SKhu05TekNS32AJstqBXgd4.ttf', [0x20,0x20,0xa0,0xa0,0x1c00,0x1c37,0x1c3b,0x1c49,0x1c4d,0x1c4f,0x200b,0x200d,0x25cc,0x25cc,]), + NotoFont.fromFlatRanges('Noto Sans Limbu', 'http://fonts.gstatic.com/s/notosanslimbu/v15/3JnlSDv90Gmq2mrzckOBBRRoNJVj0MF3OHRDnA.ttf', [0x20,0x20,0xa0,0xa0,0x965,0x965,0x1900,0x191e,0x1920,0x192b,0x1930,0x193b,0x1940,0x1940,0x1944,0x194f,0x200b,0x200d,0x25cc,0x25cc,]), + NotoFont.fromFlatRanges('Noto Sans Linear A', 'http://fonts.gstatic.com/s/notosanslineara/v16/oPWS_l16kP4jCuhpgEGmwJOiA18FZj22zmHQAGQicw.ttf', [0x20,0x20,0xa0,0xa0,0x10600,0x10736,0x10740,0x10755,0x10760,0x10767,]), + NotoFont.fromFlatRanges('Noto Sans Linear B', 'http://fonts.gstatic.com/s/notosanslinearb/v15/HhyJU4wt9vSgfHoORYOiXOckKNB737IV3BkFTq4EPw.ttf', [0x20,0x20,0xa0,0xa0,0x10000,0x1000b,0x1000d,0x10026,0x10028,0x1003a,0x1003c,0x1003d,0x1003f,0x1004d,0x10050,0x1005d,0x10080,0x100fa,0x10100,0x10102,0x10107,0x10133,0x10137,0x1013f,]), + NotoFont.fromFlatRanges('Noto Sans Lisu', 'http://fonts.gstatic.com/s/notosanslisu/v21/uk-3EGO3o6EruUbnwovcYhz6kh57_nqbcTdjJnHP2Vwt29IlxkVdig.ttf', [0x20,0x7e,0xa0,0xa3,0xa5,0xa5,0xa7,0xab,0xae,0xb0,0xb4,0xb4,0xb6,0xb8,0xba,0xbb,0xbf,0x107,0x10a,0x113,0x116,0x11b,0x11e,0x123,0x126,0x127,0x12a,0x12b,0x12e,0x133,0x136,0x137,0x139,0x13e,0x141,0x148,0x14a,0x14d,0x150,0x15b,0x15e,0x161,0x164,0x165,0x16a,0x17e,0x1cd,0x1ce,0x218,0x21b,0x237,0x237,0x2bc,0x2bc,0x2c6,0x2c7,0x2c9,0x2c9,0x2cd,0x2cd,0x2d8,0x2dd,0x300,0x304,0x306,0x308,0x30a,0x30c,0x312,0x312,0x326,0x328,0x1e80,0x1e85,0x1e9e,0x1e9e,0x1ef2,0x1ef3,0x2010,0x2010,0x2013,0x2014,0x2018,0x201a,0x201c,0x201e,0x2022,0x2022,0x2026,0x2026,0x2039,0x203a,0x20ac,0x20ac,0x2122,0x2122,0x2212,0x2212,0x25cc,0x25cc,0x300a,0x300b,0xa4d0,0xa4ff,0x11fb0,0x11fb0,]), + NotoFont.fromFlatRanges('Noto Sans Lycian', 'http://fonts.gstatic.com/s/notosanslycian/v15/QldVNSNMqAsHtsJ7UmqxBQA9r8wA5_naCJwn00E.ttf', [0x20,0x20,0xa0,0xa0,0x10280,0x1029c,]), + NotoFont.fromFlatRanges('Noto Sans Lydian', 'http://fonts.gstatic.com/s/notosanslydian/v15/c4m71mVzGN7s8FmIukZJ1v4ZlcPReUPXMoIjEQI.ttf', [0x20,0x20,0xa0,0xa0,0x10920,0x10939,0x1093f,0x1093f,]), + NotoFont.fromFlatRanges('Noto Sans Mahajani', 'http://fonts.gstatic.com/s/notosansmahajani/v15/-F6sfiVqLzI2JPCgQBnw60Agp0JrvD5Fh8ARHNh4zg.ttf', [0x20,0x20,0xa0,0xa0,0x964,0x96f,0x200c,0x200d,0x25cc,0x25cc,0xa830,0xa839,0x11150,0x11176,]), + NotoFont.fromFlatRanges('Noto Sans Malayalam', 'http://fonts.gstatic.com/s/notosansmalayalam/v21/sJoi3K5XjsSdcnzn071rL37lpAOsUThnDZIfPdbeSNzVakglNM-Qw8EaeB8Nss-_RuD9BFzEr6HxEA.ttf', [0x20,0x7e,0xa0,0xa3,0xa5,0xa5,0xa7,0xab,0xad,0xb0,0xb4,0xb4,0xb6,0xb8,0xba,0xbb,0xbf,0x107,0x10a,0x113,0x116,0x11b,0x11e,0x123,0x126,0x127,0x12a,0x12b,0x12e,0x133,0x136,0x137,0x139,0x13e,0x141,0x148,0x14a,0x14d,0x150,0x15b,0x15e,0x161,0x164,0x165,0x16a,0x17e,0x1cd,0x1ce,0x218,0x21b,0x237,0x237,0x2c6,0x2c7,0x2c9,0x2c9,0x2d8,0x2dd,0x300,0x304,0x306,0x308,0x30a,0x30c,0x312,0x312,0x323,0x323,0x326,0x328,0x951,0x952,0x964,0x965,0xd00,0xd0c,0xd0e,0xd10,0xd12,0xd44,0xd46,0xd48,0xd4a,0xd4f,0xd54,0xd63,0xd66,0xd7f,0x1cda,0x1cda,0x1e80,0x1e85,0x1e9e,0x1e9e,0x1ef2,0x1ef3,0x200b,0x200d,0x2010,0x2010,0x2013,0x2014,0x2018,0x201a,0x201c,0x201e,0x2022,0x2022,0x2026,0x2026,0x2039,0x203a,0x20ac,0x20ac,0x20b9,0x20b9,0x2122,0x2122,0x2212,0x2212,0x25cc,0x25cc,0xa830,0xa832,]), + NotoFont.fromFlatRanges('Noto Sans Mandaic', 'http://fonts.gstatic.com/s/notosansmandaic/v15/cIfnMbdWt1w_HgCcilqhKQBo_OsMI5_A_gMk0izH.ttf', [0x20,0x20,0xa0,0xa0,0x640,0x640,0x840,0x85b,0x85e,0x85e,0x200c,0x200d,0x25cc,0x25cc,]), + NotoFont.fromFlatRanges('Noto Sans Manichaean', 'http://fonts.gstatic.com/s/notosansmanichaean/v15/taiVGntiC4--qtsfi4Jp9-_GkPZZCcrfekqCNTtFCtdX.ttf', [0x20,0x20,0xa0,0xa0,0x640,0x640,0x200c,0x200d,0x25cc,0x25cc,0xfe00,0xfe00,0x10ac0,0x10ae6,0x10aeb,0x10af6,]), + NotoFont.fromFlatRanges('Noto Sans Marchen', 'http://fonts.gstatic.com/s/notosansmarchen/v15/aFTO7OZ_Y282EP-WyG6QTOX_C8WZMHhPk652ZaHk.ttf', [0x20,0x20,0xa0,0xa0,0x25cc,0x25cc,0x11c70,0x11c8f,0x11c92,0x11ca7,0x11ca9,0x11cb6,]), + NotoFont.fromFlatRanges('Noto Sans Masaram Gondi', 'http://fonts.gstatic.com/s/notosansmasaramgondi/v15/6xK_dThFKcWIu4bpRBjRYRV7KZCbUq6n_1kPnuGe7RI9WSWX.ttf', [0x20,0x22,0x25,0x25,0x27,0x2f,0x3a,0x3f,0xa0,0xa0,0xd7,0xd7,0xf7,0xf7,0x964,0x965,0x2018,0x2019,0x201c,0x201d,0x2026,0x2026,0x2212,0x2212,0x25cc,0x25cc,0x11d00,0x11d06,0x11d08,0x11d09,0x11d0b,0x11d36,0x11d3a,0x11d3a,0x11d3c,0x11d3d,0x11d3f,0x11d47,0x11d50,0x11d59,]), + NotoFont.fromFlatRanges('Noto Sans Math', 'http://fonts.gstatic.com/s/notosansmath/v15/7Aump_cpkSecTWaHRlH2hyV5UHkG-V048PW0.ttf', [0x20,0x7e,0xa0,0xa0,0xa7,0xa7,0xac,0xac,0xb1,0xb1,0xd7,0xd7,0xf7,0xf7,0x302,0x303,0x305,0x305,0x307,0x308,0x330,0x330,0x391,0x3a1,0x3a3,0x3a9,0x3b1,0x3c9,0x3d1,0x3d1,0x3d5,0x3d6,0x3f0,0x3f1,0x3f4,0x3f5,0x2032,0x2037,0x2057,0x2057,0x20d0,0x20dc,0x20e1,0x20e1,0x20e5,0x20ef,0x2102,0x2102,0x210a,0x210e,0x2110,0x2112,0x2115,0x2115,0x2119,0x211d,0x2124,0x2124,0x2128,0x2128,0x212c,0x212d,0x212f,0x2131,0x2133,0x2138,0x213c,0x2140,0x2145,0x2149,0x2190,0x21ae,0x21b0,0x21e5,0x21f1,0x21f2,0x21f4,0x22ff,0x2308,0x230b,0x2310,0x2310,0x2319,0x2319,0x231c,0x2321,0x2336,0x237a,0x237c,0x237c,0x2395,0x2395,0x239b,0x23b6,0x23d0,0x23d0,0x23dc,0x23e1,0x2474,0x2475,0x25af,0x25af,0x25b3,0x25b3,0x25b7,0x25b7,0x25bd,0x25bd,0x25c1,0x25c1,0x25ca,0x25ca,0x25cc,0x25cc,0x25fb,0x25fb,0x266d,0x266f,0x27c0,0x27ff,0x2900,0x2aff,0x2b0e,0x2b11,0x2b30,0x2b4c,0x2bfe,0x2bfe,0xff5b,0xff5b,0xff5d,0xff5d,0x1d400,0x1d454,0x1d456,0x1d49c,0x1d49e,0x1d49f,0x1d4a2,0x1d4a2,0x1d4a5,0x1d4a6,0x1d4a9,0x1d4ac,0x1d4ae,0x1d4b9,0x1d4bb,0x1d4bb,0x1d4bd,0x1d4c3,0x1d4c5,0x1d505,0x1d507,0x1d50a,0x1d50d,0x1d514,0x1d516,0x1d51c,0x1d51e,0x1d539,0x1d53b,0x1d53e,0x1d540,0x1d544,0x1d546,0x1d546,0x1d54a,0x1d550,0x1d552,0x1d6a5,0x1d6a8,0x1d7cb,0x1d7ce,0x1d7ff,0x1ee00,0x1ee03,0x1ee05,0x1ee1f,0x1ee21,0x1ee22,0x1ee24,0x1ee24,0x1ee27,0x1ee27,0x1ee29,0x1ee32,0x1ee34,0x1ee37,0x1ee39,0x1ee39,0x1ee3b,0x1ee3b,0x1ee42,0x1ee42,0x1ee47,0x1ee47,0x1ee49,0x1ee49,0x1ee4b,0x1ee4b,0x1ee4d,0x1ee4f,0x1ee51,0x1ee52,0x1ee54,0x1ee54,0x1ee57,0x1ee57,0x1ee59,0x1ee59,0x1ee5b,0x1ee5b,0x1ee5d,0x1ee5d,0x1ee5f,0x1ee5f,0x1ee61,0x1ee62,0x1ee64,0x1ee64,0x1ee67,0x1ee6a,0x1ee6c,0x1ee72,0x1ee74,0x1ee77,0x1ee79,0x1ee7c,0x1ee7e,0x1ee7e,0x1ee80,0x1ee89,0x1ee8b,0x1ee9b,0x1eea1,0x1eea3,0x1eea5,0x1eea9,0x1eeab,0x1eebb,0x1eef0,0x1eef1,]), + NotoFont.fromFlatRanges('Noto Sans Mayan Numerals', 'http://fonts.gstatic.com/s/notosansmayannumerals/v15/PlIuFk25O6RzLfvNNVSivR09_KqYMwvvDKYjfIiE68oo6eepYQ.ttf', [0x20,0x20,0xa0,0xa0,0x1d2e0,0x1d2f3,]), + NotoFont.fromFlatRanges('Noto Sans Medefaidrin', 'http://fonts.gstatic.com/s/notosansmedefaidrin/v19/WwkzxOq6Dk-wranENynkfeVsNbRZtbOIdLb1exeM4ZeuabBfmErWlT318e5A3rw.ttf', [0x20,0x20,0xa0,0xa0,0x16e40,0x16e9a,]), + NotoFont.fromFlatRanges('Noto Sans Meetei Mayek', 'http://fonts.gstatic.com/s/notosansmeeteimayek/v10/HTxAL3QyKieByqY9eZPFweO0be7M21uSphSdhqILnmrRfJ8t_1TJ_vTW5PgeFYVa.ttf', [0x20,0x20,0xa0,0xa0,0x200b,0x200d,0x25cc,0x25cc,0xaae0,0xaaf6,0xabc0,0xabed,0xabf0,0xabf9,]), + NotoFont.fromFlatRanges('Noto Sans Meroitic', 'http://fonts.gstatic.com/s/notosansmeroitic/v16/IFS5HfRJndhE3P4b5jnZ3ITPvC6i00UDgDhTiKY9KQ.ttf', [0x20,0x20,0x3a,0x3a,0xa0,0xa0,0x2026,0x2026,0x205d,0x205d,0x10980,0x109b7,0x109bc,0x109cf,0x109d2,0x109ff,]), + NotoFont.fromFlatRanges('Noto Sans Miao', 'http://fonts.gstatic.com/s/notosansmiao/v15/Dxxz8jmXMW75w3OmoDXVV4zyZUjgUYVslLhx.ttf', [0x20,0x20,0xa0,0xa0,0x25cc,0x25cc,0x16f00,0x16f4a,0x16f4f,0x16f87,0x16f8f,0x16f9f,]), + NotoFont.fromFlatRanges('Noto Sans Modi', 'http://fonts.gstatic.com/s/notosansmodi/v15/pe03MIySN5pO62Z5YkFyT7jeav5qWVAgVol-.ttf', [0x20,0x20,0xa0,0xa0,0x200c,0x200d,0x25cc,0x25cc,0xa830,0xa839,0x11600,0x11644,0x11650,0x11659,]), + NotoFont.fromFlatRanges('Noto Sans Mongolian', 'http://fonts.gstatic.com/s/notosansmongolian/v15/VdGCAYADGIwE0EopZx8xQfHlgEAMsrToxLsg6-av1x0.ttf', [0x20,0x22,0x28,0x29,0x2d,0x2d,0x3f,0x3f,0xa0,0xa0,0x1800,0x180e,0x1810,0x1819,0x1820,0x1878,0x1880,0x18aa,0x200c,0x200d,0x2013,0x2014,0x201c,0x201d,0x202f,0x202f,0x2048,0x2049,0x2460,0x2473,0x25cc,0x25cc,0x3001,0x3002,0x300a,0x300f,0xfe3d,0xfe3e,0xfe41,0xfe44,0x11660,0x1166c,]), + NotoFont.fromFlatRanges('Noto Sans Mro', 'http://fonts.gstatic.com/s/notosansmro/v15/qWcsB6--pZv9TqnUQMhe9b39WDzRtjkho4M.ttf', [0x20,0x20,0xa0,0xa0,0x16a40,0x16a5e,0x16a60,0x16a69,0x16a6e,0x16a6f,]), + NotoFont.fromFlatRanges('Noto Sans Multani', 'http://fonts.gstatic.com/s/notosansmultani/v15/9Bty3ClF38_RfOpe1gCaZ8p30BOFO1A0pfCs5Kos.ttf', [0x20,0x20,0xa0,0xa0,0xa66,0xa6f,0x11280,0x11286,0x11288,0x11288,0x1128a,0x1128d,0x1128f,0x1129d,0x1129f,0x112a9,]), + NotoFont.fromFlatRanges('Noto Sans Myanmar', 'http://fonts.gstatic.com/s/notosansmyanmar/v19/AlZq_y1ZtY3ymOryg38hOCSdOnFq0En23OU4o1AC.ttf', [0x20,0x20,0x3f,0x3f,0xa0,0xa0,0x1000,0x109f,0x200b,0x200d,0x2018,0x2019,0x201c,0x201d,0x2026,0x2026,0x25cc,0x25cc,0xa92e,0xa92e,0xa9e0,0xa9fe,0xaa60,0xaa7f,0xfe00,0xfe00,]), + NotoFont.fromFlatRanges('Noto Sans N Ko', 'http://fonts.gstatic.com/s/notosansnko/v17/6NUP8FqDKBaKKjnr6P8v-sxPpvVBVNmme3gf.ttf', [0x20,0x20,0xa0,0xa0,0x60c,0x60c,0x61b,0x61b,0x61f,0x61f,0x66a,0x66a,0x7c0,0x7fa,0x7fd,0x7ff,0x200c,0x200f,0x25cc,0x25cc,0x2e1c,0x2e1d,0xfd3e,0xfd3f,]), + NotoFont.fromFlatRanges('Noto Sans Nabataean', 'http://fonts.gstatic.com/s/notosansnabataean/v15/IFS4HfVJndhE3P4b5jnZ34DfsjO330dNoBJ9hK8kMK4.ttf', [0x20,0x20,0xa0,0xa0,0x10880,0x1089e,0x108a7,0x108af,]), + NotoFont.fromFlatRanges('Noto Sans New Tai Lue', 'http://fonts.gstatic.com/s/notosansnewtailue/v15/H4c5BW-Pl9DZ0Xe_nHUapt7PovLXAhAnY7wwY55O4AS32A.ttf', [0x20,0x20,0xa0,0xa0,0x1980,0x19ab,0x19b0,0x19c9,0x19d0,0x19da,0x19de,0x19df,0x200c,0x200d,0x25cc,0x25cc,]), + NotoFont.fromFlatRanges('Noto Sans Newa', 'http://fonts.gstatic.com/s/notosansnewa/v15/7r3fqXp6utEsO9pI4f8ok8sWg8n_qN4R5lNU.ttf', [0x20,0x20,0xa0,0xa0,0xb7,0xb7,0x1dfb,0x1dfb,0x200c,0x200d,0x25cc,0x25cc,0x11400,0x1145b,0x1145d,0x11461,]), + NotoFont.fromFlatRanges('Noto Sans Nushu', 'http://fonts.gstatic.com/s/notosansnushu/v18/rnCw-xRQ3B7652emAbAe_Ai1IYaFWFAMArZKqQ.ttf', [0x20,0x20,0xa0,0xa0,0x16fe1,0x16fe1,0x1b170,0x1b2fb,]), + NotoFont.fromFlatRanges('Noto Sans Ogham', 'http://fonts.gstatic.com/s/notosansogham/v15/kmKlZqk1GBDGN0mY6k5lmEmww4hrt5laQxcoCA.ttf', [0x20,0x20,0xa0,0xa0,0x1680,0x169c,]), + NotoFont.fromFlatRanges('Noto Sans Ol Chiki', 'http://fonts.gstatic.com/s/notosansolchiki/v17/N0b92TJNOPt-eHmFZCdQbrL32r-4CvhzDzRwlxOQYuVALWk267I6gVrz5gQ.ttf', [0x20,0x20,0xa0,0xa0,0x1c50,0x1c7f,0x20b9,0x20b9,]), + NotoFont.fromFlatRanges('Noto Sans Old Hungarian', 'http://fonts.gstatic.com/s/notosansoldhungarian/v15/E213_cD6hP3GwCJPEUssHEM0KqLaHJXg2PiIgRfjbg5nCYXt.ttf', [0x20,0x20,0xa0,0xa0,0x200d,0x200d,0x10c80,0x10cb2,0x10cc0,0x10cf2,0x10cfa,0x10cff,]), + NotoFont.fromFlatRanges('Noto Sans Old Italic', 'http://fonts.gstatic.com/s/notosansolditalic/v15/TuGOUUFzXI5FBtUq5a8bh68BJxxEVam7tWlRdRhtCC4d.ttf', [0x20,0x20,0xa0,0xa0,0x10300,0x10323,0x1032d,0x1032f,]), + NotoFont.fromFlatRanges('Noto Sans Old North Arabian', 'http://fonts.gstatic.com/s/notosansoldnortharabian/v15/esDF30BdNv-KYGGJpKGk2tNiMt7Jar6olZDyNdr81zBQmUo_xw4ABw.ttf', [0x20,0x20,0xa0,0xa0,0x10a80,0x10a9f,]), + NotoFont.fromFlatRanges('Noto Sans Old Permic', 'http://fonts.gstatic.com/s/notosansoldpermic/v16/snf1s1q1-dF8pli1TesqcbUY4Mr-ElrwKLdXgv_dKYB5.ttf', [0x20,0x20,0xa0,0xa0,0x300,0x300,0x306,0x308,0x313,0x313,0x483,0x483,0x20db,0x20db,0x25cc,0x25cc,0x10350,0x1037a,]), + NotoFont.fromFlatRanges('Noto Sans Old Persian', 'http://fonts.gstatic.com/s/notosansoldpersian/v15/wEOjEAbNnc5caQTFG18FHrZr9Bp6-8CmIJ_tqOlQfx9CjA.ttf', [0x20,0x20,0xa0,0xa0,0x103a0,0x103c3,0x103c8,0x103d5,]), + NotoFont.fromFlatRanges('Noto Sans Old Sogdian', 'http://fonts.gstatic.com/s/notosansoldsogdian/v15/3JnjSCH90Gmq2mrzckOBBhFhdrMst48aURt7neIqM-9uyg.ttf', [0x20,0x20,0xa0,0xa0,0x10f00,0x10f27,]), + NotoFont.fromFlatRanges('Noto Sans Old South Arabian', 'http://fonts.gstatic.com/s/notosansoldsoutharabian/v15/3qT5oiOhnSyU8TNFIdhZTice3hB_HWKsEnF--0XCHiKx1OtDT9HwTA.ttf', [0x20,0x20,0xa0,0xa0,0x10a60,0x10a7f,]), + NotoFont.fromFlatRanges('Noto Sans Old Turkic', 'http://fonts.gstatic.com/s/notosansoldturkic/v15/yMJNMJVya43H0SUF_WmcGEQVqoEMKDKbsE2RjEw-Vyws.ttf', [0x20,0x20,0xa0,0xa0,0x10c00,0x10c48,]), + NotoFont.fromFlatRanges('Noto Sans Oriya', 'http://fonts.gstatic.com/s/notosansoriya/v16/AYCTpXfzfccDCstK_hrjDyADv5en5K3DZq1hIg.ttf', [0x20,0x23,0x25,0x25,0x27,0x2c,0x2e,0x3f,0x5b,0x5f,0x7b,0x7e,0xa0,0xa0,0xad,0xad,0xd7,0xd7,0xf7,0xf7,0x964,0x965,0xb01,0xb03,0xb05,0xb0c,0xb0f,0xb10,0xb13,0xb28,0xb2a,0xb30,0xb32,0xb33,0xb35,0xb39,0xb3c,0xb44,0xb47,0xb48,0xb4b,0xb4d,0xb56,0xb57,0xb5c,0xb5d,0xb5f,0xb63,0xb66,0xb77,0x200b,0x200d,0x2010,0x2010,0x2013,0x2014,0x2018,0x2019,0x201c,0x201d,0x2026,0x2026,0x20b9,0x20b9,0x2212,0x2212,0x25cc,0x25cc,]), + NotoFont.fromFlatRanges('Noto Sans Osage', 'http://fonts.gstatic.com/s/notosansosage/v15/oPWX_kB6kP4jCuhpgEGmw4mtAVtXRlaSxkrMCQ.ttf', [0x20,0x20,0xa0,0xa0,0x301,0x301,0x304,0x304,0x30b,0x30b,0x358,0x358,0x25cc,0x25cc,0x104b0,0x104d3,0x104d8,0x104fb,]), + NotoFont.fromFlatRanges('Noto Sans Osmanya', 'http://fonts.gstatic.com/s/notosansosmanya/v15/8vIS7xs32H97qzQKnzfeWzUyUpOJmz6kR47NCV5Z.ttf', [0x20,0x20,0xa0,0xa0,0x10480,0x1049d,0x104a0,0x104a9,]), + NotoFont.fromFlatRanges('Noto Sans Pahawh Hmong', 'http://fonts.gstatic.com/s/notosanspahawhhmong/v15/bWtp7e_KfBziStx7lIzKKaMUOBEA3UPQDW7krzc_c48aMpM.ttf', [0x20,0x20,0xa0,0xa0,0x200c,0x200d,0x25cc,0x25cc,0x16b00,0x16b45,0x16b50,0x16b59,0x16b5b,0x16b61,0x16b63,0x16b77,0x16b7d,0x16b8f,]), + NotoFont.fromFlatRanges('Noto Sans Palmyrene', 'http://fonts.gstatic.com/s/notosanspalmyrene/v15/ZgNPjOdKPa7CHqq0h37c_ASCWvH93SFCPnK5ZpdNtcA.ttf', [0x20,0x20,0xa0,0xa0,0x10860,0x1087f,]), + NotoFont.fromFlatRanges('Noto Sans Pau Cin Hau', 'http://fonts.gstatic.com/s/notosanspaucinhau/v16/x3d-cl3IZKmUqiMg_9wBLLtzl22EayN7ehIdjEWqKMxsKw.ttf', [0x20,0x20,0xa0,0xa0,0x11ac0,0x11af8,]), + NotoFont.fromFlatRanges('Noto Sans Phags Pa', 'http://fonts.gstatic.com/s/notosansphagspa/v15/pxiZyoo6v8ZYyWh5WuPeJzMkd4SrGChkqkSsrvNXiA.ttf', [0x20,0x20,0xa0,0xa0,0x1801,0x1803,0x1805,0x1805,0x200b,0x200f,0x2025,0x2026,0x25cc,0x25cc,0x3001,0x3002,0x3007,0x3011,0x3014,0x301b,0xa840,0xa877,0xfe00,0xfe00,]), + NotoFont.fromFlatRanges('Noto Sans Phoenician', 'http://fonts.gstatic.com/s/notosansphoenician/v15/jizFRF9Ksm4Bt9PvcTaEkIHiTVtxmFtS5X7Jot-p5561.ttf', [0x20,0x20,0xa0,0xa0,0x10900,0x1091b,0x1091f,0x1091f,]), + NotoFont.fromFlatRanges('Noto Sans Psalter Pahlavi', 'http://fonts.gstatic.com/s/notosanspsalterpahlavi/v15/rP2Vp3K65FkAtHfwd-eISGznYihzggmsicPfud3w1G3KsUQBct4.ttf', [0x20,0x20,0xa0,0xa0,0x640,0x640,0x200c,0x200d,0x25cc,0x25cc,0x10b80,0x10b91,0x10b99,0x10b9c,0x10ba9,0x10baf,]), + NotoFont.fromFlatRanges('Noto Sans Rejang', 'http://fonts.gstatic.com/s/notosansrejang/v15/Ktk2AKuMeZjqPnXgyqrib7DIogqwN4O3WYZB_sU.ttf', [0x20,0x20,0xa0,0xa0,0x200b,0x200d,0x25cc,0x25cc,0xa930,0xa953,0xa95f,0xa95f,]), + NotoFont.fromFlatRanges('Noto Sans Runic', 'http://fonts.gstatic.com/s/notosansrunic/v15/H4c_BXWPl9DZ0Xe_nHUaus7W68WWaxpvHtgIYg.ttf', [0x20,0x20,0xa0,0xa0,0x16a0,0x16f8,]), + NotoFont.fromFlatRanges('Noto Sans SC', 'http://fonts.gstatic.com/s/notosanssc/v26/k3kXo84MPvpLmixcA63oeALhL4iJ-Q7m8w.otf', [0x20,0x7e,0xa0,0x103,0x110,0x113,0x11a,0x11b,0x128,0x12b,0x143,0x144,0x147,0x148,0x14c,0x14f,0x152,0x153,0x168,0x16d,0x192,0x192,0x1a0,0x1a1,0x1af,0x1b0,0x1cd,0x1dc,0x1f8,0x1f9,0x251,0x251,0x261,0x261,0x2bb,0x2bb,0x2c7,0x2c7,0x2c9,0x2cb,0x2d9,0x2d9,0x2ea,0x2eb,0x300,0x301,0x304,0x304,0x307,0x307,0x30c,0x30c,0x391,0x3a1,0x3a3,0x3a9,0x3b1,0x3c9,0x401,0x401,0x410,0x44f,0x451,0x451,0x1e3e,0x1e3f,0x1ea0,0x1ef9,0x2002,0x2003,0x2010,0x2016,0x2018,0x201a,0x201c,0x201e,0x2020,0x2022,0x2025,0x2027,0x2030,0x2030,0x2032,0x2033,0x2035,0x2035,0x2039,0x203c,0x2042,0x2042,0x2047,0x2049,0x2051,0x2051,0x2074,0x2074,0x20a9,0x20a9,0x20ab,0x20ac,0x20dd,0x20de,0x2100,0x2100,0x2103,0x2103,0x2105,0x2105,0x2109,0x210a,0x210f,0x210f,0x2113,0x2113,0x2116,0x2116,0x2121,0x2122,0x2126,0x2127,0x212b,0x212b,0x212e,0x212e,0x2135,0x2135,0x213b,0x213b,0x2160,0x216b,0x2170,0x217b,0x2190,0x2199,0x21b8,0x21b9,0x21c4,0x21c6,0x21cb,0x21cc,0x21d0,0x21d0,0x21d2,0x21d2,0x21d4,0x21d4,0x21e6,0x21e9,0x21f5,0x21f5,0x2200,0x2200,0x2202,0x2203,0x2205,0x220b,0x220f,0x220f,0x2211,0x2213,0x2215,0x2215,0x221a,0x221a,0x221d,0x2220,0x2223,0x2223,0x2225,0x222e,0x2234,0x2237,0x223d,0x223d,0x2243,0x2243,0x2245,0x2245,0x2248,0x2248,0x224c,0x224c,0x2252,0x2252,0x2260,0x2262,0x2264,0x2267,0x226a,0x226b,0x226e,0x226f,0x2272,0x2273,0x2276,0x2277,0x2282,0x2287,0x228a,0x228b,0x2295,0x2299,0x22a0,0x22a0,0x22a5,0x22a5,0x22bf,0x22bf,0x22da,0x22db,0x22ef,0x22ef,0x2305,0x2307,0x2312,0x2312,0x2318,0x2318,0x2329,0x232a,0x23b0,0x23b1,0x23be,0x23cc,0x23ce,0x23ce,0x23da,0x23db,0x2423,0x2423,0x2460,0x25ab,0x25b1,0x25b3,0x25b6,0x25b7,0x25bc,0x25bd,0x25c0,0x25c1,0x25c6,0x25cc,0x25ce,0x25d3,0x25e2,0x25e6,0x25ef,0x25ef,0x2600,0x2603,0x2605,0x2606,0x2609,0x2609,0x260e,0x260f,0x2616,0x2617,0x261c,0x261f,0x262f,0x262f,0x2640,0x2642,0x2660,0x266f,0x2672,0x267d,0x26a0,0x26a0,0x26bd,0x26be,0x2702,0x2702,0x2713,0x2713,0x271a,0x271a,0x273d,0x273d,0x273f,0x2740,0x2756,0x2756,0x2776,0x2793,0x27a1,0x27a1,0x2934,0x2935,0x29bf,0x29bf,0x29fa,0x29fb,0x2b05,0x2b07,0x2b1a,0x2b1a,0x2b95,0x2b95,0x2e3a,0x2e3b,0x2e80,0x2e99,0x2e9b,0x2ef3,0x2f00,0x2fd5,0x2ff0,0x2ffb,0x3000,0x303f,0x3041,0x3096,0x3099,0x30ff,0x3105,0x312f,0x3131,0x3163,0x3165,0x318e,0x3190,0x31bb,0x31c0,0x31e3,0x31f0,0x321e,0x3220,0x332b,0x332d,0x4db5,0x4e00,0x9fef,0xf900,0xf903,0xf905,0xf906,0xf90b,0xf90c,0xf915,0xf915,0xf917,0xf91a,0xf921,0xf921,0xf92c,0xf92d,0xf92f,0xf92f,0xf931,0xf935,0xf937,0xf93a,0xf943,0xf943,0xf947,0xf94a,0xf94e,0xf94e,0xf952,0xf953,0xf95e,0xf95e,0xf962,0xf967,0xf96e,0xf96e,0xf972,0xf972,0xf974,0xf974,0xf976,0xf976,0xf979,0xf97b,0xf97e,0xf980,0xf984,0xf985,0xf98a,0xf98c,0xf98e,0xf98e,0xf993,0xf993,0xf995,0xf995,0xf998,0xf998,0xf9ae,0xf9ae,0xf9b3,0xf9b3,0xf9b7,0xf9b7,0xf9bb,0xf9bb,0xf9bd,0xf9be,0xf9c0,0xf9c0,0xf9c5,0xf9c6,0xf9d0,0xf9d0,0xf9d8,0xf9d9,0xf9dc,0xf9e0,0xf9e2,0xf9e4,0xf9e7,0xf9e7,0xf9e9,0xf9e9,0xf9f1,0xf9f1,0xf9f4,0xf9f5,0xf9fa,0xf9fa,0xf9fd,0xf9fd,0xf9ff,0xf9ff,0xfa02,0xfa02,0xfa05,0xfa08,0xfa0a,0xfa0a,0xfa0c,0xfa0f,0xfa11,0xfa11,0xfa13,0xfa14,0xfa18,0xfa18,0xfa1f,0xfa21,0xfa23,0xfa24,0xfa27,0xfa29,0xfa2f,0xfa2f,0xfa33,0xfa35,0xfa37,0xfa38,0xfa3a,0xfa3a,0xfa47,0xfa47,0xfa49,0xfa49,0xfa4b,0xfa4b,0xfa5d,0xfa5e,0xfb00,0xfb04,0xfe10,0xfe19,0xfe30,0xfe52,0xfe54,0xfe66,0xfe68,0xfe6b,0xff01,0xff9f,0xffa1,0xffbe,0xffc2,0xffc7,0xffca,0xffcf,0xffd2,0xffd7,0xffda,0xffdc,0xffe0,0xffe6,0xffe8,0xffee,0x1f100,0x1f10c,0x1f110,0x1f16c,0x1f170,0x1f1ac,0x1f200,0x1f202,0x1f210,0x1f23b,0x1f240,0x1f248,0x1f250,0x1f251,0x20087,0x20087,0x20089,0x20089,0x200cc,0x200cc,0x20164,0x20164,0x20628,0x20628,0x20676,0x20676,0x20cd0,0x20cd0,0x2139a,0x2139a,0x21413,0x21413,0x215d7,0x215d7,0x2298f,0x2298f,0x235cb,0x235cb,0x23c97,0x23c98,0x23e23,0x23e23,0x241fe,0x241fe,0x2420e,0x2420e,0x248e9,0x248e9,0x249db,0x249db,0x24a01,0x24a01,0x24a7d,0x24a7d,0x24ac9,0x24ac9,0x25532,0x25532,0x25562,0x25562,0x255a8,0x255a8,0x25ad7,0x25ad7,0x25ed7,0x25ed7,0x26221,0x26221,0x2648d,0x2648d,0x26676,0x26676,0x2677c,0x2677c,0x26951,0x26951,0x26b5c,0x26b5c,0x26c21,0x26c21,0x278b2,0x278b2,0x27eaf,0x27eaf,0x27fb7,0x27fb7,0x27ff9,0x27ff9,0x28408,0x28408,0x28678,0x28678,0x28695,0x28695,0x287e0,0x287e0,0x28b49,0x28b49,0x28c47,0x28c47,0x28c4f,0x28c4f,0x28c51,0x28c51,0x28c54,0x28c54,0x28e0f,0x28e0f,0x28e99,0x28e99,0x2967f,0x2967f,0x29810,0x29810,0x29f7e,0x29f7e,0x29f83,0x29f83,0x29f8c,0x29f8c,0x2a7dd,0x2a7dd,0x2a8fb,0x2a8fb,0x2a917,0x2a917,0x2aa30,0x2aa30,0x2aa36,0x2aa36,0x2aa58,0x2aa58,0x2afa2,0x2afa2,0x2b127,0x2b128,0x2b137,0x2b138,0x2b1ed,0x2b1ed,0x2b300,0x2b300,0x2b363,0x2b363,0x2b36f,0x2b36f,0x2b372,0x2b372,0x2b37d,0x2b37d,0x2b404,0x2b404,0x2b410,0x2b410,0x2b413,0x2b413,0x2b461,0x2b461,0x2b4e7,0x2b4e7,0x2b4ef,0x2b4ef,0x2b4f6,0x2b4f6,0x2b4f9,0x2b4f9,0x2b50d,0x2b50e,0x2b536,0x2b536,0x2b5ae,0x2b5af,0x2b5b3,0x2b5b3,0x2b5e7,0x2b5e7,0x2b5f4,0x2b5f4,0x2b61c,0x2b61d,0x2b626,0x2b628,0x2b62a,0x2b62a,0x2b62c,0x2b62c,0x2b695,0x2b696,0x2b6ad,0x2b6ad,0x2b6ed,0x2b6ed,0x2b7a9,0x2b7a9,0x2b7c5,0x2b7c5,0x2b7e6,0x2b7e6,0x2b7f9,0x2b7f9,0x2b7fc,0x2b7fc,0x2b806,0x2b806,0x2b80a,0x2b80a,0x2b81c,0x2b81c,0x2b8b8,0x2b8b8,0x2bac7,0x2bac7,0x2bb5f,0x2bb5f,0x2bb62,0x2bb62,0x2bb7c,0x2bb7c,0x2bb83,0x2bb83,0x2bc1b,0x2bc1b,0x2bd77,0x2bd77,0x2bd87,0x2bd87,0x2bdf7,0x2bdf7,0x2be29,0x2be29,0x2c029,0x2c02a,0x2c0a9,0x2c0a9,0x2c0ca,0x2c0ca,0x2c1d5,0x2c1d5,0x2c1d9,0x2c1d9,0x2c1f9,0x2c1f9,0x2c27c,0x2c27c,0x2c288,0x2c288,0x2c2a4,0x2c2a4,0x2c317,0x2c317,0x2c35b,0x2c35b,0x2c361,0x2c361,0x2c364,0x2c364,0x2c488,0x2c488,0x2c494,0x2c494,0x2c497,0x2c497,0x2c542,0x2c542,0x2c613,0x2c613,0x2c618,0x2c618,0x2c621,0x2c621,0x2c629,0x2c629,0x2c62b,0x2c62d,0x2c62f,0x2c62f,0x2c642,0x2c642,0x2c64a,0x2c64b,0x2c72c,0x2c72c,0x2c72f,0x2c72f,0x2c79f,0x2c79f,0x2c7c1,0x2c7c1,0x2c7fd,0x2c7fd,0x2c8d9,0x2c8d9,0x2c8de,0x2c8de,0x2c8e1,0x2c8e1,0x2c8f3,0x2c8f3,0x2c907,0x2c907,0x2c90a,0x2c90a,0x2c91d,0x2c91d,0x2ca02,0x2ca02,0x2ca0e,0x2ca0e,0x2ca7d,0x2ca7d,0x2caa9,0x2caa9,0x2cb29,0x2cb29,0x2cb2d,0x2cb2e,0x2cb31,0x2cb31,0x2cb38,0x2cb39,0x2cb3b,0x2cb3b,0x2cb3f,0x2cb3f,0x2cb41,0x2cb41,0x2cb4a,0x2cb4a,0x2cb4e,0x2cb4e,0x2cb5a,0x2cb5b,0x2cb64,0x2cb64,0x2cb69,0x2cb69,0x2cb6c,0x2cb6c,0x2cb6f,0x2cb6f,0x2cb73,0x2cb73,0x2cb76,0x2cb76,0x2cb78,0x2cb78,0x2cb7c,0x2cb7c,0x2cbb1,0x2cbb1,0x2cbbf,0x2cbc0,0x2cbce,0x2cbce,0x2cc56,0x2cc56,0x2cc5f,0x2cc5f,0x2ccf5,0x2ccf6,0x2ccfd,0x2ccfd,0x2ccff,0x2ccff,0x2cd02,0x2cd03,0x2cd0a,0x2cd0a,0x2cd8b,0x2cd8b,0x2cd8d,0x2cd8d,0x2cd8f,0x2cd90,0x2cd9f,0x2cda0,0x2cda8,0x2cda8,0x2cdad,0x2cdae,0x2cdd5,0x2cdd5,0x2ce18,0x2ce18,0x2ce1a,0x2ce1a,0x2ce23,0x2ce23,0x2ce26,0x2ce26,0x2ce2a,0x2ce2a,0x2ce7c,0x2ce7c,0x2ce88,0x2ce88,0x2ce93,0x2ce93,0x2d544,0x2d544,0x2f884,0x2f884,0x2f8b6,0x2f8b6,0x30edd,0x30ede,0x3106c,0x3106c,]), + NotoFont.fromFlatRanges('Noto Sans Saurashtra', 'http://fonts.gstatic.com/s/notosanssaurashtra/v15/ea8GacQ0Wfz_XKWXe6OtoA8w8zvmYwTef9ndjhPTSIx9.ttf', [0x20,0x20,0xa0,0xa0,0x200b,0x200d,0x25cc,0x25cc,0xa880,0xa8c5,0xa8ce,0xa8d9,]), + NotoFont.fromFlatRanges('Noto Sans Sharada', 'http://fonts.gstatic.com/s/notosanssharada/v15/gok0H7rwAEdtF9N8-mdTGALG6p0kwoXLPOwr4H8a.ttf', [0x20,0x20,0xa0,0xa0,0x951,0x951,0x1cd7,0x1cd7,0x1cd9,0x1cd9,0x1cdc,0x1cdd,0x1ce0,0x1ce0,0x200c,0x200d,0x25cc,0x25cc,0x11180,0x111df,]), + NotoFont.fromFlatRanges('Noto Sans Shavian', 'http://fonts.gstatic.com/s/notosansshavian/v15/CHy5V_HZE0jxJBQlqAeCKjJvQBNF4EFQSplv2Cwg.ttf', [0x20,0x20,0xa0,0xa0,0x10450,0x1047f,]), + NotoFont.fromFlatRanges('Noto Sans Siddham', 'http://fonts.gstatic.com/s/notosanssiddham/v15/OZpZg-FwqiNLe9PELUikxTWDoCCeGqndk3Ic92ZH.ttf', [0x20,0x20,0xa0,0xa0,0x200c,0x200d,0x25cc,0x25cc,0x11580,0x115b5,0x115b8,0x115dd,]), + NotoFont.fromFlatRanges('Noto Sans Sinhala', 'http://fonts.gstatic.com/s/notosanssinhala/v25/yMJ2MJBya43H0SUF_WmcBEEf4rQVO2P524V5N_MxQzQtb-tf5dJbC30Fu9zUwg2a5lgLpJwbQRM.ttf', [0x20,0x23,0x25,0x25,0x27,0x3f,0x5b,0x5f,0x7b,0x7e,0xa0,0xa0,0xad,0xad,0xd7,0xd7,0xf7,0xf7,0x964,0x965,0xd81,0xd83,0xd85,0xd96,0xd9a,0xdb1,0xdb3,0xdbb,0xdbd,0xdbd,0xdc0,0xdc6,0xdca,0xdca,0xdcf,0xdd4,0xdd6,0xdd6,0xdd8,0xddf,0xde6,0xdef,0xdf2,0xdf4,0x200b,0x200d,0x2013,0x2014,0x2018,0x2019,0x201c,0x201d,0x2026,0x2026,0x2212,0x2212,0x25cc,0x25cc,0x111e1,0x111f4,]), + NotoFont.fromFlatRanges('Noto Sans Sogdian', 'http://fonts.gstatic.com/s/notosanssogdian/v15/taiQGn5iC4--qtsfi4Jp6eHPnfxQBo--Pm6KHidM.ttf', [0x20,0x20,0xa0,0xa0,0x640,0x640,0x200c,0x200c,0x25cc,0x25cc,0x10f30,0x10f59,]), + NotoFont.fromFlatRanges('Noto Sans Sora Sompeng', 'http://fonts.gstatic.com/s/notosanssorasompeng/v17/PlIRFkO5O6RzLfvNNVSioxM2_OTrEhPyDLolKvCsHzCxWuGkYHR818DpZXJQd4Mu.ttf', [0x20,0x20,0x2d,0x2d,0xa0,0xa0,0x2010,0x2010,0x110d0,0x110e8,0x110f0,0x110f9,]), + NotoFont.fromFlatRanges('Noto Sans Soyombo', 'http://fonts.gstatic.com/s/notosanssoyombo/v15/RWmSoL-Y6-8q5LTtXs6MF6q7xsxgY0FrIFOcK25W.ttf', [0x20,0x20,0xa0,0xa0,0x25cc,0x25cc,0x11a50,0x11aa2,]), + NotoFont.fromFlatRanges('Noto Sans Sundanese', 'http://fonts.gstatic.com/s/notosanssundanese/v17/FwZw7_84xUkosG2xJo2gm7nFwSLQkdymq2mkz3Gz1_b6ctxpNNHCizv7fQES.ttf', [0x20,0x20,0x2d,0x2d,0xa0,0xa0,0x1b80,0x1bbf,0x1cc0,0x1cc7,0x200b,0x200d,0x2010,0x2010,0x25cc,0x25cc,]), + NotoFont.fromFlatRanges('Noto Sans Syloti Nagri', 'http://fonts.gstatic.com/s/notosanssylotinagri/v15/uU9eCAQZ75uhfF9UoWDRiY3q7Sf_VFV3m4dGFVfxN87gsj0.ttf', [0x20,0x20,0xa0,0xa0,0x964,0x965,0x9e6,0x9ef,0x200b,0x200d,0x2010,0x2011,0x2055,0x2055,0x25cc,0x25cc,0xa800,0xa82c,]), + NotoFont.fromFlatRanges('Noto Sans Syriac', 'http://fonts.gstatic.com/s/notosanssyriac/v15/Ktk2AKuMeZjqPnXgyqribqzQqgW0N4O3WYZB_sU.ttf', [0x20,0x21,0x28,0x2b,0x2d,0x2f,0x3a,0x3a,0x3d,0x3d,0x5b,0x5d,0xa0,0xa0,0xab,0xab,0xb0,0xb0,0xbb,0xbb,0x303,0x304,0x307,0x308,0x30a,0x30a,0x320,0x320,0x323,0x325,0x32d,0x32e,0x330,0x331,0x60c,0x60c,0x61b,0x61b,0x61f,0x61f,0x621,0x621,0x640,0x640,0x64b,0x655,0x660,0x66c,0x670,0x670,0x700,0x70d,0x70f,0x74a,0x74d,0x74f,0x200c,0x200f,0x2026,0x2026,0x2044,0x2044,0x2212,0x2212,0x25cc,0x25cc,0x2670,0x2671,]), + NotoFont.fromFlatRanges('Noto Sans TC', 'http://fonts.gstatic.com/s/notosanstc/v26/-nF7OG829Oofr2wohFbTp9iFOSsLA_ZJ1g.otf', [0x20,0x7e,0xa0,0x103,0x110,0x113,0x11a,0x11b,0x128,0x12b,0x143,0x144,0x147,0x148,0x14c,0x14f,0x152,0x153,0x168,0x16d,0x192,0x192,0x1a0,0x1a1,0x1af,0x1b0,0x1cd,0x1dc,0x1f8,0x1f9,0x251,0x251,0x261,0x261,0x2bb,0x2bb,0x2c7,0x2c7,0x2c9,0x2cb,0x2d9,0x2d9,0x2ea,0x2eb,0x300,0x301,0x304,0x304,0x307,0x307,0x30c,0x30c,0x391,0x3a1,0x3a3,0x3a9,0x3b1,0x3c9,0x401,0x401,0x410,0x44f,0x451,0x451,0x1e3e,0x1e3f,0x1ea0,0x1ef9,0x2002,0x2003,0x2010,0x2016,0x2018,0x201a,0x201c,0x201e,0x2020,0x2022,0x2025,0x2027,0x2030,0x2030,0x2032,0x2033,0x2035,0x2035,0x2039,0x203c,0x2042,0x2042,0x2047,0x2049,0x2051,0x2051,0x2074,0x2074,0x20a9,0x20a9,0x20ab,0x20ac,0x20dd,0x20de,0x2100,0x2100,0x2103,0x2103,0x2105,0x2105,0x2109,0x210a,0x210f,0x210f,0x2113,0x2113,0x2116,0x2116,0x2121,0x2122,0x2126,0x2127,0x212b,0x212b,0x212e,0x212e,0x2135,0x2135,0x213b,0x213b,0x2160,0x216b,0x2170,0x217b,0x2190,0x2199,0x21b8,0x21b9,0x21c4,0x21c6,0x21cb,0x21cc,0x21d0,0x21d0,0x21d2,0x21d2,0x21d4,0x21d4,0x21e6,0x21e9,0x21f5,0x21f5,0x2200,0x2200,0x2202,0x2203,0x2205,0x220b,0x220f,0x220f,0x2211,0x2213,0x2215,0x2215,0x221a,0x221a,0x221d,0x2220,0x2223,0x2223,0x2225,0x222e,0x2234,0x2237,0x223d,0x223d,0x2243,0x2243,0x2245,0x2245,0x2248,0x2248,0x224c,0x224c,0x2252,0x2252,0x2260,0x2262,0x2264,0x2267,0x226a,0x226b,0x226e,0x226f,0x2272,0x2273,0x2276,0x2277,0x2282,0x2287,0x228a,0x228b,0x2295,0x2299,0x22a0,0x22a0,0x22a5,0x22a5,0x22bf,0x22bf,0x22da,0x22db,0x22ef,0x22ef,0x2305,0x2307,0x2312,0x2312,0x2318,0x2318,0x2329,0x232a,0x23b0,0x23b1,0x23be,0x23cc,0x23ce,0x23ce,0x23da,0x23db,0x2423,0x2423,0x2460,0x25ab,0x25b1,0x25b3,0x25b6,0x25b7,0x25bc,0x25bd,0x25c0,0x25c1,0x25c6,0x25cc,0x25ce,0x25d3,0x25e2,0x25e6,0x25ef,0x25ef,0x2600,0x2603,0x2605,0x2606,0x2609,0x2609,0x260e,0x260f,0x2616,0x2617,0x261c,0x261f,0x262f,0x262f,0x2640,0x2642,0x2660,0x266f,0x2672,0x267d,0x26a0,0x26a0,0x26bd,0x26be,0x2702,0x2702,0x2713,0x2713,0x271a,0x271a,0x273d,0x273d,0x273f,0x2740,0x2756,0x2756,0x2776,0x2793,0x27a1,0x27a1,0x2934,0x2935,0x29bf,0x29bf,0x29fa,0x29fb,0x2b05,0x2b07,0x2b1a,0x2b1a,0x2b95,0x2b95,0x2e3a,0x2e3b,0x2e80,0x2e99,0x2e9b,0x2ef3,0x2f00,0x2fd5,0x2ff0,0x2ffb,0x3000,0x303f,0x3041,0x3096,0x3099,0x30ff,0x3105,0x312f,0x3131,0x3163,0x3165,0x318e,0x3190,0x31bb,0x31c0,0x31e3,0x31f0,0x321e,0x3220,0x332b,0x332d,0x33ff,0x3435,0x3435,0x3440,0x3440,0x344a,0x344a,0x344c,0x344c,0x3464,0x3464,0x3473,0x3473,0x347a,0x347a,0x347d,0x347e,0x3493,0x3493,0x3496,0x3496,0x34a5,0x34a5,0x34af,0x34af,0x34bc,0x34bc,0x34c1,0x34c1,0x34c8,0x34c8,0x34df,0x34df,0x34e4,0x34e4,0x34e6,0x34e6,0x34fb,0x34fb,0x3506,0x3506,0x353e,0x353e,0x3551,0x3551,0x3553,0x3553,0x3559,0x3559,0x3561,0x3561,0x356d,0x356d,0x3570,0x3570,0x3572,0x3572,0x3577,0x3578,0x3584,0x3584,0x3597,0x3598,0x35a1,0x35a1,0x35a5,0x35a5,0x35ad,0x35ad,0x35bf,0x35bf,0x35c1,0x35c1,0x35c5,0x35c5,0x35c7,0x35c7,0x35ca,0x35ca,0x35ce,0x35ce,0x35d2,0x35d2,0x35d6,0x35d6,0x35db,0x35db,0x35dd,0x35dd,0x35f1,0x35f3,0x35fb,0x35fb,0x35fe,0x35fe,0x3609,0x3609,0x3618,0x3618,0x361a,0x361a,0x3623,0x3623,0x3625,0x3625,0x362d,0x362d,0x3635,0x3635,0x3639,0x3639,0x363e,0x363e,0x3647,0x3649,0x364e,0x364e,0x365f,0x365f,0x3661,0x3661,0x367a,0x367a,0x3681,0x3681,0x369a,0x369a,0x36a5,0x36a5,0x36aa,0x36aa,0x36ac,0x36ac,0x36b0,0x36b1,0x36b5,0x36b5,0x36b9,0x36b9,0x36bc,0x36bc,0x36c1,0x36c1,0x36c3,0x36c5,0x36c7,0x36c8,0x36d3,0x36d4,0x36d6,0x36d6,0x36dd,0x36dd,0x36e1,0x36e2,0x36e5,0x36e6,0x36f5,0x36f5,0x3701,0x3701,0x3703,0x3703,0x3708,0x3708,0x370a,0x370a,0x370d,0x370d,0x371c,0x371c,0x3722,0x3723,0x3725,0x3725,0x372c,0x372d,0x3730,0x3730,0x3732,0x3733,0x373a,0x373a,0x3740,0x3740,0x3743,0x3743,0x3762,0x3762,0x376f,0x376f,0x3797,0x3797,0x37a0,0x37a0,0x37b9,0x37b9,0x37be,0x37be,0x37d6,0x37d6,0x37f2,0x37f2,0x37f8,0x37f8,0x37fb,0x37fb,0x380f,0x380f,0x3819,0x3819,0x3820,0x3820,0x382d,0x382d,0x3836,0x3836,0x3838,0x3838,0x3863,0x3863,0x3875,0x3875,0x38a0,0x38a0,0x38c3,0x38c3,0x38cc,0x38cc,0x38d1,0x38d1,0x38d4,0x38d4,0x38fa,0x38fa,0x3908,0x3908,0x3914,0x3914,0x3927,0x3927,0x3932,0x3932,0x393f,0x393f,0x394d,0x394d,0x3963,0x3963,0x3978,0x3978,0x3980,0x3980,0x3989,0x398a,0x3992,0x3992,0x3999,0x3999,0x399b,0x399b,0x39a1,0x39a1,0x39a4,0x39a4,0x39b8,0x39b8,0x39dc,0x39dc,0x39e2,0x39e2,0x39e5,0x39e5,0x39ec,0x39ec,0x39f8,0x39f8,0x39fb,0x39fb,0x39fe,0x39fe,0x3a01,0x3a01,0x3a03,0x3a03,0x3a06,0x3a06,0x3a17,0x3a18,0x3a29,0x3a2a,0x3a34,0x3a34,0x3a4b,0x3a4b,0x3a52,0x3a52,0x3a57,0x3a57,0x3a5c,0x3a5c,0x3a5e,0x3a5e,0x3a66,0x3a67,0x3a97,0x3a97,0x3aab,0x3aab,0x3abd,0x3abd,0x3ada,0x3ada,0x3ade,0x3ade,0x3ae0,0x3ae0,0x3af0,0x3af0,0x3af2,0x3af2,0x3af5,0x3af5,0x3afb,0x3afb,0x3b0e,0x3b0e,0x3b19,0x3b19,0x3b22,0x3b22,0x3b2b,0x3b2b,0x3b39,0x3b39,0x3b42,0x3b42,0x3b58,0x3b58,0x3b60,0x3b60,0x3b71,0x3b72,0x3b7b,0x3b7c,0x3b80,0x3b80,0x3b95,0x3b96,0x3b99,0x3b99,0x3ba1,0x3ba1,0x3bbc,0x3bbc,0x3bbe,0x3bbe,0x3bc2,0x3bc2,0x3bc4,0x3bc4,0x3bd7,0x3bd7,0x3bdd,0x3bdd,0x3bec,0x3bec,0x3bf2,0x3bf4,0x3c0d,0x3c0d,0x3c11,0x3c11,0x3c15,0x3c15,0x3c18,0x3c18,0x3c54,0x3c54,0x3c8b,0x3c8b,0x3ccb,0x3ccb,0x3ccd,0x3ccd,0x3cd1,0x3cd1,0x3cd6,0x3cd6,0x3cdc,0x3cdc,0x3ceb,0x3ceb,0x3cef,0x3cef,0x3d12,0x3d13,0x3d1d,0x3d1d,0x3d32,0x3d32,0x3d3b,0x3d3b,0x3d46,0x3d46,0x3d4c,0x3d4c,0x3d4e,0x3d4e,0x3d51,0x3d51,0x3d5f,0x3d5f,0x3d62,0x3d62,0x3d69,0x3d6a,0x3d6f,0x3d6f,0x3d75,0x3d75,0x3d7d,0x3d7d,0x3d85,0x3d85,0x3d88,0x3d88,0x3d8a,0x3d8a,0x3d8f,0x3d8f,0x3d91,0x3d91,0x3da5,0x3da5,0x3dad,0x3dad,0x3db4,0x3db4,0x3dbf,0x3dbf,0x3dc6,0x3dc7,0x3dc9,0x3dc9,0x3dcc,0x3dcd,0x3dd3,0x3dd3,0x3ddb,0x3ddb,0x3de7,0x3de8,0x3deb,0x3deb,0x3df3,0x3df4,0x3df7,0x3df7,0x3dfc,0x3dfd,0x3e06,0x3e06,0x3e40,0x3e40,0x3e43,0x3e43,0x3e48,0x3e48,0x3e55,0x3e55,0x3e74,0x3e74,0x3ea8,0x3eaa,0x3ead,0x3ead,0x3eb1,0x3eb1,0x3eb8,0x3eb8,0x3ebf,0x3ebf,0x3ec2,0x3ec2,0x3ec7,0x3ec7,0x3eca,0x3eca,0x3ecc,0x3ecc,0x3ed0,0x3ed1,0x3ed6,0x3ed7,0x3eda,0x3edb,0x3ede,0x3ede,0x3ee1,0x3ee2,0x3ee7,0x3ee7,0x3ee9,0x3ee9,0x3eeb,0x3eec,0x3ef0,0x3ef0,0x3ef3,0x3ef4,0x3efa,0x3efa,0x3efc,0x3efc,0x3eff,0x3f00,0x3f04,0x3f04,0x3f06,0x3f07,0x3f0e,0x3f0e,0x3f53,0x3f53,0x3f58,0x3f59,0x3f63,0x3f63,0x3f7c,0x3f7c,0x3f93,0x3f93,0x3fc0,0x3fc0,0x3fc8,0x3fc8,0x3fd7,0x3fd7,0x3fdc,0x3fdc,0x3fe5,0x3fe5,0x3fed,0x3fed,0x3ff9,0x3ffa,0x4004,0x4004,0x4009,0x4009,0x401d,0x401d,0x4039,0x4039,0x4045,0x4045,0x4053,0x4053,0x4057,0x4057,0x4062,0x4062,0x4065,0x4065,0x406a,0x406a,0x406f,0x406f,0x4071,0x4071,0x40a8,0x40a8,0x40b4,0x40b4,0x40bb,0x40bb,0x40bf,0x40bf,0x40c8,0x40c8,0x40d8,0x40d8,0x40df,0x40df,0x40f8,0x40f8,0x40fa,0x40fa,0x4102,0x4104,0x4109,0x4109,0x410e,0x410e,0x4131,0x4132,0x4167,0x4167,0x416c,0x416c,0x416e,0x416e,0x417c,0x417c,0x417f,0x417f,0x4181,0x4181,0x4190,0x4190,0x41b2,0x41b2,0x41c4,0x41c4,0x41ca,0x41ca,0x41cf,0x41cf,0x41db,0x41db,0x41ed,0x41ed,0x41ef,0x41ef,0x41f9,0x41f9,0x4211,0x4211,0x4223,0x4223,0x4240,0x4240,0x4260,0x4260,0x426a,0x426a,0x4276,0x4276,0x427a,0x427a,0x428c,0x428c,0x4294,0x4294,0x42a2,0x42a2,0x42b5,0x42b5,0x42b9,0x42b9,0x42bc,0x42bc,0x42f4,0x42f4,0x42fb,0x42fc,0x430a,0x430a,0x432b,0x432b,0x436e,0x436e,0x4397,0x4397,0x439a,0x439a,0x43ba,0x43ba,0x43c1,0x43c1,0x43d9,0x43d9,0x43df,0x43df,0x43ed,0x43ed,0x43f0,0x43f0,0x43f2,0x43f2,0x4401,0x4402,0x4413,0x4413,0x4425,0x4425,0x442d,0x442d,0x447a,0x447a,0x448f,0x448f,0x4491,0x4491,0x449f,0x44a0,0x44a2,0x44a2,0x44b0,0x44b0,0x44b7,0x44b7,0x44bd,0x44bd,0x44c0,0x44c0,0x44c3,0x44c3,0x44c5,0x44c5,0x44ce,0x44ce,0x44dd,0x44df,0x44e1,0x44e1,0x44e4,0x44e4,0x44e9,0x44ec,0x44f4,0x44f4,0x4503,0x4504,0x4509,0x4509,0x450b,0x450b,0x4516,0x4516,0x451b,0x451b,0x451d,0x451d,0x4527,0x4527,0x452e,0x452e,0x4533,0x4533,0x4536,0x4536,0x453b,0x453b,0x453d,0x453d,0x453f,0x453f,0x4543,0x4543,0x4551,0x4552,0x4555,0x4555,0x4558,0x4558,0x455c,0x455c,0x4561,0x4562,0x456a,0x456a,0x456d,0x456d,0x4577,0x4578,0x4585,0x4585,0x45a6,0x45a6,0x45b3,0x45b3,0x45da,0x45da,0x45e9,0x45ea,0x4603,0x4603,0x4606,0x4606,0x460f,0x460f,0x4615,0x4615,0x4617,0x4617,0x465b,0x465b,0x467a,0x467a,0x4680,0x4680,0x46a1,0x46a1,0x46ae,0x46ae,0x46bb,0x46bb,0x46cf,0x46d0,0x46f5,0x46f5,0x46f7,0x46f7,0x4713,0x4713,0x4718,0x4718,0x4736,0x4736,0x4744,0x4744,0x474e,0x474f,0x477c,0x477c,0x4798,0x4798,0x47a6,0x47a6,0x47d5,0x47d5,0x47ed,0x47ed,0x47f4,0x47f4,0x4800,0x4800,0x480b,0x480b,0x4837,0x4837,0x485d,0x485d,0x4871,0x4871,0x489b,0x489b,0x48ad,0x48ae,0x48d0,0x48d0,0x48dd,0x48dd,0x48ed,0x48ed,0x48f3,0x48f3,0x48fa,0x48fa,0x4906,0x4906,0x4911,0x4911,0x491e,0x491e,0x4925,0x4925,0x492a,0x492a,0x492d,0x492d,0x492f,0x4930,0x4935,0x4935,0x493c,0x493c,0x493e,0x493e,0x4945,0x4945,0x4951,0x4951,0x4953,0x4953,0x4965,0x4965,0x496a,0x496a,0x4972,0x4972,0x4989,0x4989,0x49a1,0x49a1,0x49a7,0x49a7,0x49df,0x49df,0x49e5,0x49e5,0x49e7,0x49e7,0x4a0f,0x4a0f,0x4a1d,0x4a1d,0x4a24,0x4a24,0x4a35,0x4a35,0x4a96,0x4a96,0x4aa4,0x4aa4,0x4ab4,0x4ab4,0x4ab8,0x4ab8,0x4ad1,0x4ad1,0x4ae4,0x4ae4,0x4aff,0x4aff,0x4b10,0x4b10,0x4b19,0x4b19,0x4b20,0x4b20,0x4b2c,0x4b2c,0x4b37,0x4b37,0x4b6f,0x4b70,0x4b72,0x4b72,0x4b7b,0x4b7b,0x4b7e,0x4b7e,0x4b8e,0x4b8e,0x4b90,0x4b90,0x4b93,0x4b93,0x4b96,0x4b97,0x4b9d,0x4b9d,0x4bbd,0x4bbe,0x4bc0,0x4bc0,0x4c04,0x4c04,0x4c07,0x4c07,0x4c0e,0x4c0e,0x4c32,0x4c32,0x4c3b,0x4c3b,0x4c3e,0x4c3e,0x4c40,0x4c40,0x4c47,0x4c47,0x4c57,0x4c57,0x4c5b,0x4c5b,0x4c6d,0x4c6d,0x4c77,0x4c77,0x4c7b,0x4c7b,0x4c7d,0x4c7d,0x4c81,0x4c81,0x4c85,0x4c85,0x4ca4,0x4ca4,0x4cae,0x4cae,0x4cb0,0x4cb0,0x4cb7,0x4cb7,0x4ccd,0x4ccd,0x4ce1,0x4ce2,0x4ced,0x4ced,0x4d07,0x4d07,0x4d09,0x4d09,0x4d10,0x4d10,0x4d34,0x4d34,0x4d76,0x4d77,0x4d89,0x4d89,0x4d91,0x4d91,0x4d9c,0x4d9c,0x4e00,0x4e01,0x4e03,0x4e04,0x4e07,0x4e11,0x4e14,0x4e16,0x4e18,0x4e1a,0x4e1c,0x4e1c,0x4e1e,0x4e1f,0x4e21,0x4e22,0x4e24,0x4e24,0x4e26,0x4e26,0x4e28,0x4e28,0x4e2a,0x4e33,0x4e36,0x4e39,0x4e3b,0x4e3d,0x4e3f,0x4e3f,0x4e42,0x4e43,0x4e45,0x4e45,0x4e47,0x4e49,0x4e4b,0x4e4b,0x4e4d,0x4e4f,0x4e52,0x4e53,0x4e56,0x4e56,0x4e58,0x4e5f,0x4e69,0x4e6a,0x4e73,0x4e73,0x4e78,0x4e78,0x4e7e,0x4e89,0x4e8b,0x4e8e,0x4e91,0x4e95,0x4e98,0x4e9b,0x4e9e,0x4ea6,0x4ea8,0x4ea8,0x4eab,0x4eae,0x4eb3,0x4eb3,0x4eb6,0x4eb7,0x4eb9,0x4ebc,0x4ebf,0x4ec4,0x4ec6,0x4ecb,0x4ecd,0x4ece,0x4ed4,0x4eda,0x4edc,0x4edf,0x4ee1,0x4ee1,0x4ee3,0x4ee5,0x4ee8,0x4eeb,0x4eee,0x4eee,0x4ef0,0x4ef8,0x4efb,0x4efb,0x4efd,0x4efd,0x4eff,0x4f05,0x4f08,0x4f0b,0x4f0d,0x4f15,0x4f17,0x4f1a,0x4f1d,0x4f1d,0x4f22,0x4f22,0x4f28,0x4f29,0x4f2c,0x4f2d,0x4f2f,0x4f30,0x4f32,0x4f34,0x4f36,0x4f3f,0x4f41,0x4f43,0x4f45,0x4f49,0x4f4b,0x4f64,0x4f67,0x4f67,0x4f69,0x4f6c,0x4f6e,0x4f70,0x4f72,0x4f8b,0x4f8d,0x4f8d,0x4f8f,0x4f92,0x4f94,0x4f98,0x4f9a,0x4f9e,0x4fa2,0x4fa2,0x4fa8,0x4fa8,0x4fab,0x4fab,0x4fae,0x4fb0,0x4fb2,0x4fb7,0x4fb9,0x4fbb,0x4fbd,0x4fbd,0x4fbf,0x4fc5,0x4fc7,0x4fd1,0x4fd3,0x4fd4,0x4fd6,0x4fe1,0x4fe4,0x4fe5,0x4fec,0x4fec,0x4fee,0x4ffa,0x4ffd,0x4ffe,0x5000,0x5000,0x5003,0x5003,0x5005,0x5009,0x500b,0x500f,0x5011,0x501c,0x501e,0x5023,0x5025,0x5031,0x5033,0x5035,0x5037,0x5037,0x503b,0x503c,0x5040,0x5041,0x5043,0x5043,0x5045,0x504f,0x5051,0x5051,0x5053,0x5053,0x5055,0x5058,0x505a,0x5066,0x5068,0x5070,0x5072,0x5077,0x507a,0x507a,0x507d,0x507d,0x5080,0x5083,0x5085,0x5085,0x5087,0x5088,0x508b,0x508e,0x5090,0x5092,0x5094,0x5096,0x5098,0x509e,0x50a2,0x50a3,0x50a6,0x50a6,0x50ac,0x50b8,0x50ba,0x50bf,0x50c1,0x50c2,0x50c4,0x50cb,0x50cd,0x50d1,0x50d3,0x50d7,0x50d9,0x50db,0x50dd,0x50dd,0x50df,0x50e1,0x50e3,0x50ea,0x50ec,0x50f1,0x50f3,0x50f6,0x50f8,0x50f9,0x50fb,0x510e,0x5110,0x5115,0x5117,0x5118,0x511a,0x511a,0x511c,0x511c,0x511f,0x5122,0x5124,0x5126,0x5129,0x512b,0x512d,0x512e,0x5130,0x5135,0x5137,0x513d,0x513f,0x5141,0x5143,0x5149,0x514b,0x514d,0x5151,0x5152,0x5154,0x5157,0x5159,0x5163,0x5165,0x5165,0x5167,0x516e,0x5171,0x5171,0x5174,0x5179,0x517c,0x517c,0x5180,0x5180,0x5182,0x5182,0x5186,0x518a,0x518d,0x518d,0x518f,0x518f,0x5191,0x5198,0x519a,0x519a,0x519c,0x519c,0x519e,0x519e,0x51a0,0x51a0,0x51a2,0x51a2,0x51a4,0x51a5,0x51a7,0x51a8,0x51aa,0x51ac,0x51ae,0x51ae,0x51b0,0x51b9,0x51bc,0x51be,0x51c3,0x51d4,0x51d7,0x51d8,0x51db,0x51e2,0x51e4,0x51e4,0x51ed,0x51ed,0x51f0,0x51f1,0x51f3,0x51f6,0x51f8,0x51fa,0x51fc,0x51fe,0x5200,0x5203,0x5205,0x520c,0x520e,0x520e,0x5210,0x5213,0x5216,0x5217,0x521c,0x5221,0x5224,0x522a,0x522e,0x522e,0x5230,0x5238,0x523a,0x523c,0x5241,0x5241,0x5243,0x5244,0x5246,0x5247,0x5249,0x524f,0x5252,0x5252,0x5254,0x5257,0x5259,0x5262,0x5268,0x526f,0x5272,0x5275,0x5277,0x527d,0x527f,0x5284,0x5287,0x528d,0x528f,0x5291,0x5293,0x5294,0x5296,0x529b,0x529f,0x52a1,0x52a3,0x52a4,0x52a6,0x52a6,0x52a8,0x52ae,0x52b5,0x52b5,0x52b9,0x52b9,0x52bb,0x52bc,0x52be,0x52be,0x52c0,0x52c3,0x52c5,0x52c5,0x52c7,0x52c7,0x52c9,0x52c9,0x52cc,0x52cd,0x52d0,0x52d3,0x52d5,0x52d9,0x52db,0x52db,0x52dd,0x52e4,0x52e6,0x52e6,0x52e9,0x52e9,0x52eb,0x52eb,0x52ef,0x52f1,0x52f3,0x52f5,0x52f7,0x52fc,0x52fe,0x52ff,0x5301,0x5301,0x5305,0x5306,0x5308,0x530b,0x530d,0x5312,0x5315,0x5317,0x5319,0x531a,0x531c,0x531d,0x531f,0x5324,0x5327,0x5327,0x532a,0x532a,0x532c,0x532d,0x532f,0x5334,0x5337,0x5339,0x533b,0x5345,0x5347,0x534a,0x534c,0x534e,0x5351,0x5354,0x5357,0x5357,0x535a,0x535a,0x535c,0x5361,0x5363,0x5364,0x5366,0x5367,0x5369,0x5369,0x536c,0x5375,0x5377,0x5379,0x537b,0x537f,0x5382,0x5382,0x5384,0x5384,0x538a,0x538a,0x538e,0x538f,0x5392,0x5394,0x5396,0x539a,0x539c,0x53a0,0x53a2,0x53a2,0x53a4,0x53ae,0x53b0,0x53b0,0x53b2,0x53b2,0x53b4,0x53b4,0x53b6,0x53b6,0x53b9,0x53b9,0x53bb,0x53bb,0x53c1,0x53c3,0x53c5,0x53c5,0x53c8,0x53cd,0x53d0,0x53d2,0x53d4,0x53d4,0x53d6,0x53db,0x53df,0x53e6,0x53e8,0x53f3,0x53f5,0x53f8,0x53fb,0x53fc,0x53fe,0x53fe,0x5401,0x5401,0x5403,0x5404,0x5406,0x5414,0x5416,0x5416,0x5418,0x5421,0x5423,0x5439,0x543b,0x5443,0x5445,0x5448,0x544a,0x544f,0x5454,0x5454,0x5460,0x546d,0x546f,0x5478,0x547a,0x5482,0x5484,0x5488,0x548b,0x5498,0x549a,0x549a,0x549c,0x549c,0x549e,0x549e,0x54a0,0x54b4,0x54b6,0x54c9,0x54cb,0x54d0,0x54d6,0x54d6,0x54da,0x54da,0x54de,0x54de,0x54e0,0x54eb,0x54ed,0x54ef,0x54f1,0x54f3,0x54f7,0x54f8,0x54fa,0x54fd,0x54ff,0x54ff,0x5501,0x5514,0x5517,0x5518,0x551a,0x551a,0x551e,0x551e,0x5523,0x5523,0x5525,0x5528,0x552a,0x5539,0x553b,0x553c,0x553e,0x5541,0x5543,0x554b,0x554d,0x5553,0x5555,0x5557,0x555c,0x555f,0x5561,0x5566,0x5569,0x556b,0x5571,0x5573,0x5575,0x5577,0x5579,0x5579,0x557b,0x5584,0x5586,0x5595,0x5598,0x559a,0x559c,0x559d,0x559f,0x559f,0x55a1,0x55ae,0x55b0,0x55b5,0x55b9,0x55bc,0x55bf,0x55df,0x55e1,0x55ea,0x55ec,0x55ec,0x55ee,0x55f2,0x55f5,0x55f7,0x55f9,0x5602,0x5604,0x5606,0x5608,0x5609,0x560c,0x5617,0x561b,0x5623,0x5625,0x5625,0x5627,0x5627,0x5629,0x562a,0x562c,0x5630,0x5632,0x563b,0x563d,0x5643,0x5645,0x5646,0x5648,0x564a,0x564c,0x5650,0x5652,0x5654,0x5657,0x565a,0x565d,0x565e,0x5660,0x5666,0x5668,0x5674,0x5676,0x567c,0x567e,0x5687,0x5689,0x5690,0x5692,0x5693,0x5695,0x5695,0x5697,0x569a,0x569c,0x569f,0x56a1,0x56a1,0x56a4,0x56a8,0x56aa,0x56af,0x56b1,0x56b7,0x56b9,0x56b9,0x56bc,0x56c3,0x56c5,0x56c6,0x56c8,0x56cd,0x56d1,0x56d1,0x56d3,0x56d4,0x56d6,0x56d7,0x56da,0x56db,0x56dd,0x56e2,0x56e4,0x56e5,0x56e7,0x56e7,0x56ea,0x56eb,0x56ed,0x56f1,0x56f7,0x56f7,0x56f9,0x56fb,0x56fd,0x56fd,0x56ff,0x5704,0x5707,0x570d,0x5712,0x5716,0x5718,0x5718,0x571a,0x5720,0x5722,0x5723,0x5728,0x572a,0x572c,0x5730,0x5732,0x5734,0x573b,0x573b,0x573d,0x5743,0x5745,0x5747,0x5749,0x5752,0x5754,0x5754,0x5757,0x5757,0x575b,0x575b,0x575f,0x575f,0x5761,0x5762,0x5764,0x5764,0x5766,0x576b,0x576d,0x576d,0x576f,0x5777,0x577a,0x5780,0x5782,0x5783,0x5788,0x5788,0x578a,0x578d,0x578f,0x5790,0x5793,0x5795,0x5797,0x57a5,0x57a7,0x57a7,0x57aa,0x57aa,0x57ae,0x57ae,0x57b3,0x57b6,0x57b8,0x57bf,0x57c1,0x57c4,0x57c6,0x57c8,0x57cb,0x57cc,0x57ce,0x57d0,0x57d2,0x57d2,0x57d4,0x57d5,0x57d7,0x57d7,0x57dc,0x57e7,0x57e9,0x57e9,0x57ec,0x57fe,0x5800,0x580e,0x5810,0x5810,0x5812,0x5812,0x5814,0x5814,0x5818,0x5819,0x581b,0x581e,0x5820,0x582a,0x582c,0x583b,0x583d,0x583d,0x583f,0x5840,0x5844,0x5844,0x5847,0x584f,0x5851,0x5855,0x5857,0x585f,0x5862,0x5865,0x5868,0x5869,0x586b,0x586d,0x586f,0x586f,0x5871,0x5876,0x5879,0x5883,0x5885,0x588b,0x588e,0x5894,0x5896,0x5896,0x5898,0x589a,0x589c,0x58a1,0x58a3,0x58a3,0x58a5,0x58ac,0x58ae,0x58b1,0x58b3,0x58b3,0x58b5,0x58b6,0x58ba,0x58bf,0x58c1,0x58c2,0x58c5,0x58c9,0x58cb,0x58cb,0x58ce,0x58d6,0x58d8,0x58e0,0x58e2,0x58e4,0x58e7,0x58e9,0x58eb,0x58ec,0x58ef,0x58f0,0x58f2,0x58f4,0x58f9,0x58ff,0x5902,0x5907,0x590a,0x590a,0x590c,0x590f,0x5911,0x5912,0x5914,0x5917,0x5919,0x591a,0x591c,0x591d,0x591f,0x5920,0x5922,0x5922,0x5924,0x5925,0x5927,0x5927,0x5929,0x592f,0x5931,0x5932,0x5934,0x5934,0x5937,0x5938,0x593c,0x593c,0x593e,0x593e,0x5940,0x5940,0x5944,0x5945,0x5947,0x594a,0x594e,0x5951,0x5953,0x5955,0x5957,0x5958,0x595a,0x595a,0x595c,0x595c,0x5960,0x5962,0x5965,0x5965,0x5967,0x5967,0x5969,0x596e,0x5970,0x5979,0x597b,0x5985,0x5989,0x598a,0x598d,0x5990,0x5992,0x5994,0x5996,0x599a,0x599d,0x59a8,0x59ac,0x59ac,0x59ae,0x59c1,0x59c3,0x59d4,0x59d6,0x59d6,0x59d8,0x59de,0x59e0,0x59e1,0x59e3,0x59e6,0x59e8,0x5a03,0x5a09,0x5a0d,0x5a0f,0x5a0f,0x5a11,0x5a13,0x5a15,0x5a1c,0x5a1e,0x5a21,0x5a23,0x5a25,0x5a27,0x5a27,0x5a29,0x5a2e,0x5a33,0x5a33,0x5a35,0x5a39,0x5a3c,0x5a3e,0x5a40,0x5a4a,0x5a4c,0x5a4d,0x5a50,0x5a6e,0x5a70,0x5a71,0x5a77,0x5a7f,0x5a81,0x5a84,0x5a86,0x5a86,0x5a88,0x5a88,0x5a8a,0x5a8c,0x5a8e,0x5a97,0x5a99,0x5aa2,0x5aa4,0x5aa7,0x5aa9,0x5aac,0x5aae,0x5ac4,0x5ac6,0x5acf,0x5ad1,0x5ad1,0x5ad3,0x5ad3,0x5ad5,0x5ae6,0x5ae8,0x5aee,0x5af0,0x5af0,0x5af2,0x5afb,0x5afd,0x5aff,0x5b01,0x5b03,0x5b05,0x5b05,0x5b07,0x5b09,0x5b0b,0x5b0d,0x5b0f,0x5b11,0x5b13,0x5b17,0x5b19,0x5b1b,0x5b1d,0x5b21,0x5b23,0x5b28,0x5b2a,0x5b30,0x5b32,0x5b32,0x5b34,0x5b34,0x5b38,0x5b38,0x5b3c,0x5b41,0x5b43,0x5b48,0x5b4a,0x5b51,0x5b53,0x5b58,0x5b5a,0x5b5d,0x5b5f,0x5b5f,0x5b62,0x5b66,0x5b68,0x5b69,0x5b6b,0x5b6e,0x5b70,0x5b78,0x5b7a,0x5b7d,0x5b7f,0x5b85,0x5b87,0x5b89,0x5b8b,0x5b8c,0x5b8e,0x5b90,0x5b92,0x5b93,0x5b95,0x5b9f,0x5ba2,0x5ba8,0x5baa,0x5baa,0x5bac,0x5bae,0x5bb0,0x5bb0,0x5bb3,0x5bb9,0x5bbf,0x5bc7,0x5bca,0x5bce,0x5bd0,0x5bd9,0x5bdb,0x5bdb,0x5bde,0x5bec,0x5bee,0x5bf3,0x5bf5,0x5bf6,0x5bf8,0x5bf8,0x5bfa,0x5bfa,0x5bff,0x5bff,0x5c01,0x5c01,0x5c03,0x5c05,0x5c07,0x5c16,0x5c1a,0x5c1a,0x5c1c,0x5c1c,0x5c1e,0x5c20,0x5c22,0x5c25,0x5c28,0x5c28,0x5c2a,0x5c2a,0x5c2c,0x5c2c,0x5c30,0x5c31,0x5c33,0x5c33,0x5c37,0x5c3c,0x5c3e,0x5c41,0x5c44,0x5c51,0x5c53,0x5c56,0x5c58,0x5c59,0x5c5c,0x5c5e,0x5c60,0x5c60,0x5c62,0x5c65,0x5c67,0x5c6a,0x5c6c,0x5c6f,0x5c71,0x5c71,0x5c73,0x5c74,0x5c78,0x5c7c,0x5c7e,0x5c7e,0x5c85,0x5c86,0x5c88,0x5c8d,0x5c8f,0x5c95,0x5c99,0x5c9a,0x5c9c,0x5cb1,0x5cb3,0x5cb3,0x5cb5,0x5cb8,0x5cba,0x5cba,0x5cc1,0x5cc2,0x5cc6,0x5ccc,0x5cce,0x5cdb,0x5cde,0x5cdf,0x5ce5,0x5ce5,0x5ce8,0x5cea,0x5cec,0x5cf1,0x5cf4,0x5cf9,0x5cfb,0x5cfd,0x5cff,0x5d01,0x5d06,0x5d07,0x5d0b,0x5d12,0x5d14,0x5d1b,0x5d1d,0x5d20,0x5d22,0x5d29,0x5d2c,0x5d2c,0x5d2e,0x5d3a,0x5d3c,0x5d43,0x5d45,0x5d4c,0x5d4e,0x5d4e,0x5d50,0x5d52,0x5d55,0x5d57,0x5d59,0x5d59,0x5d5b,0x5d5b,0x5d5e,0x5d5e,0x5d62,0x5d63,0x5d65,0x5d65,0x5d67,0x5d69,0x5d6b,0x5d6c,0x5d6f,0x5d72,0x5d74,0x5d74,0x5d77,0x5d82,0x5d84,0x5d8b,0x5d8d,0x5d8e,0x5d92,0x5d95,0x5d97,0x5d97,0x5d99,0x5d9a,0x5d9c,0x5da2,0x5da4,0x5da4,0x5da7,0x5db2,0x5db4,0x5dba,0x5dbc,0x5dbd,0x5dc0,0x5dc3,0x5dc6,0x5dc7,0x5dc9,0x5dc9,0x5dcb,0x5dcb,0x5dcd,0x5dcd,0x5dcf,0x5dcf,0x5dd1,0x5dd2,0x5dd4,0x5dd8,0x5ddb,0x5ddb,0x5ddd,0x5de2,0x5de5,0x5de8,0x5deb,0x5deb,0x5dee,0x5dee,0x5df0,0x5df5,0x5df7,0x5df7,0x5df9,0x5df9,0x5dfd,0x5dff,0x5e02,0x5e04,0x5e06,0x5e06,0x5e09,0x5e0c,0x5e0e,0x5e0e,0x5e11,0x5e12,0x5e14,0x5e1b,0x5e1d,0x5e1d,0x5e1f,0x5e25,0x5e28,0x5e29,0x5e2b,0x5e2b,0x5e2d,0x5e2e,0x5e33,0x5e34,0x5e36,0x5e38,0x5e3d,0x5e3e,0x5e40,0x5e45,0x5e48,0x5e48,0x5e4a,0x5e4f,0x5e53,0x5e55,0x5e57,0x5e59,0x5e5b,0x5e63,0x5e66,0x5e70,0x5e72,0x5e76,0x5e78,0x5e80,0x5e82,0x5e84,0x5e86,0x5e8d,0x5e8f,0x5e8f,0x5e92,0x5e92,0x5e95,0x5e97,0x5e99,0x5e9c,0x5ea0,0x5ea0,0x5ea2,0x5ea8,0x5eaa,0x5eae,0x5eb0,0x5eb9,0x5ebd,0x5ebe,0x5ec1,0x5ec2,0x5ec4,0x5ece,0x5ed0,0x5ee3,0x5ee5,0x5ee9,0x5eec,0x5eec,0x5eee,0x5eef,0x5ef1,0x5ef4,0x5ef6,0x5efc,0x5efe,0x5eff,0x5f01,0x5f02,0x5f04,0x5f05,0x5f07,0x5f08,0x5f0a,0x5f0f,0x5f12,0x5f15,0x5f17,0x5f18,0x5f1a,0x5f1b,0x5f1d,0x5f1d,0x5f1f,0x5f1f,0x5f22,0x5f29,0x5f2d,0x5f2e,0x5f30,0x5f31,0x5f33,0x5f33,0x5f35,0x5f38,0x5f3a,0x5f3c,0x5f40,0x5f40,0x5f43,0x5f46,0x5f48,0x5f51,0x5f54,0x5f54,0x5f56,0x5f59,0x5f5c,0x5f5e,0x5f61,0x5f65,0x5f67,0x5f67,0x5f69,0x5f6d,0x5f6f,0x5f74,0x5f76,0x5f79,0x5f7b,0x5f83,0x5f85,0x5f8c,0x5f90,0x5f92,0x5f96,0x5f99,0x5f9b,0x5f9c,0x5f9e,0x5fa1,0x5fa4,0x5faf,0x5fb1,0x5fb2,0x5fb5,0x5fb7,0x5fb9,0x5fc5,0x5fc9,0x5fc9,0x5fcc,0x5fcd,0x5fcf,0x5fd2,0x5fd4,0x5fd9,0x5fdb,0x5fdb,0x5fdd,0x5fe1,0x5fe3,0x5fe5,0x5fe8,0x5fe8,0x5fea,0x5feb,0x5fed,0x5fef,0x5ff1,0x5ff1,0x5ff3,0x5ff5,0x5ff7,0x5ff8,0x5ffa,0x5ffb,0x5ffd,0x5ffd,0x5fff,0x6000,0x6009,0x6017,0x6019,0x601e,0x6020,0x602f,0x6031,0x6035,0x6037,0x6037,0x6039,0x6039,0x603b,0x603b,0x6040,0x6047,0x6049,0x604d,0x6050,0x6050,0x6052,0x6055,0x6058,0x605b,0x605d,0x605f,0x6062,0x6070,0x6072,0x6072,0x6075,0x6075,0x6077,0x6077,0x607e,0x6081,0x6083,0x608a,0x608c,0x608e,0x6090,0x6090,0x6092,0x6092,0x6094,0x6097,0x609a,0x60a0,0x60a2,0x60a4,0x60a6,0x60a8,0x60b0,0x60c1,0x60c3,0x60cf,0x60d1,0x60d1,0x60d3,0x60d5,0x60d7,0x60e4,0x60e6,0x60e9,0x60f0,0x6101,0x6103,0x6110,0x6112,0x6116,0x6118,0x611d,0x611f,0x6120,0x6122,0x6123,0x6127,0x6129,0x612b,0x612c,0x612e,0x6130,0x6132,0x6132,0x6134,0x6134,0x6136,0x6137,0x613b,0x613b,0x613d,0x6142,0x6144,0x6150,0x6152,0x6156,0x6158,0x6168,0x616a,0x616c,0x616e,0x6177,0x6179,0x617a,0x617c,0x617e,0x6180,0x6183,0x6187,0x6187,0x6189,0x618e,0x6190,0x6196,0x6198,0x619d,0x619f,0x619f,0x61a1,0x61a2,0x61a4,0x61a4,0x61a7,0x61ba,0x61bc,0x61bc,0x61be,0x61c3,0x61c5,0x61cd,0x61cf,0x61d0,0x61d3,0x61d3,0x61d6,0x61d6,0x61d8,0x61d8,0x61da,0x61da,0x61de,0x61e0,0x61e2,0x61eb,0x61ed,0x61ee,0x61f0,0x61f2,0x61f5,0x6201,0x6203,0x6204,0x6207,0x620a,0x620c,0x620e,0x6210,0x6212,0x6214,0x6216,0x6219,0x621b,0x621f,0x6225,0x6227,0x6227,0x6229,0x622e,0x6230,0x6230,0x6232,0x6234,0x6236,0x6237,0x6239,0x623a,0x623d,0x6243,0x6246,0x624e,0x6250,0x6254,0x6258,0x625c,0x625e,0x625e,0x6260,0x6266,0x6268,0x6268,0x626d,0x6274,0x6276,0x6277,0x6279,0x628a,0x628c,0x628c,0x628e,0x6298,0x629d,0x629d,0x62a4,0x62a4,0x62a6,0x62a6,0x62a8,0x62b1,0x62b3,0x62b6,0x62b8,0x62b9,0x62bb,0x62bf,0x62c1,0x62dc,0x62df,0x62df,0x62e5,0x62e5,0x62eb,0x6303,0x6307,0x6309,0x630b,0x6311,0x6313,0x6316,0x6318,0x6318,0x6328,0x632f,0x6331,0x633e,0x6340,0x6351,0x6354,0x635a,0x635d,0x635d,0x6364,0x6365,0x6367,0x6369,0x636b,0x6372,0x6375,0x637d,0x637f,0x6385,0x6387,0x6392,0x6394,0x6394,0x6396,0x6399,0x639b,0x63a5,0x63a7,0x63b1,0x63b9,0x63b9,0x63bd,0x63be,0x63c0,0x63d3,0x63d5,0x63eb,0x63ed,0x63f6,0x63f8,0x63f9,0x63fb,0x63fc,0x63fe,0x63fe,0x6406,0x6407,0x6409,0x6410,0x6412,0x6418,0x641a,0x641c,0x641e,0x6428,0x642a,0x6430,0x6432,0x643b,0x643d,0x6441,0x6443,0x6443,0x644b,0x644b,0x644d,0x644e,0x6450,0x6454,0x6458,0x6461,0x6465,0x6469,0x646b,0x647d,0x647f,0x647f,0x6482,0x6482,0x6485,0x6485,0x6487,0x648d,0x648f,0x6493,0x6495,0x649a,0x649c,0x64a0,0x64a2,0x64a6,0x64a9,0x64a9,0x64ab,0x64b4,0x64b6,0x64b6,0x64bb,0x64c5,0x64c7,0x64c7,0x64c9,0x64cb,0x64cd,0x64d0,0x64d2,0x64d4,0x64d6,0x64db,0x64dd,0x64dd,0x64e0,0x64ed,0x64ef,0x64f4,0x64f7,0x64f8,0x64fa,0x6501,0x6503,0x6504,0x6506,0x6507,0x6509,0x650a,0x650c,0x6511,0x6513,0x6519,0x651b,0x6526,0x6529,0x6530,0x6532,0x6539,0x653b,0x653b,0x653d,0x653f,0x6541,0x6541,0x6543,0x6543,0x6545,0x6546,0x6548,0x654a,0x654d,0x654d,0x654f,0x654f,0x6551,0x6551,0x6553,0x655a,0x655c,0x655f,0x6562,0x6568,0x656a,0x656d,0x656f,0x656f,0x6572,0x657c,0x657f,0x6589,0x658b,0x658c,0x6590,0x6592,0x6594,0x6597,0x6599,0x6599,0x659b,0x65a2,0x65a4,0x65a5,0x65a7,0x65a8,0x65aa,0x65ac,0x65ae,0x65b0,0x65b2,0x65b3,0x65b5,0x65b9,0x65bb,0x65bf,0x65c1,0x65c6,0x65cb,0x65d4,0x65d6,0x65d7,0x65da,0x65db,0x65dd,0x65e3,0x65e5,0x65e6,0x65e8,0x65e9,0x65ec,0x65f5,0x65fa,0x65fd,0x65ff,0x6600,0x6602,0x6615,0x6618,0x6618,0x661c,0x6628,0x662b,0x662b,0x662d,0x6636,0x6639,0x663a,0x6641,0x6645,0x6647,0x664d,0x664f,0x664f,0x6651,0x6653,0x6657,0x6657,0x6659,0x6668,0x666a,0x666c,0x666e,0x6674,0x6676,0x667e,0x6680,0x6680,0x6684,0x668e,0x6690,0x6692,0x6694,0x669a,0x669d,0x669d,0x669f,0x66a2,0x66a4,0x66a4,0x66a8,0x66ab,0x66ad,0x66bb,0x66bd,0x66c0,0x66c4,0x66c4,0x66c6,0x66cf,0x66d2,0x66d2,0x66d6,0x66d6,0x66d8,0x66de,0x66e0,0x66e0,0x66e3,0x66e4,0x66e6,0x66e9,0x66eb,0x66ee,0x66f0,0x66f4,0x66f6,0x66f9,0x66fc,0x66fc,0x66fe,0x6705,0x6708,0x6710,0x6712,0x6719,0x671b,0x671b,0x671d,0x6723,0x6725,0x6728,0x672a,0x672e,0x6731,0x6731,0x6733,0x6736,0x6738,0x673f,0x6744,0x6749,0x674b,0x6751,0x6753,0x6753,0x6755,0x6757,0x6759,0x675a,0x675c,0x6762,0x6767,0x6767,0x676a,0x677f,0x6781,0x6787,0x6789,0x6789,0x678b,0x6795,0x6797,0x679a,0x679c,0x679d,0x679f,0x67a0,0x67a4,0x67a4,0x67ac,0x67ac,0x67ae,0x67bb,0x67bf,0x67c6,0x67c8,0x67d4,0x67d6,0x67df,0x67e2,0x67e7,0x67e9,0x67fa,0x67fc,0x67fc,0x67fe,0x6804,0x680d,0x680d,0x6810,0x6810,0x6812,0x6814,0x6816,0x6818,0x681a,0x6822,0x6825,0x6826,0x6828,0x682b,0x682d,0x682f,0x6831,0x683e,0x6840,0x6851,0x6853,0x6856,0x685d,0x685d,0x6865,0x6865,0x686b,0x686b,0x686d,0x686f,0x6871,0x6872,0x6874,0x6879,0x687b,0x688c,0x688f,0x6894,0x6896,0x6898,0x689b,0x689d,0x689f,0x68a4,0x68a6,0x68b6,0x68b9,0x68b9,0x68bd,0x68bd,0x68c1,0x68c1,0x68c3,0x68ce,0x68d0,0x68d8,0x68da,0x68da,0x68dc,0x68e1,0x68e3,0x68e4,0x68e6,0x68ec,0x68ee,0x68fd,0x6900,0x6915,0x6917,0x691b,0x6925,0x6925,0x692a,0x692a,0x692c,0x692c,0x692f,0x6930,0x6932,0x6939,0x693b,0x6946,0x6948,0x694c,0x694e,0x694f,0x6951,0x697b,0x6980,0x6980,0x6982,0x6983,0x6985,0x6986,0x698a,0x698a,0x698d,0x698e,0x6990,0x6991,0x6993,0x699c,0x699e,0x69b7,0x69b9,0x69b9,0x69bb,0x69c4,0x69c6,0x69c6,0x69c9,0x69d1,0x69d3,0x69d6,0x69d9,0x69d9,0x69e1,0x69e2,0x69e4,0x69e9,0x69eb,0x69ee,0x69f1,0x69f4,0x69f6,0x6a0d,0x6a0f,0x6a0f,0x6a11,0x6a11,0x6a13,0x6a21,0x6a23,0x6a23,0x6a25,0x6a29,0x6a2b,0x6a2d,0x6a32,0x6a35,0x6a38,0x6a41,0x6a43,0x6a49,0x6a4b,0x6a5b,0x6a5d,0x6a6b,0x6a6d,0x6a6d,0x6a6f,0x6a6f,0x6a71,0x6a71,0x6a74,0x6a74,0x6a76,0x6a76,0x6a7a,0x6a7a,0x6a7e,0x6a85,0x6a87,0x6a87,0x6a89,0x6a8a,0x6a8c,0x6a97,0x6a99,0x6aa8,0x6aab,0x6aaf,0x6ab1,0x6abb,0x6abd,0x6abe,0x6ac2,0x6ac3,0x6ac5,0x6acd,0x6acf,0x6ad1,0x6ad3,0x6ad4,0x6ad8,0x6ae1,0x6ae5,0x6ae5,0x6ae7,0x6ae8,0x6aea,0x6aec,0x6aee,0x6af1,0x6af3,0x6af3,0x6af6,0x6af6,0x6af8,0x6afc,0x6b00,0x6b00,0x6b02,0x6b05,0x6b08,0x6b0b,0x6b0f,0x6b13,0x6b16,0x6b1a,0x6b1d,0x6b1e,0x6b20,0x6b21,0x6b23,0x6b23,0x6b25,0x6b25,0x6b28,0x6b28,0x6b2c,0x6b2d,0x6b2f,0x6b2f,0x6b31,0x6b3f,0x6b41,0x6b43,0x6b45,0x6b4e,0x6b50,0x6b52,0x6b54,0x6b57,0x6b59,0x6b59,0x6b5b,0x6b5c,0x6b5e,0x6b67,0x6b6a,0x6b6a,0x6b6d,0x6b6d,0x6b6f,0x6b6f,0x6b72,0x6b72,0x6b74,0x6b74,0x6b76,0x6b7b,0x6b7e,0x6b84,0x6b86,0x6b86,0x6b88,0x6b8a,0x6b8c,0x6b8f,0x6b91,0x6b91,0x6b94,0x6b99,0x6b9b,0x6b9b,0x6b9e,0x6ba0,0x6ba2,0x6ba7,0x6baa,0x6bab,0x6bad,0x6bb0,0x6bb2,0x6bb3,0x6bb5,0x6bb7,0x6bba,0x6bba,0x6bbc,0x6bbd,0x6bbf,0x6bc1,0x6bc3,0x6bcd,0x6bcf,0x6bd0,0x6bd2,0x6bd4,0x6bd6,0x6bd8,0x6bda,0x6bdc,0x6bde,0x6bde,0x6be0,0x6be4,0x6be6,0x6be8,0x6bea,0x6bec,0x6bef,0x6bf0,0x6bf2,0x6bf3,0x6bf7,0x6c06,0x6c08,0x6c09,0x6c0b,0x6c0d,0x6c0f,0x6c11,0x6c13,0x6c16,0x6c18,0x6c1d,0x6c1f,0x6c21,0x6c23,0x6c28,0x6c2a,0x6c2c,0x6c2e,0x6c3b,0x6c3d,0x6c43,0x6c46,0x6c46,0x6c49,0x6c50,0x6c52,0x6c52,0x6c54,0x6c55,0x6c57,0x6c61,0x6c65,0x6c6b,0x6c6d,0x6c76,0x6c78,0x6c7b,0x6c7d,0x6c90,0x6c92,0x6c96,0x6c98,0x6c9d,0x6c9f,0x6c9f,0x6ca2,0x6ca2,0x6caa,0x6cb4,0x6cb6,0x6cc7,0x6cc9,0x6cd7,0x6cd9,0x6ce3,0x6ce5,0x6ce5,0x6ce7,0x6cf3,0x6cf5,0x6cf5,0x6cf9,0x6cf9,0x6cff,0x6d12,0x6d16,0x6d1b,0x6d1d,0x6d20,0x6d22,0x6d22,0x6d24,0x6d42,0x6d4e,0x6d4e,0x6d57,0x6d5c,0x6d5e,0x6d6a,0x6d6c,0x6d72,0x6d74,0x6d98,0x6d9a,0x6d9a,0x6da4,0x6da5,0x6daa,0x6dac,0x6dae,0x6daf,0x6db1,0x6db5,0x6db7,0x6dc0,0x6dc2,0x6dc2,0x6dc4,0x6dcd,0x6dcf,0x6de6,0x6de8,0x6df7,0x6df9,0x6dfe,0x6e00,0x6e00,0x6e02,0x6e05,0x6e0a,0x6e0a,0x6e0f,0x6e0f,0x6e15,0x6e15,0x6e18,0x6e1d,0x6e1f,0x6e36,0x6e38,0x6e41,0x6e43,0x6e47,0x6e49,0x6e4b,0x6e4d,0x6e69,0x6e6b,0x6e6b,0x6e6e,0x6e6f,0x6e71,0x6e74,0x6e76,0x6e79,0x6e7c,0x6e7c,0x6e86,0x6e86,0x6e88,0x6e89,0x6e8b,0x6e8b,0x6e8d,0x6e90,0x6e92,0x6e94,0x6e96,0x6ea7,0x6eaa,0x6eab,0x6eae,0x6ed6,0x6ed8,0x6edd,0x6ee2,0x6ee2,0x6ee8,0x6ee9,0x6eeb,0x6eef,0x6ef1,0x6ef2,0x6ef4,0x6f0f,0x6f12,0x6f1a,0x6f1c,0x6f1c,0x6f1e,0x6f27,0x6f29,0x6f41,0x6f43,0x6f44,0x6f4e,0x6f58,0x6f5a,0x6f64,0x6f66,0x6f67,0x6f69,0x6f70,0x6f72,0x6f74,0x6f76,0x6f82,0x6f84,0x6f8e,0x6f90,0x6f90,0x6f92,0x6f97,0x6f9d,0x6fb6,0x6fb8,0x6fc4,0x6fc6,0x6fcf,0x6fd3,0x6fd5,0x6fd8,0x6fe4,0x6fe6,0x6fe9,0x6feb,0x6ff2,0x6ff4,0x6ff4,0x6ff6,0x6ff8,0x6ffa,0x6ffc,0x6ffe,0x7001,0x7003,0x7007,0x7009,0x700f,0x7011,0x7011,0x7014,0x7024,0x7026,0x702c,0x702f,0x7035,0x7037,0x703c,0x703e,0x7046,0x7048,0x704d,0x7050,0x7052,0x7054,0x7058,0x705a,0x706c,0x706e,0x7071,0x7074,0x707a,0x707c,0x707f,0x7081,0x7086,0x7089,0x708b,0x708e,0x708f,0x7091,0x7096,0x7098,0x709a,0x709f,0x70a1,0x70a3,0x70a7,0x70a9,0x70a9,0x70ab,0x70b1,0x70b3,0x70b5,0x70b7,0x70be,0x70c0,0x70c0,0x70c4,0x70c8,0x70ca,0x70da,0x70dc,0x70e2,0x70e4,0x70e4,0x70ef,0x70f1,0x70f3,0x7100,0x7102,0x7102,0x7104,0x7106,0x7109,0x710e,0x7110,0x7110,0x7113,0x7113,0x7117,0x7117,0x7119,0x7123,0x7125,0x7126,0x7128,0x7129,0x712b,0x712c,0x712e,0x7136,0x713a,0x713b,0x713e,0x713e,0x7140,0x7147,0x7149,0x7154,0x7156,0x715a,0x715c,0x716c,0x716e,0x716e,0x7170,0x7178,0x717a,0x717e,0x7180,0x7182,0x7184,0x718a,0x718c,0x718c,0x718e,0x7192,0x7194,0x7194,0x7196,0x71a5,0x71a7,0x71aa,0x71ac,0x71ad,0x71af,0x71b5,0x71b7,0x71ba,0x71bc,0x71cb,0x71ce,0x71d2,0x71d4,0x71d6,0x71d8,0x71dd,0x71df,0x71e2,0x71e4,0x71e8,0x71eb,0x71ee,0x71f0,0x71f2,0x71f4,0x71f6,0x71f8,0x71f9,0x71fb,0x7203,0x7205,0x7207,0x7209,0x720a,0x720c,0x7210,0x7213,0x7217,0x7219,0x721b,0x721d,0x721f,0x7222,0x722e,0x7230,0x7230,0x7235,0x7236,0x7238,0x723b,0x723d,0x7242,0x7244,0x7244,0x7246,0x724c,0x724f,0x7250,0x7252,0x7253,0x7255,0x7263,0x7266,0x7267,0x7269,0x726a,0x726c,0x726c,0x726e,0x7270,0x7272,0x7274,0x7276,0x7279,0x727b,0x7282,0x7284,0x7289,0x728b,0x7298,0x729a,0x729b,0x729d,0x729f,0x72a1,0x72aa,0x72ac,0x72b0,0x72b2,0x72b2,0x72b4,0x72b5,0x72ba,0x72ba,0x72bd,0x72bd,0x72bf,0x72c6,0x72c9,0x72ce,0x72d0,0x72d2,0x72d4,0x72d4,0x72d6,0x72da,0x72dc,0x72dc,0x72df,0x72e4,0x72e6,0x72e6,0x72e8,0x72eb,0x72f3,0x72f4,0x72f6,0x7302,0x7304,0x7304,0x7307,0x7308,0x730a,0x730c,0x730f,0x7313,0x7316,0x7319,0x731b,0x731e,0x7322,0x7323,0x7325,0x732e,0x7330,0x733c,0x733e,0x7345,0x7348,0x734a,0x734c,0x7352,0x7357,0x735b,0x735d,0x7362,0x7365,0x736c,0x736e,0x7378,0x737a,0x738c,0x738e,0x738f,0x7392,0x7398,0x739c,0x73a2,0x73a4,0x73ad,0x73b2,0x73bc,0x73be,0x73c0,0x73c2,0x73d0,0x73d2,0x73de,0x73e0,0x73eb,0x73ed,0x73ef,0x73f3,0x740d,0x7411,0x7412,0x7414,0x7417,0x7419,0x7426,0x7428,0x743a,0x743c,0x743c,0x743f,0x7457,0x7459,0x7465,0x7467,0x7476,0x7479,0x747a,0x747c,0x7483,0x7485,0x748d,0x7490,0x7490,0x7492,0x7492,0x7494,0x7495,0x7497,0x74a1,0x74a3,0x74ab,0x74ad,0x74ad,0x74af,0x74b2,0x74b4,0x74bb,0x74bd,0x74c3,0x74c5,0x74c6,0x74c8,0x74c8,0x74ca,0x74cc,0x74cf,0x74d0,0x74d3,0x74e9,0x74ec,0x74ec,0x74ee,0x74ee,0x74f0,0x74f2,0x74f4,0x74f8,0x74fb,0x74fb,0x74fd,0x7500,0x7502,0x7505,0x7507,0x7508,0x750b,0x751a,0x751c,0x751f,0x7521,0x7522,0x7525,0x7526,0x7528,0x7535,0x7537,0x753b,0x753d,0x7540,0x7542,0x7542,0x7546,0x7548,0x754a,0x754f,0x7551,0x7551,0x7553,0x7555,0x7559,0x755d,0x755f,0x7560,0x7562,0x7567,0x756a,0x7570,0x7572,0x7572,0x7576,0x757a,0x757d,0x7580,0x7583,0x7584,0x7586,0x7587,0x758a,0x7592,0x7594,0x7595,0x7598,0x759a,0x759d,0x759e,0x75a2,0x75a5,0x75a7,0x75a7,0x75aa,0x75ab,0x75b0,0x75b6,0x75b8,0x75c5,0x75c7,0x75c8,0x75ca,0x75d2,0x75d4,0x75d5,0x75d7,0x75e4,0x75e6,0x75e7,0x75ed,0x75ed,0x75ef,0x7603,0x7607,0x760d,0x760f,0x7611,0x7613,0x7616,0x7619,0x7629,0x762c,0x762d,0x762f,0x7635,0x7638,0x7638,0x763a,0x763d,0x7640,0x7640,0x7642,0x7643,0x7646,0x7649,0x764c,0x7654,0x7656,0x765a,0x765c,0x765c,0x765f,0x7662,0x7664,0x7667,0x7669,0x766a,0x766c,0x7676,0x7678,0x767f,0x7681,0x7682,0x7684,0x7684,0x7686,0x768b,0x768e,0x7690,0x7692,0x7693,0x7695,0x7696,0x7699,0x769e,0x76a1,0x76a1,0x76a4,0x76a6,0x76aa,0x76ab,0x76ad,0x76b0,0x76b4,0x76b5,0x76b7,0x76b8,0x76ba,0x76bb,0x76bd,0x76bf,0x76c2,0x76c6,0x76c8,0x76ca,0x76cc,0x76ce,0x76d2,0x76d4,0x76d6,0x76d6,0x76d9,0x76df,0x76e1,0x76e1,0x76e3,0x76e7,0x76e9,0x76ea,0x76ec,0x76f5,0x76f7,0x76fc,0x76fe,0x76fe,0x7701,0x7701,0x7703,0x7705,0x7707,0x770c,0x770e,0x7713,0x7715,0x7715,0x7719,0x771b,0x771d,0x7720,0x7722,0x7729,0x772b,0x772b,0x772d,0x772d,0x772f,0x772f,0x7731,0x773e,0x7740,0x7740,0x7743,0x7747,0x774a,0x774f,0x7752,0x7752,0x7754,0x7756,0x7758,0x775c,0x775e,0x7763,0x7765,0x776f,0x7772,0x7772,0x7777,0x7785,0x7787,0x7789,0x778b,0x778f,0x7791,0x7791,0x7793,0x7793,0x7795,0x7795,0x7797,0x77a3,0x77a5,0x77a5,0x77a7,0x77a8,0x77aa,0x77ad,0x77af,0x77b7,0x77b9,0x77bf,0x77c2,0x77c5,0x77c7,0x77c7,0x77c9,0x77d0,0x77d3,0x77d5,0x77d7,0x77de,0x77e0,0x77e0,0x77e2,0x77e3,0x77e5,0x77e9,0x77ec,0x77f4,0x77f7,0x77fe,0x7802,0x7803,0x7805,0x7806,0x7808,0x7809,0x780c,0x7814,0x7818,0x7818,0x781c,0x7823,0x7825,0x7835,0x7837,0x7839,0x783c,0x783d,0x7842,0x7845,0x7847,0x784e,0x7850,0x7854,0x785c,0x785e,0x7860,0x7860,0x7862,0x7862,0x7864,0x7866,0x7868,0x7871,0x7879,0x787c,0x787e,0x7881,0x7883,0x7889,0x788c,0x788f,0x7891,0x7891,0x7893,0x789a,0x789e,0x78a5,0x78a7,0x78ad,0x78af,0x78b4,0x78b6,0x78b6,0x78b8,0x78bc,0x78be,0x78be,0x78c1,0x78c1,0x78c3,0x78c5,0x78c7,0x78d5,0x78d7,0x78d8,0x78da,0x78db,0x78dd,0x78e5,0x78e7,0x78ea,0x78ec,0x78f5,0x78f7,0x78f7,0x78f9,0x78ff,0x7901,0x7902,0x7904,0x7906,0x7909,0x7909,0x790c,0x790c,0x790e,0x790e,0x7910,0x7914,0x7917,0x7917,0x7919,0x7919,0x791b,0x791e,0x7921,0x7921,0x7923,0x792f,0x7931,0x7936,0x7938,0x7942,0x7944,0x794c,0x794f,0x7965,0x7967,0x796b,0x796d,0x796d,0x7970,0x7974,0x7979,0x797a,0x797c,0x7983,0x7986,0x7988,0x798a,0x798b,0x798d,0x799d,0x799f,0x79a2,0x79a4,0x79ae,0x79b0,0x79b4,0x79b6,0x79bb,0x79bd,0x79c1,0x79c4,0x79c6,0x79c8,0x79d2,0x79d4,0x79d6,0x79d8,0x79d8,0x79dc,0x79e0,0x79e2,0x79e4,0x79e6,0x79e7,0x79e9,0x79ee,0x79f1,0x79f1,0x79f4,0x79f4,0x79f6,0x79f8,0x79fa,0x79fb,0x7a00,0x7a00,0x7a02,0x7a06,0x7a08,0x7a08,0x7a0a,0x7a0e,0x7a10,0x7a15,0x7a17,0x7a1c,0x7a1e,0x7a20,0x7a22,0x7a22,0x7a26,0x7a26,0x7a28,0x7a28,0x7a2a,0x7a32,0x7a37,0x7a37,0x7a39,0x7a40,0x7a43,0x7a4e,0x7a54,0x7a54,0x7a56,0x7a58,0x7a5a,0x7a5c,0x7a5f,0x7a62,0x7a65,0x7a65,0x7a67,0x7a69,0x7a6b,0x7a6e,0x7a70,0x7a72,0x7a74,0x7a76,0x7a78,0x7a7b,0x7a7d,0x7a81,0x7a83,0x7a8c,0x7a8f,0x7a99,0x7a9e,0x7aa0,0x7aa2,0x7aa3,0x7aa8,0x7aac,0x7aae,0x7ab8,0x7aba,0x7abc,0x7abe,0x7ac5,0x7ac7,0x7acb,0x7acf,0x7acf,0x7ad1,0x7ad1,0x7ad3,0x7ad3,0x7ad8,0x7add,0x7adf,0x7ae0,0x7ae2,0x7ae7,0x7ae9,0x7aeb,0x7aed,0x7aef,0x7af6,0x7af7,0x7af9,0x7b01,0x7b04,0x7b06,0x7b08,0x7b0c,0x7b0e,0x7b14,0x7b18,0x7b1b,0x7b1d,0x7b20,0x7b22,0x7b35,0x7b38,0x7b39,0x7b3b,0x7b3b,0x7b40,0x7b40,0x7b42,0x7b52,0x7b54,0x7b56,0x7b58,0x7b58,0x7b60,0x7b67,0x7b69,0x7b69,0x7b6c,0x7b78,0x7b7b,0x7b7b,0x7b82,0x7b82,0x7b84,0x7b85,0x7b87,0x7b88,0x7b8a,0x7b92,0x7b94,0x7b9d,0x7ba0,0x7ba4,0x7bac,0x7baf,0x7bb1,0x7bb2,0x7bb4,0x7bb5,0x7bb7,0x7bb9,0x7bbe,0x7bbe,0x7bc0,0x7bc1,0x7bc4,0x7bc7,0x7bc9,0x7bcc,0x7bce,0x7bd0,0x7bd4,0x7bd5,0x7bd8,0x7bec,0x7bf0,0x7bf4,0x7bf7,0x7c03,0x7c05,0x7c07,0x7c09,0x7c12,0x7c15,0x7c15,0x7c19,0x7c19,0x7c1b,0x7c23,0x7c25,0x7c2d,0x7c30,0x7c30,0x7c33,0x7c33,0x7c35,0x7c35,0x7c37,0x7c39,0x7c3b,0x7c40,0x7c42,0x7c45,0x7c47,0x7c4a,0x7c4c,0x7c4d,0x7c50,0x7c51,0x7c53,0x7c54,0x7c56,0x7c57,0x7c59,0x7c5d,0x7c5f,0x7c60,0x7c63,0x7c67,0x7c69,0x7c70,0x7c72,0x7c75,0x7c78,0x7c81,0x7c83,0x7c86,0x7c88,0x7c8a,0x7c8c,0x7c8e,0x7c91,0x7c92,0x7c94,0x7c98,0x7c9c,0x7c9c,0x7c9e,0x7c9f,0x7ca1,0x7ca3,0x7ca5,0x7ca8,0x7cac,0x7cac,0x7cae,0x7caf,0x7cb1,0x7cb5,0x7cb8,0x7cbf,0x7cc2,0x7cc3,0x7cc5,0x7cc5,0x7cc7,0x7cce,0x7cd0,0x7cd7,0x7cd9,0x7cda,0x7cdc,0x7ce0,0x7ce2,0x7ce2,0x7ce6,0x7ce8,0x7cea,0x7cea,0x7cec,0x7cf9,0x7cfb,0x7cfe,0x7d00,0x7d22,0x7d25,0x7d25,0x7d28,0x7d29,0x7d2b,0x7d2c,0x7d2e,0x7d33,0x7d35,0x7d36,0x7d38,0x7d47,0x7d4a,0x7d4a,0x7d4d,0x7d56,0x7d58,0x7d58,0x7d5a,0x7d5f,0x7d61,0x7d63,0x7d66,0x7d6b,0x7d6d,0x7d73,0x7d79,0x7d7d,0x7d7f,0x7d81,0x7d83,0x7d86,0x7d88,0x7d89,0x7d8b,0x7d8f,0x7d91,0x7d97,0x7d9c,0x7da4,0x7da6,0x7db5,0x7db7,0x7dc2,0x7dc4,0x7dc7,0x7dc9,0x7dd0,0x7dd2,0x7dd4,0x7dd7,0x7de1,0x7de3,0x7dea,0x7dec,0x7dec,0x7dee,0x7df7,0x7df9,0x7dfe,0x7e03,0x7e03,0x7e07,0x7e17,0x7e1a,0x7e25,0x7e27,0x7e27,0x7e29,0x7e2b,0x7e2d,0x7e49,0x7e4c,0x7e4c,0x7e50,0x7e5c,0x7e5e,0x7e63,0x7e65,0x7e65,0x7e67,0x7e70,0x7e72,0x7e82,0x7e86,0x7e88,0x7e8a,0x7e8f,0x7e91,0x7e9c,0x7e9f,0x7e9f,0x7ea4,0x7ea4,0x7eac,0x7eac,0x7eba,0x7eba,0x7ec7,0x7ec7,0x7ecf,0x7ecf,0x7edf,0x7edf,0x7f06,0x7f06,0x7f36,0x7f3a,0x7f3d,0x7f41,0x7f43,0x7f45,0x7f47,0x7f55,0x7f58,0x7f58,0x7f5b,0x7f61,0x7f63,0x7f63,0x7f65,0x7f6e,0x7f70,0x7f73,0x7f75,0x7f7f,0x7f83,0x7f83,0x7f85,0x7f8f,0x7f91,0x7f97,0x7f9a,0x7f9e,0x7fa0,0x7fa9,0x7fac,0x7fc3,0x7fc5,0x7fc5,0x7fc7,0x7fc7,0x7fc9,0x7fd2,0x7fd4,0x7fd5,0x7fd7,0x7fd7,0x7fdb,0x7fe3,0x7fe5,0x7ff5,0x7ff7,0x8008,0x800b,0x8012,0x8014,0x8019,0x801b,0x8021,0x8024,0x8026,0x8028,0x802a,0x802c,0x802c,0x802e,0x8031,0x8033,0x8037,0x8039,0x8039,0x803b,0x803f,0x8043,0x8043,0x8046,0x8048,0x804a,0x804a,0x804f,0x8052,0x8054,0x8054,0x8056,0x8056,0x8058,0x8058,0x805a,0x805e,0x8061,0x8064,0x8066,0x8067,0x806c,0x806c,0x806f,0x8073,0x8075,0x8079,0x807d,0x8080,0x8082,0x8082,0x8084,0x8087,0x8089,0x808c,0x808f,0x8090,0x8092,0x8093,0x8095,0x8096,0x8098,0x809d,0x809f,0x809f,0x80a1,0x80a3,0x80a5,0x80a5,0x80a7,0x80a7,0x80a9,0x80ab,0x80ad,0x80af,0x80b1,0x80b2,0x80b4,0x80b8,0x80ba,0x80ba,0x80bc,0x80bd,0x80c2,0x80ca,0x80cc,0x80d1,0x80d4,0x80de,0x80e0,0x80e1,0x80e3,0x80e6,0x80e9,0x80e9,0x80ec,0x80ed,0x80ef,0x80f6,0x80f8,0x80fe,0x8100,0x8103,0x8105,0x810a,0x810c,0x810c,0x810e,0x810e,0x8112,0x8112,0x8114,0x811b,0x811d,0x811f,0x8121,0x8125,0x8127,0x8127,0x8129,0x812d,0x812f,0x8132,0x8134,0x8134,0x8137,0x8137,0x8139,0x813a,0x813d,0x813e,0x8142,0x8144,0x8146,0x8148,0x814a,0x8156,0x8159,0x815c,0x815e,0x815e,0x8160,0x8162,0x8164,0x8167,0x8169,0x8169,0x816b,0x8174,0x8176,0x817a,0x817c,0x817d,0x817f,0x8180,0x8182,0x8184,0x8186,0x818d,0x818f,0x818f,0x8193,0x8193,0x8195,0x8195,0x8197,0x81a0,0x81a2,0x81a3,0x81a5,0x81ac,0x81ae,0x81ae,0x81b0,0x81b7,0x81b9,0x81ca,0x81cc,0x81cd,0x81cf,0x81d2,0x81d5,0x81d5,0x81d7,0x81db,0x81dd,0x81ea,0x81ec,0x81ef,0x81f2,0x81f4,0x81f6,0x81fc,0x81fe,0x8202,0x8204,0x8205,0x8207,0x820d,0x8210,0x8212,0x8214,0x8216,0x8218,0x8218,0x821a,0x8222,0x8225,0x8226,0x8228,0x822d,0x822f,0x822f,0x8232,0x823a,0x823c,0x8240,0x8242,0x8242,0x8244,0x8245,0x8247,0x8247,0x8249,0x8249,0x824b,0x824b,0x824e,0x825c,0x825e,0x825f,0x8261,0x8266,0x8268,0x8269,0x826b,0x826f,0x8271,0x8272,0x8274,0x8280,0x8283,0x8285,0x8287,0x8287,0x828a,0x828b,0x828d,0x8294,0x8298,0x829b,0x829d,0x82b1,0x82b3,0x82c0,0x82c2,0x82c4,0x82ca,0x82ca,0x82cf,0x82d9,0x82db,0x82dc,0x82de,0x82e8,0x82ea,0x8309,0x830b,0x830d,0x8316,0x831e,0x8320,0x8320,0x8322,0x8322,0x8324,0x832d,0x832f,0x832f,0x8331,0x833d,0x833f,0x8345,0x8347,0x8354,0x8356,0x8357,0x8362,0x8363,0x8366,0x8366,0x836f,0x836f,0x8373,0x8378,0x837a,0x837f,0x8381,0x8381,0x8383,0x8383,0x8385,0x839e,0x83a0,0x83a0,0x83a2,0x83ac,0x83ae,0x83b0,0x83b9,0x83b9,0x83bd,0x83cf,0x83d1,0x83d1,0x83d3,0x83d9,0x83db,0x83e5,0x83e7,0x83f6,0x83f8,0x83ff,0x8401,0x8401,0x8403,0x8407,0x8409,0x8414,0x8416,0x8416,0x8418,0x8418,0x841b,0x841c,0x8420,0x8421,0x8423,0x8424,0x8426,0x8426,0x8429,0x8429,0x842b,0x8440,0x8442,0x844e,0x8450,0x8469,0x846b,0x847a,0x847d,0x8480,0x8482,0x8482,0x8484,0x8484,0x8486,0x8486,0x8488,0x8488,0x848d,0x8494,0x8496,0x84a4,0x84a7,0x84b2,0x84b4,0x84b4,0x84b6,0x84b6,0x84b8,0x84c2,0x84c4,0x84c7,0x84c9,0x84d4,0x84d6,0x84d7,0x84da,0x84db,0x84de,0x84de,0x84e1,0x84e2,0x84e4,0x84e5,0x84e7,0x84ec,0x84ee,0x84f4,0x84f6,0x8500,0x8502,0x851a,0x851c,0x8521,0x8523,0x8531,0x8533,0x8534,0x8538,0x8538,0x853b,0x853b,0x853d,0x853e,0x8540,0x854e,0x8551,0x855b,0x855d,0x8571,0x8573,0x8573,0x8575,0x857c,0x857e,0x857e,0x8580,0x8591,0x8593,0x85a4,0x85a6,0x85aa,0x85af,0x85b1,0x85b3,0x85ba,0x85bd,0x85c9,0x85cb,0x85cb,0x85cd,0x85d2,0x85d5,0x85da,0x85dc,0x85e6,0x85e8,0x85f2,0x85f4,0x85f4,0x85f6,0x8602,0x8604,0x8607,0x8609,0x860d,0x860f,0x8611,0x8613,0x8614,0x8616,0x861c,0x861e,0x862a,0x862c,0x862f,0x8631,0x8636,0x8638,0x863c,0x863e,0x8640,0x8642,0x8643,0x8645,0x8648,0x864b,0x864e,0x8650,0x8650,0x8652,0x8656,0x8659,0x8659,0x865b,0x865c,0x865e,0x865f,0x8661,0x8665,0x8667,0x8674,0x8677,0x8677,0x8679,0x867c,0x867e,0x867e,0x8685,0x8687,0x868a,0x868e,0x8690,0x869a,0x869c,0x869e,0x86a0,0x86a5,0x86a7,0x86aa,0x86ad,0x86ad,0x86af,0x86c9,0x86cb,0x86cc,0x86d0,0x86d1,0x86d3,0x86d4,0x86d6,0x86df,0x86e2,0x86e4,0x86e6,0x86e6,0x86e8,0x86ed,0x86ef,0x86ef,0x86f5,0x86fb,0x86fe,0x86fe,0x8700,0x870e,0x8711,0x8713,0x8715,0x8715,0x8718,0x871c,0x871e,0x871e,0x8720,0x872a,0x872c,0x872e,0x8730,0x8735,0x8737,0x8738,0x873a,0x873c,0x873e,0x8743,0x8746,0x8746,0x874c,0x8771,0x8773,0x877b,0x877d,0x877d,0x8781,0x8789,0x878b,0x878d,0x878f,0x8794,0x8796,0x8798,0x879a,0x879f,0x87a2,0x87a5,0x87a9,0x87c6,0x87c8,0x87cc,0x87ce,0x87ce,0x87d1,0x87d4,0x87d6,0x87e8,0x87ea,0x87ef,0x87f2,0x87f7,0x87f9,0x87fc,0x87fe,0x8806,0x8808,0x880d,0x880f,0x8811,0x8813,0x8819,0x881b,0x881d,0x881f,0x8833,0x8835,0x8839,0x883b,0x8846,0x8848,0x8848,0x884a,0x884f,0x8852,0x8853,0x8855,0x8857,0x8859,0x885b,0x885d,0x885e,0x8860,0x8865,0x8867,0x886b,0x886d,0x8872,0x8874,0x8877,0x8879,0x8879,0x887c,0x8884,0x8887,0x8889,0x888b,0x8893,0x8895,0x88a2,0x88a4,0x88a4,0x88a7,0x88a8,0x88aa,0x88ac,0x88ae,0x88ae,0x88b1,0x88b2,0x88b4,0x88ba,0x88bc,0x88c2,0x88c5,0x88c5,0x88c7,0x88c7,0x88c9,0x88d0,0x88d2,0x88d2,0x88d4,0x88df,0x88e1,0x88e1,0x88e6,0x88e8,0x88eb,0x88ec,0x88ee,0x8902,0x8905,0x8907,0x8909,0x890c,0x890e,0x890e,0x8910,0x891a,0x891e,0x891f,0x8921,0x8927,0x8929,0x8933,0x8935,0x8938,0x893b,0x893e,0x8941,0x8944,0x8946,0x8947,0x8949,0x8949,0x894b,0x894d,0x894f,0x8954,0x8956,0x8966,0x8969,0x896f,0x8971,0x8974,0x8976,0x8977,0x8979,0x897c,0x897e,0x8983,0x8985,0x898b,0x898f,0x898f,0x8991,0x8991,0x8993,0x8998,0x899b,0x899f,0x89a1,0x89a7,0x89a9,0x89aa,0x89ac,0x89af,0x89b2,0x89b2,0x89b6,0x89b7,0x89b9,0x89ba,0x89bc,0x89c1,0x89c6,0x89c6,0x89d2,0x89d6,0x89d9,0x89dd,0x89df,0x89e9,0x89eb,0x89ed,0x89f0,0x89f4,0x89f6,0x89f8,0x89fa,0x89fc,0x89fe,0x8a00,0x8a02,0x8a04,0x8a07,0x8a08,0x8a0a,0x8a0a,0x8a0c,0x8a0c,0x8a0e,0x8a13,0x8a15,0x8a18,0x8a1b,0x8a1f,0x8a22,0x8a23,0x8a25,0x8a25,0x8a27,0x8a27,0x8a29,0x8a2d,0x8a30,0x8a31,0x8a34,0x8a34,0x8a36,0x8a36,0x8a38,0x8a41,0x8a44,0x8a46,0x8a48,0x8a4a,0x8a4c,0x8a52,0x8a54,0x8a59,0x8a5b,0x8a5b,0x8a5e,0x8a5e,0x8a60,0x8a63,0x8a66,0x8a69,0x8a6b,0x8a6e,0x8a70,0x8a77,0x8a79,0x8a7c,0x8a7e,0x8a7f,0x8a81,0x8a87,0x8a8b,0x8a8d,0x8a8f,0x8a96,0x8a98,0x8a9a,0x8a9c,0x8a9c,0x8a9e,0x8a9e,0x8aa0,0x8aa1,0x8aa3,0x8aac,0x8aaf,0x8ab0,0x8ab2,0x8ab2,0x8ab4,0x8ab4,0x8ab6,0x8ab6,0x8ab8,0x8ac0,0x8ac2,0x8ac9,0x8acb,0x8acd,0x8acf,0x8acf,0x8ad1,0x8ae2,0x8ae4,0x8ae4,0x8ae6,0x8ae8,0x8aea,0x8aeb,0x8aed,0x8afc,0x8afe,0x8b02,0x8b04,0x8b08,0x8b0a,0x8b20,0x8b22,0x8b28,0x8b2a,0x8b31,0x8b33,0x8b33,0x8b35,0x8b37,0x8b39,0x8b43,0x8b45,0x8b5a,0x8b5c,0x8b60,0x8b62,0x8b63,0x8b65,0x8b6d,0x8b6f,0x8b70,0x8b74,0x8b74,0x8b77,0x8b7b,0x8b7d,0x8b86,0x8b88,0x8b88,0x8b8a,0x8b8c,0x8b8e,0x8b90,0x8b92,0x8b96,0x8b98,0x8b9c,0x8b9e,0x8ba0,0x8bbe,0x8bbe,0x8be2,0x8be2,0x8c37,0x8c37,0x8c39,0x8c39,0x8c3b,0x8c3f,0x8c41,0x8c43,0x8c45,0x8c51,0x8c54,0x8c57,0x8c5a,0x8c5a,0x8c5c,0x8c5d,0x8c5f,0x8c5f,0x8c61,0x8c62,0x8c64,0x8c66,0x8c68,0x8c6d,0x8c6f,0x8c73,0x8c75,0x8c7b,0x8c7d,0x8c7d,0x8c80,0x8c82,0x8c84,0x8c86,0x8c89,0x8c8a,0x8c8c,0x8c8d,0x8c8f,0x8c95,0x8c97,0x8ca5,0x8ca7,0x8cad,0x8caf,0x8cb0,0x8cb2,0x8cc5,0x8cc7,0x8cc8,0x8cca,0x8cca,0x8ccc,0x8ccd,0x8ccf,0x8ccf,0x8cd1,0x8cd7,0x8cd9,0x8cee,0x8cf0,0x8cf5,0x8cf7,0x8cfe,0x8d00,0x8d00,0x8d02,0x8d0d,0x8d0f,0x8d19,0x8d1b,0x8d1d,0x8d64,0x8d64,0x8d66,0x8d69,0x8d6b,0x8d70,0x8d72,0x8d74,0x8d76,0x8d7b,0x8d7d,0x8d7d,0x8d80,0x8d82,0x8d84,0x8d85,0x8d89,0x8d8a,0x8d8c,0x8d96,0x8d99,0x8d99,0x8d9b,0x8d9c,0x8d9f,0x8da1,0x8da3,0x8da3,0x8da5,0x8daf,0x8db2,0x8db7,0x8db9,0x8dba,0x8dbc,0x8dbc,0x8dbe,0x8dc3,0x8dc5,0x8dc8,0x8dcb,0x8dd1,0x8dd3,0x8ddd,0x8ddf,0x8de4,0x8de6,0x8dec,0x8dee,0x8df4,0x8dfa,0x8dfa,0x8dfc,0x8e07,0x8e09,0x8e0a,0x8e0d,0x8e2b,0x8e2d,0x8e2e,0x8e30,0x8e31,0x8e33,0x8e36,0x8e38,0x8e3a,0x8e3c,0x8e42,0x8e44,0x8e50,0x8e53,0x8e57,0x8e59,0x8e6a,0x8e6c,0x8e6d,0x8e6f,0x8e6f,0x8e71,0x8e78,0x8e7a,0x8e7c,0x8e7e,0x8e7e,0x8e80,0x8e82,0x8e84,0x8e8e,0x8e90,0x8e98,0x8e9a,0x8e9a,0x8e9d,0x8ea1,0x8ea3,0x8ead,0x8eb0,0x8eb0,0x8eb2,0x8eb2,0x8eb6,0x8eb6,0x8eb9,0x8eba,0x8ebc,0x8ebd,0x8ec0,0x8ec0,0x8ec2,0x8ec3,0x8ec9,0x8ecf,0x8ed1,0x8ed4,0x8ed7,0x8ed8,0x8eda,0x8ee2,0x8ee4,0x8ee9,0x8eeb,0x8eef,0x8ef1,0x8ef2,0x8ef4,0x8efc,0x8efe,0x8f03,0x8f05,0x8f0b,0x8f0d,0x8f0e,0x8f10,0x8f20,0x8f23,0x8f26,0x8f29,0x8f2a,0x8f2c,0x8f30,0x8f32,0x8f39,0x8f3b,0x8f3c,0x8f3e,0x8f4b,0x8f4d,0x8f64,0x8f66,0x8f67,0x8f6e,0x8f6e,0x8f93,0x8f93,0x8f9b,0x8f9c,0x8f9f,0x8fa0,0x8fa3,0x8fa3,0x8fa5,0x8fa8,0x8fad,0x8fbc,0x8fbe,0x8fbf,0x8fc1,0x8fc2,0x8fc4,0x8fc6,0x8fc9,0x8fd7,0x8fda,0x8fda,0x8fe0,0x8fe6,0x8fe8,0x8fe8,0x8fea,0x8feb,0x8fed,0x8fee,0x8ff0,0x8ff0,0x8ff4,0x9006,0x9008,0x9008,0x900b,0x900d,0x900f,0x9012,0x9014,0x9017,0x9019,0x9024,0x902d,0x902f,0x9031,0x9038,0x903c,0x903f,0x9041,0x9042,0x9044,0x9044,0x9046,0x9047,0x9049,0x9056,0x9058,0x9059,0x905b,0x905e,0x9060,0x9064,0x9067,0x9069,0x906b,0x9070,0x9072,0x9088,0x908a,0x908b,0x908d,0x908d,0x908f,0x9091,0x9094,0x9095,0x9097,0x9099,0x909b,0x909b,0x909e,0x90a3,0x90a5,0x90a8,0x90aa,0x90aa,0x90ae,0x90b6,0x90b8,0x90b8,0x90bb,0x90bb,0x90bd,0x90bf,0x90c1,0x90c1,0x90c3,0x90c5,0x90c7,0x90c8,0x90ca,0x90cb,0x90ce,0x90ce,0x90d4,0x90dd,0x90df,0x90e5,0x90e8,0x90ed,0x90ef,0x90f5,0x90f9,0x9109,0x910b,0x910b,0x910d,0x9112,0x9114,0x9114,0x9116,0x9124,0x9126,0x9136,0x9138,0x913b,0x913e,0x9141,0x9143,0x9153,0x9155,0x915a,0x915c,0x915c,0x915e,0x9165,0x9167,0x916a,0x916c,0x916c,0x916e,0x9170,0x9172,0x917a,0x917c,0x917c,0x9180,0x9187,0x9189,0x9193,0x9196,0x9196,0x9199,0x91a3,0x91a5,0x91a5,0x91a7,0x91b7,0x91b9,0x91be,0x91c0,0x91c7,0x91c9,0x91c9,0x91cb,0x91d1,0x91d3,0x91da,0x91dc,0x91dd,0x91df,0x91df,0x91e2,0x91ee,0x91f1,0x91f1,0x91f3,0x91fa,0x91fd,0x920a,0x920c,0x921a,0x921c,0x921c,0x921e,0x921e,0x9221,0x9221,0x9223,0x9228,0x922a,0x922b,0x922d,0x922e,0x9230,0x923a,0x923c,0x9241,0x9244,0x9246,0x9248,0x9258,0x925a,0x925b,0x925d,0x9267,0x926b,0x9270,0x9272,0x9272,0x9276,0x928f,0x9291,0x9291,0x9293,0x929d,0x92a0,0x92ac,0x92ae,0x92ae,0x92b1,0x92b7,0x92b9,0x92bc,0x92be,0x92d5,0x92d7,0x92d9,0x92db,0x92db,0x92dd,0x92e1,0x92e3,0x92f4,0x92f6,0x9304,0x9306,0x9309,0x930b,0x9310,0x9312,0x9316,0x9318,0x931b,0x931d,0x9331,0x9333,0x9336,0x9338,0x9339,0x933c,0x933c,0x9340,0x9352,0x9354,0x935c,0x935e,0x936e,0x9370,0x9371,0x9373,0x937e,0x9380,0x938a,0x938c,0x9392,0x9394,0x93aa,0x93ac,0x93b5,0x93b7,0x93b8,0x93bb,0x93bb,0x93bd,0x93bd,0x93bf,0x93c0,0x93c2,0x93c4,0x93c6,0x93c8,0x93ca,0x93e4,0x93e6,0x93e8,0x93ec,0x93ec,0x93ee,0x93ee,0x93f0,0x93f1,0x93f3,0x9401,0x9403,0x9404,0x9406,0x9419,0x941b,0x941b,0x941d,0x941d,0x9420,0x9420,0x9424,0x9433,0x9435,0x9440,0x9442,0x944d,0x944f,0x9452,0x9454,0x9455,0x9457,0x9458,0x945b,0x945b,0x945d,0x945e,0x9460,0x9460,0x9462,0x9465,0x9467,0x9479,0x947b,0x9483,0x9485,0x9485,0x949f,0x949f,0x94a2,0x94a2,0x94c1,0x94c1,0x94c3,0x94c3,0x94dc,0x94dc,0x94f6,0x94f6,0x952d,0x952d,0x9547,0x9547,0x9577,0x9578,0x957a,0x957d,0x957f,0x9580,0x9582,0x9583,0x9585,0x9586,0x9588,0x9589,0x958b,0x9594,0x9596,0x9599,0x959b,0x959c,0x959e,0x95ae,0x95b0,0x95b2,0x95b5,0x95b7,0x95b9,0x95c0,0x95c3,0x95c3,0x95c5,0x95cd,0x95d0,0x95d6,0x95da,0x95dc,0x95de,0x95e5,0x95e8,0x95e8,0x95f4,0x95f4,0x961c,0x961e,0x9620,0x9624,0x9628,0x9628,0x962a,0x962a,0x962c,0x9633,0x9638,0x963d,0x963f,0x9645,0x964a,0x9651,0x9653,0x9654,0x9656,0x9656,0x9658,0x9658,0x965b,0x965f,0x9661,0x9664,0x9669,0x966d,0x966f,0x9678,0x967b,0x967e,0x9680,0x9681,0x9683,0x968b,0x968d,0x968f,0x9691,0x9699,0x969b,0x969c,0x969e,0x969e,0x96a1,0x96a5,0x96a7,0x96aa,0x96ac,0x96ac,0x96ae,0x96ae,0x96b0,0x96b1,0x96b3,0x96b4,0x96b6,0x96b6,0x96b8,0x96b9,0x96bb,0x96bd,0x96bf,0x96ce,0x96d2,0x96df,0x96e1,0x96e3,0x96e5,0x96e5,0x96e8,0x96ea,0x96ef,0x96f2,0x96f4,0x96fb,0x96fd,0x96fd,0x96ff,0x9700,0x9702,0x9709,0x970b,0x970b,0x970d,0x9713,0x9716,0x9716,0x9718,0x9719,0x971b,0x972c,0x972e,0x9732,0x9734,0x9736,0x9738,0x973a,0x973d,0x9744,0x9746,0x974b,0x9751,0x9752,0x9755,0x9758,0x975a,0x9762,0x9766,0x9766,0x9768,0x976a,0x976c,0x976e,0x9770,0x9774,0x9776,0x9778,0x977a,0x9785,0x9787,0x978b,0x978d,0x978f,0x9794,0x9794,0x9797,0x97a6,0x97a8,0x97a8,0x97aa,0x97ae,0x97b1,0x97b4,0x97b6,0x97bb,0x97bd,0x97c9,0x97cb,0x97d0,0x97d2,0x97d9,0x97dc,0x97e1,0x97e3,0x97e3,0x97e5,0x97e6,0x97ed,0x97ee,0x97f0,0x97f3,0x97f5,0x97f6,0x97f8,0x97fb,0x97fd,0x9808,0x980a,0x980a,0x980c,0x9818,0x981b,0x9821,0x9823,0x9824,0x9826,0x9829,0x982b,0x982b,0x982d,0x9830,0x9832,0x9835,0x9837,0x9839,0x983b,0x983b,0x9841,0x9841,0x9843,0x9853,0x9856,0x9859,0x985b,0x9860,0x9862,0x986c,0x986f,0x9875,0x98a8,0x98a9,0x98ac,0x98af,0x98b1,0x98b4,0x98b6,0x98c4,0x98c6,0x98cc,0x98ce,0x98ce,0x98db,0x98dc,0x98de,0x98e3,0x98e5,0x98e7,0x98e9,0x98ed,0x98ef,0x98ef,0x98f1,0x98f2,0x98f4,0x98f6,0x98f9,0x98fa,0x98fc,0x98fe,0x9900,0x9900,0x9902,0x9903,0x9905,0x9905,0x9907,0x990a,0x990c,0x990c,0x990e,0x990e,0x9910,0x991c,0x991e,0x991f,0x9921,0x9921,0x9924,0x9925,0x9927,0x9933,0x9935,0x9935,0x9937,0x9943,0x9945,0x9945,0x9947,0x994e,0x9950,0x9959,0x995b,0x995f,0x9961,0x9963,0x9996,0x9999,0x999b,0x999e,0x99a1,0x99a1,0x99a3,0x99a8,0x99aa,0x99b5,0x99b8,0x99bd,0x99c1,0x99c5,0x99c7,0x99c7,0x99c9,0x99c9,0x99cb,0x99dd,0x99df,0x99e7,0x99e9,0x99ea,0x99ec,0x99ee,0x99f0,0x99f1,0x99f4,0x99ff,0x9a01,0x9a07,0x9a09,0x9a11,0x9a14,0x9a16,0x9a19,0x9a27,0x9a29,0x9a32,0x9a34,0x9a46,0x9a48,0x9a4a,0x9a4c,0x9a50,0x9a52,0x9a5c,0x9a5e,0x9a60,0x9a62,0x9a6c,0x9a8f,0x9a8f,0x9aa8,0x9aa8,0x9aab,0x9aab,0x9aad,0x9aad,0x9aaf,0x9ab4,0x9ab6,0x9ac2,0x9ac6,0x9ac7,0x9aca,0x9aca,0x9acd,0x9acd,0x9acf,0x9ad8,0x9adc,0x9adc,0x9adf,0x9ae3,0x9ae6,0x9ae7,0x9aeb,0x9aef,0x9af1,0x9af4,0x9af6,0x9af7,0x9af9,0x9aff,0x9b01,0x9b06,0x9b08,0x9b12,0x9b14,0x9b1a,0x9b1e,0x9b20,0x9b22,0x9b25,0x9b27,0x9b2b,0x9b2d,0x9b2f,0x9b31,0x9b35,0x9b37,0x9b37,0x9b39,0x9b3c,0x9b3e,0x9b46,0x9b48,0x9b48,0x9b4a,0x9b52,0x9b54,0x9b56,0x9b58,0x9b5b,0x9b5f,0x9b61,0x9b64,0x9b64,0x9b66,0x9b69,0x9b6c,0x9b6c,0x9b6f,0x9b71,0x9b74,0x9b77,0x9b7a,0x9b83,0x9b85,0x9b88,0x9b8b,0x9b8b,0x9b8d,0x9b93,0x9b95,0x9b95,0x9b97,0x9b97,0x9b9a,0x9b9b,0x9b9d,0x9ba2,0x9ba4,0x9ba6,0x9ba8,0x9ba8,0x9baa,0x9bab,0x9bad,0x9bb0,0x9bb5,0x9bb6,0x9bb8,0x9bb9,0x9bbd,0x9bbd,0x9bbf,0x9bc1,0x9bc3,0x9bc4,0x9bc6,0x9bca,0x9bcf,0x9bcf,0x9bd3,0x9bd7,0x9bd9,0x9bde,0x9be0,0x9be2,0x9be4,0x9bed,0x9bf0,0x9bf1,0x9bf4,0x9bf4,0x9bf7,0x9bf8,0x9bfd,0x9bfd,0x9bff,0x9bff,0x9c02,0x9c02,0x9c05,0x9c0e,0x9c10,0x9c10,0x9c12,0x9c15,0x9c17,0x9c17,0x9c1b,0x9c1d,0x9c1f,0x9c21,0x9c23,0x9c26,0x9c28,0x9c29,0x9c2b,0x9c2d,0x9c2f,0x9c2f,0x9c31,0x9c37,0x9c39,0x9c41,0x9c44,0x9c50,0x9c52,0x9c59,0x9c5d,0x9c60,0x9c62,0x9c63,0x9c66,0x9c68,0x9c6d,0x9c6e,0x9c71,0x9c75,0x9c77,0x9c7c,0x9ce5,0x9ce7,0x9ce9,0x9cea,0x9ced,0x9ced,0x9cf1,0x9cf7,0x9cf9,0x9cfd,0x9cff,0x9d00,0x9d02,0x9d09,0x9d0c,0x9d0c,0x9d10,0x9d10,0x9d12,0x9d12,0x9d14,0x9d19,0x9d1b,0x9d1b,0x9d1d,0x9d23,0x9d25,0x9d26,0x9d28,0x9d29,0x9d2d,0x9d31,0x9d33,0x9d34,0x9d36,0x9d39,0x9d3b,0x9d3b,0x9d3d,0x9d45,0x9d49,0x9d4c,0x9d4e,0x9d54,0x9d56,0x9d61,0x9d67,0x9d75,0x9d77,0x9d79,0x9d7b,0x9d8c,0x9d90,0x9d90,0x9d92,0x9d94,0x9d96,0x9dad,0x9daf,0x9daf,0x9db1,0x9dc5,0x9dc7,0x9ddf,0x9de1,0x9de6,0x9de8,0x9de9,0x9deb,0x9df0,0x9df2,0x9e07,0x9e09,0x9e15,0x9e17,0x9e1f,0x9e75,0x9e75,0x9e79,0x9e7d,0x9e7f,0x9e8e,0x9e90,0x9ea2,0x9ea4,0x9eb1,0x9eb4,0x9eb7,0x9ebb,0x9ec4,0x9ec6,0x9ec8,0x9ecc,0x9ed1,0x9ed3,0x9ed6,0x9ed8,0x9ed8,0x9eda,0x9ee0,0x9ee2,0x9ee2,0x9ee4,0x9ee8,0x9eeb,0x9eeb,0x9eed,0x9f02,0x9f06,0x9f0a,0x9f0e,0x9f10,0x9f12,0x9f13,0x9f15,0x9f1c,0x9f1e,0x9f1e,0x9f20,0x9f20,0x9f22,0x9f39,0x9f3b,0x9f3b,0x9f3d,0x9f3e,0x9f40,0x9f50,0x9f52,0x9f67,0x9f69,0x9f6c,0x9f6e,0x9f72,0x9f74,0x9f7b,0x9f7e,0x9f7f,0x9f8d,0x9f8e,0x9f90,0x9f92,0x9f94,0x9f99,0x9f9c,0x9f9c,0x9f9f,0x9fa0,0x9fa2,0x9fa2,0x9fa4,0x9fb3,0x9fc7,0x9fcb,0x9fd0,0x9fd0,0xf900,0xf903,0xf905,0xf907,0xf90b,0xf90b,0xf90d,0xf90d,0xf915,0xf915,0xf917,0xf917,0xf91a,0xf91a,0xf922,0xf922,0xf92d,0xf92d,0xf931,0xf931,0xf937,0xf937,0xf939,0xf93a,0xf943,0xf943,0xf947,0xf948,0xf94a,0xf94a,0xf952,0xf952,0xf95e,0xf95e,0xf962,0xf962,0xf965,0xf965,0xf967,0xf967,0xf972,0xf972,0xf976,0xf976,0xf978,0xf979,0xf97e,0xf97e,0xf980,0xf980,0xf986,0xf986,0xf98a,0xf98a,0xf98e,0xf98e,0xf995,0xf995,0xf99c,0xf99c,0xf99f,0xf99f,0xf9b5,0xf9b5,0xf9bb,0xf9bb,0xf9bd,0xf9bd,0xf9c5,0xf9c6,0xf9c8,0xf9c8,0xf9d8,0xf9d8,0xf9dc,0xf9de,0xf9e0,0xf9e0,0xf9e4,0xf9e4,0xf9e7,0xf9e7,0xf9e9,0xf9e9,0xf9f4,0xf9f5,0xf9fa,0xf9fa,0xf9fd,0xf9fd,0xf9ff,0xf9ff,0xfa02,0xfa02,0xfa05,0xfa08,0xfa0a,0xfa0a,0xfa0c,0xfa0d,0xfa33,0xfa35,0xfa3a,0xfa3a,0xfa49,0xfa49,0xfa4b,0xfa4b,0xfa5d,0xfa5e,0xfb00,0xfb04,0xfe10,0xfe19,0xfe30,0xfe52,0xfe54,0xfe66,0xfe68,0xfe6b,0xff01,0xff9f,0xffa1,0xffbe,0xffc2,0xffc7,0xffca,0xffcf,0xffd2,0xffd7,0xffda,0xffdc,0xffe0,0xffe6,0xffe8,0xffee,0x1f100,0x1f10c,0x1f110,0x1f16c,0x1f170,0x1f1ac,0x1f200,0x1f202,0x1f210,0x1f23b,0x1f240,0x1f248,0x1f250,0x1f251,0x20021,0x20021,0x2003e,0x2003e,0x20046,0x20046,0x2004e,0x2004e,0x20068,0x20068,0x20086,0x20087,0x2008a,0x2008a,0x20094,0x20094,0x200ca,0x200cd,0x200d1,0x200d1,0x200ee,0x200ee,0x2010c,0x2010c,0x2010e,0x2010e,0x20118,0x20118,0x201a4,0x201a4,0x201a9,0x201a9,0x201ab,0x201ab,0x201c1,0x201c1,0x201d4,0x201d4,0x201f2,0x201f2,0x20204,0x20204,0x2020c,0x2020c,0x20214,0x20214,0x20239,0x20239,0x2025b,0x2025b,0x20274,0x20275,0x20299,0x20299,0x2029e,0x2029e,0x202a0,0x202a0,0x202b7,0x202b7,0x202bf,0x202c0,0x202e5,0x202e5,0x2030a,0x2030a,0x20325,0x20325,0x20341,0x20341,0x20345,0x20347,0x2037e,0x20380,0x203a0,0x203a0,0x203a7,0x203a7,0x203b5,0x203b5,0x203c9,0x203c9,0x203cb,0x203cb,0x203f5,0x203f5,0x203fc,0x203fc,0x20413,0x20414,0x2041f,0x2041f,0x20465,0x20465,0x20487,0x20487,0x2048e,0x2048e,0x20491,0x20492,0x204a3,0x204a3,0x204d7,0x204d7,0x204fc,0x204fc,0x204fe,0x204fe,0x20547,0x20547,0x2058e,0x2058e,0x205a5,0x205a5,0x205b3,0x205b3,0x205c3,0x205c3,0x205ca,0x205ca,0x205d0,0x205d0,0x205d5,0x205d5,0x205df,0x205e0,0x205eb,0x205eb,0x20611,0x20611,0x20615,0x20615,0x20619,0x2061a,0x20628,0x20628,0x20630,0x20630,0x20656,0x20656,0x20676,0x20676,0x2070e,0x2070e,0x20731,0x20731,0x20779,0x20779,0x2082c,0x2082c,0x20873,0x20873,0x208d5,0x208d5,0x20916,0x20916,0x20923,0x20923,0x20954,0x20954,0x20979,0x20979,0x209e7,0x209e7,0x20a11,0x20a11,0x20a50,0x20a50,0x20a6f,0x20a6f,0x20a8a,0x20a8a,0x20ab4,0x20ab4,0x20ac2,0x20ac2,0x20acd,0x20acd,0x20b0d,0x20b0d,0x20b8f,0x20b8f,0x20b9f,0x20b9f,0x20ba8,0x20ba9,0x20bbf,0x20bbf,0x20bc6,0x20bc6,0x20bcb,0x20bcb,0x20be2,0x20be2,0x20beb,0x20beb,0x20bfb,0x20bfb,0x20bff,0x20bff,0x20c0b,0x20c0b,0x20c0d,0x20c0d,0x20c20,0x20c20,0x20c34,0x20c34,0x20c3a,0x20c3b,0x20c41,0x20c43,0x20c53,0x20c53,0x20c65,0x20c65,0x20c77,0x20c78,0x20c7c,0x20c7c,0x20c8d,0x20c8d,0x20c96,0x20c96,0x20c9c,0x20c9c,0x20cb5,0x20cb5,0x20cb8,0x20cb8,0x20ccf,0x20ccf,0x20cd3,0x20cd6,0x20cdd,0x20cdd,0x20ced,0x20ced,0x20cff,0x20cff,0x20d15,0x20d15,0x20d28,0x20d28,0x20d31,0x20d32,0x20d46,0x20d49,0x20d4c,0x20d4e,0x20d6f,0x20d6f,0x20d71,0x20d71,0x20d74,0x20d74,0x20d7c,0x20d7c,0x20d7e,0x20d7f,0x20d96,0x20d96,0x20d9c,0x20d9c,0x20da7,0x20da7,0x20db2,0x20db2,0x20dc8,0x20dc8,0x20e04,0x20e04,0x20e09,0x20e0a,0x20e0d,0x20e11,0x20e16,0x20e16,0x20e1d,0x20e1d,0x20e4c,0x20e4c,0x20e6d,0x20e6d,0x20e73,0x20e73,0x20e75,0x20e7b,0x20e8c,0x20e8c,0x20e96,0x20e96,0x20e98,0x20e98,0x20e9d,0x20e9d,0x20ea2,0x20ea2,0x20eaa,0x20eac,0x20eb6,0x20eb6,0x20ed7,0x20ed8,0x20edd,0x20edd,0x20ef8,0x20efb,0x20f1d,0x20f1d,0x20f26,0x20f26,0x20f2d,0x20f2e,0x20f30,0x20f31,0x20f3b,0x20f3b,0x20f4c,0x20f4c,0x20f64,0x20f64,0x20f8d,0x20f8d,0x20f90,0x20f90,0x20fad,0x20fad,0x20fb4,0x20fb6,0x20fbc,0x20fbc,0x20fdf,0x20fdf,0x20fea,0x20fed,0x21014,0x21014,0x2101d,0x2101e,0x2104f,0x2104f,0x2105c,0x2105c,0x2106f,0x2106f,0x21075,0x21078,0x2107b,0x2107b,0x21088,0x21088,0x21096,0x21096,0x2109d,0x2109d,0x210b4,0x210b4,0x210bf,0x210c1,0x210c7,0x210c9,0x210cf,0x210cf,0x210d3,0x210d3,0x210e4,0x210e4,0x210f4,0x210f6,0x2112f,0x2112f,0x2113b,0x2113b,0x2113d,0x2113d,0x21145,0x21145,0x21148,0x21148,0x2114f,0x2114f,0x21180,0x21180,0x21187,0x21187,0x211d9,0x211d9,0x2123c,0x2123c,0x2124f,0x2124f,0x2127c,0x2127c,0x212a8,0x212a9,0x212b0,0x212b0,0x212e3,0x212e3,0x212fe,0x212fe,0x21302,0x21305,0x21336,0x21336,0x2133a,0x2133a,0x21375,0x21376,0x2138e,0x2138e,0x21398,0x21398,0x2139c,0x2139c,0x213c5,0x213c6,0x213ed,0x213ed,0x213fe,0x213fe,0x21413,0x21413,0x21416,0x21416,0x21424,0x21424,0x2143f,0x2143f,0x21452,0x21452,0x21454,0x21455,0x2148a,0x2148a,0x21497,0x21497,0x214b6,0x214b6,0x214e8,0x214e8,0x214fd,0x214fd,0x21577,0x21577,0x21582,0x21582,0x21596,0x21596,0x2160a,0x2160a,0x21613,0x21613,0x21619,0x21619,0x2163e,0x2163e,0x21661,0x21661,0x21692,0x21692,0x216b8,0x216b8,0x216ba,0x216ba,0x216c0,0x216c2,0x216d3,0x216d3,0x216d5,0x216d5,0x216df,0x216df,0x216e6,0x216e8,0x216fa,0x216fc,0x216fe,0x216fe,0x2170d,0x2170d,0x21710,0x21710,0x21726,0x21726,0x2173a,0x2173c,0x21757,0x21757,0x2176c,0x21771,0x21773,0x21774,0x217ab,0x217ab,0x217b0,0x217b5,0x217c3,0x217c3,0x217c7,0x217c7,0x217d9,0x217dc,0x217df,0x217df,0x217ef,0x217ef,0x217f5,0x217f6,0x217f8,0x217fc,0x21820,0x21820,0x21828,0x2182a,0x2182d,0x2182d,0x21839,0x2183b,0x21840,0x21840,0x21845,0x21845,0x21852,0x21852,0x2185e,0x2185e,0x21861,0x21864,0x21877,0x21877,0x2187b,0x2187b,0x21883,0x21885,0x2189e,0x218a2,0x218be,0x218bf,0x218d1,0x218d1,0x218d6,0x218d9,0x218fa,0x218fa,0x21903,0x21905,0x21910,0x21912,0x21915,0x21915,0x2191c,0x2191c,0x21922,0x21922,0x21927,0x21927,0x2193b,0x2193b,0x21944,0x21944,0x21958,0x21958,0x2196a,0x2196a,0x2197c,0x2197c,0x21980,0x21980,0x21983,0x21983,0x21988,0x21988,0x21996,0x21996,0x219db,0x219db,0x219f3,0x219f3,0x21a2d,0x21a2d,0x21a34,0x21a34,0x21a45,0x21a45,0x21a4b,0x21a4b,0x21a63,0x21a63,0x21b44,0x21b44,0x21bc1,0x21bc2,0x21c2a,0x21c2a,0x21c70,0x21c70,0x21ca2,0x21ca2,0x21ca5,0x21ca5,0x21cac,0x21cac,0x21d46,0x21d46,0x21d53,0x21d53,0x21d5e,0x21d5e,0x21d90,0x21d90,0x21db6,0x21db6,0x21dba,0x21dba,0x21dca,0x21dca,0x21dd1,0x21dd1,0x21deb,0x21deb,0x21df9,0x21df9,0x21e1c,0x21e1c,0x21e23,0x21e23,0x21e37,0x21e37,0x21e3d,0x21e3d,0x21e89,0x21e89,0x21ea4,0x21ea4,0x21ea8,0x21ea8,0x21ec8,0x21ec8,0x21ed5,0x21ed5,0x21f0f,0x21f0f,0x21f15,0x21f15,0x21f6a,0x21f6a,0x21f9e,0x21f9e,0x21fa1,0x21fa1,0x21fe8,0x21fe8,0x22045,0x22045,0x22049,0x22049,0x2207e,0x2207e,0x2209a,0x2209a,0x220c7,0x220c7,0x220fc,0x220fc,0x2212a,0x2212a,0x2215b,0x2215b,0x22173,0x22173,0x2217a,0x2217a,0x221a1,0x221a1,0x221c1,0x221c1,0x221c3,0x221c3,0x22208,0x22208,0x2227c,0x2227c,0x22321,0x22321,0x22325,0x22325,0x223bd,0x223bd,0x223d0,0x223d0,0x223d7,0x223d7,0x223fa,0x223fa,0x22465,0x22465,0x22471,0x22471,0x2248b,0x2248b,0x22491,0x22491,0x224b0,0x224b0,0x224bc,0x224bc,0x224c1,0x224c1,0x224c9,0x224c9,0x224cc,0x224cc,0x224ed,0x224ed,0x22513,0x22513,0x2251b,0x2251b,0x22530,0x22530,0x22554,0x22554,0x2258d,0x2258d,0x225af,0x225af,0x225be,0x225be,0x2261b,0x2261c,0x2262b,0x2262b,0x22668,0x22668,0x2267a,0x2267a,0x22696,0x22696,0x22698,0x22698,0x226f4,0x226f6,0x22712,0x22712,0x22714,0x22714,0x2271b,0x2271b,0x2271f,0x2271f,0x2272a,0x2272a,0x22775,0x22775,0x22781,0x22781,0x22796,0x22796,0x227b4,0x227b5,0x227cd,0x227cd,0x22803,0x22803,0x2285f,0x22860,0x22871,0x22871,0x228ad,0x228ad,0x228c1,0x228c1,0x228f7,0x228f7,0x22926,0x22926,0x22939,0x22939,0x2294f,0x2294f,0x22967,0x22967,0x2296b,0x2296b,0x22980,0x22980,0x22993,0x22993,0x22a66,0x22a66,0x22acf,0x22acf,0x22ad5,0x22ad5,0x22ae6,0x22ae6,0x22ae8,0x22ae8,0x22b0e,0x22b0e,0x22b22,0x22b22,0x22b3f,0x22b3f,0x22b43,0x22b43,0x22b6a,0x22b6a,0x22bca,0x22bca,0x22bce,0x22bce,0x22c26,0x22c27,0x22c38,0x22c38,0x22c4c,0x22c4c,0x22c51,0x22c51,0x22c55,0x22c55,0x22c62,0x22c62,0x22c88,0x22c88,0x22c9b,0x22c9b,0x22ca1,0x22ca1,0x22ca9,0x22ca9,0x22cb2,0x22cb2,0x22cb7,0x22cb7,0x22cc2,0x22cc2,0x22cc6,0x22cc6,0x22cc9,0x22cc9,0x22d07,0x22d08,0x22d12,0x22d12,0x22d44,0x22d44,0x22d4c,0x22d4c,0x22d67,0x22d67,0x22d8d,0x22d8d,0x22d95,0x22d95,0x22da0,0x22da0,0x22da3,0x22da4,0x22db7,0x22db7,0x22dee,0x22dee,0x22e0d,0x22e0d,0x22e36,0x22e36,0x22e42,0x22e42,0x22e78,0x22e78,0x22e8b,0x22e8b,0x22eb3,0x22eb3,0x22eef,0x22eef,0x22f74,0x22f74,0x22fcc,0x22fcc,0x22fe3,0x22fe3,0x23033,0x23033,0x23044,0x23044,0x2304b,0x2304b,0x23066,0x23066,0x2307d,0x2307e,0x2308e,0x2308e,0x230b7,0x230b7,0x230bc,0x230bc,0x230da,0x230da,0x23103,0x23103,0x2313d,0x2313d,0x2317d,0x2317d,0x23182,0x23182,0x231a4,0x231a5,0x231b3,0x231b3,0x231c8,0x231c9,0x231ea,0x231ea,0x231f7,0x231f9,0x2320f,0x2320f,0x23225,0x23225,0x2322f,0x2322f,0x23231,0x23234,0x23256,0x23256,0x2325e,0x2325e,0x23262,0x23262,0x23281,0x23281,0x23289,0x2328a,0x232ab,0x232ad,0x232d2,0x232d2,0x232e0,0x232e1,0x23300,0x23300,0x2330a,0x2330a,0x2331f,0x2331f,0x233b4,0x233b4,0x233cc,0x233cc,0x233de,0x233de,0x233e6,0x233e6,0x233f4,0x233f5,0x233f9,0x233fa,0x233fe,0x233fe,0x23400,0x23400,0x2343f,0x2343f,0x23450,0x23450,0x2346f,0x2346f,0x23472,0x23472,0x234e5,0x234e5,0x23519,0x23519,0x23530,0x23530,0x23551,0x23551,0x2355a,0x2355a,0x23567,0x23567,0x23595,0x23595,0x23599,0x23599,0x2359c,0x2359c,0x235bb,0x235bb,0x235cd,0x235cf,0x235f3,0x235f3,0x23600,0x23600,0x23617,0x23617,0x2361a,0x2361a,0x2363c,0x2363c,0x23640,0x23640,0x23659,0x23659,0x2365f,0x2365f,0x23677,0x23677,0x2368e,0x2368e,0x2369e,0x2369e,0x236a6,0x236a6,0x236ad,0x236ad,0x236ba,0x236ba,0x236df,0x236df,0x236ee,0x236ee,0x23703,0x23703,0x23716,0x23716,0x23720,0x23720,0x2372d,0x2372d,0x2372f,0x2372f,0x2373f,0x2373f,0x23766,0x23766,0x23781,0x23781,0x237a2,0x237a2,0x237bc,0x237bc,0x237c2,0x237c2,0x237d5,0x237d7,0x2383a,0x2383a,0x239c2,0x239c2,0x23aa7,0x23aa7,0x23adb,0x23adb,0x23aee,0x23aee,0x23afa,0x23afa,0x23b1a,0x23b1a,0x23b5a,0x23b5a,0x23c63,0x23c63,0x23c99,0x23c9b,0x23cb5,0x23cb5,0x23cb7,0x23cb7,0x23cc7,0x23cc9,0x23cfc,0x23cff,0x23d40,0x23d40,0x23d5b,0x23d5b,0x23d7e,0x23d7e,0x23d8f,0x23d8f,0x23db6,0x23dbd,0x23de3,0x23de3,0x23df8,0x23df8,0x23e06,0x23e06,0x23e11,0x23e11,0x23e2c,0x23e31,0x23e39,0x23e39,0x23e88,0x23e8b,0x23eb9,0x23eb9,0x23ebf,0x23ebf,0x23ed7,0x23ed7,0x23ef7,0x23efc,0x23f35,0x23f35,0x23f41,0x23f41,0x23f4a,0x23f4a,0x23f61,0x23f61,0x23f7f,0x23f82,0x23f8f,0x23f8f,0x23fb4,0x23fb4,0x23fb7,0x23fb7,0x23fc0,0x23fc0,0x23fc5,0x23fc5,0x23feb,0x23ff0,0x24011,0x24011,0x24039,0x2403d,0x24057,0x24057,0x24085,0x24085,0x2408b,0x2408d,0x24091,0x24091,0x240c9,0x240c9,0x240e1,0x240e1,0x240ec,0x240ec,0x24104,0x24104,0x2410f,0x2410f,0x24119,0x24119,0x2413f,0x24140,0x24144,0x24144,0x2414e,0x2414e,0x24155,0x24157,0x2415c,0x2415c,0x2415f,0x2415f,0x24161,0x24161,0x24177,0x24177,0x2417a,0x2417a,0x241a3,0x241a5,0x241ac,0x241ac,0x241b5,0x241b5,0x241cd,0x241cd,0x241e2,0x241e2,0x241fc,0x241fc,0x2421b,0x2421b,0x2424b,0x2424b,0x24256,0x24256,0x24259,0x24259,0x24276,0x24278,0x24284,0x24284,0x24293,0x24293,0x24295,0x24295,0x242a5,0x242a5,0x242bf,0x242bf,0x242c1,0x242c1,0x242c9,0x242ca,0x242ee,0x242ee,0x242fa,0x242fa,0x2430d,0x2430d,0x2431a,0x2431a,0x24334,0x24334,0x24348,0x24348,0x24362,0x24365,0x2438c,0x2438c,0x24396,0x24396,0x2439c,0x2439c,0x243bd,0x243bd,0x243c1,0x243c1,0x243e9,0x243ea,0x243f2,0x243f2,0x243f8,0x243f8,0x24404,0x24404,0x24435,0x24436,0x2445a,0x2445b,0x24473,0x24473,0x24487,0x24488,0x244b9,0x244b9,0x244bc,0x244bc,0x244ce,0x244ce,0x244d3,0x244d3,0x244d6,0x244d6,0x24505,0x24505,0x24521,0x24521,0x24578,0x24578,0x245c8,0x245c8,0x24618,0x24618,0x2462a,0x2462a,0x24665,0x24665,0x24674,0x24674,0x24697,0x24697,0x246d4,0x246d4,0x24706,0x24706,0x24725,0x24725,0x2472f,0x2472f,0x2478f,0x2478f,0x247e0,0x247e0,0x24812,0x24812,0x24823,0x24823,0x24882,0x24882,0x248e9,0x248e9,0x248f0,0x248f3,0x248fb,0x248fb,0x248ff,0x24901,0x2490c,0x2490c,0x24916,0x24917,0x24919,0x24919,0x2492f,0x2492f,0x24933,0x24934,0x2493e,0x24943,0x24962,0x24963,0x24974,0x24976,0x2497b,0x2497b,0x2497f,0x2497f,0x24982,0x24982,0x24988,0x2498f,0x24994,0x24994,0x249a4,0x249a4,0x249a7,0x249a7,0x249a9,0x249a9,0x249ab,0x249ad,0x249b7,0x249bb,0x249c5,0x249c5,0x249d0,0x249d0,0x249da,0x249da,0x249de,0x249df,0x249e3,0x249e3,0x249e5,0x249e5,0x249ec,0x249ed,0x249f6,0x249f9,0x249fb,0x249fb,0x24a0e,0x24a0e,0x24a12,0x24a13,0x24a15,0x24a15,0x24a21,0x24a2a,0x24a3e,0x24a3e,0x24a42,0x24a42,0x24a45,0x24a45,0x24a4a,0x24a4a,0x24a4e,0x24a51,0x24a5d,0x24a5d,0x24a65,0x24a67,0x24a71,0x24a71,0x24a77,0x24a7a,0x24a8c,0x24a8c,0x24a93,0x24a96,0x24aa4,0x24aa7,0x24ab1,0x24ab3,0x24aba,0x24abc,0x24ac0,0x24ac0,0x24ac7,0x24ac7,0x24aca,0x24aca,0x24ad1,0x24ad1,0x24adf,0x24adf,0x24ae2,0x24ae2,0x24ae9,0x24ae9,0x24b0f,0x24b0f,0x24b6e,0x24b6e,0x24bf5,0x24bf5,0x24c09,0x24c09,0x24c9e,0x24c9f,0x24cc9,0x24cc9,0x24cd9,0x24cd9,0x24d06,0x24d06,0x24d13,0x24d13,0x24db8,0x24db8,0x24dea,0x24deb,0x24e3b,0x24e3b,0x24e50,0x24e50,0x24ea5,0x24ea5,0x24ea7,0x24ea7,0x24f0e,0x24f0e,0x24f5c,0x24f5c,0x24f82,0x24f82,0x24f86,0x24f86,0x24f97,0x24f97,0x24f9a,0x24f9a,0x24fa9,0x24fa9,0x24fb8,0x24fb8,0x24fc2,0x24fc2,0x2502c,0x2502c,0x25052,0x25052,0x2509d,0x2509d,0x2512b,0x2512b,0x25148,0x25148,0x2517d,0x2517e,0x251cd,0x251cd,0x251e3,0x251e3,0x251e6,0x251e7,0x25220,0x25221,0x25250,0x25250,0x25299,0x25299,0x252c7,0x252c7,0x252d8,0x252d8,0x2530e,0x2530e,0x25311,0x25311,0x25313,0x25313,0x25419,0x25419,0x25425,0x25425,0x2542f,0x25430,0x25446,0x25446,0x2546c,0x2546c,0x2546e,0x2546e,0x2549a,0x2549a,0x25531,0x25531,0x25535,0x25535,0x2553f,0x2553f,0x2555b,0x2555e,0x25562,0x25562,0x25565,0x25566,0x25581,0x25581,0x25584,0x25584,0x2558f,0x2558f,0x255b9,0x255b9,0x255d5,0x255d5,0x255db,0x255db,0x255e0,0x255e0,0x25605,0x25605,0x25635,0x25635,0x25651,0x25651,0x25683,0x25683,0x25695,0x25695,0x256e3,0x256e3,0x256f6,0x256f6,0x25706,0x25706,0x2571d,0x2571d,0x25725,0x25725,0x2573d,0x2573d,0x25772,0x25772,0x257c7,0x257c7,0x257df,0x257e1,0x25857,0x25857,0x2585d,0x2585d,0x25872,0x25872,0x258c8,0x258c8,0x258de,0x258de,0x258e1,0x258e1,0x25903,0x25903,0x25946,0x25946,0x25956,0x25956,0x259ac,0x259ac,0x259cc,0x259cc,0x25a54,0x25a54,0x25a95,0x25a95,0x25a9c,0x25a9c,0x25aae,0x25aaf,0x25ad7,0x25ad7,0x25ae9,0x25ae9,0x25b74,0x25b74,0x25b89,0x25b89,0x25bb3,0x25bb4,0x25bc6,0x25bc6,0x25be4,0x25be4,0x25be8,0x25be8,0x25c01,0x25c01,0x25c06,0x25c06,0x25c21,0x25c21,0x25c4a,0x25c4a,0x25c65,0x25c65,0x25c91,0x25c91,0x25ca4,0x25ca4,0x25cc0,0x25cc1,0x25cfe,0x25cfe,0x25d20,0x25d20,0x25d30,0x25d30,0x25d43,0x25d43,0x25d99,0x25d99,0x25db9,0x25db9,0x25e0e,0x25e0e,0x25e49,0x25e49,0x25e81,0x25e83,0x25ea6,0x25ea6,0x25ebc,0x25ebc,0x25ed7,0x25ed8,0x25f1a,0x25f1a,0x25f4b,0x25f4b,0x25fe1,0x25fe2,0x26021,0x26021,0x26029,0x26029,0x26048,0x26048,0x26064,0x26064,0x26083,0x26083,0x26097,0x26097,0x260a4,0x260a5,0x26102,0x26102,0x26121,0x26121,0x26159,0x2615c,0x261ad,0x261ae,0x261b2,0x261b2,0x261dd,0x261dd,0x26258,0x26258,0x26261,0x26261,0x2626a,0x2626b,0x262d0,0x262d0,0x26335,0x26335,0x2634b,0x2634c,0x26351,0x26351,0x263be,0x263be,0x263f5,0x263f5,0x263f8,0x263f8,0x26402,0x26402,0x26410,0x26412,0x2644a,0x2644a,0x26469,0x26469,0x26484,0x26484,0x26488,0x26489,0x2648d,0x2648d,0x26498,0x26498,0x26512,0x26512,0x26572,0x26572,0x265a0,0x265a0,0x265ad,0x265ad,0x265bf,0x265bf,0x26612,0x26612,0x26626,0x26626,0x266af,0x266af,0x266b1,0x266b1,0x266b5,0x266b5,0x266da,0x266da,0x266e8,0x266e8,0x266fc,0x266fc,0x26716,0x26716,0x26741,0x26741,0x26799,0x26799,0x267b3,0x267b4,0x267cc,0x267cc,0x2681c,0x2681c,0x26846,0x26846,0x2685e,0x2685e,0x2686e,0x2686e,0x26888,0x26888,0x2688a,0x2688a,0x26893,0x26893,0x268c7,0x268c7,0x2690e,0x2690e,0x26911,0x26911,0x26926,0x26926,0x26939,0x26939,0x26951,0x26951,0x269a8,0x269a8,0x269b5,0x269b5,0x269f2,0x269f2,0x269fa,0x269fa,0x26a2d,0x26a2e,0x26a34,0x26a34,0x26a42,0x26a42,0x26a51,0x26a52,0x26b05,0x26b05,0x26b0a,0x26b0a,0x26b13,0x26b13,0x26b15,0x26b15,0x26b23,0x26b23,0x26b28,0x26b28,0x26b50,0x26b53,0x26b5b,0x26b5b,0x26b75,0x26b75,0x26b82,0x26b82,0x26b96,0x26b97,0x26b9d,0x26b9d,0x26bb3,0x26bb3,0x26bc0,0x26bc0,0x26bf7,0x26bf7,0x26c21,0x26c21,0x26c40,0x26c41,0x26c46,0x26c46,0x26c7e,0x26c82,0x26ca4,0x26ca4,0x26cb7,0x26cb8,0x26cbd,0x26cbd,0x26cc0,0x26cc0,0x26cc3,0x26cc3,0x26cd1,0x26cd1,0x26d22,0x26d2a,0x26d51,0x26d51,0x26d74,0x26d74,0x26da0,0x26da7,0x26dae,0x26dae,0x26ddc,0x26ddc,0x26dea,0x26deb,0x26df0,0x26df0,0x26e00,0x26e00,0x26e05,0x26e05,0x26e07,0x26e07,0x26e12,0x26e12,0x26e42,0x26e45,0x26e6e,0x26e6e,0x26e72,0x26e72,0x26e77,0x26e77,0x26e84,0x26e84,0x26e88,0x26e88,0x26e8b,0x26e8b,0x26e99,0x26e99,0x26ed0,0x26ed7,0x26f26,0x26f26,0x26f73,0x26f74,0x26f9f,0x26f9f,0x26fa1,0x26fa1,0x26fbe,0x26fbe,0x26fde,0x26fdf,0x2700e,0x2700e,0x2704b,0x2704b,0x27052,0x27053,0x27088,0x27088,0x270ad,0x270af,0x270cd,0x270cd,0x270d2,0x270d2,0x270f0,0x270f0,0x270f8,0x270f8,0x27109,0x27109,0x2710c,0x2710d,0x27126,0x27127,0x27164,0x27165,0x27175,0x27175,0x271cd,0x271cd,0x2721b,0x2721b,0x27267,0x27267,0x27280,0x27280,0x27285,0x27285,0x2728b,0x2728b,0x272b2,0x272b2,0x272b6,0x272b6,0x272e6,0x272e6,0x27352,0x27352,0x2739a,0x2739a,0x273ff,0x273ff,0x27422,0x27422,0x27450,0x27450,0x27484,0x27484,0x27486,0x27486,0x27574,0x27574,0x275a3,0x275a3,0x275e0,0x275e0,0x275e4,0x275e4,0x275fd,0x275fe,0x27607,0x27607,0x2760c,0x2760c,0x27632,0x27632,0x27639,0x27639,0x27655,0x27657,0x27694,0x27694,0x2770f,0x2770f,0x27735,0x27736,0x27741,0x27741,0x2775e,0x2775e,0x27784,0x27785,0x277cc,0x277cc,0x27858,0x27858,0x27870,0x27870,0x2789d,0x2789d,0x278b2,0x278b2,0x278c8,0x278c8,0x27924,0x27924,0x27967,0x27967,0x2797a,0x2797a,0x279a0,0x279a0,0x279dd,0x279dd,0x279fd,0x279fd,0x27a0a,0x27a0a,0x27a0e,0x27a0e,0x27a3e,0x27a3e,0x27a53,0x27a53,0x27a59,0x27a59,0x27a79,0x27a79,0x27a84,0x27a84,0x27abd,0x27abe,0x27af4,0x27af4,0x27b06,0x27b06,0x27b0b,0x27b0b,0x27b18,0x27b18,0x27b38,0x27b3a,0x27b48,0x27b48,0x27b65,0x27b65,0x27bef,0x27bef,0x27bf4,0x27bf4,0x27c12,0x27c12,0x27c6c,0x27c6c,0x27cb1,0x27cb1,0x27cc5,0x27cc5,0x27d2f,0x27d2f,0x27d53,0x27d54,0x27d66,0x27d66,0x27d73,0x27d73,0x27d84,0x27d84,0x27d8f,0x27d8f,0x27d98,0x27d98,0x27dbd,0x27dbd,0x27ddc,0x27ddc,0x27e4d,0x27e4d,0x27e4f,0x27e4f,0x27f2e,0x27f2e,0x27fb7,0x27fb7,0x27ff9,0x27ff9,0x28002,0x28002,0x28009,0x28009,0x2801e,0x2801e,0x28023,0x28024,0x28048,0x28048,0x28083,0x28083,0x28090,0x28090,0x280bd,0x280be,0x280e8,0x280e9,0x280f4,0x280f4,0x2812e,0x2812e,0x2814f,0x2814f,0x2815d,0x2815d,0x2816f,0x2816f,0x28189,0x28189,0x281af,0x281af,0x281bc,0x281bc,0x28207,0x28207,0x28218,0x28218,0x2821a,0x2821a,0x28256,0x28256,0x2827c,0x2827c,0x2829b,0x2829b,0x282cd,0x282cd,0x282e2,0x282e2,0x28306,0x28306,0x28318,0x28318,0x2832f,0x2832f,0x2833a,0x2833a,0x28365,0x28365,0x2836d,0x2836d,0x2837d,0x2837d,0x2838a,0x2838a,0x28412,0x28412,0x28468,0x28468,0x2846c,0x2846c,0x28473,0x28473,0x28482,0x28482,0x28501,0x28501,0x2853c,0x2853d,0x2856c,0x2856c,0x285e8,0x285e8,0x285f4,0x285f4,0x28600,0x28600,0x2860b,0x2860b,0x28625,0x28625,0x2863b,0x2863b,0x286aa,0x286ab,0x286b2,0x286b2,0x286bc,0x286bc,0x286d8,0x286d8,0x286e6,0x286e6,0x2870f,0x2870f,0x28713,0x28713,0x28804,0x28804,0x2882b,0x2882b,0x2890d,0x2890d,0x28933,0x28933,0x28948,0x28949,0x28956,0x28956,0x28964,0x28964,0x28968,0x28968,0x2896c,0x2896d,0x2897e,0x2897e,0x28989,0x28989,0x289a8,0x289a8,0x289aa,0x289ab,0x289b8,0x289b8,0x289bc,0x289bc,0x289c0,0x289c0,0x289dc,0x289dc,0x289de,0x289de,0x289e1,0x289e1,0x289e3,0x289e4,0x289e7,0x289e8,0x289f9,0x289fc,0x28a0f,0x28a0f,0x28a16,0x28a16,0x28a25,0x28a25,0x28a29,0x28a29,0x28a32,0x28a32,0x28a36,0x28a36,0x28a44,0x28a4b,0x28a59,0x28a5a,0x28a81,0x28a83,0x28a9a,0x28a9c,0x28ac0,0x28ac0,0x28ac6,0x28ac6,0x28acb,0x28acc,0x28ace,0x28ace,0x28ade,0x28ae3,0x28ae5,0x28ae5,0x28aea,0x28aea,0x28afc,0x28afc,0x28b0c,0x28b0c,0x28b13,0x28b13,0x28b21,0x28b22,0x28b2b,0x28b2d,0x28b2f,0x28b2f,0x28b46,0x28b46,0x28b4c,0x28b4c,0x28b4e,0x28b4e,0x28b50,0x28b50,0x28b63,0x28b66,0x28b6c,0x28b6c,0x28b8f,0x28b8f,0x28b99,0x28b99,0x28b9c,0x28b9d,0x28bb9,0x28bb9,0x28bc2,0x28bc2,0x28bc5,0x28bc5,0x28bd4,0x28bd4,0x28bd7,0x28bd7,0x28bd9,0x28bda,0x28be7,0x28bec,0x28bf5,0x28bf5,0x28bff,0x28bff,0x28c03,0x28c03,0x28c09,0x28c09,0x28c1c,0x28c1d,0x28c23,0x28c23,0x28c26,0x28c26,0x28c2b,0x28c2b,0x28c30,0x28c30,0x28c39,0x28c39,0x28c3b,0x28c3b,0x28cca,0x28cca,0x28ccd,0x28ccd,0x28cd2,0x28cd2,0x28d34,0x28d34,0x28d99,0x28d99,0x28db9,0x28db9,0x28e0f,0x28e0f,0x28e36,0x28e36,0x28e39,0x28e39,0x28e65,0x28e66,0x28e97,0x28e97,0x28eac,0x28eac,0x28eb2,0x28eb3,0x28ed9,0x28ed9,0x28ee7,0x28ee7,0x28fc5,0x28fc5,0x29079,0x29079,0x29088,0x29088,0x2908b,0x2908b,0x29093,0x29093,0x290af,0x290b1,0x290c0,0x290c0,0x290e4,0x290e5,0x290ec,0x290ed,0x2910d,0x2910d,0x29110,0x29110,0x2913c,0x2913c,0x2914d,0x2914d,0x2915b,0x2915b,0x2915e,0x2915e,0x29170,0x29170,0x2919c,0x2919c,0x291a8,0x291a8,0x291d5,0x291d5,0x291eb,0x291eb,0x2941d,0x2941d,0x29420,0x29420,0x29433,0x29433,0x2943f,0x2943f,0x29448,0x29448,0x294d0,0x294d0,0x294d9,0x294da,0x294e5,0x294e5,0x294e7,0x294e7,0x2959e,0x2959e,0x295b0,0x295b0,0x295b8,0x295b8,0x295d7,0x295d7,0x295e9,0x295e9,0x295f4,0x295f4,0x2967f,0x2967f,0x29720,0x29720,0x29732,0x29732,0x297d4,0x297d4,0x29810,0x29810,0x29857,0x29857,0x298a4,0x298a4,0x298d1,0x298d1,0x298ea,0x298ea,0x298f1,0x298f1,0x298fa,0x298fa,0x29903,0x29903,0x29905,0x29905,0x2992f,0x2992f,0x29945,0x29945,0x29947,0x29949,0x2995d,0x2995d,0x2996a,0x2996a,0x2999d,0x2999d,0x299c3,0x299c3,0x299c9,0x299c9,0x29a28,0x29a28,0x29a4d,0x29a4d,0x29b05,0x29b05,0x29b0e,0x29b0e,0x29bd5,0x29bd5,0x29c73,0x29c73,0x29cad,0x29cad,0x29d3e,0x29d3e,0x29d5a,0x29d5a,0x29d7c,0x29d7c,0x29d98,0x29d98,0x29d9b,0x29d9b,0x29df6,0x29df6,0x29e06,0x29e06,0x29e2d,0x29e2d,0x29e68,0x29e68,0x29eac,0x29eac,0x29eb0,0x29eb0,0x29ec3,0x29ec3,0x29ef8,0x29ef8,0x29f23,0x29f23,0x29f30,0x29f30,0x29fb7,0x29fb7,0x29fde,0x29fde,0x2a014,0x2a014,0x2a087,0x2a087,0x2a0b9,0x2a0b9,0x2a0e1,0x2a0e1,0x2a0ed,0x2a0ed,0x2a0f3,0x2a0f3,0x2a0f8,0x2a0f8,0x2a0fe,0x2a0fe,0x2a107,0x2a107,0x2a123,0x2a123,0x2a133,0x2a134,0x2a150,0x2a150,0x2a192,0x2a193,0x2a1ab,0x2a1ab,0x2a1b4,0x2a1b5,0x2a1df,0x2a1df,0x2a1f5,0x2a1f5,0x2a220,0x2a220,0x2a233,0x2a233,0x2a293,0x2a293,0x2a29f,0x2a29f,0x2a2b2,0x2a2b2,0x2a2b4,0x2a2b4,0x2a2b6,0x2a2b6,0x2a2ba,0x2a2ba,0x2a2bd,0x2a2bd,0x2a2df,0x2a2df,0x2a2ff,0x2a2ff,0x2a351,0x2a351,0x2a3a9,0x2a3a9,0x2a3ed,0x2a3ed,0x2a434,0x2a434,0x2a45b,0x2a45b,0x2a5c6,0x2a5c6,0x2a5cb,0x2a5cb,0x2a601,0x2a601,0x2a632,0x2a632,0x2a64a,0x2a64a,0x2a65b,0x2a65b,0x2a6a9,0x2a6a9,0x2adff,0x2adff,0x2d544,0x2d544,0x2f825,0x2f825,0x2f83b,0x2f83b,0x2f840,0x2f840,0x2f878,0x2f878,0x2f894,0x2f894,0x2f8a6,0x2f8a6,0x2f8cd,0x2f8cd,0x2f8db,0x2f8db,0x2f994,0x2f994,0x2f9b2,0x2f9b2,0x2f9bc,0x2f9bc,0x2f9d4,0x2f9d4,0x30edd,0x30ede,0x3106c,0x3106c,]), + NotoFont.fromFlatRanges('Noto Sans Tagalog', 'http://fonts.gstatic.com/s/notosanstagalog/v15/J7aFnoNzCnFcV9ZI-sUYuvote1R0wwEAA8jHexnL.ttf', [0x20,0x20,0xa0,0xa0,0x1700,0x170c,0x170e,0x1714,0x1735,0x1736,0x200b,0x200d,0x25cc,0x25cc,]), + NotoFont.fromFlatRanges('Noto Sans Tagbanwa', 'http://fonts.gstatic.com/s/notosanstagbanwa/v15/Y4GWYbB8VTEp4t3MKJSMmQdIKjRtt_nZRjQEaYpGoQ.ttf', [0x20,0x20,0xa0,0xa0,0x1735,0x1736,0x1760,0x176c,0x176e,0x1770,0x1772,0x1773,0x200b,0x200d,0x25cc,0x25cc,]), + NotoFont.fromFlatRanges('Noto Sans Tai Le', 'http://fonts.gstatic.com/s/notosanstaile/v15/vEFK2-VODB8RrNDvZSUmVxEATwR58tK1W77HtMo.ttf', [0x20,0x20,0xa0,0xa0,0x300,0x301,0x307,0x308,0x30c,0x30c,0x1040,0x1049,0x1950,0x196d,0x1970,0x1974,0x200b,0x200d,0x25cc,0x25cc,0x3001,0x3002,0x3008,0x300b,]), + NotoFont.fromFlatRanges('Noto Sans Tai Tham', 'http://fonts.gstatic.com/s/notosanstaitham/v17/kJEbBv0U4hgtwxDUw2x9q7tbjLIfbPGHBoaVSAZ3MdLJBCUbPgquyaRGKMw.ttf', [0x20,0x20,0xa0,0xa0,0x1a20,0x1a5e,0x1a60,0x1a7c,0x1a7f,0x1a89,0x1a90,0x1a99,0x1aa0,0x1aad,0x200b,0x200d,0x2219,0x2219,]), + NotoFont.fromFlatRanges('Noto Sans Tai Viet', 'http://fonts.gstatic.com/s/notosanstaiviet/v15/8QIUdj3HhN_lv4jf9vsE-9GMOLsaSPZr644fWsRO9w.ttf', [0x20,0x20,0xa0,0xa0,0x200b,0x200d,0x25cc,0x25cc,0xa78b,0xa78c,0xaa80,0xaac2,0xaadb,0xaadf,]), + NotoFont.fromFlatRanges('Noto Sans Takri', 'http://fonts.gstatic.com/s/notosanstakri/v15/TuGJUVpzXI5FBtUq5a8bnKIOdTwQNO_W3khJXg.ttf', [0x20,0x20,0xa0,0xa0,0x964,0x965,0x200c,0x200d,0x25cc,0x25cc,0xa830,0xa839,0x11680,0x116b8,0x116c0,0x116c9,]), + NotoFont.fromFlatRanges('Noto Sans Tamil', 'http://fonts.gstatic.com/s/notosanstamil/v21/ieVc2YdFI3GCY6SyQy1KfStzYKZgzN1z4LKDbeZce-0429tBManUktuex7vGo70RqKDt_EvT.ttf', [0x20,0x7e,0xa0,0xa3,0xa5,0xa5,0xa7,0xab,0xad,0xb0,0xb2,0xb4,0xb6,0xb8,0xba,0xbb,0xbf,0x107,0x10a,0x113,0x116,0x11b,0x11e,0x123,0x126,0x127,0x12a,0x12b,0x12e,0x133,0x136,0x137,0x139,0x13e,0x141,0x148,0x14a,0x14d,0x150,0x15b,0x15e,0x161,0x164,0x165,0x16a,0x17e,0x1cd,0x1ce,0x218,0x21b,0x237,0x237,0x2c6,0x2c7,0x2c9,0x2c9,0x2d8,0x2dd,0x300,0x304,0x306,0x308,0x30a,0x30c,0x312,0x312,0x326,0x328,0x951,0x952,0x964,0x965,0xb82,0xb83,0xb85,0xb8a,0xb8e,0xb90,0xb92,0xb95,0xb99,0xb9a,0xb9c,0xb9c,0xb9e,0xb9f,0xba3,0xba4,0xba8,0xbaa,0xbae,0xbb9,0xbbe,0xbc2,0xbc6,0xbc8,0xbca,0xbcd,0xbd0,0xbd0,0xbd7,0xbd7,0xbe6,0xbfa,0x1cda,0x1cda,0x1e80,0x1e85,0x1e9e,0x1e9e,0x1ef2,0x1ef3,0x200b,0x200d,0x2010,0x2010,0x2013,0x2014,0x2018,0x201a,0x201c,0x201e,0x2022,0x2022,0x2026,0x2026,0x2039,0x203a,0x2074,0x2074,0x2082,0x2084,0x20ac,0x20ac,0x20b9,0x20b9,0x2122,0x2122,0x2212,0x2212,0x25cc,0x25cc,0xa8f3,0xa8f3,0x11301,0x11301,0x11303,0x11303,0x1133b,0x1133c,]), + NotoFont.fromFlatRanges('Noto Sans Tamil Supplement', 'http://fonts.gstatic.com/s/notosanstamilsupplement/v19/DdTz78kEtnooLS5rXF1DaruiCd_bFp_Ph4sGcn7ax_vsAeMkeq1x.ttf', [0x11fc0,0x11ff1,0x11fff,0x11fff,]), + NotoFont.fromFlatRanges('Noto Sans Telugu', 'http://fonts.gstatic.com/s/notosanstelugu/v19/0FlxVOGZlE2Rrtr-HmgkMWJNjJ5_RyT8o8c7fHkeg-esVC5dzHkHIJQqrEntezbqQUbf-3v37w.ttf', [0x20,0x7e,0xa0,0xa3,0xa5,0xa5,0xa7,0xab,0xad,0xb0,0xb4,0xb4,0xb6,0xb8,0xba,0xbb,0xbf,0x107,0x10a,0x113,0x116,0x11b,0x11e,0x123,0x126,0x127,0x12a,0x12b,0x12e,0x133,0x136,0x137,0x139,0x13e,0x141,0x148,0x14a,0x14d,0x150,0x15b,0x15e,0x161,0x164,0x165,0x16a,0x17e,0x1cd,0x1ce,0x218,0x21b,0x237,0x237,0x2c6,0x2c7,0x2c9,0x2c9,0x2d8,0x2dd,0x300,0x304,0x306,0x308,0x30a,0x30c,0x312,0x312,0x326,0x328,0x951,0x952,0x964,0x965,0xc00,0xc0c,0xc0e,0xc10,0xc12,0xc28,0xc2a,0xc39,0xc3d,0xc44,0xc46,0xc48,0xc4a,0xc4d,0xc55,0xc56,0xc58,0xc5a,0xc60,0xc63,0xc66,0xc6f,0xc77,0xc7f,0x1cda,0x1cda,0x1cf2,0x1cf2,0x1e80,0x1e85,0x1e9e,0x1e9e,0x1ef2,0x1ef3,0x200b,0x200d,0x2010,0x2010,0x2013,0x2014,0x2018,0x201a,0x201c,0x201e,0x2022,0x2022,0x2026,0x2026,0x2039,0x203a,0x20ac,0x20ac,0x20b9,0x20b9,0x2122,0x2122,0x2212,0x2212,0x25cc,0x25cc,]), + NotoFont.fromFlatRanges('Noto Sans Thaana', 'http://fonts.gstatic.com/s/notosansthaana/v16/C8c14dM-vnz-s-3jaEsxlxHkBH-WZOETXfoQrfQ9Y4XrbhLhnu4-tbNu.ttf', [0x20,0x21,0x28,0x29,0x2c,0x2c,0x2e,0x2e,0x3a,0x3b,0xa0,0xa0,0x60c,0x60c,0x61b,0x61b,0x61f,0x61f,0x660,0x66c,0x780,0x7b1,0x200b,0x200f,0x2018,0x2019,0x201c,0x201d,0x25cc,0x25cc,0xfdf2,0xfdf2,0xfdfd,0xfdfd,]), + NotoFont.fromFlatRanges('Noto Sans Thai', 'http://fonts.gstatic.com/s/notosansthai/v20/iJWnBXeUZi_OHPqn4wq6hQ2_hbJ1xyN9wd43SofNWcd1MKVQt_So_9CdU5RtpzF-QRvzzXg.ttf', [0x20,0x7e,0xa0,0xa3,0xa5,0xa5,0xa7,0xab,0xae,0xb0,0xb4,0xb4,0xb6,0xb8,0xba,0xbb,0xbf,0x107,0x10a,0x113,0x116,0x11b,0x11e,0x123,0x126,0x127,0x12a,0x12b,0x12e,0x133,0x136,0x137,0x139,0x13e,0x141,0x148,0x14a,0x14d,0x150,0x15b,0x15e,0x161,0x164,0x165,0x16a,0x17e,0x1cd,0x1ce,0x218,0x21b,0x237,0x237,0x2bc,0x2bc,0x2c6,0x2c7,0x2c9,0x2c9,0x2d7,0x2dd,0x300,0x304,0x306,0x308,0x30a,0x30c,0x312,0x312,0x326,0x328,0x331,0x331,0xe01,0xe3a,0xe3f,0xe5b,0x1e80,0x1e85,0x1e9e,0x1e9e,0x1ef2,0x1ef3,0x200b,0x200d,0x2010,0x2010,0x2013,0x2014,0x2018,0x201a,0x201c,0x201e,0x2022,0x2022,0x2026,0x2026,0x2039,0x203a,0x20ac,0x20ac,0x2122,0x2122,0x2212,0x2212,0x25cc,0x25cc,]), + NotoFont.fromFlatRanges('Noto Sans Tifinagh', 'http://fonts.gstatic.com/s/notosanstifinagh/v15/I_uzMoCduATTei9eI8dawkHIwvmhCvbn6rnEcXfs4Q.ttf', [0x20,0x20,0xa0,0xa0,0x2c7,0x2c7,0x301,0x302,0x304,0x304,0x306,0x307,0x309,0x309,0x323,0x323,0x331,0x331,0x200c,0x200d,0x202e,0x202e,0x25cc,0x25cc,0x2d30,0x2d67,0x2d6f,0x2d70,0x2d7f,0x2d7f,]), + NotoFont.fromFlatRanges('Noto Sans Tirhuta', 'http://fonts.gstatic.com/s/notosanstirhuta/v15/t5t6IQYRNJ6TWjahPR6X-M-apUyby7uGUBsTrn5P.ttf', [0x20,0x20,0xa0,0xa0,0x951,0x952,0x964,0x965,0x9f4,0x9f7,0x1cf2,0x1cf2,0x200c,0x200d,0x25cc,0x25cc,0xa830,0xa839,0x11480,0x114c7,0x114d0,0x114d9,]), + NotoFont.fromFlatRanges('Noto Sans Ugaritic', 'http://fonts.gstatic.com/s/notosansugaritic/v15/3qTwoiqhnSyU8TNFIdhZVCwbjCpkAXXkMhoIkiazfg.ttf', [0x20,0x20,0xa0,0xa0,0x10380,0x1039d,0x1039f,0x1039f,]), + NotoFont.fromFlatRanges('Noto Sans Vai', 'http://fonts.gstatic.com/s/notosansvai/v15/NaPecZTSBuhTirw6IaFn_UrURMTsDIRSfr0.ttf', [0x20,0x20,0xa0,0xa0,0xa500,0xa62b,]), + NotoFont.fromFlatRanges('Noto Sans Wancho', 'http://fonts.gstatic.com/s/notosanswancho/v15/zrf-0GXXyfn6Fs0lH9P4cUubP0GBqAPopiRfKp8.ttf', [0x20,0x20,0x22,0x22,0x27,0x29,0x2c,0x2f,0x5b,0x5d,0x7b,0x7b,0x7d,0x7d,0xa0,0xa0,0x201c,0x201d,0x25cc,0x25cc,0x1e2c0,0x1e2f9,0x1e2ff,0x1e2ff,]), + NotoFont.fromFlatRanges('Noto Sans Warang Citi', 'http://fonts.gstatic.com/s/notosanswarangciti/v15/EYqtmb9SzL1YtsZSScyKDXIeOv3w-zgsNvKRpeVCCXzdgA.ttf', [0x20,0x20,0x27,0x27,0xa0,0xa0,0x200c,0x200d,0x118a0,0x118f2,0x118ff,0x118ff,]), + NotoFont.fromFlatRanges('Noto Sans Yi', 'http://fonts.gstatic.com/s/notosansyi/v15/sJoD3LFXjsSdcnzn071rO3apxVDJNVgSNg.ttf', [0x20,0x20,0xa0,0xa0,0x3001,0x3002,0x3008,0x3011,0x3014,0x301b,0x30fb,0x30fb,0xa000,0xa48c,0xa490,0xa4c6,0xff61,0xff65,]), + NotoFont.fromFlatRanges('Noto Sans Zanabazar Square', 'http://fonts.gstatic.com/s/notosanszanabazarsquare/v15/Cn-jJsuGWQxOjaGwMQ6fOicyxLBEMRfDtkzl4uagQtJxOCEgN0Gc.ttf', [0x20,0x20,0xa0,0xa0,0x25cc,0x25cc,0x11a00,0x11a47,]), +]; diff --git a/lib/web_ui/lib/src/engine/canvaskit/font_fallbacks.dart b/lib/web_ui/lib/src/engine/canvaskit/font_fallbacks.dart index 926592249aa1c..0cbc7ea3a790c 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/font_fallbacks.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/font_fallbacks.dart @@ -3,17 +3,20 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:convert'; import 'dart:typed_data'; +import 'package:ui/src/engine/canvaskit/renderer.dart'; + import '../dom.dart'; import '../font_change_util.dart'; -import '../platform_dispatcher.dart'; +import '../initialization.dart'; +import '../renderer.dart'; import '../util.dart'; import 'canvaskit_api.dart'; +import 'font_fallback_data.dart'; import 'fonts.dart'; -import 'initialization.dart'; import 'interval_tree.dart'; +import 'noto_font.dart'; /// Global static font fallback data. class FontFallbackData { @@ -27,13 +30,9 @@ class FontFallbackData { /// Used for tests. static void debugReset() { _instance = FontFallbackData(); + notoDownloadQueue = FallbackFontDownloadQueue(); } - /// Whether or not "Noto Sans Symbols" and "Noto Color Emoji" fonts have been - /// downloaded. We download these as fallbacks when no other font covers the - /// given code units. - bool registeredSymbolsAndEmoji = false; - /// Code units that no known font has a glyph for. final Set codeUnitsWithNoKnownFont = {}; @@ -47,15 +46,9 @@ class FontFallbackData { final Map> ranges = >{}; - for (final NotoFont font in _notoFonts) { - // TODO(yjbanov): instead of mutating the font tree during reset, it's - // better to construct an immutable tree of resolved fonts - // pointing back to the original NotoFont objects. Then - // resetting the tree would be a matter of reconstructing - // the new resolved tree. - font.reset(); + for (final NotoFont font in fallbackFonts) { // ignore: prefer_foreach - for (final CodeunitRange range in font.approximateUnicodeRanges) { + for (final CodeunitRange range in font.computeUnicodeRanges()) { ranges.putIfAbsent(font, () => []).add(range); } } @@ -68,8 +61,6 @@ class FontFallbackData { final List globalFontFallbacks = ['Roboto']; - final Map fontFallbackCounts = {}; - /// A list of code units to check against the global fallback fonts. final Set _codeUnitsToCheckAgainstFallbackFonts = {}; @@ -84,6 +75,9 @@ class FontFallbackData { void ensureFontsSupportText(String text, List fontFamilies) { // TODO(hterkelsen): Make this faster for the common case where the text // is supported by the given fonts. + if (debugDisableFontFallbacks) { + return; + } // If the text is ASCII, then skip this check. bool isAscii = true; @@ -120,7 +114,7 @@ class FontFallbackData { final List fonts = []; for (final String font in fontFamilies) { final List? typefacesForFamily = - skiaFontCollection.familyToFontMap[font]; + CanvasKitRenderer.instance.fontCollection.familyToFontMap[font]; if (typefacesForFamily != null) { fonts.addAll(typefacesForFamily); } @@ -146,9 +140,7 @@ class FontFallbackData { _codeUnitsToCheckAgainstFallbackFonts.addAll(missingCodeUnits); if (!_scheduledCodeUnitCheck) { _scheduledCodeUnitCheck = true; - // ignore: invalid_use_of_visible_for_testing_member - EnginePlatformDispatcher.instance.rasterizer! - .addPostFrameCallback(_ensureFallbackFonts); + CanvasKitRenderer.instance.rasterizer.addPostFrameCallback(_ensureFallbackFonts); } } } @@ -165,6 +157,9 @@ class FontFallbackData { _scheduledCodeUnitCheck = false; // We don't know if the remaining code units are covered by our fallback // fonts. Check them and update the cache. + if (_codeUnitsToCheckAgainstFallbackFonts.isEmpty) { + return; + } final List codeUnits = _codeUnitsToCheckAgainstFallbackFonts.toList(); _codeUnitsToCheckAgainstFallbackFonts.clear(); final List codeUnitsSupported = @@ -173,7 +168,7 @@ class FontFallbackData { for (final String font in globalFontFallbacks) { final List? fontsForFamily = - skiaFontCollection.familyToFontMap[font]; + CanvasKitRenderer.instance.fontCollection.familyToFontMap[font]; if (fontsForFamily == null) { printWarning('A fallback font was registered but we ' 'cannot retrieve the typeface for it.'); @@ -225,23 +220,19 @@ class FontFallbackData { printWarning('Failed to parse fallback font $family as a font.'); return; } - fontFallbackCounts.putIfAbsent(family, () => 0); - final int fontFallbackTag = fontFallbackCounts[family]!; - fontFallbackCounts[family] = fontFallbackCounts[family]! + 1; - final String countedFamily = '$family $fontFallbackTag'; // Insert emoji font before all other fallback fonts so we use the emoji // whenever it's available. - registeredFallbackFonts.add(RegisteredFont(bytes, countedFamily, typeface)); + registeredFallbackFonts.add(RegisteredFont(bytes, family, typeface)); // Insert emoji font before all other fallback fonts so we use the emoji // whenever it's available. - if (family == 'Noto Color Emoji Compat') { + if (family == 'Noto Emoji') { if (globalFontFallbacks.first == 'Roboto') { - globalFontFallbacks.insert(1, countedFamily); + globalFontFallbacks.insert(1, family); } else { - globalFontFallbacks.insert(0, countedFamily); + globalFontFallbacks.insert(0, family); } } else { - globalFontFallbacks.add(countedFamily); + globalFontFallbacks.add(family); } } } @@ -262,209 +253,22 @@ Future findFontsForMissingCodeunits(List codeUnits) async { } } - for (final NotoFont font in fonts) { - await font.ensureResolved(); - } - // The call to `findMinimumFontsForCodeUnits` will remove all code units that // were matched by `fonts` from `unmatchedCodeUnits`. final Set unmatchedCodeUnits = Set.from(coveredCodeUnits); fonts = findMinimumFontsForCodeUnits(unmatchedCodeUnits, fonts); - final Set<_ResolvedNotoSubset> resolvedFonts = <_ResolvedNotoSubset>{}; - for (final int codeUnit in coveredCodeUnits) { - for (final NotoFont font in fonts) { - if (font.resolvedFont == null) { - // We failed to resolve the font earlier. - continue; - } - resolvedFonts.addAll(font.resolvedFont!.tree.intersections(codeUnit)); - } - } - resolvedFonts.forEach(notoDownloadQueue.add); + fonts.forEach(notoDownloadQueue.add); // We looked through the Noto font tree and didn't find any font families - // covering some code units, or we did find a font family, but when we - // downloaded the fonts we found that they actually didn't cover them. So - // we try looking them up in the symbols and emojis fonts. + // covering some code units. if (missingCodeUnits.isNotEmpty || unmatchedCodeUnits.isNotEmpty) { - if (!data.registeredSymbolsAndEmoji) { - await _registerSymbolsAndEmoji(); - } else { - if (!notoDownloadQueue.isPending) { - printWarning( - 'Could not find a set of Noto fonts to display all missing ' - 'characters. Please add a font asset for the missing characters.' - ' See: https://flutter.dev/docs/cookbook/design/fonts'); - data.codeUnitsWithNoKnownFont.addAll(missingCodeUnits); - } - } - } -} - -/// Parse the CSS file for a font and make a list of resolved subsets. -/// -/// A CSS file from Google Fonts looks like this: -/// -/// /* [0] */ -/// @font-face { -/// font-family: 'Noto Sans KR'; -/// font-style: normal; -/// font-weight: 400; -/// src: url(https://fonts.gstatic.com/s/notosanskr/v13/PbykFmXiEBPT4ITbgNA5Cgm20xz64px_1hVWr0wuPNGmlQNMEfD4.0.woff2) format('woff2'); -/// unicode-range: U+f9ca-fa0b, U+ff03-ff05, U+ff07, U+ff0a-ff0b, U+ff0d-ff19, U+ff1b, U+ff1d, U+ff20-ff5b, U+ff5d, U+ffe0-ffe3, U+ffe5-ffe6; -/// } -/// /* [1] */ -/// @font-face { -/// font-family: 'Noto Sans KR'; -/// font-style: normal; -/// font-weight: 400; -/// src: url(https://fonts.gstatic.com/s/notosanskr/v13/PbykFmXiEBPT4ITbgNA5Cgm20xz64px_1hVWr0wuPNGmlQNMEfD4.1.woff2) format('woff2'); -/// unicode-range: U+f92f-f980, U+f982-f9c9; -/// } -/// /* [2] */ -/// @font-face { -/// font-family: 'Noto Sans KR'; -/// font-style: normal; -/// font-weight: 400; -/// src: url(https://fonts.gstatic.com/s/notosanskr/v13/PbykFmXiEBPT4ITbgNA5Cgm20xz64px_1hVWr0wuPNGmlQNMEfD4.2.woff2) format('woff2'); -/// unicode-range: U+d723-d728, U+d72a-d733, U+d735-d748, U+d74a-d74f, U+d752-d753, U+d755-d757, U+d75a-d75f, U+d762-d764, U+d766-d768, U+d76a-d76b, U+d76d-d76f, U+d771-d787, U+d789-d78b, U+d78d-d78f, U+d791-d797, U+d79a, U+d79c, U+d79e-d7a3, U+f900-f909, U+f90b-f92e; -/// } -_ResolvedNotoFont? _makeResolvedNotoFontFromCss(String css, String name) { - final List<_ResolvedNotoSubset> subsets = <_ResolvedNotoSubset>[]; - bool resolvingFontFace = false; - String? fontFaceUrl; - List? fontFaceUnicodeRanges; - for (final String line in LineSplitter.split(css)) { - // Search for the beginning of a @font-face. - if (!resolvingFontFace) { - if (line == '@font-face {') { - resolvingFontFace = true; - } else { - continue; - } - } else { - // We are resolving a @font-face, read out the url and ranges. - if (line.startsWith(' src:')) { - final int urlStart = line.indexOf('url('); - if (urlStart == -1) { - printWarning('Unable to resolve Noto font URL: $line'); - return null; - } - final int urlEnd = line.indexOf(')'); - fontFaceUrl = line.substring(urlStart + 4, urlEnd); - } else if (line.startsWith(' unicode-range:')) { - fontFaceUnicodeRanges = []; - final String rangeString = line.substring(17, line.length - 1); - final List rawRanges = rangeString.split(', '); - for (final String rawRange in rawRanges) { - final List startEnd = rawRange.split('-'); - if (startEnd.length == 1) { - final String singleRange = startEnd.single; - assert(singleRange.startsWith('U+')); - final int rangeValue = - int.parse(singleRange.substring(2), radix: 16); - fontFaceUnicodeRanges.add(CodeunitRange(rangeValue, rangeValue)); - } else { - assert(startEnd.length == 2); - final String startRange = startEnd[0]; - final String endRange = startEnd[1]; - assert(startRange.startsWith('U+')); - final int startValue = - int.parse(startRange.substring(2), radix: 16); - final int endValue = int.parse(endRange, radix: 16); - fontFaceUnicodeRanges.add(CodeunitRange(startValue, endValue)); - } - } - } else if (line == '}') { - if (fontFaceUrl == null || fontFaceUnicodeRanges == null) { - printWarning('Unable to parse Google Fonts CSS: $css'); - return null; - } - subsets - .add(_ResolvedNotoSubset(fontFaceUrl, name, fontFaceUnicodeRanges)); - resolvingFontFace = false; - } else { - continue; - } - } - } - - if (resolvingFontFace) { - printWarning('Unable to parse Google Fonts CSS: $css'); - return null; - } - - final Map<_ResolvedNotoSubset, List> rangesMap = - <_ResolvedNotoSubset, List>{}; - for (final _ResolvedNotoSubset subset in subsets) { - // ignore: prefer_foreach - for (final CodeunitRange range in subset.ranges) { - rangesMap.putIfAbsent(subset, () => []).add(range); - } - } - - if (rangesMap.isEmpty) { - printWarning('Parsed Google Fonts CSS was empty: $css'); - return null; - } - - final IntervalTree<_ResolvedNotoSubset> tree = - IntervalTree<_ResolvedNotoSubset>.createFromRanges(rangesMap); - - return _ResolvedNotoFont(name, subsets, tree); -} - -/// In the case where none of the known Noto Fonts cover a set of code units, -/// try the Symbols and Emoji fonts. We don't know the exact range of code units -/// that are covered by these fonts, so we download them and hope for the best. -Future _registerSymbolsAndEmoji() async { - final FontFallbackData data = FontFallbackData.instance; - if (data.registeredSymbolsAndEmoji) { - return; - } - data.registeredSymbolsAndEmoji = true; - const String emojiUrl = - 'https://fonts.googleapis.com/css2?family=Noto+Color+Emoji+Compat'; - const String symbolsUrl = - 'https://fonts.googleapis.com/css2?family=Noto+Sans+Symbols'; - - final String emojiCss = - await notoDownloadQueue.downloader.downloadAsString(emojiUrl); - final String symbolsCss = - await notoDownloadQueue.downloader.downloadAsString(symbolsUrl); - - String? extractUrlFromCss(String css) { - for (final String line in LineSplitter.split(css)) { - if (line.startsWith(' src:')) { - final int urlStart = line.indexOf('url('); - if (urlStart == -1) { - printWarning('Unable to resolve Noto font URL: $line'); - return null; - } - final int urlEnd = line.indexOf(')'); - return line.substring(urlStart + 4, urlEnd); - } + if (!notoDownloadQueue.isPending) { + printWarning('Could not find a set of Noto fonts to display all missing ' + 'characters. Please add a font asset for the missing characters.' + ' See: https://flutter.dev/docs/cookbook/design/fonts'); + data.codeUnitsWithNoKnownFont.addAll(missingCodeUnits); } - printWarning('Unable to determine URL for Noto font'); - return null; - } - - final String? emojiFontUrl = extractUrlFromCss(emojiCss); - final String? symbolsFontUrl = extractUrlFromCss(symbolsCss); - - if (emojiFontUrl != null) { - notoDownloadQueue.add(_ResolvedNotoSubset( - emojiFontUrl, 'Noto Color Emoji Compat', const [])); - } else { - printWarning('Error parsing CSS for Noto Emoji font.'); - } - - if (symbolsFontUrl != null) { - notoDownloadQueue.add(_ResolvedNotoSubset( - symbolsFontUrl, 'Noto Sans Symbols', const [])); - } else { - printWarning('Error parsing CSS for Noto Symbols font.'); } } @@ -493,7 +297,7 @@ Set findMinimumFontsForCodeUnits( for (final NotoFont font in fonts) { int codeUnitsCovered = 0; for (final int codeUnit in codeUnits) { - if (font.resolvedFont?.tree.containsDeep(codeUnit) ?? false) { + if (font.contains(codeUnit)) { codeUnitsCovered++; } } @@ -535,321 +339,47 @@ Set findMinimumFontsForCodeUnits( if (bestFonts.contains(_notoSansJP)) { bestFont = _notoSansJP; } + } else if (language == 'ko') { + if (bestFonts.contains(_notoSansKR)) { + bestFont = _notoSansKR; + } + } else if (bestFonts.contains(_notoSansSC)) { + bestFont = _notoSansSC; + } + } else { + // To be predictable, if there is a tie for best font, choose a font + // from this list first, then just choose the first font. + if (bestFonts.contains(_notoSymbols)) { + bestFont = _notoSymbols; + } else if (bestFonts.contains(_notoSansSC)) { + bestFont = _notoSansSC; } } } codeUnits.removeWhere((int codeUnit) { - return bestFont.resolvedFont!.tree.containsDeep(codeUnit); + return bestFont.contains(codeUnit); }); - minimumFonts.addAll(bestFonts); + minimumFonts.add(bestFont); } return minimumFonts; } -class NotoFont { - final String name; - final List approximateUnicodeRanges; - - Completer? _decodingCompleter; - _ResolvedNotoFont? resolvedFont; - - NotoFont(this.name, this.approximateUnicodeRanges); - - String get googleFontsCssUrl => - 'https://fonts.googleapis.com/css2?family=${name.replaceAll(' ', '+')}'; - - Future ensureResolved() async { - if (resolvedFont == null) { - if (_decodingCompleter == null) { - _decodingCompleter = Completer(); - final String googleFontCss = await notoDownloadQueue.downloader - .downloadAsString(googleFontsCssUrl); - final _ResolvedNotoFont? googleFont = - _makeResolvedNotoFontFromCss(googleFontCss, name); - resolvedFont = googleFont; - _decodingCompleter!.complete(); - } else { - await _decodingCompleter!.future; - } - } - } - - void reset() { - resolvedFont = null; - _decodingCompleter = null; - } -} - -class CodeunitRange { - final int start; - final int end; - - const CodeunitRange(this.start, this.end); - - bool contains(int codeUnit) { - return start <= codeUnit && codeUnit <= end; - } - - @override - bool operator ==(dynamic other) { - if (other is! CodeunitRange) { - return false; - } - final CodeunitRange range = other; - return range.start == start && range.end == end; - } - - @override - int get hashCode => Object.hash(start, end); - - @override - String toString() => '[$start, $end]'; -} - -class _ResolvedNotoFont { - final String name; - final List<_ResolvedNotoSubset> subsets; - final IntervalTree<_ResolvedNotoSubset> tree; - - const _ResolvedNotoFont(this.name, this.subsets, this.tree); -} - -class _ResolvedNotoSubset { - final String url; - final String family; - final List ranges; - - _ResolvedNotoSubset(this.url, this.family, this.ranges); - - @override - String toString() => '_ResolvedNotoSubset($family, $url)'; -} +NotoFont _notoSansSC = fallbackFonts.singleWhere((NotoFont font) => font.name == 'Noto Sans SC'); +NotoFont _notoSansTC = fallbackFonts.singleWhere((NotoFont font) => font.name == 'Noto Sans TC'); +NotoFont _notoSansHK = fallbackFonts.singleWhere((NotoFont font) => font.name == 'Noto Sans HK'); +NotoFont _notoSansJP = fallbackFonts.singleWhere((NotoFont font) => font.name == 'Noto Sans JP'); +NotoFont _notoSansKR = fallbackFonts.singleWhere((NotoFont font) => font.name == 'Noto Sans KR'); +List _cjkFonts = [_notoSansSC, _notoSansTC, _notoSansHK, _notoSansJP, _notoSansKR]; -NotoFont _notoSansSC = NotoFont('Noto Sans SC', [ - const CodeunitRange(12288, 12591), - const CodeunitRange(12800, 13311), - const CodeunitRange(19968, 40959), - const CodeunitRange(65072, 65135), - const CodeunitRange(65280, 65519), -]); - -NotoFont _notoSansTC = NotoFont('Noto Sans TC', [ - const CodeunitRange(12288, 12351), - const CodeunitRange(12549, 12585), - const CodeunitRange(19968, 40959), -]); - -NotoFont _notoSansHK = NotoFont('Noto Sans HK', [ - const CodeunitRange(12288, 12351), - const CodeunitRange(12549, 12585), - const CodeunitRange(19968, 40959), -]); - -NotoFont _notoSansJP = NotoFont('Noto Sans JP', [ - const CodeunitRange(12288, 12543), - const CodeunitRange(19968, 40959), - const CodeunitRange(65280, 65519), -]); - -List _cjkFonts = [ - _notoSansSC, - _notoSansTC, - _notoSansHK, - _notoSansJP, -]; - -List _notoFonts = [ - _notoSansSC, - _notoSansTC, - _notoSansHK, - _notoSansJP, - NotoFont('Noto Naskh Arabic UI', [ - const CodeunitRange(1536, 1791), - const CodeunitRange(8204, 8206), - const CodeunitRange(8208, 8209), - const CodeunitRange(8271, 8271), - const CodeunitRange(11841, 11841), - const CodeunitRange(64336, 65023), - const CodeunitRange(65132, 65276), - ]), - NotoFont('Noto Sans Armenian', [ - const CodeunitRange(1328, 1424), - const CodeunitRange(64275, 64279), - ]), - NotoFont('Noto Sans Bengali UI', [ - const CodeunitRange(2404, 2405), - const CodeunitRange(2433, 2555), - const CodeunitRange(8204, 8205), - const CodeunitRange(8377, 8377), - const CodeunitRange(9676, 9676), - ]), - NotoFont('Noto Sans Myanmar UI', [ - const CodeunitRange(4096, 4255), - const CodeunitRange(8204, 8205), - const CodeunitRange(9676, 9676), - ]), - NotoFont('Noto Sans Egyptian Hieroglyphs', [ - const CodeunitRange(77824, 78894), - ]), - NotoFont('Noto Sans Ethiopic', [ - const CodeunitRange(4608, 5017), - const CodeunitRange(11648, 11742), - const CodeunitRange(43777, 43822), - ]), - NotoFont('Noto Sans Georgian', [ - const CodeunitRange(1417, 1417), - const CodeunitRange(4256, 4351), - const CodeunitRange(11520, 11567), - ]), - NotoFont('Noto Sans Gujarati UI', [ - const CodeunitRange(2404, 2405), - const CodeunitRange(2688, 2815), - const CodeunitRange(8204, 8205), - const CodeunitRange(8377, 8377), - const CodeunitRange(9676, 9676), - const CodeunitRange(43056, 43065), - ]), - NotoFont('Noto Sans Gurmukhi UI', [ - const CodeunitRange(2404, 2405), - const CodeunitRange(2561, 2677), - const CodeunitRange(8204, 8205), - const CodeunitRange(8377, 8377), - const CodeunitRange(9676, 9676), - const CodeunitRange(9772, 9772), - const CodeunitRange(43056, 43065), - ]), - NotoFont('Noto Sans Hebrew', [ - const CodeunitRange(1424, 1535), - const CodeunitRange(8362, 8362), - const CodeunitRange(9676, 9676), - const CodeunitRange(64285, 64335), - ]), - NotoFont('Noto Sans Devanagari UI', [ - const CodeunitRange(2304, 2431), - const CodeunitRange(7376, 7414), - const CodeunitRange(7416, 7417), - const CodeunitRange(8204, 8205), - const CodeunitRange(8360, 8360), - const CodeunitRange(8377, 8377), - const CodeunitRange(9676, 9676), - const CodeunitRange(43056, 43065), - const CodeunitRange(43232, 43259), - ]), - NotoFont('Noto Sans Kannada UI', [ - const CodeunitRange(2404, 2405), - const CodeunitRange(3202, 3314), - const CodeunitRange(8204, 8205), - const CodeunitRange(8377, 8377), - const CodeunitRange(9676, 9676), - ]), - NotoFont('Noto Sans Khmer UI', [ - const CodeunitRange(6016, 6143), - const CodeunitRange(8204, 8204), - const CodeunitRange(9676, 9676), - ]), - NotoFont('Noto Sans KR', [ - const CodeunitRange(12593, 12686), - const CodeunitRange(12800, 12828), - const CodeunitRange(12896, 12923), - const CodeunitRange(44032, 55215), - ]), - NotoFont('Noto Sans Lao UI', [ - const CodeunitRange(3713, 3807), - const CodeunitRange(9676, 9676), - ]), - NotoFont('Noto Sans Malayalam UI', [ - const CodeunitRange(775, 775), - const CodeunitRange(803, 803), - const CodeunitRange(2404, 2405), - const CodeunitRange(3330, 3455), - const CodeunitRange(8204, 8205), - const CodeunitRange(8377, 8377), - const CodeunitRange(9676, 9676), - ]), - NotoFont('Noto Sans Sinhala', [ - const CodeunitRange(2404, 2405), - const CodeunitRange(3458, 3572), - const CodeunitRange(8204, 8205), - const CodeunitRange(9676, 9676), - ]), - NotoFont('Noto Sans Tamil UI', [ - const CodeunitRange(2404, 2405), - const CodeunitRange(2946, 3066), - const CodeunitRange(8204, 8205), - const CodeunitRange(8377, 8377), - const CodeunitRange(9676, 9676), - ]), - NotoFont('Noto Sans Telugu UI', [ - const CodeunitRange(2385, 2386), - const CodeunitRange(2404, 2405), - const CodeunitRange(3072, 3199), - const CodeunitRange(7386, 7386), - const CodeunitRange(8204, 8205), - const CodeunitRange(9676, 9676), - ]), - NotoFont('Noto Sans Thai UI', [ - const CodeunitRange(3585, 3675), - const CodeunitRange(8204, 8205), - const CodeunitRange(9676, 9676), - ]), - NotoFont('Noto Sans', [ - const CodeunitRange(0, 255), - const CodeunitRange(305, 305), - const CodeunitRange(338, 339), - const CodeunitRange(699, 700), - const CodeunitRange(710, 710), - const CodeunitRange(730, 730), - const CodeunitRange(732, 732), - const CodeunitRange(8192, 8303), - const CodeunitRange(8308, 8308), - const CodeunitRange(8364, 8364), - const CodeunitRange(8482, 8482), - const CodeunitRange(8593, 8593), - const CodeunitRange(8595, 8595), - const CodeunitRange(8722, 8722), - const CodeunitRange(8725, 8725), - const CodeunitRange(65279, 65279), - const CodeunitRange(65533, 65533), - const CodeunitRange(1024, 1119), - const CodeunitRange(1168, 1169), - const CodeunitRange(1200, 1201), - const CodeunitRange(8470, 8470), - const CodeunitRange(1120, 1327), - const CodeunitRange(7296, 7304), - const CodeunitRange(8372, 8372), - const CodeunitRange(11744, 11775), - const CodeunitRange(42560, 42655), - const CodeunitRange(65070, 65071), - const CodeunitRange(880, 1023), - const CodeunitRange(7936, 8191), - const CodeunitRange(256, 591), - const CodeunitRange(601, 601), - const CodeunitRange(7680, 7935), - const CodeunitRange(8224, 8224), - const CodeunitRange(8352, 8363), - const CodeunitRange(8365, 8399), - const CodeunitRange(8467, 8467), - const CodeunitRange(11360, 11391), - const CodeunitRange(42784, 43007), - const CodeunitRange(258, 259), - const CodeunitRange(272, 273), - const CodeunitRange(296, 297), - const CodeunitRange(360, 361), - const CodeunitRange(416, 417), - const CodeunitRange(431, 432), - const CodeunitRange(7840, 7929), - const CodeunitRange(8363, 8363), - ]), -]; +NotoFont _notoSymbols = fallbackFonts.singleWhere((NotoFont font) => font.name == 'Noto Sans Symbols'); class FallbackFontDownloadQueue { NotoDownloader downloader = NotoDownloader(); - final Set<_ResolvedNotoSubset> downloadedSubsets = <_ResolvedNotoSubset>{}; - final Map pendingSubsets = - {}; + final Set downloadedFonts = {}; + final Map pendingFonts = {}; - bool get isPending => pendingSubsets.isNotEmpty || _fontsLoading != null; + bool get isPending => pendingFonts.isNotEmpty || _fontsLoading != null; Future? _fontsLoading; bool get debugIsLoadingFonts => _fontsLoading != null; @@ -861,9 +391,9 @@ class FallbackFontDownloadQueue { if (_fontsLoading != null) { await _fontsLoading; } - if (pendingSubsets.isNotEmpty) { + if (pendingFonts.isNotEmpty) { await Future.delayed(const Duration(milliseconds: 100)); - if (pendingSubsets.isEmpty) { + if (pendingFonts.isEmpty) { await Future.delayed(const Duration(milliseconds: 100)); } } @@ -873,13 +403,13 @@ class FallbackFontDownloadQueue { } } - void add(_ResolvedNotoSubset subset) { - if (downloadedSubsets.contains(subset) || - pendingSubsets.containsKey(subset.url)) { + void add(NotoFont font) { + if (downloadedFonts.contains(font) || + pendingFonts.containsKey(font.url)) { return; } - final bool firstInBatch = pendingSubsets.isEmpty; - pendingSubsets[subset.url] = subset; + final bool firstInBatch = pendingFonts.isEmpty; + pendingFonts[font.url] = font; if (firstInBatch) { Timer.run(startDownloads); } @@ -888,20 +418,20 @@ class FallbackFontDownloadQueue { Future startDownloads() async { final Map> downloads = >{}; final Map downloadedData = {}; - for (final _ResolvedNotoSubset subset in pendingSubsets.values) { - downloads[subset.url] = Future(() async { + for (final NotoFont font in pendingFonts.values) { + downloads[font.url] = Future(() async { ByteBuffer buffer; try { - buffer = await downloader.downloadAsBytes(subset.url, - debugDescription: subset.family); + buffer = await downloader.downloadAsBytes(font.url, + debugDescription: font.name); } catch (e) { - pendingSubsets.remove(subset.url); - printWarning('Failed to load font ${subset.family} at ${subset.url}'); + pendingFonts.remove(font.url); + printWarning('Failed to load font ${font.name} at ${font.url}'); printWarning(e.toString()); return; } - downloadedSubsets.add(subset); - downloadedData[subset.url] = buffer.asUint8List(); + downloadedFonts.add(font); + downloadedData[font.url] = buffer.asUint8List(); }); } @@ -913,11 +443,11 @@ class FallbackFontDownloadQueue { final List downloadOrder = (downloadedData.keys.toList()..sort()).reversed.toList(); for (final String url in downloadOrder) { - final _ResolvedNotoSubset subset = pendingSubsets.remove(url)!; + final NotoFont font = pendingFonts.remove(url)!; final Uint8List bytes = downloadedData[url]!; - FontFallbackData.instance.registerFallbackFont(subset.family, bytes); - if (pendingSubsets.isEmpty) { - _fontsLoading = skiaFontCollection.ensureFontsLoaded(); + FontFallbackData.instance.registerFallbackFont(font.name, bytes); + if (pendingFonts.isEmpty) { + _fontsLoading = renderer.fontCollection.ensureFontsLoaded(); try { await _fontsLoading; } finally { @@ -927,7 +457,7 @@ class FallbackFontDownloadQueue { } } - if (pendingSubsets.isNotEmpty) { + if (pendingFonts.isNotEmpty) { await startDownloads(); } } diff --git a/lib/web_ui/lib/src/engine/canvaskit/fonts.dart b/lib/web_ui/lib/src/engine/canvaskit/fonts.dart index 536bfe41471fa..07b51ded8276b 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/fonts.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/fonts.dart @@ -7,6 +7,7 @@ import 'dart:typed_data'; import '../assets.dart'; import '../dom.dart'; +import '../fonts.dart'; import '../util.dart'; import 'canvaskit_api.dart'; import 'font_fallbacks.dart'; @@ -23,7 +24,7 @@ const String _robotoUrl = const String _ahemUrl = '/assets/fonts/ahem.ttf'; /// Manages the fonts used in the Skia-based backend. -class SkiaFontCollection { +class SkiaFontCollection implements FontCollection { final Set _registeredFontFamilies = {}; /// Fonts that started the download process. @@ -50,6 +51,7 @@ class SkiaFontCollection { final Map> familyToFontMap = >{}; + @override Future ensureFontsLoaded() async { await _loadFonts(); @@ -91,6 +93,7 @@ class SkiaFontCollection { _pendingFonts.clear(); } + @override Future loadFontFromList(Uint8List list, {String? fontFamily}) async { if (fontFamily == null) { fontFamily = _readActualFamilyName(list); @@ -112,6 +115,7 @@ class SkiaFontCollection { } /// Loads fonts from `FontManifest.json`. + @override Future registerFonts(AssetManager assetManager) async { ByteData byteData; @@ -164,6 +168,7 @@ class SkiaFontCollection { /// `FontManifest.json` has higher priority than the default test font URLs. /// This allows customizing test environments where fonts are loaded from /// different URLs. + @override void debugRegisterTestFonts() { if (!_isFontFamilyRegistered('Ahem')) { _registerFont(_ahemUrl, 'Ahem'); @@ -218,10 +223,19 @@ class SkiaFontCollection { SkFontMgr? skFontMgr; TypefaceFontProvider? fontProvider; + + @override + void clear() {} } /// Represents a font that has been registered. class RegisteredFont { + RegisteredFont(this.bytes, this.family, this.typeface) { + // This is a hack which causes Skia to cache the decoded font. + final SkFont skFont = SkFont(typeface); + skFont.getGlyphBounds([0], null, null); + } + /// The font family name for this font. final String family; @@ -232,10 +246,4 @@ class RegisteredFont { /// /// This is used to determine which code points are supported by this font. final SkTypeface typeface; - - RegisteredFont(this.bytes, this.family, this.typeface) { - // This is a hack which causes Skia to cache the decoded font. - final SkFont skFont = SkFont(typeface); - skFont.getGlyphBounds([0], null, null); - } } diff --git a/lib/web_ui/lib/src/engine/canvaskit/image.dart b/lib/web_ui/lib/src/engine/canvaskit/image.dart index 3f98f10bd4e7f..921d5fe024f48 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/image.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/image.dart @@ -83,7 +83,6 @@ class ImageCodecException implements Exception { const String _kNetworkImageMessage = 'Failed to load network image.'; typedef HttpRequestFactory = DomXMLHttpRequest Function(); -// ignore: prefer_function_declarations_over_variables HttpRequestFactory httpRequestFactory = () => createDomXMLHttpRequest(); void debugRestoreHttpRequestFactory() { httpRequestFactory = () => createDomXMLHttpRequest(); @@ -345,11 +344,11 @@ class CkImage implements ui.Image, StackTraceDebugger { /// Data for a single frame of an animated image. class AnimatedImageFrameInfo implements ui.FrameInfo { + AnimatedImageFrameInfo(this._duration, this._image); + final Duration _duration; final CkImage _image; - AnimatedImageFrameInfo(this._duration, this._image); - @override Duration get duration => _duration; diff --git a/lib/web_ui/lib/src/engine/canvaskit/image_filter.dart b/lib/web_ui/lib/src/engine/canvaskit/image_filter.dart index faf3fb992d0cd..231e89f900d9c 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/image_filter.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/image_filter.dart @@ -135,7 +135,7 @@ class _CkBlurImageFilter extends CkImageFilter { class _CkMatrixImageFilter extends CkImageFilter { _CkMatrixImageFilter( {required Float64List matrix, required this.filterQuality}) - : this.matrix = Float64List.fromList(matrix), // ignore: unnecessary_this + : matrix = Float64List.fromList(matrix), super._(); final Float64List matrix; diff --git a/lib/web_ui/lib/src/engine/canvaskit/image_web_codecs.dart b/lib/web_ui/lib/src/engine/canvaskit/image_web_codecs.dart index 2eda07a20f465..c3ffcc7719fc9 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/image_web_codecs.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/image_web_codecs.dart @@ -42,6 +42,14 @@ void debugRestoreWebDecoderExpireDuration() { /// Image decoder backed by the browser's `ImageDecoder`. class CkBrowserImageDecoder implements ui.Codec { + CkBrowserImageDecoder._({ + required this.contentType, + required this.targetWidth, + required this.targetHeight, + required this.data, + required this.debugSource, + }); + static Future create({ required Uint8List data, required String debugSource, @@ -79,14 +87,6 @@ class CkBrowserImageDecoder implements ui.Codec { return decoder; } - CkBrowserImageDecoder._({ - required this.contentType, - required this.targetWidth, - required this.targetHeight, - required this.data, - required this.debugSource, - }); - final String contentType; final int? targetWidth; final int? targetHeight; diff --git a/lib/web_ui/lib/src/engine/canvaskit/initialization.dart b/lib/web_ui/lib/src/engine/canvaskit/initialization.dart deleted file mode 100644 index 9e673ab183606..0000000000000 --- a/lib/web_ui/lib/src/engine/canvaskit/initialization.dart +++ /dev/null @@ -1,121 +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. -library canvaskit_initialization; - -import 'dart:async'; - -import '../../engine.dart' show kProfileMode; -import '../browser_detection.dart'; -import '../configuration.dart'; -import '../dom.dart'; -import '../safe_browser_api.dart'; -import 'canvaskit_api.dart'; -import 'fonts.dart'; -import 'util.dart'; - -/// Whether to use CanvasKit as the rendering backend. -final bool useCanvasKit = FlutterConfiguration.flutterWebAutoDetect - ? _hasCanvasKit - : FlutterConfiguration.useSkia; - -/// Returns true if CanvasKit is used. -/// -/// Otherwise, returns false. -final bool _hasCanvasKit = _detectCanvasKit(); - -bool _detectCanvasKit() { - if (requestedRendererType != null) { - return requestedRendererType! == 'canvaskit'; - } - // If requestedRendererType is not specified, use CanvasKit for desktop and - // html for mobile. - return isDesktop; -} - -String get canvasKitBuildUrl => - configuration.canvasKitBaseUrl + (kProfileMode ? 'profiling/' : ''); -String get canvasKitJavaScriptBindingsUrl => - '${canvasKitBuildUrl}canvaskit.js'; -String canvasKitWasmModuleUrl(String canvasKitBase, String file) => - canvasKitBase + file; - -/// Downloads CanvasKit and instantiates its WASM module. -/// -/// Uses a cached implemenation if it exists. Otherwise downloads CanvasKit. -/// Assigns the global [canvasKit] object. -/// -/// Does not put any UI onto the page. It is therefore safe to call this -/// function while the page is showing non-Flutter UI, such as a loading -/// indicator or a splash screen. -/// -/// See also: -/// -/// * `initializeEngineUi`, which puts UI elements on the page. -Future initializeCanvasKit({String? canvasKitBase}) async { - if (windowFlutterCanvasKit != null) { - canvasKit = windowFlutterCanvasKit!; - } else if (useH5vccCanvasKit) { - if (h5vcc?.canvasKit == null) { - throw CanvasKitError('H5vcc CanvasKit implementation not found.'); - } - canvasKit = h5vcc!.canvasKit!; - windowFlutterCanvasKit = canvasKit; - } else { - canvasKit = await downloadCanvasKit(canvasKitBase: canvasKitBase); - windowFlutterCanvasKit = canvasKit; - } -} - -/// Download and initialize the CanvasKit module. -/// -/// Downloads the CanvasKit JavaScript, then calls `CanvasKitInit` to download -/// and intialize the CanvasKit wasm. -Future downloadCanvasKit({String? canvasKitBase}) async { - await _downloadCanvasKitJs(canvasKitBase: canvasKitBase); - final Completer canvasKitInitCompleter = Completer(); - final CanvasKitInitPromise canvasKitInitPromise = - CanvasKitInit(CanvasKitInitOptions( - locateFile: allowInterop((String file, String unusedBase) => - canvasKitWasmModuleUrl(canvasKitBase ?? canvasKitBuildUrl, file)), - )); - canvasKitInitPromise.then(allowInterop((CanvasKit ck) { - canvasKitInitCompleter.complete(ck); - })); - return canvasKitInitCompleter.future; -} - -/// Downloads the CanvasKit JavaScript file at [canvasKitBase]. -Future _downloadCanvasKitJs({String? canvasKitBase}) { - final String canvasKitJavaScriptUrl = canvasKitBase != null - ? '${canvasKitBase}canvaskit.js' - : canvasKitJavaScriptBindingsUrl; - - final DomHTMLScriptElement canvasKitScript = createDomHTMLScriptElement(); - canvasKitScript.src = canvasKitJavaScriptUrl; - - final Completer canvasKitLoadCompleter = Completer(); - late DomEventListener callback; - void loadEventHandler(DomEvent _) { - canvasKitLoadCompleter.complete(); - canvasKitScript.removeEventListener('load', callback); - } - callback = allowInterop(loadEventHandler); - canvasKitScript.addEventListener('load', callback); - - patchCanvasKitModule(canvasKitScript); - - return canvasKitLoadCompleter.future; -} - -/// The Skia font collection. -SkiaFontCollection get skiaFontCollection => _skiaFontCollection!; -SkiaFontCollection? _skiaFontCollection; - -/// Initializes [skiaFontCollection]. -void ensureSkiaFontCollectionInitialized() { - _skiaFontCollection ??= SkiaFontCollection(); -} - -/// The scene host, where the root canvas and overlay canvases are added to. -DomElement? skiaSceneHost; diff --git a/lib/web_ui/lib/src/engine/canvaskit/interval_tree.dart b/lib/web_ui/lib/src/engine/canvaskit/interval_tree.dart index bcf5b73d00ec3..7f10373c51527 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/interval_tree.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/interval_tree.dart @@ -4,13 +4,10 @@ import 'dart:math' as math; -import 'font_fallbacks.dart' show CodeunitRange; +import 'noto_font.dart' show CodeunitRange; /// A tree which stores a set of intervals that can be queried for intersection. class IntervalTree { - /// The root node of the interval tree. - final IntervalTreeNode root; - IntervalTree._(this.root); /// Creates an interval tree from a mapping of [T] values to a list of ranges. @@ -76,6 +73,9 @@ class IntervalTree { return IntervalTree._(root); } + /// The root node of the interval tree. + final IntervalTreeNode root; + /// Returns the list of objects which have been associated with intervals that /// intersect with [x]. List intersections(int x) { @@ -91,6 +91,8 @@ class IntervalTree { } class IntervalTreeNode { + IntervalTreeNode(this.value, this.low, this.high) : computedHigh = high; + final T value; final int low; final int high; @@ -99,8 +101,6 @@ class IntervalTreeNode { IntervalTreeNode? left; IntervalTreeNode? right; - IntervalTreeNode(this.value, this.low, this.high) : computedHigh = high; - Iterable enumerateAllElements() sync* { if (left != null) { yield* left!.enumerateAllElements(); diff --git a/lib/web_ui/lib/src/engine/canvaskit/layer.dart b/lib/web_ui/lib/src/engine/canvaskit/layer.dart index c81b4458aee18..c74b547ee3654 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/layer.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/layer.dart @@ -47,6 +47,8 @@ abstract class Layer implements ui.EngineLayer { /// A context shared by all layers during the preroll pass. class PrerollContext { + PrerollContext(this.rasterCache, this.viewEmbedder); + /// A raster cache. Used to register candidates for caching. final RasterCache? rasterCache; @@ -55,8 +57,6 @@ class PrerollContext { final MutatorsStack mutatorsStack = MutatorsStack(); - PrerollContext(this.rasterCache, this.viewEmbedder); - ui.Rect get cullRect { ui.Rect cullRect = ui.Rect.largest; for (final Mutator m in mutatorsStack) { @@ -82,6 +82,13 @@ class PrerollContext { /// A context shared by all layers during the paint pass. class PaintContext { + PaintContext( + this.internalNodesCanvas, + this.leafNodesCanvas, + this.rasterCache, + this.viewEmbedder, + ); + /// A multi-canvas that applies clips, transforms, and opacity /// operations to all canvases (root canvas and overlay canvases for the /// platform views). @@ -95,13 +102,6 @@ class PaintContext { /// A compositor for embedded HTML views. final HtmlViewEmbedder? viewEmbedder; - - PaintContext( - this.internalNodesCanvas, - this.leafNodesCanvas, - this.rasterCache, - this.viewEmbedder, - ); } /// A layer that contains child layers. @@ -167,11 +167,11 @@ class RootLayer extends ContainerLayer { class BackdropFilterEngineLayer extends ContainerLayer implements ui.BackdropFilterEngineLayer { + BackdropFilterEngineLayer(this._filter, this._blendMode); + final ui.ImageFilter _filter; final ui.BlendMode _blendMode; - BackdropFilterEngineLayer(this._filter, this._blendMode); - @override void preroll(PrerollContext prerollContext, Matrix4 matrix) { final ui.Rect childBounds = prerollChildren(prerollContext, matrix); @@ -194,13 +194,13 @@ class BackdropFilterEngineLayer extends ContainerLayer /// A layer that clips its child layers by a given [Path]. class ClipPathEngineLayer extends ContainerLayer implements ui.ClipPathEngineLayer { + ClipPathEngineLayer(this._clipPath, this._clipBehavior) + : assert(_clipBehavior != ui.Clip.none); + /// The path used to clip child layers. final CkPath _clipPath; final ui.Clip _clipBehavior; - ClipPathEngineLayer(this._clipPath, this._clipBehavior) - : assert(_clipBehavior != ui.Clip.none); - @override void preroll(PrerollContext prerollContext, Matrix4 matrix) { prerollContext.mutatorsStack.pushClipPath(_clipPath); @@ -234,13 +234,13 @@ class ClipPathEngineLayer extends ContainerLayer /// A layer that clips its child layers by a given [Rect]. class ClipRectEngineLayer extends ContainerLayer implements ui.ClipRectEngineLayer { + ClipRectEngineLayer(this._clipRect, this._clipBehavior) + : assert(_clipBehavior != ui.Clip.none); + /// The rectangle used to clip child layers. final ui.Rect _clipRect; final ui.Clip _clipBehavior; - ClipRectEngineLayer(this._clipRect, this._clipBehavior) - : assert(_clipBehavior != ui.Clip.none); - @override void preroll(PrerollContext prerollContext, Matrix4 matrix) { prerollContext.mutatorsStack.pushClipRect(_clipRect); @@ -275,13 +275,13 @@ class ClipRectEngineLayer extends ContainerLayer /// A layer that clips its child layers by a given [RRect]. class ClipRRectEngineLayer extends ContainerLayer implements ui.ClipRRectEngineLayer { + ClipRRectEngineLayer(this._clipRRect, this._clipBehavior) + : assert(_clipBehavior != ui.Clip.none); + /// The rounded rectangle used to clip child layers. final ui.RRect _clipRRect; final ui.Clip? _clipBehavior; - ClipRRectEngineLayer(this._clipRRect, this._clipBehavior) - : assert(_clipBehavior != ui.Clip.none); - @override void preroll(PrerollContext prerollContext, Matrix4 matrix) { prerollContext.mutatorsStack.pushClipRRect(_clipRRect); @@ -313,11 +313,11 @@ class ClipRRectEngineLayer extends ContainerLayer /// A layer that paints its children with the given opacity. class OpacityEngineLayer extends ContainerLayer implements ui.OpacityEngineLayer { + OpacityEngineLayer(this._alpha, this._offset); + final int _alpha; final ui.Offset _offset; - OpacityEngineLayer(this._alpha, this._offset); - @override void preroll(PrerollContext prerollContext, Matrix4 matrix) { final Matrix4 childMatrix = Matrix4.copy(matrix); @@ -354,11 +354,11 @@ class OpacityEngineLayer extends ContainerLayer /// A layer that transforms its child layers by the given transform matrix. class TransformEngineLayer extends ContainerLayer implements ui.TransformEngineLayer { + TransformEngineLayer(this._transform); + /// The matrix with which to transform the child layers. final Matrix4 _transform; - TransformEngineLayer(this._transform); - @override void preroll(PrerollContext prerollContext, Matrix4 matrix) { final Matrix4 childMatrix = matrix.multiplied(_transform); @@ -447,6 +447,8 @@ class ShaderMaskEngineLayer extends ContainerLayer /// A layer containing a [Picture]. class PictureLayer extends Layer { + PictureLayer(this.picture, this.offset, this.isComplex, this.willChange); + /// The picture to paint into the canvas. final CkPicture picture; @@ -459,8 +461,6 @@ class PictureLayer extends Layer { /// A hint to the compositor that this picture is likely to change. final bool willChange; - PictureLayer(this.picture, this.offset, this.isComplex, this.willChange); - @override void preroll(PrerollContext prerollContext, Matrix4 matrix) { paintBounds = picture.cullRect!.shift(offset); @@ -468,7 +468,7 @@ class PictureLayer extends Layer { @override void paint(PaintContext paintContext) { - assert(picture != null); // ignore: unnecessary_null_comparison + assert(picture != null); assert(needsPainting); paintContext.leafNodesCanvas!.save(); @@ -485,12 +485,6 @@ class PictureLayer extends Layer { /// on the given elevation. class PhysicalShapeEngineLayer extends ContainerLayer implements ui.PhysicalShapeEngineLayer { - final double _elevation; - final ui.Color _color; - final ui.Color? _shadowColor; // ignore: use_late_for_private_fields_and_variables - final CkPath _path; - final ui.Clip _clipBehavior; - PhysicalShapeEngineLayer( this._elevation, this._color, @@ -499,6 +493,12 @@ class PhysicalShapeEngineLayer extends ContainerLayer this._clipBehavior, ); + final double _elevation; + final ui.Color _color; + final ui.Color? _shadowColor; // ignore: use_late_for_private_fields_and_variables + final CkPath _path; + final ui.Clip _clipBehavior; + @override void preroll(PrerollContext prerollContext, Matrix4 matrix) { prerollChildren(prerollContext, matrix); diff --git a/lib/web_ui/lib/src/engine/canvaskit/layer_scene_builder.dart b/lib/web_ui/lib/src/engine/canvaskit/layer_scene_builder.dart index eb6d860a0e072..bca671f68026b 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/layer_scene_builder.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/layer_scene_builder.dart @@ -13,10 +13,10 @@ import 'path.dart'; import 'picture.dart'; class LayerScene implements ui.Scene { - final LayerTree layerTree; - LayerScene(RootLayer rootLayer) : layerTree = LayerTree(rootLayer); + final LayerTree layerTree; + @override void dispose() {} @@ -147,7 +147,7 @@ class LayerSceneBuilder implements ui.SceneBuilder { ui.ColorFilter filter, { ui.ColorFilterEngineLayer? oldLayer, }) { - assert(filter != null); // ignore: unnecessary_null_comparison + assert(filter != null); return pushLayer(ColorFilterEngineLayer(filter)); } @@ -156,7 +156,7 @@ class LayerSceneBuilder implements ui.SceneBuilder { ui.ImageFilter filter, { ui.ImageFilterEngineLayer? oldLayer, }) { - assert(filter != null); // ignore: unnecessary_null_comparison + assert(filter != null); return pushLayer(ImageFilterEngineLayer(filter)); } diff --git a/lib/web_ui/lib/src/engine/canvaskit/layer_tree.dart b/lib/web_ui/lib/src/engine/canvaskit/layer_tree.dart index 8edcbc56d14da..576e1b2533221 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/layer_tree.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/layer_tree.dart @@ -86,6 +86,8 @@ class LayerTree { /// A single frame to be rendered. class Frame { + Frame(this.canvas, this.rasterCache, this.viewEmbedder); + /// The canvas to render this frame to. final CkCanvas canvas; @@ -95,8 +97,6 @@ class Frame { /// 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}) { timeAction(kProfilePrerollFrame, () { diff --git a/lib/web_ui/lib/src/engine/canvaskit/noto_font.dart b/lib/web_ui/lib/src/engine/canvaskit/noto_font.dart new file mode 100644 index 0000000000000..83964c6f4222c --- /dev/null +++ b/lib/web_ui/lib/src/engine/canvaskit/noto_font.dart @@ -0,0 +1,98 @@ +// 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. + +class NotoFont { + const NotoFont(this.name, this.url, this._rangeStarts, this._rangeEnds); + + factory NotoFont.fromFlatRanges(String name, String url, List flatRanges) { + final List starts = []; + final List ends = []; + for (int i = 0; i < flatRanges.length; i += 2) { + starts.add(flatRanges[i]); + ends.add(flatRanges[i+1]); + } + return NotoFont(name, url, starts, ends); + } + + final String name; + final String url; + // A sorted list of Unicode range start points. + final List _rangeStarts; + + // A sorted list of Unicode range end points. + final List _rangeEnds; + + List computeUnicodeRanges() { + final List result = []; + for (int i = 0; i < _rangeStarts.length; i++) { + result.add(CodeunitRange(_rangeStarts[i], _rangeEnds[i])); + } + return result; + } + + // Returns `true` if this font has a glyph for the given [codeunit]. + bool contains(int codeUnit) { + // Binary search through the starts and ends to see if there + // is a range that contains the codeunit. + int min = 0; + int max = _rangeStarts.length - 1; + // Search for the first rangeStart that is greater than codeunit. + while (min < max) { + final int mid = (min + max) ~/ 2; + if (_rangeStarts[mid] > codeUnit) { + if (mid == 0) { + final int rangeStart = _rangeStarts[mid]; + final int rangeEnd = _rangeEnds[mid]; + return rangeStart <= codeUnit && codeUnit <= rangeEnd; + } + final int rangeStart = _rangeStarts[mid - 1]; + if (rangeStart <= codeUnit) { + final int rangeEnd = _rangeEnds[mid - 1]; + return rangeStart <= codeUnit && codeUnit <= rangeEnd; + } else { + max = mid - 1; + } + } else if (_rangeStarts[mid] < codeUnit) { + // If this is the last index, check if the codeunit is contained within it. + if (mid == _rangeStarts.length) { + final int rangeStart = _rangeStarts[mid]; + final int rangeEnd = _rangeEnds[mid]; + return rangeStart <= codeUnit && codeUnit <= rangeEnd; + } + min = mid + 1; + } else { + // _rangeStarts[mid] == codeUnit + return true; + } + } + return false; + } +} + +class CodeunitRange { + const CodeunitRange(this.start, this.end); + + final int start; + final int end; + + + bool contains(int codeUnit) { + return start <= codeUnit && codeUnit <= end; + } + + @override + bool operator ==(dynamic other) { + if (other is! CodeunitRange) { + return false; + } + final CodeunitRange range = other; + return range.start == start && range.end == end; + } + + @override + int get hashCode => Object.hash(start, end); + + @override + String toString() => '[$start, $end]'; +} diff --git a/lib/web_ui/lib/src/engine/canvaskit/path.dart b/lib/web_ui/lib/src/engine/canvaskit/path.dart index 96217e010f972..3bbf4f90cb8b4 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/path.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/path.dart @@ -86,7 +86,7 @@ class CkPath extends ManagedSkiaObject implements ui.Path { @override void addPolygon(List points, bool close) { - assert(points != null); // ignore: unnecessary_null_comparison + assert(points != null); final SkFloat32List encodedPoints = toMallocedSkPoints(points); skiaObject.addPoly(encodedPoints.toTypedArray(), close); freeFloat32List(encodedPoints); diff --git a/lib/web_ui/lib/src/engine/canvaskit/picture.dart b/lib/web_ui/lib/src/engine/canvaskit/picture.dart index 9a63d33e9659b..fa5a7c263f852 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/picture.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/picture.dart @@ -2,8 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:typed_data'; + import 'package:ui/ui.dart' as ui; +import '../dom.dart'; import '../profiler.dart'; import '../util.dart'; import 'canvas.dart'; @@ -17,9 +20,6 @@ import 'skia_object_cache.dart'; /// class may have their Skia counterparts deleted before finalization registry /// or [SkiaObjectCache] decide to delete it. class CkPicture extends ManagedSkiaObject implements ui.Picture { - final ui.Rect? cullRect; - final CkPictureSnapshot? _snapshot; - CkPicture(SkPicture super.picture, this.cullRect, this._snapshot) : assert( browserSupportsFinalizationRegistry && _snapshot == null || @@ -27,6 +27,8 @@ class CkPicture extends ManagedSkiaObject implements ui.Picture { 'If the browser does not support FinalizationRegistry (WeakRef), then we must have a picture snapshot to be able to resurrect it.', ); + final ui.Rect? cullRect; + final CkPictureSnapshot? _snapshot; @override int get approximateBytesUsed => 0; @@ -98,12 +100,27 @@ class CkPicture extends ManagedSkiaObject implements ui.Picture { @override ui.Image toImageSync(int width, int height) { assert(debugCheckNotDisposed('Cannot convert picture to image.')); - final SkSurface skSurface = canvasKit.MakeSurface(width, height); + final DomCanvasElement tempCanvas = + createDomCanvasElement(width: width, height: height); + final SkSurface skSurface = canvasKit.MakeWebGLCanvasSurface(tempCanvas); final SkCanvas skCanvas = skSurface.getCanvas(); skCanvas.drawPicture(skiaObject); final SkImage skImage = skSurface.makeImageSnapshot(); + final SkImageInfo imageInfo = SkImageInfo( + alphaType: canvasKit.AlphaType.Premul, + colorType: canvasKit.ColorType.RGBA_8888, + colorSpace: SkColorSpaceSRGB, + width: width, + height: height, + ); + final Uint8List pixels = skImage.readPixels(0, 0, imageInfo); + final SkImage? rasterImage = canvasKit.MakeImage(imageInfo, pixels, 4 * width); skSurface.dispose(); - return CkImage(skImage); + tempCanvas.remove(); + if (rasterImage == null) { + throw StateError('Unable to convert image pixels into SkImage.'); + } + return CkImage(rasterImage); } @override diff --git a/lib/web_ui/lib/src/engine/canvaskit/platform_message.dart b/lib/web_ui/lib/src/engine/canvaskit/platform_message.dart index dea8d0ba14fab..07ad515e08049 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/platform_message.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/platform_message.dart @@ -6,11 +6,11 @@ import 'dart:typed_data'; // TODO(hterkelsen): Delete this once the slots change lands? class PlatformMessage { + PlatformMessage(this.channel, this.data, this.response); + final String channel; final ByteData data; final PlatformMessageResponse response; - - PlatformMessage(this.channel, this.data, this.response); } class PlatformMessageResponse { diff --git a/lib/web_ui/lib/src/engine/canvaskit/renderer.dart b/lib/web_ui/lib/src/engine/canvaskit/renderer.dart new file mode 100644 index 0000000000000..0ebf6848e8615 --- /dev/null +++ b/lib/web_ui/lib/src/engine/canvaskit/renderer.dart @@ -0,0 +1,379 @@ +// 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:math' as math; +import 'dart:typed_data'; + +import 'package:ui/ui.dart' as ui; + +import '../dom.dart'; +import '../embedder.dart'; +import '../html_image_codec.dart'; +import '../profiler.dart'; +import '../renderer.dart'; +import 'canvaskit_api.dart'; +import 'canvaskit_canvas.dart'; +import 'fonts.dart'; +import 'image.dart'; +import 'image_filter.dart'; +import 'layer_scene_builder.dart'; +import 'painting.dart'; +import 'path.dart'; +import 'picture_recorder.dart'; +import 'rasterizer.dart'; +import 'shader.dart'; +import 'text.dart'; +import 'util.dart'; +import 'vertices.dart'; + +class CanvasKitRenderer implements Renderer { + static CanvasKitRenderer get instance => _instance; + static late CanvasKitRenderer _instance; + + @override + String get rendererTag => 'canvaskit'; + + late final SkiaFontCollection _fontCollection = SkiaFontCollection(); + + @override + SkiaFontCollection get fontCollection => _fontCollection; + + /// The scene host, where the root canvas and overlay canvases are added to. + DomElement? _sceneHost; + DomElement? get sceneHost => _sceneHost; + + late Rasterizer rasterizer = Rasterizer(); + + set resourceCacheMaxBytes(int bytes) => rasterizer.setSkiaResourceCacheMaxBytes(bytes); + + @override + Future initialize() async { + if (windowFlutterCanvasKit != null) { + canvasKit = windowFlutterCanvasKit!; + } else if (useH5vccCanvasKit) { + if (h5vcc?.canvasKit == null) { + throw CanvasKitError('H5vcc CanvasKit implementation not found.'); + } + canvasKit = h5vcc!.canvasKit!; + windowFlutterCanvasKit = canvasKit; + } else { + canvasKit = await downloadCanvasKit(); + windowFlutterCanvasKit = canvasKit; + } + + _instance = this; + } + + @override + void reset(FlutterViewEmbedder embedder) { + // CanvasKit uses a static scene element that never gets replaced, so it's + // added eagerly during initialization here and never touched, unless the + // system is reset due to hot restart or in a test. + _sceneHost = createDomElement('flt-scene'); + embedder.addSceneToSceneHost(_sceneHost); + } + + @override + ui.Paint createPaint() => CkPaint(); + + @override + ui.Vertices createVertices( + ui.VertexMode mode, + List positions, { + List? textureCoordinates, + List? colors, + List? indices, + }) => CkVertices( + mode, + positions, + textureCoordinates: textureCoordinates, + colors: colors, + indices: indices); + + @override + ui.Vertices createVerticesRaw( + ui.VertexMode mode, + Float32List positions, { + Float32List? textureCoordinates, + Int32List? colors, + Uint16List? indices, + }) => CkVertices.raw( + mode, + positions, + textureCoordinates: textureCoordinates, + colors: colors, + indices: indices); + + @override + ui.Canvas createCanvas(ui.PictureRecorder recorder, [ui.Rect? cullRect]) => + CanvasKitCanvas(recorder, cullRect); + + @override + ui.Gradient createLinearGradient( + ui.Offset from, + ui.Offset to, + List colors, [ + List? colorStops, + ui.TileMode tileMode = ui.TileMode.clamp, + Float32List? matrix4 + ]) => CkGradientLinear(from, to, colors, colorStops, tileMode, matrix4); + + @override + ui.Gradient createRadialGradient( + ui.Offset center, + double radius, + List colors, [ + List? colorStops, + ui.TileMode tileMode = ui.TileMode.clamp, + Float32List? matrix4, + ui.Offset? focal, + double focalRadius = 0.0, + ]) => CkGradientRadial(center, radius, colors, colorStops, tileMode, matrix4); + + @override + ui.Gradient createConicalGradient( + ui.Offset focal, + double focalRadius, + ui.Offset center, + double radius, + List colors, + [List? colorStops, + ui.TileMode tileMode = ui.TileMode.clamp, + Float32List? matrix] + ) => CkGradientConical( + focal, + focalRadius, + center, + radius, + colors, + colorStops, + tileMode, + matrix); + + @override + ui.Gradient createSweepGradient( + ui.Offset center, + List colors, [ + List? colorStops, + ui.TileMode tileMode = ui.TileMode.clamp, + double startAngle = 0.0, + double endAngle = math.pi * 2, + Float32List? matrix4 + ]) => CkGradientSweep(center, colors, colorStops, tileMode, startAngle, endAngle, matrix4); + + @override + ui.PictureRecorder createPictureRecorder() => CkPictureRecorder(); + + @override + ui.SceneBuilder createSceneBuilder() => LayerSceneBuilder(); + + @override + ui.ImageFilter createBlurImageFilter({ + double sigmaX = 0.0, + double sigmaY = 0.0, + ui.TileMode tileMode = ui.TileMode.clamp + }) => CkImageFilter.blur(sigmaX: sigmaX, sigmaY: sigmaY, tileMode: tileMode); + + @override + ui.ImageFilter createDilateImageFilter({double radiusX = 0.0, double radiusY = 0.0}) { + // TODO(fzyzcjy): implement dilate. https://github.com/flutter/flutter/issues/101085 + throw UnimplementedError('ImageFilter.dilate not implemented for CanvasKit.'); + } + + @override + ui.ImageFilter createErodeImageFilter({double radiusX = 0.0, double radiusY = 0.0}) { + // TODO(fzyzcjy): implement erode. https://github.com/flutter/flutter/issues/101085 + throw UnimplementedError('ImageFilter.erode not implemented for CanvasKit.'); + } + + @override + ui.ImageFilter createMatrixImageFilter( + Float64List matrix4, { + ui.FilterQuality filterQuality = ui.FilterQuality.low + }) => CkImageFilter.matrix(matrix: matrix4, filterQuality: filterQuality); + + @override + ui.ImageFilter composeImageFilters({required ui.ImageFilter outer, required ui.ImageFilter inner}) { + // TODO(ferhat): add implementation + throw UnimplementedError('ImageFilter.compose not implemented for CanvasKit.'); + } + + @override + Future instantiateImageCodec( + Uint8List list, { + int? targetWidth, + int? targetHeight, + bool allowUpscaling = true + }) async => skiaInstantiateImageCodec( + list, + targetWidth, + targetHeight); + + @override + Future instantiateImageCodecFromUrl( + Uri uri, { + WebOnlyImageCodecChunkCallback? chunkCallback + }) => skiaInstantiateWebImageCodec(uri.toString(), chunkCallback); + + @override + void decodeImageFromPixels( + Uint8List pixels, + int width, + int height, + ui.PixelFormat format, + ui.ImageDecoderCallback callback, { + int? rowBytes, + int? targetWidth, + int? targetHeight, + bool allowUpscaling = true + }) => skiaDecodeImageFromPixels( + pixels, + width, + height, + format, + callback, + rowBytes: rowBytes, + targetWidth: targetWidth, + targetHeight: targetHeight, + allowUpscaling: allowUpscaling + ); + + @override + ui.ImageShader createImageShader( + ui.Image image, + ui.TileMode tmx, + ui.TileMode tmy, + Float64List matrix4, + ui.FilterQuality? filterQuality + ) => CkImageShader(image, tmx, tmy, matrix4, filterQuality); + + @override + ui.Path createPath() => CkPath(); + + @override + ui.Path copyPath(ui.Path src) => CkPath.from(src as CkPath); + + @override + ui.Path combinePaths(ui.PathOperation op, ui.Path path1, ui.Path path2) => + CkPath.combine(op, path1, path2); + + @override + ui.TextStyle createTextStyle({ + 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.TextLeadingDistribution? leadingDistribution, + ui.Locale? locale, + ui.Paint? background, + ui.Paint? foreground, + List? shadows, + List? fontFeatures, + List? fontVariations + }) => CkTextStyle( + color: color, + decoration: decoration, + decorationColor: decorationColor, + decorationStyle: decorationStyle, + decorationThickness: decorationThickness, + fontWeight: fontWeight, + fontStyle: fontStyle, + textBaseline: textBaseline, + fontFamily: fontFamily, + fontFamilyFallback: fontFamilyFallback, + fontSize: fontSize, + letterSpacing: letterSpacing, + wordSpacing: wordSpacing, + height: height, + leadingDistribution: leadingDistribution, + locale: locale, + background: background as CkPaint?, + foreground: foreground as CkPaint?, + shadows: shadows, + fontFeatures: fontFeatures, + ); + + @override + ui.ParagraphStyle createParagraphStyle({ + ui.TextAlign? textAlign, + ui.TextDirection? textDirection, + int? maxLines, + String? fontFamily, + double? fontSize, + double? height, + ui.TextHeightBehavior? textHeightBehavior, + ui.FontWeight? fontWeight, + ui.FontStyle? fontStyle, + ui.StrutStyle? strutStyle, + String? ellipsis, + ui.Locale? locale + }) => CkParagraphStyle( + textAlign: textAlign, + textDirection: textDirection, + maxLines: maxLines, + fontFamily: fontFamily, + fontSize: fontSize, + height: height, + textHeightBehavior: textHeightBehavior, + fontWeight: fontWeight, + fontStyle: fontStyle, + strutStyle: strutStyle, + ellipsis: ellipsis, + locale: locale, + ); + + @override + ui.StrutStyle createStrutStyle({ + String? fontFamily, + List? fontFamilyFallback, + double? fontSize, + double? height, + ui.TextLeadingDistribution? leadingDistribution, + double? leading, + ui.FontWeight? fontWeight, + ui.FontStyle? fontStyle, + bool? forceStrutHeight + }) => CkStrutStyle( + fontFamily: fontFamily, + fontFamilyFallback: fontFamilyFallback, + fontSize: fontSize, + height: height, + leadingDistribution: leadingDistribution, + leading: leading, + fontWeight: fontWeight, + fontStyle: fontStyle, + forceStrutHeight: forceStrutHeight, + ); + + @override + ui.ParagraphBuilder createParagraphBuilder(ui.ParagraphStyle style) => + CkParagraphBuilder(style); + + @override + void renderScene(ui.Scene scene) { + // "Build finish" and "raster start" happen back-to-back because we + // render on the same thread, so there's no overhead from hopping to + // another thread. + // + // CanvasKit works differently from the HTML renderer in that in HTML + // we update the DOM in SceneBuilder.build, which is these function calls + // here are CanvasKit-only. + frameTimingsOnBuildFinish(); + frameTimingsOnRasterStart(); + + rasterizer.draw((scene as LayerScene).layerTree); + frameTimingsOnRasterFinish(); + } +} diff --git a/lib/web_ui/lib/src/engine/canvaskit/shader.dart b/lib/web_ui/lib/src/engine/canvaskit/shader.dart index f8c25d4827cca..531a251da5744 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/shader.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/shader.dart @@ -11,7 +11,6 @@ import '../util.dart'; import '../validators.dart'; import 'canvaskit_api.dart'; import 'image.dart'; -import 'initialization.dart'; import 'skia_object_cache.dart'; abstract class CkShader extends ManagedSkiaObject @@ -28,10 +27,10 @@ class CkGradientSweep extends CkShader implements ui.Gradient { CkGradientSweep(this.center, this.colors, this.colorStops, this.tileMode, this.startAngle, this.endAngle, this.matrix4) : assert(offsetIsValid(center)), - assert(colors != null), // ignore: unnecessary_null_comparison - assert(tileMode != null), // ignore: unnecessary_null_comparison - assert(startAngle != null), // ignore: unnecessary_null_comparison - assert(endAngle != null), // ignore: unnecessary_null_comparison + assert(colors != null), + assert(tileMode != null), + assert(startAngle != null), + assert(endAngle != null), assert(startAngle < endAngle), assert(matrix4 == null || matrix4IsValid(matrix4)) { validateColorStops(colors, colorStops); @@ -77,8 +76,8 @@ class CkGradientLinear extends CkShader implements ui.Gradient { Float32List? matrix, ) : assert(offsetIsValid(from)), assert(offsetIsValid(to)), - assert(colors != null), // ignore: unnecessary_null_comparison - assert(tileMode != null), // ignore: unnecessary_null_comparison + assert(colors != null), + assert(tileMode != null), matrix4 = matrix { if (assertionsEnabled) { assert(matrix4 == null || matrix4IsValid(matrix4!)); @@ -95,8 +94,6 @@ class CkGradientLinear extends CkShader implements ui.Gradient { @override SkShader createDefault() { - assert(useCanvasKit); - return canvasKit.Shader.MakeLinearGradient( toSkPoint(from), toSkPoint(to), @@ -124,8 +121,6 @@ class CkGradientRadial extends CkShader implements ui.Gradient { @override SkShader createDefault() { - assert(useCanvasKit); - return canvasKit.Shader.MakeRadialGradient( toSkPoint(center), radius, @@ -156,7 +151,6 @@ class CkGradientConical extends CkShader implements ui.Gradient { @override SkShader createDefault() { - assert(useCanvasKit); return canvasKit.Shader.MakeTwoPointConicalGradient( toSkPoint(focal), focalRadius, @@ -224,4 +218,25 @@ class CkImageShader extends CkShader implements ui.ImageShader { void delete() { rawSkiaObject?.delete(); } + + bool _disposed = false; + + @override + bool get debugDisposed { + late bool disposed; + assert(() { + disposed = _disposed; + return true; + }()); + return disposed; + } + + @override + void dispose() { + assert(() { + _disposed = true; + return true; + }()); + _image.dispose(); + } } diff --git a/lib/web_ui/lib/src/engine/canvaskit/skia_object_cache.dart b/lib/web_ui/lib/src/engine/canvaskit/skia_object_cache.dart index d05a262c6df57..21debea93cfb9 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/skia_object_cache.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/skia_object_cache.dart @@ -6,9 +6,10 @@ import 'dart:collection'; import 'package:meta/meta.dart'; -import '../../engine.dart' show EnginePlatformDispatcher, Instrumentation; +import '../../engine.dart' show Instrumentation; import '../util.dart'; import 'canvaskit_api.dart'; +import 'renderer.dart'; /// A cache of Skia objects whose memory Flutter manages. /// @@ -25,6 +26,10 @@ import 'canvaskit_api.dart'; /// JS-managed data structure when they are deleted so that when the associated /// object is garbage collected, so is the serialized data. class SkiaObjectCache { + SkiaObjectCache(this.maximumSize) + : _itemQueue = DoubleLinkedQueue>(), + _itemMap = , DoubleLinkedQueueEntry>>{}; + final int maximumSize; /// A doubly linked list of the objects in the cache. @@ -38,10 +43,6 @@ class SkiaObjectCache { /// move the object to the front of the queue. final Map, DoubleLinkedQueueEntry>> _itemMap; - SkiaObjectCache(this.maximumSize) - : _itemQueue = DoubleLinkedQueue>(), - _itemMap = , DoubleLinkedQueueEntry>>{}; - /// The number of objects in the cache. int get length => _itemQueue.length; @@ -90,6 +91,10 @@ class SkiaObjectCache { /// Like [SkiaObjectCache] but enforces the [maximumSize] of the cache /// synchronously instead of waiting until a post-frame callback. class SynchronousSkiaObjectCache { + SynchronousSkiaObjectCache(this.maximumSize) + : _itemQueue = DoubleLinkedQueue>(), + _itemMap = , DoubleLinkedQueueEntry>>{}; + /// This cache will never exceed this limit, even temporarily. final int maximumSize; @@ -104,10 +109,6 @@ class SynchronousSkiaObjectCache { /// move the object to the front of the queue. final Map, DoubleLinkedQueueEntry>> _itemMap; - SynchronousSkiaObjectCache(this.maximumSize) - : _itemQueue = DoubleLinkedQueue>(), - _itemMap = , DoubleLinkedQueueEntry>>{}; - /// The number of objects in the cache. int get length => _itemQueue.length; @@ -533,11 +534,7 @@ class SkiaObjects { if (_addedCleanupCallback) { return; } - // This method is @visibleForTesting but we're getting a warning about - // using a @visibleForTesting member. - // ignore: invalid_use_of_visible_for_testing_member - EnginePlatformDispatcher.instance.rasterizer! - .addPostFrameCallback(postFrameCleanUp); + CanvasKitRenderer.instance.rasterizer.addPostFrameCallback(postFrameCleanUp); _addedCleanupCallback = true; } diff --git a/lib/web_ui/lib/src/engine/canvaskit/surface.dart b/lib/web_ui/lib/src/engine/canvaskit/surface.dart index 2173506699c59..7e859ba394864 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/surface.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/surface.dart @@ -13,7 +13,7 @@ import '../util.dart'; import '../window.dart'; import 'canvas.dart'; import 'canvaskit_api.dart'; -import 'initialization.dart'; +import 'renderer.dart'; import 'surface_factory.dart'; import 'util.dart'; @@ -26,14 +26,14 @@ typedef SubmitCallback = bool Function(SurfaceFrame, CkCanvas); /// A frame which contains a canvas to be drawn into. class SurfaceFrame { - final CkSurface skiaSurface; - final SubmitCallback submitCallback; - bool _submitted; - SurfaceFrame(this.skiaSurface, this.submitCallback) : _submitted = false, - assert(skiaSurface != null), // ignore: unnecessary_null_comparison - assert(submitCallback != null); // ignore: unnecessary_null_comparison + assert(skiaSurface != null), + assert(submitCallback != null); + + final CkSurface skiaSurface; + final SubmitCallback submitCallback; + final bool _submitted; /// Submit this frame to be drawn. bool submit() { @@ -131,7 +131,7 @@ class Surface { void addToScene() { if (!_addedToScene) { - skiaSceneHost!.prepend(htmlElement); + CanvasKitRenderer.instance.sceneHost!.prepend(htmlElement); } _addedToScene = true; } @@ -161,6 +161,7 @@ class Surface { // The existing surface is still reusable. if (window.devicePixelRatio != _currentDevicePixelRatio) { _updateLogicalHtmlCanvasSize(); + _translateCanvas(); } return _surface!; } diff --git a/lib/web_ui/lib/src/engine/canvaskit/surface_factory.dart b/lib/web_ui/lib/src/engine/canvaskit/surface_factory.dart index 0ea08af2cc2ef..2165287ef169e 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/surface_factory.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/surface_factory.dart @@ -9,6 +9,18 @@ import '../../engine.dart'; /// Caches surfaces used to overlay platform views. class SurfaceFactory { + SurfaceFactory(int maximumSurfaces) + : maximumSurfaces = math.max(maximumSurfaces, 1) { + if (assertionsEnabled) { + if (maximumSurfaces < 1) { + printWarning('Attempted to create a $SurfaceFactory with ' + '$maximumSurfaces maximum surfaces. At least 1 surface is required ' + 'for rendering.'); + } + registerHotRestartListener(debugClear); + } + } + /// The lazy-initialized singleton surface factory. /// /// [debugClear] causes this singleton to be reinitialized. @@ -29,18 +41,6 @@ class SurfaceFactory { static SurfaceFactory? _instance; - SurfaceFactory(int maximumSurfaces) - : maximumSurfaces = math.max(maximumSurfaces, 1) { - if (assertionsEnabled) { - if (maximumSurfaces < 1) { - printWarning('Attempted to create a $SurfaceFactory with ' - '$maximumSurfaces maximum surfaces. At least 1 surface is required ' - 'for rendering.'); - } - registerHotRestartListener(debugClear); - } - } - /// The base surface to paint on. This is the default surface which will be /// painted to. If there are no platform views, then this surface will receive /// all painting commands. diff --git a/lib/web_ui/lib/src/engine/canvaskit/text.dart b/lib/web_ui/lib/src/engine/canvaskit/text.dart index a9a703c905a9d..01ee84fc7bd30 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/text.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/text.dart @@ -11,8 +11,8 @@ import '../safe_browser_api.dart'; import '../util.dart'; import 'canvaskit_api.dart'; import 'font_fallbacks.dart'; -import 'initialization.dart'; import 'painting.dart'; +import 'renderer.dart'; import 'skia_object_cache.dart'; import 'util.dart'; @@ -637,7 +637,7 @@ class CkParagraph extends SkiaObject implements ui.Paragraph { // lot of paragraphs are laid out _and_ rendered. To support // this use-case without blowing up memory usage we need this: // https://github.com/flutter/flutter/issues/81224 - static SynchronousSkiaObjectCache _paragraphCache = + static final SynchronousSkiaObjectCache _paragraphCache = SynchronousSkiaObjectCache(500); /// Marks this paragraph as having been used this frame. @@ -835,13 +835,6 @@ class CkLineMetrics implements ui.LineMetrics { } class CkParagraphBuilder implements ui.ParagraphBuilder { - final SkParagraphBuilder _paragraphBuilder; - final CkParagraphStyle _style; - final List<_ParagraphCommand> _commands; - int _placeholderCount; - final List _placeholderScales; - final List _styleStack; - CkParagraphBuilder(ui.ParagraphStyle style) : _commands = <_ParagraphCommand>[], _style = style as CkParagraphStyle, @@ -850,11 +843,18 @@ class CkParagraphBuilder implements ui.ParagraphBuilder { _styleStack = [], _paragraphBuilder = canvasKit.ParagraphBuilder.MakeFromFontProvider( style.skParagraphStyle, - skiaFontCollection.fontProvider, + CanvasKitRenderer.instance.fontCollection.fontProvider, ) { _styleStack.add(_style.getTextStyle()); } + final SkParagraphBuilder _paragraphBuilder; + final CkParagraphStyle _style; + final List<_ParagraphCommand> _commands; + int _placeholderCount; + final List _placeholderScales; + final List _styleStack; + @override void addPlaceholder( double width, @@ -1019,11 +1019,6 @@ class _CkParagraphPlaceholder { } class _ParagraphCommand { - final _ParagraphCommandType type; - final String? text; - final CkTextStyle? style; - final _CkParagraphPlaceholder? placeholderStyle; - const _ParagraphCommand._( this.type, this.text, @@ -1044,6 +1039,11 @@ class _ParagraphCommand { _CkParagraphPlaceholder placeholderStyle) : this._( _ParagraphCommandType.addPlaceholder, null, null, placeholderStyle); + + final _ParagraphCommandType type; + final String? text; + final CkTextStyle? style; + final _CkParagraphPlaceholder? placeholderStyle; } enum _ParagraphCommandType { diff --git a/lib/web_ui/lib/src/engine/canvaskit/vertices.dart b/lib/web_ui/lib/src/engine/canvaskit/vertices.dart index 2125a969c9284..22cfbebda12ed 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/vertices.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/vertices.dart @@ -17,8 +17,8 @@ class CkVertices extends ManagedSkiaObject implements ui.Vertices { List? colors, List? indices, }) { - assert(mode != null); // ignore: unnecessary_null_comparison - assert(positions != null); // ignore: unnecessary_null_comparison + assert(mode != null); + assert(positions != null); if (textureCoordinates != null && textureCoordinates.length != positions.length) { throw ArgumentError( @@ -49,8 +49,8 @@ class CkVertices extends ManagedSkiaObject implements ui.Vertices { Int32List? colors, Uint16List? indices, }) { - assert(mode != null); // ignore: unnecessary_null_comparison - assert(positions != null); // ignore: unnecessary_null_comparison + assert(mode != null); + assert(positions != null); if (textureCoordinates != null && textureCoordinates.length != positions.length) { throw ArgumentError( diff --git a/lib/web_ui/lib/src/engine/canvaskit/viewport_metrics.dart b/lib/web_ui/lib/src/engine/canvaskit/viewport_metrics.dart index 99347d356dbf9..255ab30bf0f82 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/viewport_metrics.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/viewport_metrics.dart @@ -3,13 +3,13 @@ // found in the LICENSE file. class ViewportMetrics { - final double devicePixelRatio; - final double physicalWidth; - final double physicalHeight; - const ViewportMetrics( this.devicePixelRatio, this.physicalWidth, this.physicalHeight, ); + + final double devicePixelRatio; + final double physicalWidth; + final double physicalHeight; } diff --git a/lib/web_ui/lib/src/engine/dom.dart b/lib/web_ui/lib/src/engine/dom.dart index ec1c03f7ec427..3c6bac0aa0b31 100644 --- a/lib/web_ui/lib/src/engine/dom.dart +++ b/lib/web_ui/lib/src/engine/dom.dart @@ -1309,22 +1309,22 @@ extension DomScreenOrientationExtension on DomScreenOrientation { // remove the listener. Caller is still responsible for calling [allowInterop] // on the listener before creating the subscription. class DomSubscription { - final String type; - final DomEventTarget target; - final DomEventListener listener; - DomSubscription(this.target, this.type, this.listener) { target.addEventListener(type, listener); } + final String type; + final DomEventTarget target; + final DomEventListener listener; + void cancel() => target.removeEventListener(type, listener); } class DomPoint { + DomPoint(this.x, this.y); + final num x; final num y; - - DomPoint(this.x, this.y); } @JS() @@ -1422,11 +1422,11 @@ extension DomListExtension on _DomList { } class _DomListIterator extends Iterator { + _DomListIterator(this.list); + final _DomList list; int index = -1; - _DomListIterator(this.list); - @override bool moveNext() { index++; @@ -1441,10 +1441,10 @@ class _DomListIterator extends Iterator { } class _DomListWrapper extends Iterable { - final _DomList list; - _DomListWrapper._(this.list); + final _DomList list; + @override Iterator get iterator => _DomListIterator(list); diff --git a/lib/web_ui/lib/src/engine/embedder.dart b/lib/web_ui/lib/src/engine/embedder.dart index ef6107e3a6023..ff65ba564948f 100644 --- a/lib/web_ui/lib/src/engine/embedder.dart +++ b/lib/web_ui/lib/src/engine/embedder.dart @@ -6,9 +6,8 @@ import 'dart:async'; import 'package:ui/ui.dart' as ui; -import '../engine.dart' show buildMode, registerHotRestartListener; +import '../engine.dart' show buildMode, registerHotRestartListener, renderer; import 'browser_detection.dart'; -import 'canvaskit/initialization.dart'; import 'configuration.dart'; import 'dom.dart'; import 'host_node.dart'; @@ -199,7 +198,7 @@ class FlutterViewEmbedder { bodyElement.setAttribute( 'flt-renderer', - '${useCanvasKit ? 'canvaskit' : 'html'} (${FlutterConfiguration.flutterWebAutoDetect ? 'auto-selected' : 'requested explicitly'})', + '${renderer.rendererTag} (${FlutterConfiguration.flutterWebAutoDetect ? 'auto-selected' : 'requested explicitly'})', ); bodyElement.setAttribute('flt-build-mode', buildMode); @@ -287,13 +286,7 @@ class FlutterViewEmbedder { _sceneHostElement = domDocument.createElement('flt-scene-host') ..style.pointerEvents = 'none'; - /// CanvasKit uses a static scene element that never gets replaced, so it's - /// added eagerly during initialization here and never touched, unless the - /// system is reset due to hot restart or in a test. - if (useCanvasKit) { - skiaSceneHost = createDomElement('flt-scene'); - addSceneToSceneHost(skiaSceneHost); - } + renderer.reset(this); final DomElement semanticsHostElement = domDocument.createElement('flt-semantics-host'); diff --git a/lib/web_ui/lib/src/engine/engine_canvas.dart b/lib/web_ui/lib/src/engine/engine_canvas.dart index bd491fb87c790..ba956254ce40f 100644 --- a/lib/web_ui/lib/src/engine/engine_canvas.dart +++ b/lib/web_ui/lib/src/engine/engine_canvas.dart @@ -3,7 +3,6 @@ // found in the LICENSE file. // For member documentation see https://api.flutter.dev/flutter/dart-ui/Canvas-class.html -// ignore_for_file: public_member_api_docs import 'dart:typed_data'; @@ -116,10 +115,6 @@ class SaveStackEntry { /// Tagged union of clipping parameters used for canvas. class SaveClipEntry { - final ui.Rect? rect; - final ui.RRect? rrect; - final ui.Path? path; - final Matrix4 currentTransform; SaveClipEntry.rect(this.rect, this.currentTransform) : rrect = null, path = null; @@ -129,6 +124,11 @@ class SaveClipEntry { SaveClipEntry.path(this.path, this.currentTransform) : rect = null, rrect = null; + + final ui.Rect? rect; + final ui.RRect? rrect; + final ui.Path? path; + final Matrix4 currentTransform; } /// Provides save stack tracking functionality to implementations of diff --git a/lib/web_ui/lib/src/engine/fonts.dart b/lib/web_ui/lib/src/engine/fonts.dart new file mode 100644 index 0000000000000..0dd6c816c8357 --- /dev/null +++ b/lib/web_ui/lib/src/engine/fonts.dart @@ -0,0 +1,15 @@ +// 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:typed_data'; + +import 'assets.dart'; + +abstract class FontCollection { + Future loadFontFromList(Uint8List list, {String? fontFamily}); + Future ensureFontsLoaded(); + Future registerFonts(AssetManager assetManager); + void debugRegisterTestFonts(); + void clear(); +} diff --git a/lib/web_ui/lib/src/engine/frame_reference.dart b/lib/web_ui/lib/src/engine/frame_reference.dart index c711b18100e14..4384840365202 100644 --- a/lib/web_ui/lib/src/engine/frame_reference.dart +++ b/lib/web_ui/lib/src/engine/frame_reference.dart @@ -86,9 +86,9 @@ class CrossFrameCache { } class _CrossFrameCacheItem { + _CrossFrameCacheItem(this.value, this.evictCallback); final T value; final CrossFrameCacheEvictCallback? evictCallback; - _CrossFrameCacheItem(this.value, this.evictCallback); void evict() { if (evictCallback != null) { evictCallback!(value); diff --git a/lib/web_ui/lib/src/engine/host_node.dart b/lib/web_ui/lib/src/engine/host_node.dart index dee8ffb1c45dd..59a91e697fabf 100644 --- a/lib/web_ui/lib/src/engine/host_node.dart +++ b/lib/web_ui/lib/src/engine/host_node.dart @@ -86,8 +86,6 @@ abstract class HostNode { /// supported in the current environment. In this case, a fallback [ElementHostNode] /// should be created instead. class ShadowDomHostNode implements HostNode { - late DomShadowRoot _shadow; - /// Build a HostNode by attaching a [DomShadowRoot] to the `root` element. /// /// This also calls [applyGlobalCssRulesToSheet], defined in dom_renderer. @@ -115,6 +113,8 @@ class ShadowDomHostNode implements HostNode { ); } + late DomShadowRoot _shadow; + @override DomElement? get activeElement => _shadow.activeElement; @@ -150,14 +150,14 @@ class ShadowDomHostNode implements HostNode { /// This is a fallback implementation, in case [ShadowDomHostNode] fails when /// being constructed. class ElementHostNode implements HostNode { - late DomElement _element; - /// Build a HostNode by attaching a child [DomElement] to the `root` element. ElementHostNode(DomElement root) { _element = domDocument.createElement('flt-element-host-node'); root.appendChild(_element); } + late DomElement _element; + @override DomElement? get activeElement => _element.ownerDocument?.activeElement; diff --git a/lib/web_ui/lib/src/engine/html/bitmap_canvas.dart b/lib/web_ui/lib/src/engine/html/bitmap_canvas.dart index 0f901d28e0067..3bebbd94b53a1 100644 --- a/lib/web_ui/lib/src/engine/html/bitmap_canvas.dart +++ b/lib/web_ui/lib/src/engine/html/bitmap_canvas.dart @@ -31,13 +31,46 @@ import 'shaders/image_shader.dart'; /// A raw HTML canvas that is directly written to. class BitmapCanvas extends EngineCanvas { + /// Allocates a canvas with enough memory to paint a picture within the given + /// [bounds]. + /// + /// 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, RenderStrategy renderStrategy, + {double density = 1.0}) + : assert(_bounds != null), + _density = density, + _renderStrategy = renderStrategy, + widthInBitmapPixels = widthToPhysical(_bounds.width), + heightInBitmapPixels = heightToPhysical(_bounds.height), + _canvasPool = CanvasPool(widthToPhysical(_bounds.width), + heightToPhysical(_bounds.height), density) { + 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.mount(rootElement as DomHTMLElement); + _setupInitialTransform(); + } + + /// Constructs bitmap canvas to capture image data. + factory BitmapCanvas.imageData(ui.Rect bounds) { + final BitmapCanvas bitmapCanvas = BitmapCanvas(bounds, RenderStrategy()); + bitmapCanvas._preserveImageData = true; + return bitmapCanvas; + } + /// The rectangle positioned relative to the parent layer's coordinate /// system's origin, within which this canvas paints. /// /// Painting outside these bounds will result in cropping. ui.Rect get bounds => _bounds; set bounds(ui.Rect newValue) { - assert(newValue != null); // ignore: unnecessary_null_comparison + assert(newValue != null); _bounds = newValue; final int newCanvasPositionX = _bounds.left.floor() - kPaddingPixels; final int newCanvasPositionY = _bounds.top.floor() - kPaddingPixels; @@ -136,39 +169,6 @@ class BitmapCanvas extends EngineCanvas { final RenderStrategy _renderStrategy; - /// Allocates a canvas with enough memory to paint a picture within the given - /// [bounds]. - /// - /// 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, RenderStrategy renderStrategy, - {double density = 1.0}) - : assert(_bounds != null), // ignore: unnecessary_null_comparison - _density = density, - _renderStrategy = renderStrategy, - widthInBitmapPixels = widthToPhysical(_bounds.width), - heightInBitmapPixels = heightToPhysical(_bounds.height), - _canvasPool = CanvasPool(widthToPhysical(_bounds.width), - heightToPhysical(_bounds.height), density) { - 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.mount(rootElement as DomHTMLElement); - _setupInitialTransform(); - } - - /// Constructs bitmap canvas to capture image data. - factory BitmapCanvas.imageData(ui.Rect bounds) { - final BitmapCanvas bitmapCanvas = BitmapCanvas(bounds, RenderStrategy()); - bitmapCanvas._preserveImageData = true; - return bitmapCanvas; - } - /// Setup cache for reusing DOM elements across frames. void setElementCache(CrossFrameCache? cache) { _elementCache = cache; @@ -218,7 +218,7 @@ class BitmapCanvas extends EngineCanvas { // Used by picture to assess if canvas is large enough to reuse as is. bool doesFitBounds(ui.Rect newBounds, double newDensity) { - assert(newBounds != null); // ignore: unnecessary_null_comparison + assert(newBounds != null); return widthInBitmapPixels >= widthToPhysical(newBounds.width) && heightInBitmapPixels >= heightToPhysical(newBounds.height) && _density == newDensity; @@ -1036,7 +1036,7 @@ class BitmapCanvas extends EngineCanvas { /// Stores paint data used by [drawPoints]. We cannot use the original paint /// data object because painting style is determined by [ui.PointMode] and /// not by [SurfacePointData.style]. - static SurfacePaintData _drawPointsPaint = SurfacePaintData() + static final SurfacePaintData _drawPointsPaint = SurfacePaintData() ..strokeCap = ui.StrokeCap.round ..strokeJoin = ui.StrokeJoin.round ..blendMode = ui.BlendMode.srcOver; @@ -1343,7 +1343,7 @@ String? stringForStrokeCap(ui.StrokeCap? strokeCap) { } String stringForStrokeJoin(ui.StrokeJoin strokeJoin) { - assert(strokeJoin != null); // ignore: unnecessary_null_comparison + assert(strokeJoin != null); switch (strokeJoin) { case ui.StrokeJoin.round: return 'round'; diff --git a/lib/web_ui/lib/src/engine/html/canvas.dart b/lib/web_ui/lib/src/engine/html/canvas.dart index 9a422ff8eed52..8f310f3865207 100644 --- a/lib/web_ui/lib/src/engine/html/canvas.dart +++ b/lib/web_ui/lib/src/engine/html/canvas.dart @@ -16,8 +16,6 @@ import 'recording_canvas.dart'; import 'render_vertices.dart'; class SurfaceCanvas implements ui.Canvas { - RecordingCanvas _canvas; - factory SurfaceCanvas(EnginePictureRecorder recorder, [ui.Rect? cullRect]) { if (recorder.isRecording) { throw ArgumentError( @@ -29,6 +27,8 @@ class SurfaceCanvas implements ui.Canvas { SurfaceCanvas._(this._canvas); + RecordingCanvas _canvas; + @override void save() { _canvas.save(); @@ -36,7 +36,7 @@ class SurfaceCanvas implements ui.Canvas { @override void saveLayer(ui.Rect? bounds, ui.Paint paint) { - assert(paint != null); // ignore: unnecessary_null_comparison + assert(paint != null); if (bounds == null) { _saveLayerWithoutBounds(paint); } else { @@ -85,7 +85,7 @@ class SurfaceCanvas implements ui.Canvas { @override void transform(Float64List matrix4) { - assert(matrix4 != null); // ignore: unnecessary_null_comparison + assert(matrix4 != null); if (matrix4.length != 16) { throw ArgumentError('"matrix4" must have 16 entries.'); } @@ -105,8 +105,8 @@ class SurfaceCanvas implements ui.Canvas { void clipRect(ui.Rect rect, {ui.ClipOp clipOp = ui.ClipOp.intersect, bool doAntiAlias = true}) { assert(rectIsValid(rect)); - assert(clipOp != null); // ignore: unnecessary_null_comparison - assert(doAntiAlias != null); // ignore: unnecessary_null_comparison + assert(clipOp != null); + assert(doAntiAlias != null); _clipRect(rect, clipOp, doAntiAlias); } @@ -117,7 +117,7 @@ class SurfaceCanvas implements ui.Canvas { @override void clipRRect(ui.RRect rrect, {bool doAntiAlias = true}) { assert(rrectIsValid(rrect)); - assert(doAntiAlias != null); // ignore: unnecessary_null_comparison + assert(doAntiAlias != null); _clipRRect(rrect, doAntiAlias); } @@ -127,9 +127,8 @@ class SurfaceCanvas implements ui.Canvas { @override void clipPath(ui.Path path, {bool doAntiAlias = true}) { - // ignore: unnecessary_null_comparison assert(path != null); // path is checked on the engine side - assert(doAntiAlias != null); // ignore: unnecessary_null_comparison + assert(doAntiAlias != null); _clipPath(path, doAntiAlias); } @@ -167,8 +166,8 @@ class SurfaceCanvas implements ui.Canvas { @override void drawColor(ui.Color color, ui.BlendMode blendMode) { - assert(color != null); // ignore: unnecessary_null_comparison - assert(blendMode != null); // ignore: unnecessary_null_comparison + assert(color != null); + assert(blendMode != null); _drawColor(color, blendMode); } @@ -180,7 +179,7 @@ class SurfaceCanvas implements ui.Canvas { void drawLine(ui.Offset p1, ui.Offset p2, ui.Paint paint) { assert(offsetIsValid(p1)); assert(offsetIsValid(p2)); - assert(paint != null); // ignore: unnecessary_null_comparison + assert(paint != null); _drawLine(p1, p2, paint); } @@ -190,7 +189,7 @@ class SurfaceCanvas implements ui.Canvas { @override void drawPaint(ui.Paint paint) { - assert(paint != null); // ignore: unnecessary_null_comparison + assert(paint != null); _drawPaint(paint); } @@ -201,7 +200,7 @@ class SurfaceCanvas implements ui.Canvas { @override void drawRect(ui.Rect rect, ui.Paint paint) { assert(rectIsValid(rect)); - assert(paint != null); // ignore: unnecessary_null_comparison + assert(paint != null); _drawRect(rect, paint); } @@ -212,7 +211,7 @@ class SurfaceCanvas implements ui.Canvas { @override void drawRRect(ui.RRect rrect, ui.Paint paint) { assert(rrectIsValid(rrect)); - assert(paint != null); // ignore: unnecessary_null_comparison + assert(paint != null); _drawRRect(rrect, paint); } @@ -224,7 +223,7 @@ class SurfaceCanvas implements ui.Canvas { void drawDRRect(ui.RRect outer, ui.RRect inner, ui.Paint paint) { assert(rrectIsValid(outer)); assert(rrectIsValid(inner)); - assert(paint != null); // ignore: unnecessary_null_comparison + assert(paint != null); _drawDRRect(outer, inner, paint); } @@ -235,7 +234,7 @@ class SurfaceCanvas implements ui.Canvas { @override void drawOval(ui.Rect rect, ui.Paint paint) { assert(rectIsValid(rect)); - assert(paint != null); // ignore: unnecessary_null_comparison + assert(paint != null); _drawOval(rect, paint); } @@ -246,7 +245,7 @@ class SurfaceCanvas implements ui.Canvas { @override void drawCircle(ui.Offset c, double radius, ui.Paint paint) { assert(offsetIsValid(c)); - assert(paint != null); // ignore: unnecessary_null_comparison + assert(paint != null); _drawCircle(c, radius, paint); } @@ -258,7 +257,7 @@ class SurfaceCanvas implements ui.Canvas { void drawArc(ui.Rect rect, double startAngle, double sweepAngle, bool useCenter, ui.Paint paint) { assert(rectIsValid(rect)); - assert(paint != null); // ignore: unnecessary_null_comparison + assert(paint != null); const double pi = math.pi; const double pi2 = 2.0 * pi; @@ -293,9 +292,8 @@ class SurfaceCanvas implements ui.Canvas { @override void drawPath(ui.Path path, ui.Paint paint) { - // ignore: unnecessary_null_comparison assert(path != null); // path is checked on the engine side - assert(paint != null); // ignore: unnecessary_null_comparison + assert(paint != null); _drawPath(path, paint); } @@ -305,10 +303,9 @@ class SurfaceCanvas implements ui.Canvas { @override void drawImage(ui.Image image, ui.Offset offset, ui.Paint paint) { - // ignore: unnecessary_null_comparison assert(image != null); // image is checked on the engine side assert(offsetIsValid(offset)); - assert(paint != null); // ignore: unnecessary_null_comparison + assert(paint != null); _drawImage(image, offset, paint); } @@ -318,11 +315,10 @@ class SurfaceCanvas implements ui.Canvas { @override void drawImageRect(ui.Image image, ui.Rect src, ui.Rect dst, ui.Paint paint) { - // ignore: unnecessary_null_comparison assert(image != null); // image is checked on the engine side assert(rectIsValid(src)); assert(rectIsValid(dst)); - assert(paint != null); // ignore: unnecessary_null_comparison + assert(paint != null); _drawImageRect(image, src, dst, paint); } @@ -375,11 +371,10 @@ class SurfaceCanvas implements ui.Canvas { @override void drawImageNine( ui.Image image, ui.Rect center, ui.Rect dst, ui.Paint paint) { - // ignore: unnecessary_null_comparison assert(image != null); // image is checked on the engine side assert(rectIsValid(center)); assert(rectIsValid(dst)); - assert(paint != null); // ignore: unnecessary_null_comparison + assert(paint != null); if (dst.isEmpty) { return; @@ -424,14 +419,13 @@ class SurfaceCanvas implements ui.Canvas { @override void drawPicture(ui.Picture picture) { - // ignore: unnecessary_null_comparison assert(picture != null); // picture is checked on the engine side _canvas.drawPicture(picture); } @override void drawParagraph(ui.Paragraph paragraph, ui.Offset offset) { - assert(paragraph != null); // ignore: unnecessary_null_comparison + assert(paragraph != null); assert(offsetIsValid(offset)); _drawParagraph(paragraph, offset); } @@ -443,9 +437,9 @@ class SurfaceCanvas implements ui.Canvas { @override void drawPoints( ui.PointMode pointMode, List points, ui.Paint paint) { - assert(pointMode != null); // ignore: unnecessary_null_comparison - assert(points != null); // ignore: unnecessary_null_comparison - assert(paint != null); // ignore: unnecessary_null_comparison + assert(pointMode != null); + assert(points != null); + assert(paint != null); final Float32List pointList = offsetListToFloat32List(points); drawRawPoints(pointMode, pointList, paint); } @@ -453,9 +447,9 @@ class SurfaceCanvas implements ui.Canvas { @override void drawRawPoints( ui.PointMode pointMode, Float32List points, ui.Paint paint) { - assert(pointMode != null); // ignore: unnecessary_null_comparison - assert(points != null); // ignore: unnecessary_null_comparison - assert(paint != null); // ignore: unnecessary_null_comparison + assert(pointMode != null); + assert(points != null); + assert(paint != null); if (points.length % 2 != 0) { throw ArgumentError('"points" must have an even number of values.'); } @@ -466,8 +460,8 @@ class SurfaceCanvas implements ui.Canvas { void drawVertices( ui.Vertices vertices, ui.BlendMode blendMode, ui.Paint paint) { //assert(vertices != null); // vertices is checked on the engine side - assert(paint != null); // ignore: unnecessary_null_comparison - assert(blendMode != null); // ignore: unnecessary_null_comparison + assert(paint != null); + assert(blendMode != null); _canvas.drawVertices( vertices as SurfaceVertices, blendMode, paint as SurfacePaint); } @@ -482,12 +476,11 @@ class SurfaceCanvas implements ui.Canvas { ui.Rect? cullRect, ui.Paint paint, ) { - // ignore: unnecessary_null_comparison assert(atlas != null); // atlas is checked on the engine side - assert(transforms != null); // ignore: unnecessary_null_comparison - assert(rects != null); // ignore: unnecessary_null_comparison + assert(transforms != null); + assert(rects != null); assert(colors == null || colors.isEmpty || blendMode != null); - assert(paint != null); // ignore: unnecessary_null_comparison + assert(paint != null); final int rectCount = rects.length; if (transforms.length != rectCount) { @@ -512,12 +505,11 @@ class SurfaceCanvas implements ui.Canvas { ui.Rect? cullRect, ui.Paint paint, ) { - // ignore: unnecessary_null_comparison assert(atlas != null); // atlas is checked on the engine side - assert(rstTransforms != null); // ignore: unnecessary_null_comparison - assert(rects != null); // ignore: unnecessary_null_comparison + assert(rstTransforms != null); + assert(rects != null); assert(colors == null || blendMode != null); - assert(paint != null); // ignore: unnecessary_null_comparison + assert(paint != null); final int rectCount = rects.length; if (rstTransforms.length != rectCount) { @@ -543,10 +535,9 @@ class SurfaceCanvas implements ui.Canvas { double elevation, bool transparentOccluder, ) { - // ignore: unnecessary_null_comparison assert(path != null); // path is checked on the engine side - assert(color != null); // ignore: unnecessary_null_comparison - assert(transparentOccluder != null); // ignore: unnecessary_null_comparison + assert(color != null); + assert(transparentOccluder != null); _canvas.drawShadow(path, color, elevation, transparentOccluder); } } diff --git a/lib/web_ui/lib/src/engine/html/color_filter.dart b/lib/web_ui/lib/src/engine/html/color_filter.dart index 5912f6962f9e9..8b6507a8a8553 100644 --- a/lib/web_ui/lib/src/engine/html/color_filter.dart +++ b/lib/web_ui/lib/src/engine/html/color_filter.dart @@ -252,8 +252,6 @@ const int kOperatorArithmetic = 6; /// Builds an [SvgFilter]. class SvgFilterBuilder { - static int _filterIdCounter = 0; - SvgFilterBuilder() : id = '_fcf${++_filterIdCounter}' { filter.id = id; @@ -268,6 +266,8 @@ class SvgFilterBuilder { filter.height!.baseVal!.valueAsString = '100%'; } + static int _filterIdCounter = 0; + final String id; final SVGSVGElement root = kSvgResourceHeader.cloneNode(false) as SVGSVGElement; diff --git a/lib/web_ui/lib/src/engine/html/dom_canvas.dart b/lib/web_ui/lib/src/engine/html/dom_canvas.dart index 34c9bec699cc3..41956fd11f064 100644 --- a/lib/web_ui/lib/src/engine/html/dom_canvas.dart +++ b/lib/web_ui/lib/src/engine/html/dom_canvas.dart @@ -22,11 +22,11 @@ import 'shaders/shader.dart'; /// A canvas that renders to DOM elements and CSS properties. class DomCanvas extends EngineCanvas with SaveElementStackTracking { + DomCanvas(this.rootElement); + @override final DomElement rootElement; - DomCanvas(this.rootElement); - /// Prepare to reuse this canvas by clearing it's current contents. @override void clear() { diff --git a/lib/web_ui/lib/src/engine/html/painting.dart b/lib/web_ui/lib/src/engine/html/painting.dart index 0616b7ca6d749..beb9f25354291 100644 --- a/lib/web_ui/lib/src/engine/html/painting.dart +++ b/lib/web_ui/lib/src/engine/html/painting.dart @@ -161,7 +161,7 @@ class SurfacePaint implements ui.Paint { @override set strokeMiterLimit(double value) { - assert(value != null); // ignore: unnecessary_null_comparison + assert(value != null); } @override diff --git a/lib/web_ui/lib/src/engine/html/path/conic.dart b/lib/web_ui/lib/src/engine/html/path/conic.dart index 9a385a5969155..fa8a338dcabfb 100644 --- a/lib/web_ui/lib/src/engine/html/path/conic.dart +++ b/lib/web_ui/lib/src/engine/html/path/conic.dart @@ -17,12 +17,12 @@ import 'path_utils.dart'; /// Skia implementation reference: /// https://github.com/google/skia/blob/main/src/core/SkGeometry.cpp class Conic { + Conic(this.p0x, this.p0y, this.p1x, this.p1y, this.p2x, this.p2y, this.fW); + double p0x, p0y, p1x, p1y, p2x, p2y; final double fW; static const int _maxSubdivisionCount = 5; - Conic(this.p0x, this.p0y, this.p1x, this.p1y, this.p2x, this.p2y, this.fW); - /// Returns array of points for the approximation of the conic as quad(s). /// /// First offset is start Point. Each pair of offsets after are quadratic diff --git a/lib/web_ui/lib/src/engine/html/path/path.dart b/lib/web_ui/lib/src/engine/html/path/path.dart index fb49a1ae7e8cd..5ced01bb17000 100644 --- a/lib/web_ui/lib/src/engine/html/path/path.dart +++ b/lib/web_ui/lib/src/engine/html/path/path.dart @@ -35,15 +35,33 @@ import 'tangent.dart'; /// 3. if we encounter Move without a preceding Close, and forceClose is true, goto #2 /// 4. if we encounter Line | Quad | Cubic after Close, cons up a Move class SurfacePath implements ui.Path { + SurfacePath() : pathRef = PathRef() { + _resetFields(); + } + + /// Creates a copy of another [Path]. + SurfacePath.from(SurfacePath source) + : pathRef = PathRef()..copy(source.pathRef, 0, 0) { + _copyFields(source); + } + + /// Creates a shifted copy of another [Path]. + SurfacePath.shiftedFrom(SurfacePath source, double offsetX, double offsetY) + : pathRef = PathRef.shiftedFrom(source.pathRef, offsetX, offsetY) { + _copyFields(source); + } + + SurfacePath.shallowCopy(SurfacePath source) + : pathRef = PathRef.shallowCopy(source.pathRef) { + _copyFields(source); + } + // Initial valid of last move to index so we can detect if a move to // needs to be inserted after contour closure. See [close]. static const int kInitialLastMoveToIndexValue = 0; PathRef pathRef; ui.PathFillType _fillType = ui.PathFillType.nonZero; - // Skia supports inverse winding as part of path fill type. - // For Flutter inverse is always false. - bool _isInverseFillType = false; // Store point index + 1 of last moveTo instruction. // If contour has been closed or path is in initial state, the value is // negated. @@ -51,10 +69,6 @@ class SurfacePath implements ui.Path { int _convexityType = SPathConvexityType.kUnknown; int _firstDirection = SPathDirection.kUnknown; - SurfacePath() : pathRef = PathRef() { - _resetFields(); - } - void _resetFields() { fLastMoveToIndex = kInitialLastMoveToIndexValue; _fillType = ui.PathFillType.nonZero; @@ -66,23 +80,6 @@ class SurfacePath implements ui.Path { _firstDirection = SPathDirection.kUnknown; } - /// Creates a copy of another [Path]. - SurfacePath.from(SurfacePath source) - : pathRef = PathRef()..copy(source.pathRef, 0, 0) { - _copyFields(source); - } - - /// Creates a shifted copy of another [Path]. - SurfacePath.shiftedFrom(SurfacePath source, double offsetX, double offsetY) - : pathRef = PathRef.shiftedFrom(source.pathRef, offsetX, offsetY) { - _copyFields(source); - } - - SurfacePath.shallowCopy(SurfacePath source) - : pathRef = PathRef.shallowCopy(source.pathRef) { - _copyFields(source); - } - void _copyFields(SurfacePath source) { _fillType = source._fillType; fLastMoveToIndex = source.fLastMoveToIndex; @@ -1209,9 +1206,8 @@ class SurfacePath implements ui.Path { @override bool contains(ui.Offset point) { assert(offsetIsValid(point)); - final bool isInverse = _isInverseFillType; if (pathRef.isEmpty) { - return isInverse; + return false; } // Check bounds including right/bottom. final ui.Rect bounds = getBounds(); @@ -1219,7 +1215,7 @@ class SurfacePath implements ui.Path { final double y = point.dy; if (x < bounds.left || y < bounds.top || x > bounds.right || y > bounds.bottom) { - return isInverse; + return false; } final PathWinding windings = PathWinding(pathRef, point.dx, point.dy); final bool evenOddFill = ui.PathFillType.evenOdd == _fillType; @@ -1228,14 +1224,14 @@ class SurfacePath implements ui.Path { w &= 1; } if (w != 0) { - return !isInverse; + return true; } final int onCurveCount = windings.onCurveCount; if (onCurveCount <= 1) { - return (onCurveCount != 0) ^ isInverse; + return onCurveCount != 0; } if ((onCurveCount & 1) != 0 || evenOddFill) { - return (onCurveCount & 1) != 0 ^ (isInverse ? 1 : 0); + return (onCurveCount & 1) != 0; } // If the point touches an even number of curves, and the fill is winding, // check for coincidence. Count coincidence as places where the on curve @@ -1288,7 +1284,7 @@ class SurfacePath implements ui.Path { } } } while (!done); - return tangents.isEmpty ? isInverse : !isInverse; + return tangents.isNotEmpty; } /// Returns a copy of the path with all the segments of every diff --git a/lib/web_ui/lib/src/engine/html/path/path_metrics.dart b/lib/web_ui/lib/src/engine/html/path/path_metrics.dart index 683050e92edf2..d0d2e647de47f 100644 --- a/lib/web_ui/lib/src/engine/html/path/path_metrics.dart +++ b/lib/web_ui/lib/src/engine/html/path/path_metrics.dart @@ -52,7 +52,7 @@ class _SurfacePathMeasure { _pathIterator = PathIterator(_path, forceClosed); final PathRef _path; - PathIterator _pathIterator; + final PathIterator _pathIterator; final List<_PathContourMeasure> _contours = <_PathContourMeasure>[]; // If the contour ends with a call to [Path.close] (which may @@ -494,7 +494,7 @@ class SurfacePathMetricIterator implements Iterator { SurfacePathMetricIterator._(this._pathMeasure); SurfacePathMetric? _pathMetric; - _SurfacePathMeasure _pathMeasure; + final _SurfacePathMeasure _pathMeasure; @override SurfacePathMetric get current => _pathMetric!; @@ -693,7 +693,6 @@ class _PathSegment { // Evaluates A * t^3 + B * t^2 + Ct + D = 0 for cubic curve. class _SkCubicCoefficients { - final double ax, ay, bx, by, cx, cy, dx, dy; _SkCubicCoefficients(double x0, double y0, double x1, double y1, double x2, double y2, double x3, double y3) : ax = x3 + (3 * (x1 - x2)) - x0, @@ -705,6 +704,8 @@ class _SkCubicCoefficients { dx = x0, dy = y0; + final double ax, ay, bx, by, cx, cy, dx, dy; + double evalX(double t) => (((ax * t + bx) * t) + cx) * t + dx; double evalY(double t) => (((ay * t + by) * t) + cy) * t + dy; diff --git a/lib/web_ui/lib/src/engine/html/path/path_ref.dart b/lib/web_ui/lib/src/engine/html/path/path_ref.dart index 836a2ffed788d..6bafcb02459b3 100644 --- a/lib/web_ui/lib/src/engine/html/path/path_ref.dart +++ b/lib/web_ui/lib/src/engine/html/path/path_ref.dart @@ -27,6 +27,67 @@ class PathRef { _resetFields(); } + /// Creates a copy of the path by pointing new path to a current + /// points,verbs and weights arrays. If original path is mutated by adding + /// more verbs, this copy only returns path at the time of copy and shares + /// typed arrays of original path. + PathRef.shallowCopy(PathRef ref) + : fPoints = ref.fPoints, + _fVerbs = ref._fVerbs { + _fVerbsCapacity = ref._fVerbsCapacity; + _fVerbsLength = ref._fVerbsLength; + + _fPointsCapacity = ref._fPointsCapacity; + _fPointsLength = ref._fPointsLength; + + _conicWeightsCapacity = ref._conicWeightsCapacity; + _conicWeightsLength = ref._conicWeightsLength; + _conicWeights = ref._conicWeights; + fBoundsIsDirty = ref.fBoundsIsDirty; + if (!fBoundsIsDirty) { + fBounds = ref.fBounds; + cachedBounds = ref.cachedBounds; + fIsFinite = ref.fIsFinite; + } + fSegmentMask = ref.fSegmentMask; + fIsOval = ref.fIsOval; + fIsRRect = ref.fIsRRect; + fIsRect = ref.fIsRect; + fRRectOrOvalIsCCW = ref.fRRectOrOvalIsCCW; + fRRectOrOvalStartIdx = ref.fRRectOrOvalStartIdx; + debugValidate(); + } + + /// Returns a new path by translating [source] by [offsetX], [offsetY]. + PathRef.shiftedFrom(PathRef source, double offsetX, double offsetY) + : fPoints = _fPointsFromSource(source, offsetX, offsetY), + _fVerbs = _fVerbsFromSource(source) { + _conicWeightsCapacity = source._conicWeightsCapacity; + _conicWeightsLength = source._conicWeightsLength; + if (source._conicWeights != null) { + _conicWeights = Float32List(_conicWeightsCapacity); + _conicWeights!.setAll(0, source._conicWeights!); + } + _fVerbsCapacity = source._fVerbsCapacity; + _fVerbsLength = source._fVerbsLength; + + _fPointsCapacity = source._fPointsCapacity; + _fPointsLength = source._fPointsLength; + fBoundsIsDirty = source.fBoundsIsDirty; + if (!fBoundsIsDirty) { + fBounds = source.fBounds!.translate(offsetX, offsetY); + cachedBounds = source.cachedBounds?.translate(offsetX, offsetY); + fIsFinite = source.fIsFinite; + } + fSegmentMask = source.fSegmentMask; + fIsOval = source.fIsOval; + fIsRRect = source.fIsRRect; + fIsRect = source.fIsRect; + fRRectOrOvalIsCCW = source.fRRectOrOvalIsCCW; + fRRectOrOvalStartIdx = source.fRRectOrOvalStartIdx; + debugValidate(); + } + // Value to use to check against to insert move(0,0) when a command // is added without moveTo. static const int kInitialLastMoveToIndex = -1; @@ -85,37 +146,6 @@ class PathRef { fPoints[index + 1] = y; } - /// Creates a copy of the path by pointing new path to a current - /// points,verbs and weights arrays. If original path is mutated by adding - /// more verbs, this copy only returns path at the time of copy and shares - /// typed arrays of original path. - PathRef.shallowCopy(PathRef ref) - : fPoints = ref.fPoints, - _fVerbs = ref._fVerbs { - _fVerbsCapacity = ref._fVerbsCapacity; - _fVerbsLength = ref._fVerbsLength; - - _fPointsCapacity = ref._fPointsCapacity; - _fPointsLength = ref._fPointsLength; - - _conicWeightsCapacity = ref._conicWeightsCapacity; - _conicWeightsLength = ref._conicWeightsLength; - _conicWeights = ref._conicWeights; - fBoundsIsDirty = ref.fBoundsIsDirty; - if (!fBoundsIsDirty) { - fBounds = ref.fBounds; - cachedBounds = ref.cachedBounds; - fIsFinite = ref.fIsFinite; - } - fSegmentMask = ref.fSegmentMask; - fIsOval = ref.fIsOval; - fIsRRect = ref.fIsRRect; - fIsRect = ref.fIsRect; - fRRectOrOvalIsCCW = ref.fRRectOrOvalIsCCW; - fRRectOrOvalStartIdx = ref.fRRectOrOvalStartIdx; - debugValidate(); - } - Float32List get points => fPoints; Float32List? get conicWeights => _conicWeights; @@ -395,36 +425,6 @@ class PathRef { return verbs; } - /// Returns a new path by translating [source] by [offsetX], [offsetY]. - PathRef.shiftedFrom(PathRef source, double offsetX, double offsetY) - : fPoints = _fPointsFromSource(source, offsetX, offsetY), - _fVerbs = _fVerbsFromSource(source) { - _conicWeightsCapacity = source._conicWeightsCapacity; - _conicWeightsLength = source._conicWeightsLength; - if (source._conicWeights != null) { - _conicWeights = Float32List(_conicWeightsCapacity); - _conicWeights!.setAll(0, source._conicWeights!); - } - _fVerbsCapacity = source._fVerbsCapacity; - _fVerbsLength = source._fVerbsLength; - - _fPointsCapacity = source._fPointsCapacity; - _fPointsLength = source._fPointsLength; - fBoundsIsDirty = source.fBoundsIsDirty; - if (!fBoundsIsDirty) { - fBounds = source.fBounds!.translate(offsetX, offsetY); - cachedBounds = source.cachedBounds?.translate(offsetX, offsetY); - fIsFinite = source.fIsFinite; - } - fSegmentMask = source.fSegmentMask; - fIsOval = source.fIsOval; - fIsRRect = source.fIsRRect; - fIsRect = source.fIsRect; - fRRectOrOvalIsCCW = source.fRRectOrOvalIsCCW; - fRRectOrOvalStartIdx = source.fRRectOrOvalStartIdx; - debugValidate(); - } - /// Copies contents from a source path [ref]. void copy( PathRef ref, int additionalReserveVerbs, int additionalReservePoints) { @@ -948,11 +948,6 @@ class PathRef { } class PathRefIterator { - final PathRef pathRef; - int _conicWeightIndex = -1; - int _verbIndex = 0; - int _pointIndex = 0; - PathRefIterator(this.pathRef) { _pointIndex = 0; if (!pathRef.isFinite) { @@ -962,6 +957,11 @@ class PathRefIterator { } } + final PathRef pathRef; + int _conicWeightIndex = -1; + int _verbIndex = 0; + int _pointIndex = 0; + /// Maximum buffer size required for points in [next] calls. static const int kMaxBufferSize = 8; diff --git a/lib/web_ui/lib/src/engine/html/path/path_utils.dart b/lib/web_ui/lib/src/engine/html/path/path_utils.dart index fde50c5e4156f..0f7dacda7c715 100644 --- a/lib/web_ui/lib/src/engine/html/path/path_utils.dart +++ b/lib/web_ui/lib/src/engine/html/path/path_utils.dart @@ -115,11 +115,11 @@ class SPathSegmentState { /// x1 = Q / A /// x2 = C / Q class QuadRoots { + QuadRoots(); + double? root0; double? root1; - QuadRoots(); - /// Returns roots as list. List get roots => (root0 == null) ? [] diff --git a/lib/web_ui/lib/src/engine/html/picture.dart b/lib/web_ui/lib/src/engine/html/picture.dart index 05487a4f57708..da6f988ba8df7 100644 --- a/lib/web_ui/lib/src/engine/html/picture.dart +++ b/lib/web_ui/lib/src/engine/html/picture.dart @@ -54,8 +54,8 @@ class PaintRequest { PaintRequest({ required this.canvasSize, required this.paintCallback, - }) : assert(canvasSize != null), // ignore: unnecessary_null_comparison - assert(paintCallback != null); // ignore: unnecessary_null_comparison + }) : assert(canvasSize != null), + assert(paintCallback != null); final ui.Size canvasSize; final ui.VoidCallback paintCallback; diff --git a/lib/web_ui/lib/src/engine/html/platform_view.dart b/lib/web_ui/lib/src/engine/html/platform_view.dart index 108ac19435b05..d35ec97995207 100644 --- a/lib/web_ui/lib/src/engine/html/platform_view.dart +++ b/lib/web_ui/lib/src/engine/html/platform_view.dart @@ -8,14 +8,14 @@ import 'surface.dart'; /// A surface containing a platform view, which is an HTML element. class PersistedPlatformView extends PersistedLeafSurface { + PersistedPlatformView(this.viewId, this.dx, this.dy, this.width, this.height); + final int viewId; final double dx; final double dy; final double width; final double height; - PersistedPlatformView(this.viewId, this.dx, this.dy, this.width, this.height); - @override DomElement createElement() { return createPlatformViewSlot(viewId); diff --git a/lib/web_ui/lib/src/engine/html/recording_canvas.dart b/lib/web_ui/lib/src/engine/html/recording_canvas.dart index 2e93839475a41..970009ef563f8 100644 --- a/lib/web_ui/lib/src/engine/html/recording_canvas.dart +++ b/lib/web_ui/lib/src/engine/html/recording_canvas.dart @@ -14,7 +14,6 @@ import '../shadow.dart'; import '../text/canvas_paragraph.dart'; import '../util.dart'; import '../vector_math.dart'; -import 'bitmap_canvas.dart'; import 'painting.dart'; import 'path/path.dart'; import 'path/path_utils.dart'; @@ -35,34 +34,13 @@ double _measureBorderRadius(double x, double y) { return clampedX * clampedX + clampedY * clampedY; } -class RawRecordingCanvas extends BitmapCanvas implements ui.PictureRecorder { - RawRecordingCanvas(ui.Size size) - : super(ui.Offset.zero & size, RenderStrategy()); - - @override - void dispose() { - clear(); - } - - RecordingCanvas beginRecording(ui.Rect bounds) => throw UnsupportedError(''); - - @override - ui.Picture endRecording() => throw UnsupportedError(''); - - late RecordingCanvas _canvas; // ignore: unused_field - - bool _isRecording = true; // ignore: unused_field - - @override - bool get isRecording => true; - - ui.Rect? cullRect; -} - /// Records canvas commands to be applied to a [EngineCanvas]. /// /// See [Canvas] for docs for these methods. class RecordingCanvas { + RecordingCanvas(ui.Rect? bounds) + : _paintBounds = _PaintBounds(bounds ?? ui.Rect.largest); + /// Computes [_pictureBounds]. final _PaintBounds _paintBounds; @@ -91,9 +69,6 @@ class RecordingCanvas { throw UnsupportedError('For debugging only.'); } - RecordingCanvas(ui.Rect? bounds) - : _paintBounds = _PaintBounds(bounds ?? ui.Rect.largest); - final RenderStrategy renderStrategy = RenderStrategy(); /// Forces arbitrary paint even for simple pictures. @@ -770,11 +745,11 @@ class PaintRestore extends PaintCommand { } class PaintTranslate extends PaintCommand { + PaintTranslate(this.dx, this.dy); + final double dx; final double dy; - PaintTranslate(this.dx, this.dy); - @override void apply(EngineCanvas canvas) { canvas.translate(dx, dy); @@ -791,11 +766,11 @@ class PaintTranslate extends PaintCommand { } class PaintScale extends PaintCommand { + PaintScale(this.sx, this.sy); + final double sx; final double sy; - PaintScale(this.sx, this.sy); - @override void apply(EngineCanvas canvas) { canvas.scale(sx, sy); @@ -812,10 +787,10 @@ class PaintScale extends PaintCommand { } class PaintRotate extends PaintCommand { - final double radians; - PaintRotate(this.radians); + final double radians; + @override void apply(EngineCanvas canvas) { canvas.rotate(radians); @@ -832,10 +807,10 @@ class PaintRotate extends PaintCommand { } class PaintTransform extends PaintCommand { - final Float32List matrix4; - PaintTransform(this.matrix4); + final Float32List matrix4; + @override void apply(EngineCanvas canvas) { canvas.transform(matrix4); @@ -852,11 +827,11 @@ class PaintTransform extends PaintCommand { } class PaintSkew extends PaintCommand { + PaintSkew(this.sx, this.sy); + final double sx; final double sy; - PaintSkew(this.sx, this.sy); - @override void apply(EngineCanvas canvas) { canvas.skew(sx, sy); @@ -873,11 +848,11 @@ class PaintSkew extends PaintCommand { } class PaintClipRect extends DrawCommand { + PaintClipRect(this.rect, this.clipOp); + final ui.Rect rect; final ui.ClipOp clipOp; - PaintClipRect(this.rect, this.clipOp); - @override void apply(EngineCanvas canvas) { canvas.clipRect(rect, clipOp); @@ -894,10 +869,10 @@ class PaintClipRect extends DrawCommand { } class PaintClipRRect extends DrawCommand { - final ui.RRect rrect; - PaintClipRRect(this.rrect); + final ui.RRect rrect; + @override void apply(EngineCanvas canvas) { canvas.clipRRect(rrect); @@ -914,10 +889,10 @@ class PaintClipRRect extends DrawCommand { } class PaintClipPath extends DrawCommand { - final SurfacePath path; - PaintClipPath(this.path); + final SurfacePath path; + @override void apply(EngineCanvas canvas) { canvas.clipPath(path); @@ -934,11 +909,11 @@ class PaintClipPath extends DrawCommand { } class PaintDrawColor extends DrawCommand { + PaintDrawColor(this.color, this.blendMode); + final ui.Color color; final ui.BlendMode blendMode; - PaintDrawColor(this.color, this.blendMode); - @override void apply(EngineCanvas canvas) { canvas.drawColor(color, blendMode); @@ -955,12 +930,12 @@ class PaintDrawColor extends DrawCommand { } class PaintDrawLine extends DrawCommand { + PaintDrawLine(this.p1, this.p2, this.paint); + final ui.Offset p1; final ui.Offset p2; final SurfacePaintData paint; - PaintDrawLine(this.p1, this.p2, this.paint); - @override void apply(EngineCanvas canvas) { canvas.drawLine(p1, p2, paint); @@ -977,10 +952,10 @@ class PaintDrawLine extends DrawCommand { } class PaintDrawPaint extends DrawCommand { - final SurfacePaintData paint; - PaintDrawPaint(this.paint); + final SurfacePaintData paint; + @override void apply(EngineCanvas canvas) { canvas.drawPaint(paint); @@ -997,10 +972,11 @@ class PaintDrawPaint extends DrawCommand { } class PaintDrawVertices extends DrawCommand { + PaintDrawVertices(this.vertices, this.blendMode, this.paint); + final ui.Vertices vertices; final ui.BlendMode blendMode; final SurfacePaintData paint; - PaintDrawVertices(this.vertices, this.blendMode, this.paint); @override void apply(EngineCanvas canvas) { @@ -1018,10 +994,11 @@ class PaintDrawVertices extends DrawCommand { } class PaintDrawPoints extends DrawCommand { + PaintDrawPoints(this.pointMode, this.points, this.paint); + final Float32List points; final ui.PointMode pointMode; final SurfacePaintData paint; - PaintDrawPoints(this.pointMode, this.points, this.paint); @override void apply(EngineCanvas canvas) { @@ -1039,11 +1016,11 @@ class PaintDrawPoints extends DrawCommand { } class PaintDrawRect extends DrawCommand { + PaintDrawRect(this.rect, this.paint); + final ui.Rect rect; final SurfacePaintData paint; - PaintDrawRect(this.rect, this.paint); - @override void apply(EngineCanvas canvas) { canvas.drawRect(rect, paint); @@ -1060,11 +1037,11 @@ class PaintDrawRect extends DrawCommand { } class PaintDrawRRect extends DrawCommand { + PaintDrawRRect(this.rrect, this.paint); + final ui.RRect rrect; final SurfacePaintData paint; - PaintDrawRRect(this.rrect, this.paint); - @override void apply(EngineCanvas canvas) { canvas.drawRRect(rrect, paint); @@ -1081,10 +1058,6 @@ class PaintDrawRRect extends DrawCommand { } class PaintDrawDRRect extends DrawCommand { - final ui.RRect outer; - final ui.RRect inner; - final SurfacePaintData paint; - ui.Path? path; PaintDrawDRRect(this.outer, this.inner, this.paint) { path = ui.Path() ..fillType = ui.PathFillType.evenOdd @@ -1093,6 +1066,11 @@ class PaintDrawDRRect extends DrawCommand { ..close(); } + final ui.RRect outer; + final ui.RRect inner; + final SurfacePaintData paint; + ui.Path? path; + @override void apply(EngineCanvas canvas) { paint.style ??= ui.PaintingStyle.fill; @@ -1110,11 +1088,11 @@ class PaintDrawDRRect extends DrawCommand { } class PaintDrawOval extends DrawCommand { + PaintDrawOval(this.rect, this.paint); + final ui.Rect rect; final SurfacePaintData paint; - PaintDrawOval(this.rect, this.paint); - @override void apply(EngineCanvas canvas) { canvas.drawOval(rect, paint); @@ -1131,12 +1109,12 @@ class PaintDrawOval extends DrawCommand { } class PaintDrawCircle extends DrawCommand { + PaintDrawCircle(this.c, this.radius, this.paint); + final ui.Offset c; final double radius; final SurfacePaintData paint; - PaintDrawCircle(this.c, this.radius, this.paint); - @override void apply(EngineCanvas canvas) { canvas.drawCircle(c, radius, paint); @@ -1153,11 +1131,11 @@ class PaintDrawCircle extends DrawCommand { } class PaintDrawPath extends DrawCommand { + PaintDrawPath(this.path, this.paint); + final SurfacePath path; final SurfacePaintData paint; - PaintDrawPath(this.path, this.paint); - @override void apply(EngineCanvas canvas) { canvas.drawPath(path, paint); @@ -1198,12 +1176,12 @@ class PaintDrawShadow extends DrawCommand { } class PaintDrawImage extends DrawCommand { + PaintDrawImage(this.image, this.offset, this.paint); + final ui.Image image; final ui.Offset offset; final SurfacePaintData paint; - PaintDrawImage(this.image, this.offset, this.paint); - @override void apply(EngineCanvas canvas) { canvas.drawImage(image, offset, paint); @@ -1220,13 +1198,13 @@ class PaintDrawImage extends DrawCommand { } class PaintDrawImageRect extends DrawCommand { + PaintDrawImageRect(this.image, this.src, this.dst, this.paint); + final ui.Image image; final ui.Rect src; final ui.Rect dst; final SurfacePaintData paint; - PaintDrawImageRect(this.image, this.src, this.dst, this.paint); - @override void apply(EngineCanvas canvas) { canvas.drawImageRect(image, src, dst, paint); @@ -1243,11 +1221,11 @@ class PaintDrawImageRect extends DrawCommand { } class PaintDrawParagraph extends DrawCommand { + PaintDrawParagraph(this.paragraph, this.offset); + final CanvasParagraph paragraph; final ui.Offset offset; - PaintDrawParagraph(this.paragraph, this.offset); - @override void apply(EngineCanvas canvas) { canvas.drawParagraph(paragraph, offset); @@ -1264,6 +1242,8 @@ class PaintDrawParagraph extends DrawCommand { } class Subpath { + Subpath(this.startX, this.startY) : commands = []; + double startX = 0.0; double startY = 0.0; double currentX = 0.0; @@ -1271,8 +1251,6 @@ class Subpath { final List commands; - Subpath(this.startX, this.startY) : commands = []; - Subpath shift(ui.Offset offset) { final Subpath result = Subpath(startX + offset.dx, startY + offset.dy) ..currentX = currentX + offset.dx @@ -1310,11 +1288,11 @@ abstract class PathCommand { } class MoveTo extends PathCommand { + const MoveTo(this.x, this.y); + final double x; final double y; - const MoveTo(this.x, this.y); - @override MoveTo shifted(ui.Offset offset) { return MoveTo(x + offset.dx, y + offset.dy); @@ -1337,11 +1315,11 @@ class MoveTo extends PathCommand { } class LineTo extends PathCommand { + const LineTo(this.x, this.y); + final double x; final double y; - const LineTo(this.x, this.y); - @override LineTo shifted(ui.Offset offset) { return LineTo(x + offset.dx, y + offset.dy); @@ -1364,6 +1342,9 @@ class LineTo extends PathCommand { } class Ellipse extends PathCommand { + const Ellipse(this.x, this.y, this.radiusX, this.radiusY, this.rotation, + this.startAngle, this.endAngle, this.anticlockwise); + final double x; final double y; final double radiusX; @@ -1373,9 +1354,6 @@ class Ellipse extends PathCommand { final double endAngle; final bool anticlockwise; - const Ellipse(this.x, this.y, this.radiusX, this.radiusY, this.rotation, - this.startAngle, this.endAngle, this.anticlockwise); - @override Ellipse shifted(ui.Offset offset) { return Ellipse(x + offset.dx, y + offset.dy, radiusX, radiusY, rotation, @@ -1496,13 +1474,13 @@ class Ellipse extends PathCommand { } class QuadraticCurveTo extends PathCommand { + const QuadraticCurveTo(this.x1, this.y1, this.x2, this.y2); + final double x1; final double y1; final double x2; final double y2; - const QuadraticCurveTo(this.x1, this.y1, this.x2, this.y2); - @override QuadraticCurveTo shifted(ui.Offset offset) { return QuadraticCurveTo( @@ -1536,6 +1514,8 @@ class QuadraticCurveTo extends PathCommand { } class BezierCurveTo extends PathCommand { + const BezierCurveTo(this.x1, this.y1, this.x2, this.y2, this.x3, this.y3); + final double x1; final double y1; final double x2; @@ -1543,8 +1523,6 @@ class BezierCurveTo extends PathCommand { final double x3; final double y3; - const BezierCurveTo(this.x1, this.y1, this.x2, this.y2, this.x3, this.y3); - @override BezierCurveTo shifted(ui.Offset offset) { return BezierCurveTo(x1 + offset.dx, y1 + offset.dy, x2 + offset.dx, @@ -1580,13 +1558,13 @@ class BezierCurveTo extends PathCommand { } class RectCommand extends PathCommand { + const RectCommand(this.x, this.y, this.width, this.height); + final double x; final double y; final double width; final double height; - const RectCommand(this.x, this.y, this.width, this.height); - @override RectCommand shifted(ui.Offset offset) { return RectCommand(x + offset.dx, y + offset.dy, width, height); @@ -1637,10 +1615,10 @@ class RectCommand extends PathCommand { } class RRectCommand extends PathCommand { - final ui.RRect rrect; - const RRectCommand(this.rrect); + final ui.RRect rrect; + @override RRectCommand shifted(ui.Offset offset) { return RRectCommand(rrect.shift(offset)); @@ -1685,6 +1663,8 @@ class CloseCommand extends PathCommand { } class _PaintBounds { + _PaintBounds(this.maxPaintBounds); + // Bounds of maximum area that is paintable by canvas ops. final ui.Rect maxPaintBounds; @@ -1709,8 +1689,6 @@ class _PaintBounds { _currentClipRight = 0.0, _currentClipBottom = 0.0; - _PaintBounds(this.maxPaintBounds); - void translate(double dx, double dy) { if (dx != 0.0 || dy != 0.0) { _currentMatrixIsIdentity = false; @@ -2045,6 +2023,8 @@ double _getPaintSpread(SurfacePaint paint) { /// Contains metrics collected by recording canvas to provide data for /// rendering heuristics (canvas use vs DOM). class RenderStrategy { + RenderStrategy(); + /// Whether paint commands contain image elements. bool hasImageElements = false; @@ -2064,8 +2044,6 @@ class RenderStrategy { /// bitmap canvas and instead render using dom primitives and svg only. bool isInsideSvgFilterTree = false; - RenderStrategy(); - /// Merges render strategy settings from a child recording. void merge(RenderStrategy childStrategy) { hasImageElements |= childStrategy.hasImageElements; diff --git a/lib/web_ui/lib/src/engine/html/render_vertices.dart b/lib/web_ui/lib/src/engine/html/render_vertices.dart index 2291da63562e3..4c8305b2660bc 100644 --- a/lib/web_ui/lib/src/engine/html/render_vertices.dart +++ b/lib/web_ui/lib/src/engine/html/render_vertices.dart @@ -21,24 +21,16 @@ import 'shaders/vertex_shaders.dart'; GlRenderer? glRenderer; class SurfaceVertices implements ui.Vertices { - final ui.VertexMode mode; - final Float32List positions; - final Int32List? colors; - final Uint16List? indices; // ignore: unused_field - SurfaceVertices( this.mode, List positions, { List? colors, List? indices, - }) : assert(mode != null), // ignore: unnecessary_null_comparison - assert(positions != null), // ignore: unnecessary_null_comparison - // ignore: unnecessary_this - this.colors = colors != null ? _int32ListFromColors(colors) : null, - // ignore: unnecessary_this - this.indices = indices != null ? Uint16List.fromList(indices) : null, - // ignore: unnecessary_this - this.positions = offsetListToFloat32List(positions) { + }) : assert(mode != null), + assert(positions != null), + colors = colors != null ? _int32ListFromColors(colors) : null, + indices = indices != null ? Uint16List.fromList(indices) : null, + positions = offsetListToFloat32List(positions) { initWebGl(); } @@ -47,11 +39,16 @@ class SurfaceVertices implements ui.Vertices { this.positions, { this.colors, this.indices, - }) : assert(mode != null), // ignore: unnecessary_null_comparison - assert(positions != null) { // ignore: unnecessary_null_comparison + }) : assert(mode != null), + assert(positions != null) { initWebGl(); } + final ui.VertexMode mode; + final Float32List positions; + final Int32List? colors; + final Uint16List? indices; + static Int32List _int32ListFromColors(List colors) { final Int32List list = Int32List(colors.length); final int len = colors.length; @@ -180,7 +177,7 @@ class _WebGlRenderer implements GlRenderer { // // Create buffer for vertex coordinates. final Object positionsBuffer = gl.createBuffer()!; - assert(positionsBuffer != null); // ignore: unnecessary_null_comparison + assert(positionsBuffer != null); Object? vao; if (imageShader != null) { @@ -372,7 +369,7 @@ class _WebGlRenderer implements GlRenderer { // Setup geometry. final Object positionsBuffer = gl.createBuffer()!; - assert(positionsBuffer != null); // ignore: unnecessary_null_comparison + assert(positionsBuffer != null); gl.bindArrayBuffer(positionsBuffer); gl.bufferData(vertices, gl.kStaticDraw); // Point an attribute to the currently bound vertex buffer object. @@ -447,7 +444,7 @@ class _WebGlRenderer implements GlRenderer { @override void drawHairline( DomCanvasRenderingContext2D? ctx, Float32List positions) { - assert(positions != null); // ignore: unnecessary_null_comparison + assert(positions != null); final int pointCount = positions.length ~/ 2; ctx!.lineWidth = 1.0; ctx.beginPath(); diff --git a/lib/web_ui/lib/src/engine/html/renderer.dart b/lib/web_ui/lib/src/engine/html/renderer.dart new file mode 100644 index 0000000000000..70b69908c26a2 --- /dev/null +++ b/lib/web_ui/lib/src/engine/html/renderer.dart @@ -0,0 +1,336 @@ +// 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:math' as math; +import 'dart:typed_data'; + +import 'package:ui/src/engine.dart'; +import 'package:ui/ui.dart' as ui; + +class HtmlRenderer implements Renderer { + static HtmlRenderer get instance => _instance; + static late HtmlRenderer _instance; + + @override + String get rendererTag => 'html'; + + late final HtmlFontCollection _fontCollection = HtmlFontCollection(); + + late FlutterViewEmbedder _viewEmbedder; + + @override + HtmlFontCollection get fontCollection => _fontCollection; + + @override + void initialize() { + scheduleMicrotask(() { + // Access [lineLookup] to force the lazy unpacking of line break data + // now. Removing this line won't break anything. It's just an optimization + // to make the unpacking happen while we are waiting for network requests. + lineLookup; + }); + + _instance = this; + } + + @override + void reset(FlutterViewEmbedder embedder) { + _viewEmbedder = embedder; + } + + @override + ui.Paint createPaint() => SurfacePaint(); + + @override + ui.Vertices createVertices( + ui.VertexMode mode, + List positions, { + List? textureCoordinates, + List? colors, + List? indices, + }) => SurfaceVertices( + mode, + positions, + colors: colors, + indices: indices); + + @override + ui.Vertices createVerticesRaw( + ui.VertexMode mode, + Float32List positions, { + Float32List? textureCoordinates, + Int32List? colors, + Uint16List? indices, + }) => SurfaceVertices.raw( + mode, + positions, + colors: colors, + indices: indices); + + @override + ui.Canvas createCanvas(ui.PictureRecorder recorder, [ui.Rect? cullRect]) => + SurfaceCanvas(recorder as EnginePictureRecorder, cullRect); + + @override + ui.Gradient createLinearGradient( + ui.Offset from, + ui.Offset to, + List colors, [ + List? colorStops, + ui.TileMode tileMode = ui.TileMode.clamp, + Float32List? matrix4 + ]) => GradientLinear(from, to, colors, colorStops, tileMode, matrix4); + + @override + ui.Gradient createRadialGradient( + ui.Offset center, + double radius, + List colors, [ + List? colorStops, + ui.TileMode tileMode = ui.TileMode.clamp, + Float32List? matrix4, + ui.Offset? focal, + double focalRadius = 0.0, + ]) => GradientRadial(center, radius, colors, colorStops, tileMode, matrix4); + + @override + ui.Gradient createConicalGradient( + ui.Offset focal, + double focalRadius, + ui.Offset center, + double radius, + List colors, + [List? colorStops, + ui.TileMode tileMode = ui.TileMode.clamp, + Float32List? matrix + ]) => GradientConical( + focal, + focalRadius, + center, + radius, + colors, + colorStops, + tileMode, + matrix); + + @override + ui.Gradient createSweepGradient( + ui.Offset center, + List colors, [ + List? colorStops, + ui.TileMode tileMode = ui.TileMode.clamp, + double startAngle = 0.0, + double endAngle = math.pi * 2, + Float32List? matrix4 + ]) => GradientSweep(center, colors, colorStops, tileMode, startAngle, endAngle, matrix4); + + @override + ui.PictureRecorder createPictureRecorder() => EnginePictureRecorder(); + + @override + ui.SceneBuilder createSceneBuilder() => SurfaceSceneBuilder(); + + // TODO(ferhat): implement TileMode. + @override + ui.ImageFilter createBlurImageFilter({ + double sigmaX = 0.0, + double sigmaY = 0.0, + ui.TileMode tileMode = ui.TileMode.clamp + }) => EngineImageFilter.blur(sigmaX: sigmaX, sigmaY: sigmaY, tileMode: tileMode); + + @override + ui.ImageFilter createDilateImageFilter({double radiusX = 0.0, double radiusY = 0.0}) { + // TODO(fzyzcjy): implement dilate. https://github.com/flutter/flutter/issues/101085 + throw UnimplementedError('ImageFilter.dilate not implemented for HTML renderer.'); + } + + @override + ui.ImageFilter createErodeImageFilter({double radiusX = 0.0, double radiusY = 0.0}) { + // TODO(fzyzcjy): implement erode. https://github.com/flutter/flutter/issues/101085 + throw UnimplementedError('ImageFilter.erode not implemented for HTML renderer.'); + } + + @override + ui.ImageFilter createMatrixImageFilter( + Float64List matrix4, { + ui.FilterQuality filterQuality = ui.FilterQuality.low + }) => EngineImageFilter.matrix(matrix: matrix4, filterQuality: filterQuality); + + @override + ui.ImageFilter composeImageFilters({required ui.ImageFilter outer, required ui.ImageFilter inner}) { + // TODO(ferhat): add implementation and remove the "ignore". + // ignore: avoid_unused_constructor_parameters + throw UnimplementedError('ImageFilter.erode not implemented for HTML renderer.'); + } + + @override + Future instantiateImageCodec( + Uint8List list, { + int? targetWidth, + int? targetHeight, + bool allowUpscaling = true}) async { + final DomBlob blob = createDomBlob([list.buffer]); + return HtmlBlobCodec(blob); + } + + @override + Future instantiateImageCodecFromUrl( + Uri uri, { + WebOnlyImageCodecChunkCallback? chunkCallback}) { + return futurize((Callback callback) { + callback(HtmlCodec(uri.toString(), chunkCallback: chunkCallback)); + return null; + }); + } + + @override + void decodeImageFromPixels( + Uint8List pixels, + int width, + int height, + ui.PixelFormat format, + ui.ImageDecoderCallback callback, { + int? rowBytes, + int? targetWidth, + int? targetHeight, + bool allowUpscaling = true + }) { + void executeCallback(ui.Codec codec) { + codec.getNextFrame().then((ui.FrameInfo frameInfo) { + callback(frameInfo.image); + }); + } + ui.createBmp(pixels, width, height, rowBytes ?? width, format).then( + executeCallback); + } + + @override + ui.ImageShader createImageShader( + ui.Image image, + ui.TileMode tmx, + ui.TileMode tmy, + Float64List matrix4, + ui.FilterQuality? filterQuality + ) => EngineImageShader(image, tmx, tmy, matrix4, filterQuality); + + @override + ui.Path createPath() => SurfacePath(); + + @override + ui.Path copyPath(ui.Path src) => SurfacePath.from(src as SurfacePath); + + @override + ui.Path combinePaths(ui.PathOperation op, ui.Path path1, ui.Path path2) { + throw UnimplementedError('combinePaths not implemented in HTML renderer.'); + } + + @override + ui.TextStyle createTextStyle({ + 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.TextLeadingDistribution? leadingDistribution, + ui.Locale? locale, + ui.Paint? background, + ui.Paint? foreground, + List? shadows, + List? fontFeatures, + List? fontVariations + }) => EngineTextStyle( + color: color, + decoration: decoration, + decorationColor: decorationColor, + decorationStyle: decorationStyle, + decorationThickness: decorationThickness, + fontWeight: fontWeight, + fontStyle: fontStyle, + textBaseline: textBaseline, + fontFamily: fontFamily, + fontFamilyFallback: fontFamilyFallback, + fontSize: fontSize, + letterSpacing: letterSpacing, + wordSpacing: wordSpacing, + height: height, + locale: locale, + background: background, + foreground: foreground, + shadows: shadows, + fontFeatures: fontFeatures, + fontVariations: fontVariations, + ); + + @override + ui.ParagraphStyle createParagraphStyle({ + ui.TextAlign? textAlign, + ui.TextDirection? textDirection, + int? maxLines, + String? fontFamily, + double? fontSize, + double? height, + ui.TextHeightBehavior? textHeightBehavior, + ui.FontWeight? fontWeight, + ui.FontStyle? fontStyle, + ui.StrutStyle? strutStyle, + String? ellipsis, + ui.Locale? locale + }) => EngineParagraphStyle( + textAlign: textAlign, + textDirection: textDirection, + maxLines: maxLines, + fontFamily: fontFamily, + fontSize: fontSize, + height: height, + textHeightBehavior: textHeightBehavior, + fontWeight: fontWeight, + fontStyle: fontStyle, + strutStyle: strutStyle, + ellipsis: ellipsis, + locale: locale, + ); + + @override + ui.StrutStyle createStrutStyle({ + String? fontFamily, + List? fontFamilyFallback, + double? fontSize, + double? height, + ui.TextLeadingDistribution? leadingDistribution, + double? leading, + ui.FontWeight? fontWeight, + ui.FontStyle? fontStyle, + bool? forceStrutHeight + }) => EngineStrutStyle( + fontFamily: fontFamily, + fontFamilyFallback: fontFamilyFallback, + fontSize: fontSize, + height: height, + leadingDistribution: leadingDistribution, + leading: leading, + fontWeight: fontWeight, + fontStyle: fontStyle, + forceStrutHeight: forceStrutHeight, + ); + + @override + ui.ParagraphBuilder createParagraphBuilder(ui.ParagraphStyle style) => + CanvasParagraphBuilder(style as EngineParagraphStyle); + + @override + void renderScene(ui.Scene scene) { + _viewEmbedder.addSceneToSceneHost((scene as SurfaceScene).webOnlyRootElement); + frameTimingsOnRasterFinish(); + } +} diff --git a/lib/web_ui/lib/src/engine/html/scene_builder.dart b/lib/web_ui/lib/src/engine/html/scene_builder.dart index d7fa62552fd4f..22ad25f5a9f89 100644 --- a/lib/web_ui/lib/src/engine/html/scene_builder.dart +++ b/lib/web_ui/lib/src/engine/html/scene_builder.dart @@ -137,7 +137,7 @@ class SurfaceSceneBuilder implements ui.SceneBuilder { ui.Clip clipBehavior = ui.Clip.antiAlias, ui.ClipRectEngineLayer? oldLayer, }) { - assert(clipBehavior != null); // ignore: unnecessary_null_comparison + assert(clipBehavior != null); return _pushSurface( PersistedClipRect(oldLayer as PersistedClipRect?, rect, clipBehavior)); } @@ -168,7 +168,7 @@ class SurfaceSceneBuilder implements ui.SceneBuilder { ui.Clip clipBehavior = ui.Clip.antiAlias, ui.ClipPathEngineLayer? oldLayer, }) { - assert(clipBehavior != null); // ignore: unnecessary_null_comparison + assert(clipBehavior != null); return _pushSurface( PersistedClipPath(oldLayer as PersistedClipPath?, path, clipBehavior)); } @@ -206,7 +206,7 @@ class SurfaceSceneBuilder implements ui.SceneBuilder { ui.ColorFilter filter, { ui.ColorFilterEngineLayer? oldLayer, }) { - assert(filter != null); // ignore: unnecessary_null_comparison + assert(filter != null); return _pushSurface( PersistedColorFilter(oldLayer as PersistedColorFilter?, filter)); } @@ -226,7 +226,7 @@ class SurfaceSceneBuilder implements ui.SceneBuilder { ui.ImageFilter filter, { ui.ImageFilterEngineLayer? oldLayer, }) { - assert(filter != null); // ignore: unnecessary_null_comparison + assert(filter != null); return _pushSurface( PersistedImageFilter(oldLayer as PersistedImageFilter?, filter)); } @@ -264,7 +264,7 @@ class SurfaceSceneBuilder implements ui.SceneBuilder { ui.ShaderMaskEngineLayer? oldLayer, ui.FilterQuality filterQuality = ui.FilterQuality.low, }) { - assert(blendMode != null); // ignore: unnecessary_null_comparison + assert(blendMode != null); return _pushSurface(PersistedShaderMask( oldLayer as PersistedShaderMask?, shader, maskRect, blendMode, filterQuality)); diff --git a/lib/web_ui/lib/src/engine/html/shaders/image_shader.dart b/lib/web_ui/lib/src/engine/html/shaders/image_shader.dart index b8c900739c8be..60cca92e3db03 100644 --- a/lib/web_ui/lib/src/engine/html/shaders/image_shader.dart +++ b/lib/web_ui/lib/src/engine/html/shaders/image_shader.dart @@ -17,8 +17,8 @@ import 'vertex_shaders.dart'; class EngineImageShader implements ui.ImageShader { EngineImageShader(ui.Image image, this.tileModeX, this.tileModeY, Float64List matrix4, this.filterQuality) - : this.image = image as HtmlImage, // ignore: unnecessary_this - this.matrix4 = Float32List.fromList(matrix4); // ignore: unnecessary_this + : image = image as HtmlImage, + matrix4 = Float32List.fromList(matrix4); final ui.TileMode tileModeX; final ui.TileMode tileModeY; @@ -187,7 +187,7 @@ class EngineImageShader implements ui.ImageShader { /// /// Create buffer for vertex coordinates. final Object positionsBuffer = gl.createBuffer()!; - assert(positionsBuffer != null); // ignore: unnecessary_null_comparison + assert(positionsBuffer != null); Object? vao; if (isWebGl2) { @@ -265,4 +265,25 @@ class EngineImageShader implements ui.ImageShader { gl.bindElementArrayBuffer(null); return context!.createPattern(bitmapImage!, 'no-repeat')!; } + + bool _disposed = false; + + @override + bool get debugDisposed { + late bool disposed; + assert(() { + disposed = _disposed; + return true; + }()); + return disposed; + } + + @override + void dispose() { + assert(() { + _disposed = true; + return true; + }()); + image.dispose(); + } } diff --git a/lib/web_ui/lib/src/engine/html/shaders/normalized_gradient.dart b/lib/web_ui/lib/src/engine/html/shaders/normalized_gradient.dart index 138f207d2d058..e3c9261fa21dc 100644 --- a/lib/web_ui/lib/src/engine/html/shaders/normalized_gradient.dart +++ b/lib/web_ui/lib/src/engine/html/shaders/normalized_gradient.dart @@ -22,12 +22,6 @@ import 'shader_builder.dart'; /// scale = (c2 - c1) / (t2 - t1) /// bias = c1 - t1 / (t2 - t1) * (c2 - c1) class NormalizedGradient { - final Float32List _thresholds; - final Float32List _bias; - final Float32List _scale; - final int thresholdCount; - final bool isOpaque; - factory NormalizedGradient(List colors, {List? stops}) { // If colorStops is not provided, then only two stops, at 0.0 and 1.0, // are implied (and colors must therefore only have two entries). @@ -102,6 +96,12 @@ class NormalizedGradient { NormalizedGradient._( this.thresholdCount, this._thresholds, this._scale, this._bias, this.isOpaque); + final Float32List _thresholds; + final Float32List _bias; + final Float32List _scale; + final int thresholdCount; + final bool isOpaque; + /// Sets uniforms for threshold, bias and scale for program. void setupUniforms(GlContext gl, GlProgram glProgram) { for (int i = 0; i < thresholdCount; i++) { diff --git a/lib/web_ui/lib/src/engine/html/shaders/shader.dart b/lib/web_ui/lib/src/engine/html/shaders/shader.dart index d77ab513175e3..a12da953df576 100644 --- a/lib/web_ui/lib/src/engine/html/shaders/shader.dart +++ b/lib/web_ui/lib/src/engine/html/shaders/shader.dart @@ -60,10 +60,10 @@ class GradientSweep extends EngineGradient { GradientSweep(this.center, this.colors, this.colorStops, this.tileMode, this.startAngle, this.endAngle, this.matrix4) : assert(offsetIsValid(center)), - assert(colors != null), // ignore: unnecessary_null_comparison - assert(tileMode != null), // ignore: unnecessary_null_comparison - assert(startAngle != null), // ignore: unnecessary_null_comparison - assert(endAngle != null), // ignore: unnecessary_null_comparison + assert(colors != null), + assert(tileMode != null), + assert(startAngle != null), + assert(endAngle != null), assert(startAngle < endAngle), super._() { validateColorStops(colors, colorStops); @@ -180,8 +180,8 @@ class GradientLinear extends EngineGradient { Float32List? matrix, ) : assert(offsetIsValid(from)), assert(offsetIsValid(to)), - assert(colors != null), // ignore: unnecessary_null_comparison - assert(tileMode != null), // ignore: unnecessary_null_comparison + assert(colors != null), + assert(tileMode != null), matrix4 = matrix == null ? null : FastMatrix32(matrix), super._() { if (assertionsEnabled) { diff --git a/lib/web_ui/lib/src/engine/html/shaders/shader_builder.dart b/lib/web_ui/lib/src/engine/html/shaders/shader_builder.dart index aa30126692699..a1e49359a4f5a 100644 --- a/lib/web_ui/lib/src/engine/html/shaders/shader_builder.dart +++ b/lib/web_ui/lib/src/engine/html/shaders/shader_builder.dart @@ -28,6 +28,13 @@ import '../../util.dart'; /// method.addStatement('${u1.name} = vec4(1.0, 1.0, 1.0, 0.0);'); /// source = builder.build(); class ShaderBuilder { + ShaderBuilder(this.version) : isWebGl2 = version == WebGLVersion.webgl2, + _isFragmentShader = false; + + ShaderBuilder.fragment(this.version) : + isWebGl2 = version == WebGLVersion.webgl2, + _isFragmentShader = true; + /// WebGL version. final int version; final List declarations = []; @@ -59,13 +66,6 @@ class ShaderBuilder { /// Lazily allocated fragment color output. ShaderDeclaration? _fragmentColorDeclaration; - ShaderBuilder(this.version) : isWebGl2 = version == WebGLVersion.webgl2, - _isFragmentShader = false; - - ShaderBuilder.fragment(this.version) : - isWebGl2 = version == WebGLVersion.webgl2, - _isFragmentShader = true; - /// Returns fragment color declaration for fragment shader. /// /// This is hard coded for webgl1 as gl_FragColor. @@ -345,10 +345,6 @@ abstract class ShaderStorageQualifier { /// Shader variable and constant declaration. class ShaderDeclaration { - final String name; - final int dataType; - final int storage; - final String constValue; ShaderDeclaration(this.name, this.dataType, this.storage) : assert(!_isGLSLReservedWord(name)), constValue = ''; @@ -356,6 +352,11 @@ class ShaderDeclaration { /// Constructs a constant. ShaderDeclaration.constant(this.name, this.dataType, this.constValue) : storage = ShaderStorageQualifier.kConst; + + final String name; + final int dataType; + final int storage; + final String constValue; } // These are used only in debug mode to assert if used as variable name. diff --git a/lib/web_ui/lib/src/engine/html/surface.dart b/lib/web_ui/lib/src/engine/html/surface.dart index 21b2a2378e211..15cc063c14597 100644 --- a/lib/web_ui/lib/src/engine/html/surface.dart +++ b/lib/web_ui/lib/src/engine/html/surface.dart @@ -223,7 +223,7 @@ abstract class PersistedSurface implements ui.EngineLayer { /// surface. PersistedSurfaceState get state => _state; set state(PersistedSurfaceState newState) { - assert(newState != null); // ignore: unnecessary_null_comparison + assert(newState != null); assert(newState != _state, 'Attempted to set state that the surface is already in. This likely indicates a bug in the compositor.'); assert(_debugValidateStateTransition(newState)); @@ -414,7 +414,7 @@ abstract class PersistedSurface implements ui.EngineLayer { /// creates a new element by calling [build]. @mustCallSuper void update(covariant PersistedSurface oldSurface) { - assert(oldSurface != null); // ignore: unnecessary_null_comparison + assert(oldSurface != null); assert(!identical(oldSurface, this)); assert(debugAssertSurfaceState(this, PersistedSurfaceState.created)); assert(debugAssertSurfaceState(oldSurface, PersistedSurfaceState.active, @@ -1049,7 +1049,7 @@ abstract class PersistedContainerSurface extends PersistedSurface { final PersistedSurface child = _children[i]; final DomHTMLElement childElement = child.rootElement! as DomHTMLElement; - assert(childElement != null); // ignore: unnecessary_null_comparison + assert(childElement != null); if (!isStationary) { if (refNode == null) { containerElement!.append(childElement); diff --git a/lib/web_ui/lib/src/engine/html/surface_stats.dart b/lib/web_ui/lib/src/engine/html/surface_stats.dart index 06f7f321d0bfb..339bf85ec2d05 100644 --- a/lib/web_ui/lib/src/engine/html/surface_stats.dart +++ b/lib/web_ui/lib/src/engine/html/surface_stats.dart @@ -237,7 +237,7 @@ void debugPrintSurfaceStats(PersistedScene scene, int frameNumber) { void countReusesRecursively(PersistedSurface surface) { final DebugSurfaceStats stats = surfaceStatsFor(surface); - assert(stats != null); // ignore: unnecessary_null_comparison + assert(stats != null); surfaceRetainCount += stats.retainSurfaceCount; elementReuseCount += stats.reuseElementCount; 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 b3b20a9eda102..0011196fafac5 100644 --- a/lib/web_ui/lib/src/engine/html_image_codec.dart +++ b/lib/web_ui/lib/src/engine/html_image_codec.dart @@ -25,11 +25,11 @@ typedef WebOnlyImageCodecChunkCallback = void Function( int cumulativeBytesLoaded, int expectedTotalBytes); class HtmlCodec implements ui.Codec { + HtmlCodec(this.src, {this.chunkCallback}); + final String src; final WebOnlyImageCodecChunkCallback? chunkCallback; - HtmlCodec(this.src, {this.chunkCallback}); - @override int get frameCount => 1; @@ -119,10 +119,10 @@ class HtmlCodec implements ui.Codec { } class HtmlBlobCodec extends HtmlCodec { - final DomBlob blob; - HtmlBlobCodec(this.blob) : super(domWindow.URL.createObjectURL(blob)); + final DomBlob blob; + @override void dispose() { domWindow.URL.revokeObjectURL(src); @@ -140,9 +140,10 @@ class SingleFrameInfo implements ui.FrameInfo { } class HtmlImage implements ui.Image { + HtmlImage(this.imgElement, this.width, this.height); + final DomHTMLImageElement imgElement; bool _requiresClone = false; - HtmlImage(this.imgElement, this.width, this.height); bool _disposed = false; @override diff --git a/lib/web_ui/lib/src/engine/initialization.dart b/lib/web_ui/lib/src/engine/initialization.dart index eb20b9626f5b3..025b1399f78b1 100644 --- a/lib/web_ui/lib/src/engine/initialization.dart +++ b/lib/web_ui/lib/src/engine/initialization.dart @@ -7,7 +7,6 @@ import 'dart:developer' as developer; import 'package:ui/src/engine/assets.dart'; import 'package:ui/src/engine/browser_detection.dart'; -import 'package:ui/src/engine/canvaskit/initialization.dart'; import 'package:ui/src/engine/embedder.dart'; import 'package:ui/src/engine/keyboard.dart'; import 'package:ui/src/engine/mouse_cursor.dart'; @@ -15,9 +14,8 @@ import 'package:ui/src/engine/navigation.dart'; import 'package:ui/src/engine/platform_dispatcher.dart'; import 'package:ui/src/engine/platform_views/content_manager.dart'; import 'package:ui/src/engine/profiler.dart'; +import 'package:ui/src/engine/renderer.dart'; import 'package:ui/src/engine/safe_browser_api.dart'; -import 'package:ui/src/engine/text/font_collection.dart'; -import 'package:ui/src/engine/text/line_break_properties.dart'; import 'package:ui/src/engine/window.dart'; import 'package:ui/ui.dart' as ui; @@ -141,15 +139,6 @@ Future initializeEngineServices({ } _initializationState = DebugEngineInitializationState.initializingServices; - if (!useCanvasKit) { - scheduleMicrotask(() { - // Access [lineLookup] to force the lazy unpacking of line break data - // now. Removing this line won't break anything. It's just an optimization - // to make the unpacking happen while we are waiting for network requests. - lineLookup; - }); - } - // Setup the hook that allows users to customize URL strategy before running // the app. _addUrlStrategyListener(); @@ -215,19 +204,11 @@ Future initializeEngineServices({ } }; - // This needs to be after `initializeEngine` because that is where the - // canvaskit script is added to the page. - if (useCanvasKit) { - await initializeCanvasKit(); - } + await renderer.initialize(); assetManager ??= const AssetManager(); await _setAssetManager(assetManager); - if (useCanvasKit) { - await skiaFontCollection.ensureFontsLoaded(); - } else { - await _fontCollection!.ensureFontsLoaded(); - } + await renderer.fontCollection.ensureFontsLoaded(); _initializationState = DebugEngineInitializationState.initializedServices; } @@ -262,11 +243,7 @@ Future initializeEngineUi() async { AssetManager get assetManager => _assetManager!; AssetManager? _assetManager; -FontCollection get fontCollection => _fontCollection!; -FontCollection? _fontCollection; - Future _setAssetManager(AssetManager assetManager) async { - // ignore: unnecessary_null_comparison assert(assetManager != null, 'Cannot set assetManager to null'); if (assetManager == _assetManager) { return; @@ -274,27 +251,14 @@ Future _setAssetManager(AssetManager assetManager) async { _assetManager = assetManager; - if (useCanvasKit) { - ensureSkiaFontCollectionInitialized(); - } else { - _fontCollection ??= FontCollection(); - _fontCollection!.clear(); - } + renderer.fontCollection.clear(); if (_assetManager != null) { - if (useCanvasKit) { - await skiaFontCollection.registerFonts(_assetManager!); - } else { - await _fontCollection!.registerFonts(_assetManager!); - } + await renderer.fontCollection.registerFonts(assetManager); } if (ui.debugEmulateFlutterTesterEnvironment) { - if (useCanvasKit) { - skiaFontCollection.debugRegisterTestFonts(); - } else { - _fontCollection!.debugRegisterTestFonts(); - } + renderer.fontCollection.debugRegisterTestFonts(); } } @@ -308,6 +272,19 @@ void _addUrlStrategyListener() { }); } +/// Whether to disable the font fallback system. +/// +/// We need to disable font fallbacks for some framework tests because +/// Flutter error messages may contain an arrow symbol which is not +/// covered by ASCII fonts. This causes us to try to download the +/// Noto Sans Symbols font, which kicks off a `Timer` which doesn't +/// complete before the Widget tree is disposed (this is by design). +bool get debugDisableFontFallbacks => _debugDisableFontFallbacks; +set debugDisableFontFallbacks(bool value) { + _debugDisableFontFallbacks = value; +} +bool _debugDisableFontFallbacks = false; + /// The shared instance of PlatformViewManager shared across the engine to handle /// rendering of PlatformViews into the web app. // TODO(dit): How to make this overridable from tests? diff --git a/lib/web_ui/lib/src/engine/keyboard.dart b/lib/web_ui/lib/src/engine/keyboard.dart index 8e14ca66f2250..440f249850e83 100644 --- a/lib/web_ui/lib/src/engine/keyboard.dart +++ b/lib/web_ui/lib/src/engine/keyboard.dart @@ -13,6 +13,21 @@ import 'services.dart'; /// Provides keyboard bindings, such as the `flutter/keyevent` channel. class Keyboard { + Keyboard._(this._onMacOs) { + _keydownListener = allowInterop((DomEvent event) { + _handleHtmlEvent(event); + }); + domWindow.addEventListener('keydown', _keydownListener); + + _keyupListener = allowInterop((DomEvent event) { + _handleHtmlEvent(event); + }); + domWindow.addEventListener('keyup', _keyupListener); + registerHotRestartListener(() { + dispose(); + }); + } + /// Initializes the [Keyboard] singleton. /// /// Use the [instance] getter to get the singleton after calling this method. @@ -33,21 +48,6 @@ class Keyboard { DomEventListener? _keydownListener; DomEventListener? _keyupListener; - Keyboard._(this._onMacOs) { - _keydownListener = allowInterop((DomEvent event) { - _handleHtmlEvent(event); - }); - domWindow.addEventListener('keydown', _keydownListener); - - _keyupListener = allowInterop((DomEvent event) { - _handleHtmlEvent(event); - }); - domWindow.addEventListener('keyup', _keyupListener); - registerHotRestartListener(() { - dispose(); - }); - } - /// Uninitializes the [Keyboard] singleton. /// /// After calling this method this object becomes unusable and [instance] @@ -73,7 +73,7 @@ class Keyboard { /// Initializing with `0x0` which means no meta keys are pressed. int _lastMetaState = 0x0; - bool _onMacOs; + final bool _onMacOs; // When the user enters a browser/system shortcut (e.g. `cmd+alt+i`) on macOS, // the browser doesn't send a keyup for it. This puts the framework in a diff --git a/lib/web_ui/lib/src/engine/keyboard_binding.dart b/lib/web_ui/lib/src/engine/keyboard_binding.dart index f72dfd55b9b9e..9a500efea9311 100644 --- a/lib/web_ui/lib/src/engine/keyboard_binding.dart +++ b/lib/web_ui/lib/src/engine/keyboard_binding.dart @@ -88,6 +88,10 @@ Duration _eventTimeStampToDuration(num milliseconds) { } class KeyboardBinding { + KeyboardBinding._(this.glassPaneElement) { + _setup(); + } + /// The singleton instance of this object. static KeyboardBinding? get instance => _instance; static KeyboardBinding? _instance; @@ -102,10 +106,6 @@ class KeyboardBinding { } } - KeyboardBinding._(this.glassPaneElement) { - _setup(); - } - final DomElement glassPaneElement; late KeyboardConverter _converter; final Map _listeners = {}; diff --git a/lib/web_ui/lib/src/engine/mouse_cursor.dart b/lib/web_ui/lib/src/engine/mouse_cursor.dart index 3e5979bdedd86..009ca0f2110ed 100644 --- a/lib/web_ui/lib/src/engine/mouse_cursor.dart +++ b/lib/web_ui/lib/src/engine/mouse_cursor.dart @@ -7,6 +7,8 @@ import 'util.dart'; /// Provides mouse cursor bindings, such as the `flutter/mousecursor` channel. class MouseCursor { + MouseCursor._(); + /// Initializes the [MouseCursor] singleton. /// /// Use the [instance] getter to get the singleton after calling this method. @@ -18,8 +20,6 @@ class MouseCursor { static MouseCursor? get instance => _instance; static MouseCursor? _instance; - MouseCursor._(); - // Map from Flutter's kind values to CSS's cursor values. // // This map must be kept in sync with Flutter framework's diff --git a/lib/web_ui/lib/src/engine/navigation/history.dart b/lib/web_ui/lib/src/engine/navigation/history.dart index 7921f88abc5b2..bbee4c807bb58 100644 --- a/lib/web_ui/lib/src/engine/navigation/history.dart +++ b/lib/web_ui/lib/src/engine/navigation/history.dart @@ -287,7 +287,7 @@ class SingleEntryBrowserHistory extends BrowserHistory { return originState['state']; } - Map _flutterState = {_kFlutterTag: true}; + final Map _flutterState = {_kFlutterTag: true}; /// The origin entry is the history entry that the Flutter app landed on. It's /// created by the browser when the user navigates to the url of the app. @@ -359,7 +359,7 @@ class SingleEntryBrowserHistory extends BrowserHistory { /// replaces the state of the entry so that we can recognize it later using /// [_isOriginEntry] inside [_popStateListener]. void _setupOriginEntry(UrlStrategy strategy) { - assert(strategy != null); // ignore: unnecessary_null_comparison + assert(strategy != null); strategy.replaceState(_wrapOriginState(currentState), 'origin', ''); } @@ -370,7 +370,7 @@ class SingleEntryBrowserHistory extends BrowserHistory { bool replace = false, String? path, }) { - assert(strategy != null); // ignore: unnecessary_null_comparison + assert(strategy != null); path ??= currentPath; if (replace) { strategy.replaceState(_flutterState, 'flutter', path); diff --git a/lib/web_ui/lib/src/engine/platform_dispatcher.dart b/lib/web_ui/lib/src/engine/platform_dispatcher.dart index 05cbafd311d7e..311be4fa5e391 100644 --- a/lib/web_ui/lib/src/engine/platform_dispatcher.dart +++ b/lib/web_ui/lib/src/engine/platform_dispatcher.dart @@ -6,21 +6,17 @@ import 'dart:async'; import 'dart:convert'; import 'dart:typed_data'; -import 'package:meta/meta.dart'; +import 'package:ui/src/engine/canvaskit/renderer.dart'; +import 'package:ui/src/engine/renderer.dart'; import 'package:ui/ui.dart' as ui; import '../engine.dart' show platformViewManager, registerHotRestartListener; -import 'canvaskit/initialization.dart'; -import 'canvaskit/layer_scene_builder.dart'; -import 'canvaskit/rasterizer.dart'; import 'clipboard.dart'; import 'dom.dart'; import 'embedder.dart'; -import 'html/scene.dart'; import 'mouse_cursor.dart'; import 'platform_views/message_handler.dart'; import 'plugins.dart'; -import 'profiler.dart'; import 'safe_browser_api.dart'; import 'semantics.dart'; import 'services.dart'; @@ -142,14 +138,14 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { @override Iterable get views => _windows.values; Map get windows => _windows; - Map _windows = {}; + final Map _windows = {}; /// A map of opaque platform window identifiers to window configurations. /// /// This should be considered a protected member, only to be used by /// [PlatformDispatcher] subclasses. Map get windowConfigurations => _windowConfigurations; - Map _windowConfigurations = + final Map _windowConfigurations = {}; /// A callback that is invoked whenever the platform's [devicePixelRatio], @@ -433,15 +429,13 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { final MethodCall decoded = codec.decodeMethodCall(data); switch (decoded.method) { case 'Skia.setResourceCacheMaxBytes': - if (useCanvasKit) { - // If we're in CanvasKit mode, we must also have a rasterizer. - assert(rasterizer != null); + if (renderer is CanvasKitRenderer) { assert( decoded.arguments is int, 'Argument to Skia.setResourceCacheMaxBytes must be an int, but was ${decoded.arguments.runtimeType}', ); final int cacheSizeInBytes = decoded.arguments as int; - rasterizer!.setSkiaResourceCacheMaxBytes(cacheSizeInBytes); + CanvasKitRenderer.instance.resourceCacheMaxBytes = cacheSizeInBytes; } // Also respond in HTML mode. Otherwise, apps would have to detect @@ -651,24 +645,7 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { /// painting. @override void render(ui.Scene scene, [ui.FlutterView? view]) { - if (useCanvasKit) { - // "Build finish" and "raster start" happen back-to-back because we - // render on the same thread, so there's no overhead from hopping to - // another thread. - // - // CanvasKit works differently from the HTML renderer in that in HTML - // we update the DOM in SceneBuilder.build, which is these function calls - // here are CanvasKit-only. - frameTimingsOnBuildFinish(); - frameTimingsOnRasterStart(); - - final LayerScene layerScene = scene as LayerScene; - rasterizer!.draw(layerScene.layerTree); - } else { - final SurfaceScene surfaceScene = scene as SurfaceScene; - flutterViewEmbedder.addSceneToSceneHost(surfaceScene.webOnlyRootElement); - } - frameTimingsOnRasterFinish(); + renderer.renderScene(scene); } /// Additional accessibility features that may be enabled by the platform. @@ -1144,9 +1121,6 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher { /// to set [locationStrategy] in `lib/initialization.dart`. String? _defaultRouteName; - @visibleForTesting - late Rasterizer? rasterizer = useCanvasKit ? Rasterizer() : null; - /// In Flutter, platform messages are exchanged between threads so the /// messages and responses have to be exchanged asynchronously. We simulate /// that by adding a zero-length delay to the reply. diff --git a/lib/web_ui/lib/src/engine/platform_views/message_handler.dart b/lib/web_ui/lib/src/engine/platform_views/message_handler.dart index 722ce618b6ef7..30658e57f6c91 100644 --- a/lib/web_ui/lib/src/engine/platform_views/message_handler.dart +++ b/lib/web_ui/lib/src/engine/platform_views/message_handler.dart @@ -39,17 +39,16 @@ typedef PlatformViewContentHandler = void Function(DomElement); /// some extra cleanup of its internal state, but it can do it automatically. See /// [HtmlViewEmbedder.disposeViews] class PlatformViewMessageHandler { - final MethodCodec _codec = const StandardMethodCodec(); - - final PlatformViewManager _contentManager; - final PlatformViewContentHandler? _contentHandler; - PlatformViewMessageHandler({ required PlatformViewManager contentManager, PlatformViewContentHandler? contentHandler, }) : _contentManager = contentManager, _contentHandler = contentHandler; + final MethodCodec _codec = const StandardMethodCodec(); + final PlatformViewManager _contentManager; + final PlatformViewContentHandler? _contentHandler; + /// Handle a `create` Platform View message. /// /// This will attempt to render the `contents` and of a Platform View, if its diff --git a/lib/web_ui/lib/src/engine/pointer_binding.dart b/lib/web_ui/lib/src/engine/pointer_binding.dart index 02032999fecd7..a41b550c5fd8a 100644 --- a/lib/web_ui/lib/src/engine/pointer_binding.dart +++ b/lib/web_ui/lib/src/engine/pointer_binding.dart @@ -73,6 +73,15 @@ class SafariPointerEventWorkaround { } class PointerBinding { + PointerBinding(this.glassPaneElement) + : _pointerDataConverter = PointerDataConverter(), + _detector = const PointerSupportDetector() { + if (isIosSafari) { + SafariPointerEventWorkaround.instance.workAroundMissingPointerEvents(); + } + _adapter = _createAdapter(); + } + /// The singleton instance of this object. static PointerBinding? get instance => _instance; static PointerBinding? _instance; @@ -94,19 +103,10 @@ class PointerBinding { _pointerDataConverter.clearPointerState(); } - PointerBinding(this.glassPaneElement) - : _pointerDataConverter = PointerDataConverter(), - _detector = const PointerSupportDetector() { - if (isIosSafari) { - SafariPointerEventWorkaround.instance.workAroundMissingPointerEvents(); - } - _adapter = _createAdapter(); - } - final DomElement glassPaneElement; PointerSupportDetector _detector; - PointerDataConverter _pointerDataConverter; + final PointerDataConverter _pointerDataConverter; late _BaseAdapter _adapter; /// Should be used in tests to define custom detection of pointer support. @@ -245,8 +245,8 @@ abstract class _BaseAdapter { final List<_Listener> _listeners = <_Listener>[]; final DomElement glassPaneElement; - _PointerDataCallback _callback; - PointerDataConverter _pointerDataConverter; + final _PointerDataCallback _callback; + final PointerDataConverter _pointerDataConverter; /// Each subclass is expected to override this method to attach its own event /// listeners and convert events into pointer events. @@ -687,9 +687,9 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { required DomPointerEvent event, required _SanitizedDetails details, }) { - assert(data != null); // ignore: unnecessary_null_comparison - assert(event != null); // ignore: unnecessary_null_comparison - assert(details != null); // ignore: unnecessary_null_comparison + assert(data != null); + assert(event != null); + assert(details != null); final ui.PointerDeviceKind kind = _pointerTypeToDeviceKind(event.pointerType!); final double tilt = _computeHighestTilt(event); final Duration timeStamp = _BaseAdapter._eventTimeStampToDuration(event.timeStamp!); @@ -979,9 +979,9 @@ class _MouseAdapter extends _BaseAdapter with _WheelEventListenerMixin { required DomMouseEvent event, required _SanitizedDetails details, }) { - assert(data != null); // ignore: unnecessary_null_comparison - assert(event != null); // ignore: unnecessary_null_comparison - assert(details != null); // ignore: unnecessary_null_comparison + assert(data != null); + assert(event != null); + assert(details != null); _pointerDataConverter.convert( data, change: details.change, diff --git a/lib/web_ui/lib/src/engine/pointer_converter.dart b/lib/web_ui/lib/src/engine/pointer_converter.dart index 5b9974d039d51..4f94cd49b8bfa 100644 --- a/lib/web_ui/lib/src/engine/pointer_converter.dart +++ b/lib/web_ui/lib/src/engine/pointer_converter.dart @@ -240,7 +240,7 @@ class PointerDataConverter { print('>> device=$device change=$change buttons=$buttons'); } final bool isDown = buttons != 0; - assert(change != null); // ignore: unnecessary_null_comparison + assert(change != null); if (signalKind == null || signalKind == ui.PointerSignalKind.none) { switch (change) { diff --git a/lib/web_ui/lib/src/engine/renderer.dart b/lib/web_ui/lib/src/engine/renderer.dart new file mode 100644 index 0000000000000..e66d77f2d264b --- /dev/null +++ b/lib/web_ui/lib/src/engine/renderer.dart @@ -0,0 +1,210 @@ +// 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:math' as math; +import 'dart:typed_data'; + +import 'package:ui/ui.dart' as ui; + +import 'browser_detection.dart'; +import 'canvaskit/renderer.dart'; +import 'configuration.dart'; +import 'embedder.dart'; +import 'fonts.dart'; +import 'html/renderer.dart'; +import 'html_image_codec.dart'; + +final Renderer _renderer = Renderer._internal(); +Renderer get renderer => _renderer; + +/// This class is an abstraction over the rendering backend for the web engine. +/// Which backend is selected is based off of the `--web-renderer` command-line +/// argument passed to the flutter tool. It provides many of the rendering +/// primitives of the dart:ui library, as well as other backend-specific pieces +/// of functionality needed by the rest of the generic web engine code. +abstract class Renderer { + factory Renderer._internal() { + bool useCanvasKit; + if (FlutterConfiguration.flutterWebAutoDetect) { + if (requestedRendererType != null) { + useCanvasKit = requestedRendererType == 'canvaskit'; + } else { + // If requestedRendererType is not specified, use CanvasKit for desktop and + // html for mobile. + useCanvasKit = isDesktop; + } + } else { + useCanvasKit = FlutterConfiguration.useSkia; + } + + return useCanvasKit ? CanvasKitRenderer() : HtmlRenderer(); + } + + String get rendererTag; + FontCollection get fontCollection; + + FutureOr initialize(); + void reset(FlutterViewEmbedder embedder); + + ui.Paint createPaint(); + + ui.Vertices createVertices( + ui.VertexMode mode, + List positions, { + List? textureCoordinates, + List? colors, + List? indices, + }); + ui.Vertices createVerticesRaw( + ui.VertexMode mode, + Float32List positions, { + Float32List? textureCoordinates, + Int32List? colors, + Uint16List? indices, + }); + + ui.PictureRecorder createPictureRecorder(); + ui.Canvas createCanvas(ui.PictureRecorder recorder, [ui.Rect? cullRect]); + ui.SceneBuilder createSceneBuilder(); + + ui.Gradient createLinearGradient( + ui.Offset from, + ui.Offset to, + List colors, [ + List? colorStops, + ui.TileMode tileMode = ui.TileMode.clamp, + Float32List? matrix4, + ]); + ui.Gradient createRadialGradient( + ui.Offset center, + double radius, + List colors, [ + List? colorStops, + ui.TileMode tileMode = ui.TileMode.clamp, + Float32List? matrix4, + ]); + ui.Gradient createConicalGradient( + ui.Offset focal, + double focalRadius, + ui.Offset center, + double radius, + List colors, [ + List? colorStops, + ui.TileMode tileMode = ui.TileMode.clamp, + Float32List? matrix, + ]); + ui.Gradient createSweepGradient( + ui.Offset center, + List colors, [ + List? colorStops, + ui.TileMode tileMode = ui.TileMode.clamp, + double startAngle = 0.0, + double endAngle = math.pi * 2, + Float32List? matrix4, + ]); + + ui.ImageFilter createBlurImageFilter({ + double sigmaX = 0.0, + double sigmaY = 0.0, + ui.TileMode tileMode = ui.TileMode.clamp}); + ui.ImageFilter createDilateImageFilter({ double radiusX = 0.0, double radiusY = 0.0}); + ui.ImageFilter createErodeImageFilter({ double radiusX = 0.0, double radiusY = 0.0}); + ui.ImageFilter createMatrixImageFilter( + Float64List matrix4, { + ui.FilterQuality filterQuality = ui.FilterQuality.low + }); + ui.ImageFilter composeImageFilters({required ui.ImageFilter outer, required ui.ImageFilter inner}); + + Future instantiateImageCodec( + Uint8List list, { + int? targetWidth, + int? targetHeight, + bool allowUpscaling = true, + }); + + Future instantiateImageCodecFromUrl( + Uri uri, { + WebOnlyImageCodecChunkCallback? chunkCallback, + }); + + void decodeImageFromPixels( + Uint8List pixels, + int width, + int height, + ui.PixelFormat format, + ui.ImageDecoderCallback callback, { + int? rowBytes, + int? targetWidth, + int? targetHeight, + bool allowUpscaling = true + }); + + ui.ImageShader createImageShader( + ui.Image image, + ui.TileMode tmx, + ui.TileMode tmy, + Float64List matrix4, + ui.FilterQuality? filterQuality, + ); + + ui.Path createPath(); + ui.Path copyPath(ui.Path src); + ui.Path combinePaths(ui.PathOperation op, ui.Path path1, ui.Path path2); + + ui.TextStyle createTextStyle({ + 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.TextLeadingDistribution? leadingDistribution, + ui.Locale? locale, + ui.Paint? background, + ui.Paint? foreground, + List? shadows, + List? fontFeatures, + List? fontVariations, + }); + + ui.ParagraphStyle createParagraphStyle({ + ui.TextAlign? textAlign, + ui.TextDirection? textDirection, + int? maxLines, + String? fontFamily, + double? fontSize, + double? height, + ui.TextHeightBehavior? textHeightBehavior, + ui.FontWeight? fontWeight, + ui.FontStyle? fontStyle, + ui.StrutStyle? strutStyle, + String? ellipsis, + ui.Locale? locale, + }); + + ui.StrutStyle createStrutStyle({ + String? fontFamily, + List? fontFamilyFallback, + double? fontSize, + double? height, + ui.TextLeadingDistribution? leadingDistribution, + double? leading, + ui.FontWeight? fontWeight, + ui.FontStyle? fontStyle, + bool? forceStrutHeight, + }); + + ui.ParagraphBuilder createParagraphBuilder(ui.ParagraphStyle style); + + void renderScene(ui.Scene scene); +} diff --git a/lib/web_ui/lib/src/engine/rrect_renderer.dart b/lib/web_ui/lib/src/engine/rrect_renderer.dart index 829ad367d0bfc..91ead1328414e 100644 --- a/lib/web_ui/lib/src/engine/rrect_renderer.dart +++ b/lib/web_ui/lib/src/engine/rrect_renderer.dart @@ -183,8 +183,8 @@ abstract class RRectRenderer { /// Renders RRect to a 2d canvas. class RRectToCanvasRenderer extends RRectRenderer { - final DomCanvasRenderingContext2D context; RRectToCanvasRenderer(this.context); + final DomCanvasRenderingContext2D context; @override void beginPath() { context.beginPath(); @@ -210,8 +210,8 @@ class RRectToCanvasRenderer extends RRectRenderer { /// Renders RRect to a path. class RRectToPathRenderer extends RRectRenderer { - final ui.Path path; RRectToPathRenderer(this.path); + final ui.Path path; @override void beginPath() {} diff --git a/lib/web_ui/lib/src/engine/safe_browser_api.dart b/lib/web_ui/lib/src/engine/safe_browser_api.dart index 05c1e691412e4..eb0ca60c689b3 100644 --- a/lib/web_ui/lib/src/engine/safe_browser_api.dart +++ b/lib/web_ui/lib/src/engine/safe_browser_api.dart @@ -328,7 +328,10 @@ class DecodeOptions { /// * https://www.w3.org/TR/webcodecs/#videoframe-interface @JS() @anonymous -class VideoFrame implements DomCanvasImageSource { +@staticInterop +class VideoFrame implements DomCanvasImageSource {} + +extension VideoFrameExtension on VideoFrame { external int allocationSize(); external JsPromise copyTo(Uint8List destination); external String? get format; @@ -403,12 +406,34 @@ void vertexAttribPointerGlContext( /// Compiled and cached gl program. class GlProgram { - final Object program; GlProgram(this.program); + final Object program; } /// JS Interop helper for webgl apis. class GlContext { + factory GlContext(OffScreenCanvas offScreenCanvas) { + return OffScreenCanvas.supported + ? GlContext._fromOffscreenCanvas(offScreenCanvas.offScreenCanvas!) + : GlContext._fromCanvasElement( + offScreenCanvas.canvasElement!, webGLVersion == WebGLVersion.webgl1); + } + + GlContext._fromOffscreenCanvas(DomOffscreenCanvas canvas) + : glContext = canvas.getContext('webgl2', {'premultipliedAlpha': false})!, + isOffscreen = true { + _programCache = {}; + _canvas = canvas; + } + + GlContext._fromCanvasElement(DomCanvasElement canvas, bool useWebGl1) + : glContext = canvas.getContext(useWebGl1 ? 'webgl' : 'webgl2', + {'premultipliedAlpha': false})!, + isOffscreen = false { + _programCache = {}; + _canvas = canvas; + } + final Object glContext; final bool isOffscreen; Object? _kCompileStatus; @@ -437,28 +462,6 @@ class GlContext { int? _heightInPixels; static late Map _programCache; - factory GlContext(OffScreenCanvas offScreenCanvas) { - return OffScreenCanvas.supported - ? GlContext._fromOffscreenCanvas(offScreenCanvas.offScreenCanvas!) - : GlContext._fromCanvasElement( - offScreenCanvas.canvasElement!, webGLVersion == WebGLVersion.webgl1); - } - - GlContext._fromOffscreenCanvas(DomOffscreenCanvas canvas) - : glContext = canvas.getContext('webgl2', {'premultipliedAlpha': false})!, - isOffscreen = true { - _programCache = {}; - _canvas = canvas; - } - - GlContext._fromCanvasElement(DomCanvasElement canvas, bool useWebGl1) - : glContext = canvas.getContext(useWebGl1 ? 'webgl' : 'webgl2', - {'premultipliedAlpha': false})!, - isOffscreen = false { - _programCache = {}; - _canvas = canvas; - } - void setViewportSize(int width, int height) { _widthInPixels = width; _heightInPixels = height; @@ -949,12 +952,6 @@ dynamic tileModeToGlWrapping(GlContext gl, ui.TileMode tileMode) { /// Polyfill for DomOffscreenCanvas that is not supported on some browsers. class OffScreenCanvas { - DomOffscreenCanvas? offScreenCanvas; - DomCanvasElement? canvasElement; - int width; - int height; - static bool? _supported; - OffScreenCanvas(this.width, this.height) { if (OffScreenCanvas.supported) { offScreenCanvas = createDomOffscreenCanvas(width, height); @@ -968,6 +965,12 @@ class OffScreenCanvas { } } + DomOffscreenCanvas? offScreenCanvas; + DomCanvasElement? canvasElement; + int width; + int height; + static bool? _supported; + void _updateCanvasCssSize(DomCanvasElement element) { final double cssWidth = width / EnginePlatformDispatcher.browserDevicePixelRatio; final double cssHeight = height / EnginePlatformDispatcher.browserDevicePixelRatio; diff --git a/lib/web_ui/lib/src/engine/semantics/accessibility.dart b/lib/web_ui/lib/src/engine/semantics/accessibility.dart index abae46b6dd7a5..22d93833b8ec8 100644 --- a/lib/web_ui/lib/src/engine/semantics/accessibility.dart +++ b/lib/web_ui/lib/src/engine/semantics/accessibility.dart @@ -27,6 +27,12 @@ final AccessibilityAnnouncements accessibilityAnnouncements = /// Attaches accessibility announcements coming from the 'flutter/accessibility' /// channel as temporary elements to the DOM. class AccessibilityAnnouncements { + AccessibilityAnnouncements._() { + registerHotRestartListener(() { + _removeElementTimer?.cancel(); + }); + } + /// Initializes the [AccessibilityAnnouncements] singleton if it is not /// already initialized. static AccessibilityAnnouncements get instance { @@ -35,12 +41,6 @@ class AccessibilityAnnouncements { static AccessibilityAnnouncements? _instance; - AccessibilityAnnouncements._() { - registerHotRestartListener(() { - _removeElementTimer?.cancel(); - }); - } - /// Timer that times when the accessibility element should be removed from the /// DOM. /// diff --git a/lib/web_ui/lib/src/engine/semantics/checkable.dart b/lib/web_ui/lib/src/engine/semantics/checkable.dart index 2cb442f205095..a4c7de151c6f6 100644 --- a/lib/web_ui/lib/src/engine/semantics/checkable.dart +++ b/lib/web_ui/lib/src/engine/semantics/checkable.dart @@ -50,12 +50,12 @@ _CheckableKind _checkableKindFromSemanticsFlag( /// [ui.SemanticsFlag.isInMutuallyExclusiveGroup], [ui.SemanticsFlag.isToggled], /// [ui.SemanticsFlag.hasToggledState] class Checkable extends RoleManager { - final _CheckableKind _kind; - Checkable(SemanticsObject semanticsObject) : _kind = _checkableKindFromSemanticsFlag(semanticsObject), super(Role.checkable, semanticsObject); + final _CheckableKind _kind; + @override void update() { if (semanticsObject.isFlagsDirty) { diff --git a/lib/web_ui/lib/src/engine/semantics/incrementable.dart b/lib/web_ui/lib/src/engine/semantics/incrementable.dart index fd777014ed575..6cfeb6c37f80a 100644 --- a/lib/web_ui/lib/src/engine/semantics/incrementable.dart +++ b/lib/web_ui/lib/src/engine/semantics/incrementable.dart @@ -19,30 +19,6 @@ import 'semantics.dart'; /// events. This is to prevent the browser from taking over drag gestures. Drag /// gestures must be interpreted by the Flutter framework. class Incrementable extends RoleManager { - /// The HTML element used to render semantics to the browser. - final DomHTMLInputElement _element = createDomHTMLInputElement(); - - /// The value used by the input element. - /// - /// Flutter values are strings, and are not necessarily numbers. In order to - /// convey to the browser what the available "range" of values is we - /// substitute the framework value with a generated `int` surrogate. - /// "aria-valuetext" attribute is used to cause the browser to announce the - /// framework value to the user. - int _currentSurrogateValue = 1; - - /// Disables the input [_element] when the gesture mode switches to - /// [GestureMode.pointerEvents], and enables it when the mode switches back to - /// [GestureMode.browserGestures]. - GestureModeCallback? _gestureModeListener; - - /// Whether we forwarded a semantics action to the framework and awaiting an - /// update. - /// - /// This field is used to determine whether the HTML DOM of the semantics - /// tree should be updated. - bool _pendingResync = false; - Incrementable(SemanticsObject semanticsObject) : super(Role.incrementable, semanticsObject) { semanticsObject.element.append(_element); @@ -74,6 +50,30 @@ class Incrementable extends RoleManager { semanticsObject.owner.addGestureModeListener(_gestureModeListener); } + /// The HTML element used to render semantics to the browser. + final DomHTMLInputElement _element = createDomHTMLInputElement(); + + /// The value used by the input element. + /// + /// Flutter values are strings, and are not necessarily numbers. In order to + /// convey to the browser what the available "range" of values is we + /// substitute the framework value with a generated `int` surrogate. + /// "aria-valuetext" attribute is used to cause the browser to announce the + /// framework value to the user. + int _currentSurrogateValue = 1; + + /// Disables the input [_element] when the gesture mode switches to + /// [GestureMode.pointerEvents], and enables it when the mode switches back to + /// [GestureMode.browserGestures]. + GestureModeCallback? _gestureModeListener; + + /// Whether we forwarded a semantics action to the framework and awaiting an + /// update. + /// + /// This field is used to determine whether the HTML DOM of the semantics + /// tree should be updated. + bool _pendingResync = false; + @override void update() { switch (semanticsObject.owner.gestureMode) { diff --git a/lib/web_ui/lib/src/engine/semantics/semantics.dart b/lib/web_ui/lib/src/engine/semantics/semantics.dart index d19efbe908d48..3f5448b53a3c5 100644 --- a/lib/web_ui/lib/src/engine/semantics/semantics.dart +++ b/lib/web_ui/lib/src/engine/semantics/semantics.dart @@ -382,7 +382,7 @@ abstract class RoleManager { /// /// A single role object manages exactly one [SemanticsObject]. RoleManager(this.role, this.semanticsObject) - : assert(semanticsObject != null); // ignore: unnecessary_null_comparison + : assert(semanticsObject != null); /// Role identifier. final Role role; @@ -869,7 +869,7 @@ class SemanticsObject { void updateSelf(SemanticsNodeUpdate update) { // Update all field values and their corresponding dirty flags before // applying the updates to the DOM. - assert(update.flags != null); // ignore: unnecessary_null_comparison + assert(update.flags != null); if (_flags != update.flags) { _flags = update.flags; _markFlagsDirty(); @@ -1473,8 +1473,8 @@ class EngineSemanticsOwner { /// allows the same node to be detached from one parent in the tree and /// reattached to another parent. void _attachObject({required SemanticsObject parent, required SemanticsObject child}) { - assert(child != null); // ignore: unnecessary_null_comparison - assert(parent != null); // ignore: unnecessary_null_comparison + assert(child != null); + assert(parent != null); child._parent = parent; _attachments[child.id] = parent; } @@ -1549,8 +1549,6 @@ class EngineSemanticsOwner { /// The top-level DOM element of the semantics DOM element tree. DomElement? _rootSemanticsElement; - - // ignore: prefer_function_declarations_over_variables TimestampFunction _now = () => DateTime.now(); void debugOverrideTimestampFunction(TimestampFunction value) { @@ -1615,7 +1613,7 @@ class EngineSemanticsOwner { /// The default mode is [AccessibilityMode.unknown]. AccessibilityMode get mode => _mode; set mode(AccessibilityMode value) { - assert(value != null); // ignore: unnecessary_null_comparison + assert(value != null); _mode = value; } @@ -1724,7 +1722,7 @@ class EngineSemanticsOwner { /// Callbacks are called synchronously. HTML DOM updates made in a callback /// take effect in the current animation frame and/or the current message loop /// event. - List _gestureModeListeners = []; + final List _gestureModeListeners = []; /// Calls the [callback] every time the current [GestureMode] changes. /// 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 a3502c9cf0035..9aff262f7ea8a 100644 --- a/lib/web_ui/lib/src/engine/semantics/text_field.dart +++ b/lib/web_ui/lib/src/engine/semantics/text_field.dart @@ -21,6 +21,11 @@ import 'semantics.dart'; /// 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(super.owner); + /// Initializes the [SemanticsTextEditingStrategy] singleton. /// /// This method must be called prior to accessing [instance]. @@ -35,11 +40,6 @@ class SemanticsTextEditingStrategy extends DefaultTextEditingStrategy { static SemanticsTextEditingStrategy get instance => _instance!; static SemanticsTextEditingStrategy? _instance; - /// Creates a [SemanticsTextEditingStrategy] that eagerly instantiates - /// [domElement] so the caller can insert it before calling - /// [SemanticsTextEditingStrategy.enable]. - SemanticsTextEditingStrategy(super.owner); - /// The text field whose DOM element is currently used for editing. /// /// If this field is null, no editing takes place. diff --git a/lib/web_ui/lib/src/engine/services/buffers.dart b/lib/web_ui/lib/src/engine/services/buffers.dart index b581dc2111e5d..7800966635ce1 100644 --- a/lib/web_ui/lib/src/engine/services/buffers.dart +++ b/lib/web_ui/lib/src/engine/services/buffers.dart @@ -6,6 +6,10 @@ import 'dart:collection'; import 'dart:typed_data'; abstract class _TypedDataBuffer extends ListBase { + _TypedDataBuffer(List buffer) + : _buffer = buffer, + _length = buffer.length; + static const int _initialLength = 8; /// The underlying data buffer. @@ -20,10 +24,6 @@ abstract class _TypedDataBuffer extends ListBase { /// The length of the list being built. int _length; - _TypedDataBuffer(List buffer) - : _buffer = buffer, - _length = buffer.length; - @override int get length => _length; @@ -212,8 +212,8 @@ abstract class _TypedDataBuffer extends ListBase { /// Like [insertAll], but with a guaranteed non-`null` [start] and [end]. void _insertKnownLength(int index, Iterable values, int start, int end) { - assert(values != null); // ignore: unnecessary_null_comparison - assert(end != null); // ignore: unnecessary_null_comparison + assert(values != null); + assert(end != null); if (start > values.length || end > values.length) { throw StateError('Too few elements'); } diff --git a/lib/web_ui/lib/src/engine/services/message_codec.dart b/lib/web_ui/lib/src/engine/services/message_codec.dart index 0a4285cdaa97d..cb8c26b74647b 100644 --- a/lib/web_ui/lib/src/engine/services/message_codec.dart +++ b/lib/web_ui/lib/src/engine/services/message_codec.dart @@ -32,7 +32,6 @@ abstract class MessageCodec { class MethodCall { /// Creates a [MethodCall] representing the invocation of [method] with the /// specified [arguments]. - // ignore: unnecessary_null_comparison const MethodCall(this.method, [this.arguments]) : assert(method != null); /// The name of the method to be called. @@ -102,7 +101,7 @@ class PlatformException implements Exception { required this.code, this.message, this.details, - }) : assert(code != null); // ignore: unnecessary_null_comparison + }) : assert(code != null); /// An error code. final String code; diff --git a/lib/web_ui/lib/src/engine/services/message_codecs.dart b/lib/web_ui/lib/src/engine/services/message_codecs.dart index 7beefc5f9d2b2..d084c9730cf25 100644 --- a/lib/web_ui/lib/src/engine/services/message_codecs.dart +++ b/lib/web_ui/lib/src/engine/services/message_codecs.dart @@ -160,7 +160,7 @@ class JSONMethodCodec implements MethodCodec { @override ByteData? encodeErrorEnvelope( {required String code, String? message, dynamic details}) { - assert(code != null); // ignore: unnecessary_null_comparison + assert(code != null); return const JSONMessageCodec() .encodeMessage([code, message, details]); } @@ -212,6 +212,9 @@ class JSONMethodCodec implements MethodCodec { /// The codec is extensible by subclasses overriding [writeValue] and /// [readValueOfType]. class StandardMessageCodec implements MessageCodec { + /// Creates a [MessageCodec] using the Flutter standard binary encoding. + const StandardMessageCodec(); + // The codec serializes messages as outlined below. This format must // match the Android and iOS counterparts. // @@ -264,9 +267,6 @@ class StandardMessageCodec implements MessageCodec { static const int _valueList = 12; static const int _valueMap = 13; - /// Creates a [MessageCodec] using the Flutter standard binary encoding. - const StandardMessageCodec(); - @override ByteData? encodeMessage(dynamic message) { if (message == null) { diff --git a/lib/web_ui/lib/src/engine/services/serialization.dart b/lib/web_ui/lib/src/engine/services/serialization.dart index c5e2ee53ccf33..1188abcb4e18a 100644 --- a/lib/web_ui/lib/src/engine/services/serialization.dart +++ b/lib/web_ui/lib/src/engine/services/serialization.dart @@ -122,7 +122,7 @@ class WriteBuffer { /// The byte order used is [Endian.host] throughout. class ReadBuffer { /// Creates a [ReadBuffer] for reading from the specified [data]. - ReadBuffer(this.data) : assert(data != null); // ignore: unnecessary_null_comparison + ReadBuffer(this.data) : assert(data != null); /// The underlying data being read. final ByteData data; diff --git a/lib/web_ui/lib/src/engine/test_embedding.dart b/lib/web_ui/lib/src/engine/test_embedding.dart index 5d86744a0086b..8f28532a50539 100644 --- a/lib/web_ui/lib/src/engine/test_embedding.dart +++ b/lib/web_ui/lib/src/engine/test_embedding.dart @@ -37,12 +37,12 @@ Future initializeTestFlutterViewEmbedder({double devicePixelRatio = 3.0}) const bool _debugLogHistoryActions = false; class TestHistoryEntry { + const TestHistoryEntry(this.state, this.title, this.url); + final dynamic state; final String? title; final String url; - const TestHistoryEntry(this.state, this.title, this.url); - @override String toString() { return '$runtimeType(state:$state, title:"$title", url:"$url")'; diff --git a/lib/web_ui/lib/src/engine/text/canvas_paragraph.dart b/lib/web_ui/lib/src/engine/text/canvas_paragraph.dart index 10a102ce161b9..4b35410321ef1 100644 --- a/lib/web_ui/lib/src/engine/text/canvas_paragraph.dart +++ b/lib/web_ui/lib/src/engine/text/canvas_paragraph.dart @@ -567,7 +567,7 @@ class CanvasParagraphBuilder implements ui.ParagraphBuilder { final List _spans = []; final List _styleStack = []; - RootStyleNode _rootStyleNode; + final RootStyleNode _rootStyleNode; StyleNode get _currentStyleNode => _styleStack.isEmpty ? _rootStyleNode : _styleStack[_styleStack.length - 1]; 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 04c856edf50de..864d0f9d99660 100644 --- a/lib/web_ui/lib/src/engine/text/font_collection.dart +++ b/lib/web_ui/lib/src/engine/text/font_collection.dart @@ -6,6 +6,8 @@ import 'dart:async'; import 'dart:convert'; import 'dart:typed_data'; +import 'package:ui/src/engine/fonts.dart'; + import '../assets.dart'; import '../browser_detection.dart'; import '../dom.dart'; @@ -26,12 +28,13 @@ const String robotoVariableTestFontUrl = '/assets/fonts/RobotoSlab-VariableFont_ /// [registerFonts] with it to register fonts declared in the /// font manifest. If test fonts are enabled, then call /// [registerTestFonts] as well. -class FontCollection { +class HtmlFontCollection implements FontCollection { FontManager? _assetFontManager; FontManager? _testFontManager; /// Reads the font manifest using the [assetManager] and registers all of the /// fonts declared within. + @override Future registerFonts(AssetManager assetManager) async { ByteData byteData; @@ -78,11 +81,16 @@ class FontCollection { } } - Future loadFontFromList(Uint8List list, {required String fontFamily}) { + @override + Future loadFontFromList(Uint8List list, {String? fontFamily}) { + if (fontFamily == null) { + throw AssertionError('Font family must be provided to HtmlFontCollection.'); + } return _assetFontManager!._loadFontFaceBytes(fontFamily, list); } /// Registers fonts that are used by tests. + @override void debugRegisterTestFonts() { _testFontManager = FontManager(); _testFontManager!.registerAsset( @@ -95,12 +103,14 @@ class FontCollection { /// Returns a [Future] that completes when the registered fonts are loaded /// and ready to be used. + @override Future ensureFontsLoaded() async { await _assetFontManager?.ensureFontsLoaded(); await _testFontManager?.ensureFontsLoaded(); } /// Unregister all fonts that have been registered. + @override void clear() { _assetFontManager = null; _testFontManager = null; @@ -112,6 +122,16 @@ class FontCollection { /// Manages a collection of fonts and ensures they are loaded. class FontManager { + factory FontManager() { + if (supportsFontLoadingApi) { + return FontManager._(); + } else { + return _PolyfillFontManager(); + } + } + + FontManager._(); + final List> _fontLoadingFutures = >[]; // Regular expression to detect a string with no punctuations. @@ -124,16 +144,6 @@ class FontManager { // category. static final RegExp startWithDigit = RegExp(r'\b\d'); - factory FontManager() { - if (supportsFontLoadingApi) { - return FontManager._(); - } else { - return _PolyfillFontManager(); - } - } - - FontManager._(); - /// Registers assets to Flutter Web Engine. /// /// Browsers and browsers versions differ significantly on how a valid font diff --git a/lib/web_ui/lib/src/engine/text/layout_service.dart b/lib/web_ui/lib/src/engine/text/layout_service.dart index 721caef1e8b5d..a5b91bd016cd7 100644 --- a/lib/web_ui/lib/src/engine/text/layout_service.dart +++ b/lib/web_ui/lib/src/engine/text/layout_service.dart @@ -1582,9 +1582,9 @@ class Spanometer { final CanvasParagraph paragraph; final DomCanvasRenderingContext2D context; - static RulerHost _rulerHost = RulerHost(); + static final RulerHost _rulerHost = RulerHost(); - static Map _rulers = + static final Map _rulers = {}; @visibleForTesting diff --git a/lib/web_ui/lib/src/engine/text/paragraph.dart b/lib/web_ui/lib/src/engine/text/paragraph.dart index 84e315c3240fa..7011038f71054 100644 --- a/lib/web_ui/lib/src/engine/text/paragraph.dart +++ b/lib/web_ui/lib/src/engine/text/paragraph.dart @@ -726,8 +726,8 @@ void applyTextStyleToElement({ required EngineTextStyle style, bool isSpan = false, }) { - assert(element != null); // ignore: unnecessary_null_comparison - assert(style != null); // ignore: unnecessary_null_comparison + assert(element != null); + assert(style != null); bool updateDecoration = false; final DomCSSStyleDeclaration cssStyle = element.style; diff --git a/lib/web_ui/lib/src/engine/text/word_breaker.dart b/lib/web_ui/lib/src/engine/text/word_breaker.dart index c90e5d45dcf8e..1e4fd51920006 100644 --- a/lib/web_ui/lib/src/engine/text/word_breaker.dart +++ b/lib/web_ui/lib/src/engine/text/word_breaker.dart @@ -6,11 +6,11 @@ import '../util.dart'; import 'word_break_properties.dart'; class _FindBreakDirection { + const _FindBreakDirection({required this.step}); + static const _FindBreakDirection forward = _FindBreakDirection(step: 1); static const _FindBreakDirection backward = _FindBreakDirection(step: -1); - const _FindBreakDirection({required this.step}); - final int step; } diff --git a/lib/web_ui/lib/src/engine/text_editing/autofill_hint.dart b/lib/web_ui/lib/src/engine/text_editing/autofill_hint.dart index 3059f63ee110a..7ee1cfb732431 100644 --- a/lib/web_ui/lib/src/engine/text_editing/autofill_hint.dart +++ b/lib/web_ui/lib/src/engine/text_editing/autofill_hint.dart @@ -7,14 +7,6 @@ /// See: https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/services/autofill.dart /// See: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete class BrowserAutofillHints { - static final BrowserAutofillHints _singletonInstance = - BrowserAutofillHints._(); - - /// The [BrowserAutofillHints] singleton. - static BrowserAutofillHints get instance => _singletonInstance; - - final Map _flutterToEngineMap; - BrowserAutofillHints._() : _flutterToEngineMap = { 'birthday': 'bday', @@ -73,6 +65,14 @@ class BrowserAutofillHints { 'username': 'username', }; + static final BrowserAutofillHints _singletonInstance = + BrowserAutofillHints._(); + + /// The [BrowserAutofillHints] singleton. + static BrowserAutofillHints get instance => _singletonInstance; + + final Map _flutterToEngineMap; + /// Converts the Flutter AutofillHint to the autofill hint value used by the /// browsers. /// See: https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/services/autofill.dart diff --git a/lib/web_ui/lib/src/engine/text_editing/text_capitalization.dart b/lib/web_ui/lib/src/engine/text_editing/text_capitalization.dart index f6204578d98fe..b871b737d7e7f 100644 --- a/lib/web_ui/lib/src/engine/text_editing/text_capitalization.dart +++ b/lib/web_ui/lib/src/engine/text_editing/text_capitalization.dart @@ -32,8 +32,6 @@ enum TextCapitalization { /// See: https://developers.google.com/web/updates/2015/04/autocapitalize /// https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autocapitalize class TextCapitalizationConfig { - final TextCapitalization textCapitalization; - const TextCapitalizationConfig.defaultCapitalization() : textCapitalization = TextCapitalization.none; @@ -47,6 +45,8 @@ class TextCapitalizationConfig { ? TextCapitalization.sentences : TextCapitalization.none; + final TextCapitalization textCapitalization; + /// Sets `autocapitalize` attribute on input elements. /// /// This attribute is only available for mobile browsers. 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 5c09019cd61be..f9ec958f18b79 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 @@ -352,6 +352,24 @@ class AutofillInfo { this.placeholder, }); + factory AutofillInfo.fromFrameworkMessage(Map autofill, + {TextCapitalizationConfig textCapitalization = + const TextCapitalizationConfig.defaultCapitalization()}) { + assert(autofill != null); + final String uniqueIdentifier = autofill.readString('uniqueIdentifier'); + final List? hintsList = autofill.tryList('hints'); + final String? firstHint = (hintsList == null || hintsList.isEmpty) ? null : hintsList.first as String; + final EditingState editingState = + EditingState.fromFrameworkMessage(autofill.readJson('editingValue')); + return AutofillInfo( + uniqueIdentifier: uniqueIdentifier, + autofillHint: (firstHint != null) ? BrowserAutofillHints.instance.flutterToEngine(firstHint) : null, + editingState: editingState, + placeholder: autofill.tryString('hintText'), + textCapitalization: textCapitalization, + ); + } + /// The current text and selection state of a text field. final EditingState editingState; @@ -390,24 +408,6 @@ class AutofillInfo { /// information is expected in this field. final String? placeholder; - factory AutofillInfo.fromFrameworkMessage(Map autofill, - {TextCapitalizationConfig textCapitalization = - const TextCapitalizationConfig.defaultCapitalization()}) { - assert(autofill != null); // ignore: unnecessary_null_comparison - final String uniqueIdentifier = autofill.readString('uniqueIdentifier'); - final List? hintsList = autofill.tryList('hints'); - final String? firstHint = (hintsList == null || hintsList.isEmpty) ? null : hintsList.first as String; - final EditingState editingState = - EditingState.fromFrameworkMessage(autofill.readJson('editingValue')); - return AutofillInfo( - uniqueIdentifier: uniqueIdentifier, - autofillHint: (firstHint != null) ? BrowserAutofillHints.instance.flutterToEngine(firstHint) : null, - editingState: editingState, - placeholder: autofill.tryString('hintText'), - textCapitalization: textCapitalization, - ); - } - void applyToDomElement(DomHTMLElement domElement, {bool focusedElement = false}) { final String? autofillHint = this.autofillHint; @@ -1084,10 +1084,10 @@ class SafariDesktopTextEditingStrategy extends DefaultTextEditingStrategy { /// Unless a formfactor/browser requires specific implementation for a specific /// strategy the methods in this class should be used. abstract class DefaultTextEditingStrategy with CompositionAwareMixin implements TextEditingStrategy { - final HybridTextEditing owner; - DefaultTextEditingStrategy(this.owner); + final HybridTextEditing owner; + bool isEnabled = false; /// The DOM element used for editing, if any. diff --git a/lib/web_ui/lib/src/engine/ulps.dart b/lib/web_ui/lib/src/engine/ulps.dart index 10677dc96ba43..65db11f5e7018 100644 --- a/lib/web_ui/lib/src/engine/ulps.dart +++ b/lib/web_ui/lib/src/engine/ulps.dart @@ -48,9 +48,6 @@ int twosComplimentToSignBit(int x) { } class _FloatBitConverter { - final Float32List float32List; - final Int32List int32List; - factory _FloatBitConverter() { final Float32List float32List = Float32List(1); return _FloatBitConverter._( @@ -59,6 +56,9 @@ class _FloatBitConverter { _FloatBitConverter._(this.float32List, this.int32List); + final Float32List float32List; + final Int32List int32List; + int toInt(Float32List source, int index) { float32List[0] = source[index]; return int32List[0]; diff --git a/lib/web_ui/lib/src/engine/validators.dart b/lib/web_ui/lib/src/engine/validators.dart index 43a108c161f4b..64c3dd5a2ed95 100644 --- a/lib/web_ui/lib/src/engine/validators.dart +++ b/lib/web_ui/lib/src/engine/validators.dart @@ -7,7 +7,7 @@ import 'dart:typed_data'; import 'package:ui/ui.dart' as ui; bool rectIsValid(ui.Rect rect) { - assert(rect != null, 'Rect argument was null.'); // ignore: unnecessary_null_comparison + assert(rect != null, 'Rect argument was null.'); assert( !(rect.left.isNaN || rect.right.isNaN || @@ -18,7 +18,7 @@ bool rectIsValid(ui.Rect rect) { } bool rrectIsValid(ui.RRect rrect) { - assert(rrect != null, 'RRect argument was null.'); // ignore: unnecessary_null_comparison + assert(rrect != null, 'RRect argument was null.'); assert( !(rrect.left.isNaN || rrect.right.isNaN || @@ -29,20 +29,20 @@ bool rrectIsValid(ui.RRect rrect) { } bool offsetIsValid(ui.Offset offset) { - assert(offset != null, 'Offset argument was null.'); // ignore: unnecessary_null_comparison + assert(offset != null, 'Offset argument was null.'); assert(!offset.dx.isNaN && !offset.dy.isNaN, 'Offset argument contained a NaN value.'); return true; } bool matrix4IsValid(Float32List matrix4) { - assert(matrix4 != null, 'Matrix4 argument was null.'); // ignore: unnecessary_null_comparison + assert(matrix4 != null, 'Matrix4 argument was null.'); assert(matrix4.length == 16, 'Matrix4 must have 16 entries.'); return true; } bool radiusIsValid(ui.Radius radius) { - assert(radius != null, 'Radius argument was null.'); // ignore: unnecessary_null_comparison + assert(radius != null, 'Radius argument was null.'); assert(!radius.x.isNaN && !radius.y.isNaN, 'Radius argument contained a NaN value.'); return true; diff --git a/lib/web_ui/lib/src/engine/vector_math.dart b/lib/web_ui/lib/src/engine/vector_math.dart index 08b708a27fb41..28fb5f70c6000 100644 --- a/lib/web_ui/lib/src/engine/vector_math.dart +++ b/lib/web_ui/lib/src/engine/vector_math.dart @@ -8,41 +8,6 @@ import 'dart:typed_data'; import 'util.dart'; class Matrix4 { - final Float32List _m4storage; - - /// The components of the matrix. - Float32List get storage => _m4storage; - - /// Returns a matrix that is the inverse of [other] if [other] is invertible, - /// otherwise `null`. - static Matrix4? tryInvert(Matrix4 other) { - final Matrix4 r = Matrix4.zero(); - final double determinant = r.copyInverse(other); - if (determinant == 0.0) { - return null; - } - return r; - } - - /// Return index in storage for [row], [col] value. - int index(int row, int col) => (col * 4) + row; - - /// Value at [row], [col]. - double entry(int row, int col) { - assert((row >= 0) && (row < dimension)); - assert((col >= 0) && (col < dimension)); - - return _m4storage[index(row, col)]; - } - - /// Set value at [row], [col] to be [v]. - void setEntry(int row, int col, double v) { - assert((row >= 0) && (row < dimension)); - assert((col >= 0) && (col < dimension)); - - _m4storage[index(row, col)] = v; - } - /// Constructs a new mat4. factory Matrix4( double arg0, @@ -129,6 +94,41 @@ class Matrix4 { Matrix4.fromBuffer(ByteBuffer buffer, int offset) : _m4storage = Float32List.view(buffer, offset, 16); + final Float32List _m4storage; + + /// The components of the matrix. + Float32List get storage => _m4storage; + + /// Returns a matrix that is the inverse of [other] if [other] is invertible, + /// otherwise `null`. + static Matrix4? tryInvert(Matrix4 other) { + final Matrix4 r = Matrix4.zero(); + final double determinant = r.copyInverse(other); + if (determinant == 0.0) { + return null; + } + return r; + } + + /// Return index in storage for [row], [col] value. + int index(int row, int col) => (col * 4) + row; + + /// Value at [row], [col]. + double entry(int row, int col) { + assert((row >= 0) && (row < dimension)); + assert((col >= 0) && (col < dimension)); + + return _m4storage[index(row, col)]; + } + + /// Set value at [row], [col] to be [v]. + void setEntry(int row, int col, double v) { + assert((row >= 0) && (row < dimension)); + assert((col >= 0) && (col < dimension)); + + _m4storage[index(row, col)] = v; + } + /// Sets the matrix with specified values. void setValues( double arg0, @@ -1087,6 +1087,34 @@ class Matrix4 { /// 3D column vector. class Vector3 { + /// Construct a new vector with the specified values. + factory Vector3(double x, double y, double z) => + Vector3.zero()..setValues(x, y, z); + + /// Zero vector. + Vector3.zero() : _v3storage = Float32List(3); + + /// Splat [value] into all lanes of the vector. + factory Vector3.all(double value) => Vector3.zero()..splat(value); + + /// Copy of [other]. + factory Vector3.copy(Vector3 other) => Vector3.zero()..setFrom(other); + + /// Constructs Vector3 with given Float32List as [storage]. + Vector3.fromFloat32List(this._v3storage); + + /// Constructs Vector3 with a [storage] that views given [buffer] starting at + /// [offset]. [offset] has to be multiple of [Float32List.bytesPerElement]. + Vector3.fromBuffer(ByteBuffer buffer, int offset) + : _v3storage = Float32List.view(buffer, offset, 3); + + /// Generate random vector in the range (0, 0, 0) to (1, 1, 1). You can + /// optionally pass your own random number generator. + factory Vector3.random([math.Random? rng]) { + rng ??= math.Random(); + return Vector3(rng.nextDouble(), rng.nextDouble(), rng.nextDouble()); + } + final Float32List _v3storage; /// The components of the vector. @@ -1117,34 +1145,6 @@ class Vector3 { ..z = min.z + a * (max.z - min.z); } - /// Construct a new vector with the specified values. - factory Vector3(double x, double y, double z) => - Vector3.zero()..setValues(x, y, z); - - /// Zero vector. - Vector3.zero() : _v3storage = Float32List(3); - - /// Splat [value] into all lanes of the vector. - factory Vector3.all(double value) => Vector3.zero()..splat(value); - - /// Copy of [other]. - factory Vector3.copy(Vector3 other) => Vector3.zero()..setFrom(other); - - /// Constructs Vector3 with given Float32List as [storage]. - Vector3.fromFloat32List(this._v3storage); - - /// Constructs Vector3 with a [storage] that views given [buffer] starting at - /// [offset]. [offset] has to be multiple of [Float32List.bytesPerElement]. - Vector3.fromBuffer(ByteBuffer buffer, int offset) - : _v3storage = Float32List.view(buffer, offset, 3); - - /// Generate random vector in the range (0, 0, 0) to (1, 1, 1). You can - /// optionally pass your own random number generator. - factory Vector3.random([math.Random? rng]) { - rng ??= math.Random(); - return Vector3(rng.nextDouble(), rng.nextDouble(), rng.nextDouble()); - } - /// Set the values of the vector. void setValues(double x, double y, double z) { _v3storage[0] = x; diff --git a/lib/web_ui/lib/text.dart b/lib/web_ui/lib/text.dart index ef45267cca23e..1712155ad91c3 100644 --- a/lib/web_ui/lib/text.dart +++ b/lib/web_ui/lib/text.dart @@ -19,17 +19,18 @@ enum PlaceholderAlignment { } class FontWeight { - const FontWeight._(this.index); + const FontWeight._(this.index, this.value); final int index; - static const FontWeight w100 = FontWeight._(0); - static const FontWeight w200 = FontWeight._(1); - static const FontWeight w300 = FontWeight._(2); - static const FontWeight w400 = FontWeight._(3); - static const FontWeight w500 = FontWeight._(4); - static const FontWeight w600 = FontWeight._(5); - static const FontWeight w700 = FontWeight._(6); - static const FontWeight w800 = FontWeight._(7); - static const FontWeight w900 = FontWeight._(8); + final int value; + static const FontWeight w100 = FontWeight._(0, 100); + static const FontWeight w200 = FontWeight._(1, 200); + static const FontWeight w300 = FontWeight._(2, 300); + static const FontWeight w400 = FontWeight._(3, 400); + static const FontWeight w500 = FontWeight._(4, 500); + static const FontWeight w600 = FontWeight._(5, 600); + static const FontWeight w700 = FontWeight._(6, 700); + static const FontWeight w800 = FontWeight._(7, 800); + static const FontWeight w900 = FontWeight._(8, 900); static const FontWeight normal = w400; static const FontWeight bold = w700; static const List values = [ @@ -44,7 +45,7 @@ class FontWeight { w900 ]; static FontWeight? lerp(FontWeight? a, FontWeight? b, double t) { - assert(t != null); // ignore: unnecessary_null_comparison + assert(t != null); if (a == null && b == null) { return null; } @@ -74,10 +75,10 @@ class FontWeight { class FontFeature { const FontFeature(this.feature, [this.value = 1]) - : assert(feature != null), // ignore: unnecessary_null_comparison + : assert(feature != null), assert(feature.length == 4, 'Feature tag must be exactly four characters long.'), - assert(value != null), // ignore: unnecessary_null_comparison + assert(value != null), assert(value >= 0, 'Feature value must be zero or a positive integer.'); const FontFeature.enable(String feature) : this(feature, 1); const FontFeature.disable(String feature) : this(feature, 0); @@ -341,55 +342,29 @@ abstract class TextStyle { List? shadows, List? fontFeatures, List? fontVariations, - }) { - if (engine.useCanvasKit) { - return engine.CkTextStyle( - color: color, - decoration: decoration, - decorationColor: decorationColor, - decorationStyle: decorationStyle, - decorationThickness: decorationThickness, - fontWeight: fontWeight, - fontStyle: fontStyle, - textBaseline: textBaseline, - fontFamily: fontFamily, - fontFamilyFallback: fontFamilyFallback, - fontSize: fontSize, - letterSpacing: letterSpacing, - wordSpacing: wordSpacing, - height: height, - leadingDistribution: leadingDistribution, - locale: locale, - background: background as engine.CkPaint?, - foreground: foreground as engine.CkPaint?, - shadows: shadows, - fontFeatures: fontFeatures, - ); - } else { - return engine.EngineTextStyle( - color: color, - decoration: decoration, - decorationColor: decorationColor, - decorationStyle: decorationStyle, - decorationThickness: decorationThickness, - fontWeight: fontWeight, - fontStyle: fontStyle, - textBaseline: textBaseline, - fontFamily: fontFamily, - fontFamilyFallback: fontFamilyFallback, - fontSize: fontSize, - letterSpacing: letterSpacing, - wordSpacing: wordSpacing, - height: height, - locale: locale, - background: background, - foreground: foreground, - shadows: shadows, - fontFeatures: fontFeatures, - fontVariations: fontVariations, - ); - } - } + }) => engine.renderer.createTextStyle( + color: color, + decoration: decoration, + decorationColor: decorationColor, + decorationStyle: decorationStyle, + decorationThickness: decorationThickness, + fontWeight: fontWeight, + fontStyle: fontStyle, + textBaseline: textBaseline, + fontFamily: fontFamily, + fontFamilyFallback: fontFamilyFallback, + fontSize: fontSize, + letterSpacing: letterSpacing, + wordSpacing: wordSpacing, + height: height, + leadingDistribution: leadingDistribution, + locale: locale, + background: background, + foreground: foreground, + shadows: shadows, + fontFeatures: fontFeatures, + fontVariations: fontVariations, + ); } abstract class ParagraphStyle { @@ -407,39 +382,20 @@ abstract class ParagraphStyle { StrutStyle? strutStyle, String? ellipsis, Locale? locale, - }) { - if (engine.useCanvasKit) { - return engine.CkParagraphStyle( - textAlign: textAlign, - textDirection: textDirection, - maxLines: maxLines, - fontFamily: fontFamily, - fontSize: fontSize, - height: height, - textHeightBehavior: textHeightBehavior, - fontWeight: fontWeight, - fontStyle: fontStyle, - strutStyle: strutStyle, - ellipsis: ellipsis, - locale: locale, - ); - } else { - return engine.EngineParagraphStyle( - textAlign: textAlign, - textDirection: textDirection, - maxLines: maxLines, - fontFamily: fontFamily, - fontSize: fontSize, - height: height, - textHeightBehavior: textHeightBehavior, - fontWeight: fontWeight, - fontStyle: fontStyle, - strutStyle: strutStyle, - ellipsis: ellipsis, - locale: locale, - ); - } - } + }) => engine.renderer.createParagraphStyle( + textAlign: textAlign, + textDirection: textDirection, + maxLines: maxLines, + fontFamily: fontFamily, + fontSize: fontSize, + height: height, + textHeightBehavior: textHeightBehavior, + fontWeight: fontWeight, + fontStyle: fontStyle, + strutStyle: strutStyle, + ellipsis: ellipsis, + locale: locale, + ); } abstract class StrutStyle { @@ -488,33 +444,17 @@ abstract class StrutStyle { FontWeight? fontWeight, FontStyle? fontStyle, bool? forceStrutHeight, - }) { - if (engine.useCanvasKit) { - return engine.CkStrutStyle( - fontFamily: fontFamily, - fontFamilyFallback: fontFamilyFallback, - fontSize: fontSize, - height: height, - leadingDistribution: leadingDistribution, - leading: leading, - fontWeight: fontWeight, - fontStyle: fontStyle, - forceStrutHeight: forceStrutHeight, - ); - } else { - return engine.EngineStrutStyle( - fontFamily: fontFamily, - fontFamilyFallback: fontFamilyFallback, - fontSize: fontSize, - height: height, - leadingDistribution: leadingDistribution, - leading: leading, - fontWeight: fontWeight, - fontStyle: fontStyle, - forceStrutHeight: forceStrutHeight, - ); - } - } + }) => engine.renderer.createStrutStyle( + fontFamily: fontFamily, + fontFamilyFallback: fontFamilyFallback, + fontSize: fontSize, + height: height, + leadingDistribution: leadingDistribution, + leading: leading, + fontWeight: fontWeight, + fontStyle: fontStyle, + forceStrutHeight: forceStrutHeight, + ); } // The order of this enum must match the order of the values in TextDirection.h's TextDirection. @@ -579,8 +519,8 @@ class TextPosition { const TextPosition({ required this.offset, this.affinity = TextAffinity.downstream, - }) : assert(offset != null), // ignore: unnecessary_null_comparison - assert(affinity != null); // ignore: unnecessary_null_comparison + }) : assert(offset != null), + assert(affinity != null); final int offset; final TextAffinity affinity; @@ -655,7 +595,7 @@ class TextRange { class ParagraphConstraints { const ParagraphConstraints({ required this.width, - }) : assert(width != null); // ignore: unnecessary_null_comparison + }) : assert(width != null); final double width; @override @@ -733,12 +673,8 @@ abstract class Paragraph { } abstract class ParagraphBuilder { - factory ParagraphBuilder(ParagraphStyle style) { - if (engine.useCanvasKit) { - return engine.CkParagraphBuilder(style); - } - return engine.CanvasParagraphBuilder(style as engine.EngineParagraphStyle); - } + factory ParagraphBuilder(ParagraphStyle style) => + engine.renderer.createParagraphBuilder(style); void pushStyle(TextStyle style); void pop(); void addText(String text); @@ -755,14 +691,7 @@ abstract class ParagraphBuilder { }); } -Future loadFontFromList(Uint8List list, {String? fontFamily}) { - if (engine.useCanvasKit) { - return engine.skiaFontCollection - .loadFontFromList(list, fontFamily: fontFamily) - .then((_) => engine.sendFontChangeMessage()); - } else { - return engine.fontCollection - .loadFontFromList(list, fontFamily: fontFamily!) - .then((_) => engine.sendFontChangeMessage()); - } +Future loadFontFromList(Uint8List list, {String? fontFamily}) async { + await engine.renderer.fontCollection.loadFontFromList(list, fontFamily: fontFamily); + engine.sendFontChangeMessage(); } diff --git a/lib/web_ui/lib/window.dart b/lib/web_ui/lib/window.dart index 9ef1677a84dc5..bbb033b42e395 100644 --- a/lib/web_ui/lib/window.dart +++ b/lib/web_ui/lib/window.dart @@ -167,7 +167,7 @@ enum Brightness { // TODO(dit): see https://github.com/flutter/flutter/issues/33614. class CallbackHandle { CallbackHandle.fromRawHandle(this._handle) - : assert(_handle != null, "'_handle' must not be null."); // ignore: unnecessary_null_comparison + : assert(_handle != null, "'_handle' must not be null."); final int _handle; diff --git a/lib/web_ui/pubspec.yaml b/lib/web_ui/pubspec.yaml index e623366630820..8ccf08592378d 100644 --- a/lib/web_ui/pubspec.yaml +++ b/lib/web_ui/pubspec.yaml @@ -41,10 +41,5 @@ dev_dependencies: path: ../../web_sdk/web_test_utils web_engine_tester: path: ../../web_sdk/web_engine_tester - simulators: - git: - url: https://github.com/flutter/web_installers.git - path: packages/simulators/ - ref: 9afed28b771da1c4e82a3382c4a2b31344c04522 skia_gold_client: path: ../../testing/skia_gold_client diff --git a/lib/web_ui/test/canvaskit/backdrop_filter_golden_test.dart b/lib/web_ui/test/canvaskit/backdrop_filter_golden_test.dart index a8d445b50fcf5..ca1d16f6c6826 100644 --- a/lib/web_ui/test/canvaskit/backdrop_filter_golden_test.dart +++ b/lib/web_ui/test/canvaskit/backdrop_filter_golden_test.dart @@ -49,12 +49,10 @@ void testMain() { builder.pushOffset(0, 0); builder.addPicture(ui.Offset.zero, checkerboard); builder.pushBackdropFilter(ui.ImageFilter.blur(sigmaX: 10, sigmaY: 10)); - EnginePlatformDispatcher.instance.rasterizer! - .draw(builder.build().layerTree); + CanvasKitRenderer.instance.rasterizer.draw(builder.build().layerTree); await matchGoldenFile('canvaskit_backdropfilter_blur_edges.png', region: region); }); - // TODO(hterkelsen): https://github.com/flutter/flutter/issues/60040 // TODO(hterkelsen): https://github.com/flutter/flutter/issues/71520 }, skip: isSafari || isFirefox); } diff --git a/lib/web_ui/test/canvaskit/canvas_golden_test.dart b/lib/web_ui/test/canvaskit/canvas_golden_test.dart index 8b1fc7b8d92ac..a27a2e37b2d20 100644 --- a/lib/web_ui/test/canvaskit/canvas_golden_test.dart +++ b/lib/web_ui/test/canvaskit/canvas_golden_test.dart @@ -33,6 +33,7 @@ void testMain() { setUp(() { expect(notoDownloadQueue.downloader.debugActiveDownloadCount, 0); expect(notoDownloadQueue.isPending, isFalse); + FontFallbackData.debugReset(); }); tearDown(() { @@ -219,9 +220,7 @@ void testMain() { // Render again, this time with the shadow bounds. final LayerTree layerTree = buildTestScene(paintShadowBounds: true); - final EnginePlatformDispatcher dispatcher = - ui.window.platformDispatcher as EnginePlatformDispatcher; - dispatcher.rasterizer!.draw(layerTree); + CanvasKitRenderer.instance.rasterizer.draw(layerTree); await matchGoldenFile('canvaskit_shadow_bounds.png', region: region); }); @@ -565,7 +564,6 @@ void testMain() { // some of these symbols. To make sure the test produces predictable // results we reset the fallback data forcing the engine to reload // fallbacks, which for this test will only load Noto Symbols. - FontFallbackData.debugReset(); await testTextStyle( 'symbols', outerText: '← ↑ → ↓ ', @@ -802,19 +800,18 @@ void testMain() { builder.pushOffset(0, 0); builder.addPicture(ui.Offset.zero, picture); final LayerTree layerTree = builder.build().layerTree; - EnginePlatformDispatcher.instance.rasterizer!.draw(layerTree); + CanvasKitRenderer.instance.rasterizer.draw(layerTree); // Now draw an empty layer tree and confirm that the red rectangle is // no longer drawn. final LayerSceneBuilder emptySceneBuilder = LayerSceneBuilder(); emptySceneBuilder.pushOffset(0, 0); final LayerTree emptyLayerTree = emptySceneBuilder.build().layerTree; - EnginePlatformDispatcher.instance.rasterizer!.draw(emptyLayerTree); + CanvasKitRenderer.instance.rasterizer.draw(emptyLayerTree); await matchGoldenFile('canvaskit_empty_scene.png', region: const ui.Rect.fromLTRB(0, 0, 100, 100)); }); - // TODO(hterkelsen): https://github.com/flutter/flutter/issues/60040 // TODO(hterkelsen): https://github.com/flutter/flutter/issues/71520 }, skip: isSafari || isFirefox); } @@ -822,7 +819,6 @@ void testMain() { Future testSampleText(String language, String text, {ui.TextDirection textDirection = ui.TextDirection.ltr, bool write = false}) async { - FontFallbackData.debugReset(); const double testWidth = 300; double paragraphHeight = 0; final CkPicture picture = await generatePictureWhenFontsStable(() { @@ -1331,7 +1327,7 @@ Future testTextStyle( write: write, ); expect(notoDownloadQueue.debugIsLoadingFonts, isFalse); - expect(notoDownloadQueue.pendingSubsets, isEmpty); + expect(notoDownloadQueue.pendingFonts, isEmpty); expect(notoDownloadQueue.downloader.debugActiveDownloadCount, 0); } @@ -1341,7 +1337,7 @@ Future generatePictureWhenFontsStable( PictureGenerator generator) async { CkPicture picture = generator(); // Fallback fonts start downloading as a post-frame callback. - EnginePlatformDispatcher.instance.rasterizer!.debugRunPostFrameCallbacks(); + CanvasKitRenderer.instance.rasterizer.debugRunPostFrameCallbacks(); // Font downloading begins asynchronously so we inject a timer before checking the download queue. await Future.delayed(Duration.zero); while (notoDownloadQueue.isPending || @@ -1349,7 +1345,7 @@ Future generatePictureWhenFontsStable( await notoDownloadQueue.debugWhenIdle(); await notoDownloadQueue.downloader.debugWhenIdle(); picture = generator(); - EnginePlatformDispatcher.instance.rasterizer!.debugRunPostFrameCallbacks(); + CanvasKitRenderer.instance.rasterizer.debugRunPostFrameCallbacks(); // Dummy timer for the same reason as above. await Future.delayed(Duration.zero); } diff --git a/lib/web_ui/test/canvaskit/canvas_test.dart b/lib/web_ui/test/canvaskit/canvas_test.dart index d8cbb1f0a6d15..cacb5ed4c6c3f 100644 --- a/lib/web_ui/test/canvaskit/canvas_test.dart +++ b/lib/web_ui/test/canvaskit/canvas_test.dart @@ -8,7 +8,6 @@ import 'dart:async'; import 'package:test/bootstrap/browser.dart'; import 'package:test/test.dart'; -import 'package:ui/src/engine/browser_detection.dart'; import '../engine/canvas_test.dart'; import 'common.dart'; @@ -25,6 +24,5 @@ Future testMain() async { setUpCanvasKitTest(); runCanvasTests(deviceClipRoundsOut: true); - // TODO(hterkelsen): https://github.com/flutter/flutter/issues/60040 - }, skip: isIosSafari); + }); } diff --git a/lib/web_ui/test/canvaskit/canvaskit_api_test.dart b/lib/web_ui/test/canvaskit/canvaskit_api_test.dart index a320f2d8bea85..551c9fadcba7a 100644 --- a/lib/web_ui/test/canvaskit/canvaskit_api_test.dart +++ b/lib/web_ui/test/canvaskit/canvaskit_api_test.dart @@ -59,8 +59,7 @@ void testMain() { group('SkParagraph', () { _textStyleTests(); }); - // TODO(hterkelsen): https://github.com/flutter/flutter/issues/60040 - }, skip: isIosSafari); + }); } void _blendModeTests() { @@ -693,7 +692,8 @@ void _matrix4x4CompositionTests() { final bool areEqual = await fuzzyCompareImages(incrementalMatrixImage, combinedMatrixImage); expect(areEqual, true); - }); + // TODO(hterkelsen): https://github.com/flutter/flutter/issues/109265 + }, skip: isFirefox); } void _toSkRectTests() { @@ -1371,7 +1371,8 @@ void _canvasTests() { final ByteData pngData = await image.toByteData(format: ui.ImageByteFormat.png); expect(pngData.lengthInBytes, greaterThan(0)); - }); + // TODO(hterkelsen): https://github.com/flutter/flutter/issues/109265 + }, skip: isFirefox); } void _textStyleTests() { @@ -1489,7 +1490,7 @@ void _paragraphTests() { final SkParagraphStyle paragraphStyle = canvasKit.ParagraphStyle(props); final SkParagraphBuilder builder = canvasKit.ParagraphBuilder.Make( paragraphStyle, - skiaFontCollection.skFontMgr, + CanvasKitRenderer.instance.fontCollection.skFontMgr, ); builder.addText('Hello'); @@ -1586,7 +1587,7 @@ void _paragraphTests() { final SkParagraphBuilder builder = canvasKit.ParagraphBuilder.MakeFromFontProvider( paragraphStyle, - skiaFontCollection.fontProvider, + CanvasKitRenderer.instance.fontCollection.fontProvider, ); builder.addText('hello'); diff --git a/lib/web_ui/test/canvaskit/color_filter_golden_test.dart b/lib/web_ui/test/canvaskit/color_filter_golden_test.dart index 475f8fc63148f..4a5fd312e672e 100644 --- a/lib/web_ui/test/canvaskit/color_filter_golden_test.dart +++ b/lib/web_ui/test/canvaskit/color_filter_golden_test.dart @@ -19,9 +19,7 @@ const ui.Rect region = ui.Rect.fromLTRB(0, 0, 500, 250); Future matchSceneGolden(String goldenFile, LayerScene scene, {bool write = false}) async { - final EnginePlatformDispatcher dispatcher = - ui.window.platformDispatcher as EnginePlatformDispatcher; - dispatcher.rasterizer!.draw(scene.layerTree); + CanvasKitRenderer.instance.rasterizer.draw(scene.layerTree); await matchGoldenFile(goldenFile, region: region, write: write); } @@ -136,7 +134,6 @@ void testMain() { await matchSceneGolden('canvaskit_inverse_colormatrix.png', builder.build(), write: true); }); - // TODO(hterkelsen): https://github.com/flutter/flutter/issues/60040 // TODO(hterkelsen): https://github.com/flutter/flutter/issues/71520 }, skip: isSafari || isFirefox); } diff --git a/lib/web_ui/test/canvaskit/common.dart b/lib/web_ui/test/canvaskit/common.dart index 9b3275f4da4dc..149a8bc9df6ee 100644 --- a/lib/web_ui/test/canvaskit/common.dart +++ b/lib/web_ui/test/canvaskit/common.dart @@ -21,8 +21,9 @@ const MethodCodec codec = StandardMethodCodec(); /// Common test setup for all CanvasKit unit-tests. void setUpCanvasKitTest() { setUpAll(() async { - expect(useCanvasKit, true, reason: 'This test must run in CanvasKit mode.'); + expect(renderer, isA(), reason: 'This test must run in CanvasKit mode.'); debugResetBrowserSupportsFinalizationRegistry(); + debugDisableFontFallbacks = false; await initializeEngine(assetManager: WebOnlyMockAssetManager()); }); @@ -180,12 +181,10 @@ class TestCollector implements Collector { /// layers. Future matchPictureGolden(String goldenFile, CkPicture picture, {required ui.Rect region, bool write = false}) async { - final EnginePlatformDispatcher dispatcher = - ui.window.platformDispatcher as EnginePlatformDispatcher; final LayerSceneBuilder sb = LayerSceneBuilder(); sb.pushOffset(0, 0); sb.addPicture(ui.Offset.zero, picture); - dispatcher.rasterizer!.draw(sb.build().layerTree); + CanvasKitRenderer.instance.rasterizer.draw(sb.build().layerTree); await matchGoldenFile(goldenFile, region: region, maxDiffRatePercent: 0.0, write: write); } diff --git a/lib/web_ui/test/canvaskit/embedded_views_test.dart b/lib/web_ui/test/canvaskit/embedded_views_test.dart index 1e459785a4500..7cdb18c0120dd 100644 --- a/lib/web_ui/test/canvaskit/embedded_views_test.dart +++ b/lib/web_ui/test/canvaskit/embedded_views_test.dart @@ -24,18 +24,17 @@ void testMain() { }); test('embeds interactive platform views', () async { + final Rasterizer rasterizer = CanvasKitRenderer.instance.rasterizer; ui.platformViewRegistry.registerViewFactory( 'test-platform-view', (int viewId) => createDomHTMLDivElement()..id = 'view-0', ); await createPlatformView(0, 'test-platform-view'); - final EnginePlatformDispatcher dispatcher = - ui.window.platformDispatcher as EnginePlatformDispatcher; final LayerSceneBuilder sb = LayerSceneBuilder(); sb.pushOffset(0, 0); sb.addPlatformView(0, width: 10, height: 10); - dispatcher.rasterizer!.draw(sb.build().layerTree); + rasterizer.draw(sb.build().layerTree); // The platform view is now split in two parts. The contents live // as a child of the glassPane, and the slot lives in the glassPane @@ -60,20 +59,19 @@ void testMain() { }); test('clips platform views with RRects', () async { + final Rasterizer rasterizer = CanvasKitRenderer.instance.rasterizer; ui.platformViewRegistry.registerViewFactory( 'test-platform-view', (int viewId) => createDomHTMLDivElement()..id = 'view-0', ); await createPlatformView(0, 'test-platform-view'); - final EnginePlatformDispatcher dispatcher = - ui.window.platformDispatcher as EnginePlatformDispatcher; final LayerSceneBuilder sb = LayerSceneBuilder(); sb.pushOffset(0, 0); sb.pushClipRRect( ui.RRect.fromLTRBR(0, 0, 10, 10, const ui.Radius.circular(3))); sb.addPlatformView(0, width: 10, height: 10); - dispatcher.rasterizer!.draw(sb.build().layerTree); + rasterizer.draw(sb.build().layerTree); expect( flutterViewEmbedder.sceneElement! @@ -100,14 +98,13 @@ void testMain() { }); test('correctly transforms platform views', () async { + final Rasterizer rasterizer = CanvasKitRenderer.instance.rasterizer; ui.platformViewRegistry.registerViewFactory( 'test-platform-view', (int viewId) => createDomHTMLDivElement()..id = 'view-0', ); await createPlatformView(0, 'test-platform-view'); - final EnginePlatformDispatcher dispatcher = - ui.window.platformDispatcher as EnginePlatformDispatcher; final LayerSceneBuilder sb = LayerSceneBuilder(); sb.pushOffset(0, 0); final Matrix4 scaleMatrix = Matrix4.identity() @@ -116,7 +113,7 @@ void testMain() { sb.pushTransform(scaleMatrix.toFloat64()); sb.pushOffset(3, 3); sb.addPlatformView(0, width: 10, height: 10); - dispatcher.rasterizer!.draw(sb.build().layerTree); + rasterizer.draw(sb.build().layerTree); // Transformations happen on the slot element. final DomElement slotHost = flutterViewEmbedder.sceneElement! @@ -138,11 +135,9 @@ void testMain() { ); await createPlatformView(0, 'test-platform-view'); - final EnginePlatformDispatcher dispatcher = - ui.window.platformDispatcher as EnginePlatformDispatcher; final LayerSceneBuilder sb = LayerSceneBuilder(); sb.addPlatformView(0, offset: const ui.Offset(3, 4), width: 5, height: 6); - dispatcher.rasterizer!.draw(sb.build().layerTree); + CanvasKitRenderer.instance.rasterizer.draw(sb.build().layerTree); final DomElement slotHost = flutterViewEmbedder.sceneElement! .querySelector('flt-platform-view-slot')!; @@ -178,15 +173,13 @@ void testMain() { ); await createPlatformView(0, 'test-platform-view'); - final EnginePlatformDispatcher dispatcher = - ui.window.platformDispatcher as EnginePlatformDispatcher; LayerSceneBuilder sb = LayerSceneBuilder(); sb.pushOffset(3, 3); sb.pushClipRect(ui.Rect.largest); sb.pushOffset(6, 6); sb.addPlatformView(0, width: 10, height: 10); sb.pop(); - dispatcher.rasterizer!.draw(sb.build().layerTree); + CanvasKitRenderer.instance.rasterizer.draw(sb.build().layerTree); // Transformations happen on the slot element. DomElement slotHost = flutterViewEmbedder.sceneElement! @@ -207,7 +200,7 @@ void testMain() { sb.pushClipRect(ui.Rect.largest); sb.pushOffset(9, 9); sb.addPlatformView(0, width: 10, height: 10); - dispatcher.rasterizer!.draw(sb.build().layerTree); + CanvasKitRenderer.instance.rasterizer.draw(sb.build().layerTree); // Transformations happen on the slot element. slotHost = flutterViewEmbedder.sceneElement! @@ -231,14 +224,12 @@ void testMain() { ); await createPlatformView(0, 'test-platform-view'); - final EnginePlatformDispatcher dispatcher = - ui.window.platformDispatcher as EnginePlatformDispatcher; final LayerSceneBuilder sb = LayerSceneBuilder(); sb.pushOffset(1, 1); sb.pushOffset(2, 2); sb.pushOffset(3, 3); sb.addPlatformView(0, width: 10, height: 10); - dispatcher.rasterizer!.draw(sb.build().layerTree); + CanvasKitRenderer.instance.rasterizer.draw(sb.build().layerTree); // Transformations happen on the slot element. final DomElement slotHost = flutterViewEmbedder.sceneElement! @@ -258,8 +249,6 @@ void testMain() { ); await createPlatformView(0, 'test-platform-view'); - final EnginePlatformDispatcher dispatcher = - ui.window.platformDispatcher as EnginePlatformDispatcher; final LayerSceneBuilder sb = LayerSceneBuilder(); sb.pushOffset(3, 3); sb.pushClipRect(ui.Rect.largest); @@ -267,7 +256,7 @@ void testMain() { sb.pushClipRect(ui.Rect.largest); sb.pushOffset(9, 9); sb.addPlatformView(0, width: 10, height: 10); - dispatcher.rasterizer!.draw(sb.build().layerTree); + CanvasKitRenderer.instance.rasterizer.draw(sb.build().layerTree); // Transformations happen on the slot element. final DomElement slotHost = flutterViewEmbedder.sceneElement! @@ -302,9 +291,6 @@ void testMain() { platformViewIds.add(i); } - final EnginePlatformDispatcher dispatcher = - ui.window.platformDispatcher as EnginePlatformDispatcher; - void renderTestScene({required int viewCount}) { final LayerSceneBuilder sb = LayerSceneBuilder(); sb.pushOffset(0, 0); @@ -312,7 +298,7 @@ void testMain() { sb.addPicture(ui.Offset.zero, testPicture); sb.addPlatformView(i, width: 10, height: 10); } - dispatcher.rasterizer!.draw(sb.build().layerTree); + CanvasKitRenderer.instance.rasterizer.draw(sb.build().layerTree); } // Frame 1: @@ -458,9 +444,6 @@ void testMain() { platformViewIds.add(i); } - final EnginePlatformDispatcher dispatcher = - ui.window.platformDispatcher as EnginePlatformDispatcher; - void renderTestScene(List views) { final LayerSceneBuilder sb = LayerSceneBuilder(); sb.pushOffset(0, 0); @@ -468,7 +451,7 @@ void testMain() { sb.addPicture(ui.Offset.zero, testPicture); sb.addPlatformView(view, width: 10, height: 10); } - dispatcher.rasterizer!.draw(sb.build().layerTree); + CanvasKitRenderer.instance.rasterizer.draw(sb.build().layerTree); } // Frame 1: @@ -586,13 +569,10 @@ void testMain() { ); await createPlatformView(0, 'test-platform-view'); - final EnginePlatformDispatcher dispatcher = - ui.window.platformDispatcher as EnginePlatformDispatcher; - LayerSceneBuilder sb = LayerSceneBuilder(); sb.pushOffset(0, 0); sb.addPlatformView(0, width: 10, height: 10); - dispatcher.rasterizer!.draw(sb.build().layerTree); + CanvasKitRenderer.instance.rasterizer.draw(sb.build().layerTree); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, @@ -609,7 +589,7 @@ void testMain() { sb = LayerSceneBuilder(); sb.pushOffset(0, 0); - dispatcher.rasterizer!.draw(sb.build().layerTree); + CanvasKitRenderer.instance.rasterizer.draw(sb.build().layerTree); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, @@ -623,19 +603,17 @@ void testMain() { }); test('removed the DOM node of an unrendered platform view', () async { + final Rasterizer rasterizer = CanvasKitRenderer.instance.rasterizer; ui.platformViewRegistry.registerViewFactory( 'test-platform-view', (int viewId) => createDomHTMLDivElement()..id = 'view-0', ); await createPlatformView(0, 'test-platform-view'); - final EnginePlatformDispatcher dispatcher = - ui.window.platformDispatcher as EnginePlatformDispatcher; - LayerSceneBuilder sb = LayerSceneBuilder(); sb.pushOffset(0, 0); sb.addPlatformView(0, width: 10, height: 10); - dispatcher.rasterizer!.draw(sb.build().layerTree); + CanvasKitRenderer.instance.rasterizer.draw(sb.build().layerTree); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, @@ -653,7 +631,7 @@ void testMain() { sb = LayerSceneBuilder(); sb.pushOffset(0, 0); sb.addPlatformView(1, width: 10, height: 10); - dispatcher.rasterizer!.draw(sb.build().layerTree); + rasterizer.draw(sb.build().layerTree); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, @@ -669,7 +647,7 @@ void testMain() { // the platform view. sb = LayerSceneBuilder(); sb.pushOffset(0, 0); - dispatcher.rasterizer!.draw(sb.build().layerTree); + CanvasKitRenderer.instance.rasterizer.draw(sb.build().layerTree); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, ]); @@ -685,22 +663,20 @@ void testMain() { test( 'removes old SVG clip definitions from the DOM when the view is recomposited', () async { + final Rasterizer rasterizer = CanvasKitRenderer.instance.rasterizer; ui.platformViewRegistry.registerViewFactory( 'test-platform-view', (int viewId) => createDomHTMLDivElement()..id = 'test-view', ); await createPlatformView(0, 'test-platform-view'); - final EnginePlatformDispatcher dispatcher = - ui.window.platformDispatcher as EnginePlatformDispatcher; - void renderTestScene() { final LayerSceneBuilder sb = LayerSceneBuilder(); sb.pushOffset(0, 0); sb.pushClipRRect( ui.RRect.fromLTRBR(0, 0, 10, 10, const ui.Radius.circular(3))); sb.addPlatformView(0, width: 10, height: 10); - dispatcher.rasterizer!.draw(sb.build().layerTree); + rasterizer.draw(sb.build().layerTree); } final DomNode skPathDefs = flutterViewEmbedder.sceneElement! @@ -722,28 +698,27 @@ void testMain() { test('does not crash when a prerolled platform view is not composited', () async { + final Rasterizer rasterizer = CanvasKitRenderer.instance.rasterizer; ui.platformViewRegistry.registerViewFactory( 'test-platform-view', (int viewId) => createDomHTMLDivElement()..id = 'view-0', ); await createPlatformView(0, 'test-platform-view'); - final EnginePlatformDispatcher dispatcher = - ui.window.platformDispatcher as EnginePlatformDispatcher; - final LayerSceneBuilder sb = LayerSceneBuilder(); sb.pushOffset(0, 0); sb.pushClipRect(ui.Rect.zero); sb.addPlatformView(0, width: 10, height: 10); sb.pop(); // The below line should not throw an error. - dispatcher.rasterizer!.draw(sb.build().layerTree); + rasterizer.draw(sb.build().layerTree); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, ]); }); test('does not crash when overlays are disabled', () async { + final Rasterizer rasterizer = CanvasKitRenderer.instance.rasterizer; HtmlViewEmbedder.debugDisableOverlays = true; ui.platformViewRegistry.registerViewFactory( 'test-platform-view', @@ -751,15 +726,12 @@ void testMain() { ); await createPlatformView(0, 'test-platform-view'); - final EnginePlatformDispatcher dispatcher = - ui.window.platformDispatcher as EnginePlatformDispatcher; - final LayerSceneBuilder sb = LayerSceneBuilder(); sb.pushOffset(0, 0); sb.addPlatformView(0, width: 10, height: 10); sb.pop(); // The below line should not throw an error. - dispatcher.rasterizer!.draw(sb.build().layerTree); + rasterizer.draw(sb.build().layerTree); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, @@ -768,6 +740,7 @@ void testMain() { }); test('works correctly with max overlays == 2', () async { + final Rasterizer rasterizer = CanvasKitRenderer.instance.rasterizer; debugSetConfiguration(FlutterConfiguration( JsFlutterConfiguration()..canvasKitMaximumSurfaces = 2)); SurfaceFactory.instance.debugClear(); @@ -782,15 +755,12 @@ void testMain() { await createPlatformView(0, 'test-platform-view'); await createPlatformView(1, 'test-platform-view'); - final EnginePlatformDispatcher dispatcher = - ui.window.platformDispatcher as EnginePlatformDispatcher; - LayerSceneBuilder sb = LayerSceneBuilder(); sb.pushOffset(0, 0); sb.addPlatformView(0, width: 10, height: 10); sb.pop(); // The below line should not throw an error. - dispatcher.rasterizer!.draw(sb.build().layerTree); + rasterizer.draw(sb.build().layerTree); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, @@ -804,7 +774,7 @@ void testMain() { sb.addPlatformView(0, width: 10, height: 10); sb.pop(); // The below line should not throw an error. - dispatcher.rasterizer!.draw(sb.build().layerTree); + rasterizer.draw(sb.build().layerTree); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, @@ -820,6 +790,7 @@ void testMain() { test( 'correctly renders when overlays are disabled and a subset ' 'of views is used', () async { + final Rasterizer rasterizer = CanvasKitRenderer.instance.rasterizer; HtmlViewEmbedder.debugDisableOverlays = true; ui.platformViewRegistry.registerViewFactory( 'test-platform-view', @@ -828,16 +799,13 @@ void testMain() { await createPlatformView(0, 'test-platform-view'); await createPlatformView(1, 'test-platform-view'); - final EnginePlatformDispatcher dispatcher = - ui.window.platformDispatcher as EnginePlatformDispatcher; - LayerSceneBuilder sb = LayerSceneBuilder(); sb.pushOffset(0, 0); sb.addPlatformView(0, width: 10, height: 10); sb.addPlatformView(1, width: 10, height: 10); sb.pop(); // The below line should not throw an error. - dispatcher.rasterizer!.draw(sb.build().layerTree); + rasterizer.draw(sb.build().layerTree); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, @@ -849,7 +817,7 @@ void testMain() { sb.addPlatformView(1, width: 10, height: 10); sb.pop(); // The below line should not throw an error. - dispatcher.rasterizer!.draw(sb.build().layerTree); + rasterizer.draw(sb.build().layerTree); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, @@ -859,6 +827,7 @@ void testMain() { }); test('does not create overlays for invisible platform views', () async { + final Rasterizer rasterizer = CanvasKitRenderer.instance.rasterizer; ui.platformViewRegistry.registerViewFactory( 'test-visible-view', (int viewId) => @@ -877,9 +846,6 @@ void testMain() { await createPlatformView(5, 'test-invisible-view'); await createPlatformView(6, 'test-invisible-view'); - final EnginePlatformDispatcher dispatcher = - ui.window.platformDispatcher as EnginePlatformDispatcher; - expect(platformViewManager.isInvisible(0), isFalse); expect(platformViewManager.isInvisible(1), isTrue); @@ -887,7 +853,7 @@ void testMain() { sb.pushOffset(0, 0); sb.addPlatformView(1, width: 10, height: 10); sb.pop(); - dispatcher.rasterizer!.draw(sb.build().layerTree); + rasterizer.draw(sb.build().layerTree); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, @@ -899,7 +865,7 @@ void testMain() { sb.addPlatformView(0, width: 10, height: 10); sb.addPlatformView(1, width: 10, height: 10); sb.pop(); - dispatcher.rasterizer!.draw(sb.build().layerTree); + rasterizer.draw(sb.build().layerTree); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, @@ -913,7 +879,7 @@ void testMain() { sb.addPlatformView(1, width: 10, height: 10); sb.addPlatformView(2, width: 10, height: 10); sb.pop(); - dispatcher.rasterizer!.draw(sb.build().layerTree); + rasterizer.draw(sb.build().layerTree); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, @@ -930,7 +896,7 @@ void testMain() { sb.addPlatformView(2, width: 10, height: 10); sb.addPlatformView(3, width: 10, height: 10); sb.pop(); - dispatcher.rasterizer!.draw(sb.build().layerTree); + rasterizer.draw(sb.build().layerTree); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, @@ -949,7 +915,7 @@ void testMain() { sb.addPlatformView(3, width: 10, height: 10); sb.addPlatformView(4, width: 10, height: 10); sb.pop(); - dispatcher.rasterizer!.draw(sb.build().layerTree); + rasterizer.draw(sb.build().layerTree); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, @@ -970,7 +936,7 @@ void testMain() { sb.addPlatformView(4, width: 10, height: 10); sb.addPlatformView(5, width: 10, height: 10); sb.pop(); - dispatcher.rasterizer!.draw(sb.build().layerTree); + rasterizer.draw(sb.build().layerTree); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, @@ -993,7 +959,7 @@ void testMain() { sb.addPlatformView(5, width: 10, height: 10); sb.addPlatformView(6, width: 10, height: 10); sb.pop(); - dispatcher.rasterizer!.draw(sb.build().layerTree); + rasterizer.draw(sb.build().layerTree); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, @@ -1015,7 +981,7 @@ void testMain() { sb.addPlatformView(5, width: 10, height: 10); sb.addPlatformView(6, width: 10, height: 10); sb.pop(); - dispatcher.rasterizer!.draw(sb.build().layerTree); + rasterizer.draw(sb.build().layerTree); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, @@ -1033,7 +999,7 @@ void testMain() { sb.addPlatformView(3, width: 10, height: 10); sb.addPlatformView(4, width: 10, height: 10); sb.pop(); - dispatcher.rasterizer!.draw(sb.build().layerTree); + rasterizer.draw(sb.build().layerTree); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, @@ -1050,7 +1016,7 @@ void testMain() { sb.addPlatformView(2, width: 10, height: 10); sb.addPlatformView(1, width: 10, height: 10); sb.pop(); - dispatcher.rasterizer!.draw(sb.build().layerTree); + rasterizer.draw(sb.build().layerTree); _expectSceneMatches(<_EmbeddedViewMarker>[ _overlay, _platformView, @@ -1060,8 +1026,7 @@ void testMain() { _overlay, ]); }); - // TODO(dit): https://github.com/flutter/flutter/issues/60040 - }, skip: isIosSafari); + }); } // Used to test that the platform views and overlays are in the correct order in diff --git a/lib/web_ui/test/canvaskit/fallback_fonts_golden_test.dart b/lib/web_ui/test/canvaskit/fallback_fonts_golden_test.dart index e8cf7cb60d485..66ba97d7eb9fa 100644 --- a/lib/web_ui/test/canvaskit/fallback_fonts_golden_test.dart +++ b/lib/web_ui/test/canvaskit/fallback_fonts_golden_test.dart @@ -28,10 +28,20 @@ void testMain() { ui.PlatformMessageCallback? savedCallback; setUp(() { + FontFallbackData.debugReset(); notoDownloadQueue.downloader = TestDownloader(); TestDownloader.mockDownloads.clear(); + final String notoSansArabicUrl = fallbackFonts + .singleWhere((NotoFont font) => font.name == 'Noto Sans Arabic') + .url; + final String notoEmojiUrl = fallbackFonts + .singleWhere((NotoFont font) => font.name == 'Noto Emoji') + .url; + TestDownloader.mockDownloads[notoSansArabicUrl] = + '/assets/fonts/NotoNaskhArabic-Regular.ttf'; + TestDownloader.mockDownloads[notoEmojiUrl] = + '/assets/fonts/NotoColorEmoji.ttf'; savedCallback = ui.window.onPlatformMessage; - FontFallbackData.debugReset(); }); tearDown(() { @@ -42,20 +52,8 @@ void testMain() { expect(FontFallbackData.instance.globalFontFallbacks, contains('Roboto')); }); - test('will download Noto Naskh Arabic if Arabic text is added', () async { - TestDownloader.mockDownloads[ - 'https://fonts.googleapis.com/css2?family=Noto+Naskh+Arabic+UI'] = - ''' -/* arabic */ -@font-face { - font-family: 'Noto Naskh Arabic UI'; - font-style: normal; - font-weight: 400; - src: url(/assets/fonts/NotoNaskhArabic-Regular.ttf) format('ttf'); - unicode-range: U+0600-06FF, U+200C-200E, U+2010-2011, U+204F, U+2E41, U+FB50-FDFF, U+FE80-FEFC; -} -'''; - + test('will download Noto Sans Arabic if Arabic text is added', () async { + final Rasterizer rasterizer = CanvasKitRenderer.instance.rasterizer; expect(FontFallbackData.instance.globalFontFallbacks, ['Roboto']); // Creating this paragraph should cause us to start to download the @@ -65,12 +63,11 @@ void testMain() { ); pb.addText('مرحبا'); - EnginePlatformDispatcher.instance.rasterizer! - .debugRunPostFrameCallbacks(); + rasterizer.debugRunPostFrameCallbacks(); await notoDownloadQueue.debugWhenIdle(); expect(FontFallbackData.instance.globalFontFallbacks, - contains('Noto Naskh Arabic UI 0')); + contains('Noto Sans Arabic')); final CkPictureRecorder recorder = CkPictureRecorder(); final CkCanvas canvas = recorder.beginRecording(kDefaultRegion); @@ -91,34 +88,12 @@ void testMain() { recorder.endRecording(), region: kDefaultRegion, ); - // TODO(hterkelsen): https://github.com/flutter/flutter/issues/60040 // TODO(hterkelsen): https://github.com/flutter/flutter/issues/71520 }, skip: isSafari || isFirefox); test('will put the Noto Emoji font before other fallback fonts in the list', () async { - TestDownloader.mockDownloads[ - 'https://fonts.googleapis.com/css2?family=Noto+Color+Emoji+Compat'] = - ''' -@font-face { - font-family: 'Noto Color Emoji'; - src: url(/assets/fonts/NotoColorEmoji.ttf) format('ttf'); -} -'''; - - TestDownloader.mockDownloads[ - 'https://fonts.googleapis.com/css2?family=Noto+Naskh+Arabic+UI'] = - ''' -/* arabic */ -@font-face { - font-family: 'Noto Naskh Arabic UI'; - font-style: normal; - font-weight: 400; - src: url(/assets/fonts/NotoNaskhArabic-Regular.ttf) format('ttf'); - unicode-range: U+0600-06FF, U+200C-200E, U+2010-2011, U+204F, U+2E41, U+FB50-FDFF, U+FE80-FEFC; -} -'''; - + final Rasterizer rasterizer = CanvasKitRenderer.instance.rasterizer; expect(FontFallbackData.instance.globalFontFallbacks, ['Roboto']); // Creating this paragraph should cause us to start to download the @@ -128,12 +103,11 @@ void testMain() { ); pb.addText('مرحبا'); - EnginePlatformDispatcher.instance.rasterizer! - .debugRunPostFrameCallbacks(); + rasterizer.debugRunPostFrameCallbacks(); await notoDownloadQueue.debugWhenIdle(); expect(FontFallbackData.instance.globalFontFallbacks, - ['Roboto', 'Noto Naskh Arabic UI 0']); + ['Roboto', 'Noto Sans Arabic']); pb = CkParagraphBuilder( CkParagraphStyle(), @@ -144,28 +118,19 @@ void testMain() { final CkParagraph paragraph = pb.build(); paragraph.layout(const ui.ParagraphConstraints(width: 1000)); - EnginePlatformDispatcher.instance.rasterizer! - .debugRunPostFrameCallbacks(); + rasterizer.debugRunPostFrameCallbacks(); await notoDownloadQueue.debugWhenIdle(); expect(FontFallbackData.instance.globalFontFallbacks, [ 'Roboto', - 'Noto Color Emoji Compat 0', - 'Noto Naskh Arabic UI 0', + 'Noto Emoji', + 'Noto Sans Arabic', ]); }); test('will download Noto Emojis and Noto Symbols if no matching Noto Font', () async { - TestDownloader.mockDownloads[ - 'https://fonts.googleapis.com/css2?family=Noto+Color+Emoji+Compat'] = - ''' -@font-face { - font-family: 'Noto Color Emoji'; - src: url(/assets/fonts/NotoColorEmoji.ttf) format('ttf'); -} -'''; - + final Rasterizer rasterizer = CanvasKitRenderer.instance.rasterizer; expect(FontFallbackData.instance.globalFontFallbacks, ['Roboto']); // Creating this paragraph should cause us to start to download the @@ -175,12 +140,11 @@ void testMain() { ); pb.addText('Hello 😊'); - EnginePlatformDispatcher.instance.rasterizer! - .debugRunPostFrameCallbacks(); + rasterizer.debugRunPostFrameCallbacks(); await notoDownloadQueue.debugWhenIdle(); expect(FontFallbackData.instance.globalFontFallbacks, - contains('Noto Color Emoji Compat 0')); + contains('Noto Emoji')); final CkPictureRecorder recorder = CkPictureRecorder(); final CkCanvas canvas = recorder.beginRecording(kDefaultRegion); @@ -201,67 +165,37 @@ void testMain() { recorder.endRecording(), region: kDefaultRegion, ); - // TODO(hterkelsen): https://github.com/flutter/flutter/issues/60040 // TODO(hterkelsen): https://github.com/flutter/flutter/issues/71520 }, skip: isSafari || isFirefox); - test('will gracefully fail if we cannot parse the Google Fonts CSS', - () async { - TestDownloader.mockDownloads[ - 'https://fonts.googleapis.com/css2?family=Noto+Naskh+Arabic+UI'] = - 'invalid CSS... this should cause our parser to fail'; - - expect(FontFallbackData.instance.globalFontFallbacks, ['Roboto']); - - // Creating this paragraph should cause us to start to download the - // fallback font. - final CkParagraphBuilder pb = CkParagraphBuilder( - CkParagraphStyle(), - ); - pb.addText('مرحبا'); - - // Flush microtasks and test that we didn't start any downloads. - EnginePlatformDispatcher.instance.rasterizer! - .debugRunPostFrameCallbacks(); - await Future.delayed(Duration.zero); - - expect(notoDownloadQueue.isPending, isFalse); - expect(FontFallbackData.instance.globalFontFallbacks, ['Roboto']); - }); - // Regression test for https://github.com/flutter/flutter/issues/75836 // When we had this bug our font fallback resolution logic would end up in an // infinite loop and this test would freeze and time out. test( 'Can find fonts for two adjacent unmatched code units from different fonts', () async { + final Rasterizer rasterizer = CanvasKitRenderer.instance.rasterizer; final LoggingDownloader loggingDownloader = LoggingDownloader(NotoDownloader()); notoDownloadQueue.downloader = loggingDownloader; // Try rendering text that requires fallback fonts, initially before the fonts are loaded. CkParagraphBuilder(CkParagraphStyle()).addText('ヽಠ'); - EnginePlatformDispatcher.instance.rasterizer! - .debugRunPostFrameCallbacks(); - await notoDownloadQueue.downloader.debugWhenIdle(); + rasterizer.debugRunPostFrameCallbacks(); + await notoDownloadQueue.debugWhenIdle(); expect( loggingDownloader.log, [ - 'https://fonts.googleapis.com/css2?family=Noto+Sans+SC', - 'https://fonts.googleapis.com/css2?family=Noto+Sans+JP', - 'https://fonts.googleapis.com/css2?family=Noto+Sans+Kannada+UI', 'Noto Sans SC', - 'Noto Sans JP', - 'Noto Sans Kannada UI', + 'Noto Sans Kannada', ], ); // Do the same thing but this time with loaded fonts. loggingDownloader.log.clear(); CkParagraphBuilder(CkParagraphStyle()).addText('ヽಠ'); - EnginePlatformDispatcher.instance.rasterizer! - .debugRunPostFrameCallbacks(); - await notoDownloadQueue.downloader.debugWhenIdle(); + rasterizer.debugRunPostFrameCallbacks(); + await notoDownloadQueue.debugWhenIdle(); expect(loggingDownloader.log, isEmpty); }); @@ -278,7 +212,7 @@ void testMain() { FontFallbackData.instance.notoTree; for (final NotoFont font in notoTree.root.enumerateAllElements()) { testedFonts.add(font.name); - for (final CodeunitRange range in font.approximateUnicodeRanges) { + for (final CodeunitRange range in font.computeUnicodeRanges()) { for (int codeUnit = range.start; codeUnit < range.end; codeUnit += 1) { @@ -293,30 +227,146 @@ void testMain() { testedFonts, unorderedEquals({ 'Noto Sans', - 'Noto Sans Malayalam UI', + 'Noto Emoji', + 'Noto Sans Symbols', + 'Noto Sans Symbols 2', + 'Noto Sans Adlam', + 'Noto Sans Anatolian Hieroglyphs', + 'Noto Sans Arabic', 'Noto Sans Armenian', + 'Noto Sans Avestan', + 'Noto Sans Balinese', + 'Noto Sans Bamum', + 'Noto Sans Bassa Vah', + 'Noto Sans Batak', + 'Noto Sans Bengali', + 'Noto Sans Bhaiksuki', + 'Noto Sans Brahmi', + 'Noto Sans Buginese', + 'Noto Sans Buhid', + 'Noto Sans Canadian Aboriginal', + 'Noto Sans Carian', + 'Noto Sans Caucasian Albanian', + 'Noto Sans Chakma', + 'Noto Sans Cham', + 'Noto Sans Cherokee', + 'Noto Sans Coptic', + 'Noto Sans Cuneiform', + 'Noto Sans Cypriot', + 'Noto Sans Deseret', + 'Noto Sans Devanagari', + 'Noto Sans Duployan', + 'Noto Sans Egyptian Hieroglyphs', + 'Noto Sans Elbasan', + 'Noto Sans Elymaic', 'Noto Sans Georgian', + 'Noto Sans Glagolitic', + 'Noto Sans Gothic', + 'Noto Sans Grantha', + 'Noto Sans Gujarati', + 'Noto Sans Gunjala Gondi', + 'Noto Sans Gurmukhi', + 'Noto Sans HK', + 'Noto Sans Hanunoo', + 'Noto Sans Hatran', 'Noto Sans Hebrew', - 'Noto Naskh Arabic UI', - 'Noto Sans Devanagari UI', - 'Noto Sans Telugu UI', - 'Noto Sans Tamil UI', - 'Noto Sans Kannada UI', - 'Noto Sans Sinhala', - 'Noto Sans Gurmukhi UI', - 'Noto Sans Gujarati UI', - 'Noto Sans Bengali UI', - 'Noto Sans Thai UI', - 'Noto Sans Lao UI', - 'Noto Sans Myanmar UI', - 'Noto Sans Ethiopic', - 'Noto Sans Khmer UI', - 'Noto Sans SC', + 'Noto Sans Imperial Aramaic', + 'Noto Sans Indic Siyaq Numbers', + 'Noto Sans Inscriptional Pahlavi', + 'Noto Sans Inscriptional Parthian', 'Noto Sans JP', - 'Noto Sans TC', - 'Noto Sans HK', + 'Noto Sans Javanese', 'Noto Sans KR', - 'Noto Sans Egyptian Hieroglyphs', + 'Noto Sans Kaithi', + 'Noto Sans Kannada', + 'Noto Sans Kayah Li', + 'Noto Sans Kharoshthi', + 'Noto Sans Khmer', + 'Noto Sans Khojki', + 'Noto Sans Khudawadi', + 'Noto Sans Lao', + 'Noto Sans Lepcha', + 'Noto Sans Limbu', + 'Noto Sans Linear A', + 'Noto Sans Linear B', + 'Noto Sans Lisu', + 'Noto Sans Lycian', + 'Noto Sans Lydian', + 'Noto Sans Mahajani', + 'Noto Sans Malayalam', + 'Noto Sans Mandaic', + 'Noto Sans Manichaean', + 'Noto Sans Marchen', + 'Noto Sans Masaram Gondi', + 'Noto Sans Math', + 'Noto Sans Mayan Numerals', + 'Noto Sans Medefaidrin', + 'Noto Sans Meetei Mayek', + 'Noto Sans Meroitic', + 'Noto Sans Miao', + 'Noto Sans Modi', + 'Noto Sans Mongolian', + 'Noto Sans Mro', + 'Noto Sans Multani', + 'Noto Sans Myanmar', + 'Noto Sans N Ko', + 'Noto Sans Nabataean', + 'Noto Sans New Tai Lue', + 'Noto Sans Newa', + 'Noto Sans Nushu', + 'Noto Sans Ogham', + 'Noto Sans Ol Chiki', + 'Noto Sans Old Hungarian', + 'Noto Sans Old Italic', + 'Noto Sans Old North Arabian', + 'Noto Sans Old Permic', + 'Noto Sans Old Persian', + 'Noto Sans Old Sogdian', + 'Noto Sans Old South Arabian', + 'Noto Sans Old Turkic', + 'Noto Sans Oriya', + 'Noto Sans Osage', + 'Noto Sans Osmanya', + 'Noto Sans Pahawh Hmong', + 'Noto Sans Palmyrene', + 'Noto Sans Pau Cin Hau', + 'Noto Sans Phags Pa', + 'Noto Sans Phoenician', + 'Noto Sans Psalter Pahlavi', + 'Noto Sans Rejang', + 'Noto Sans Runic', + 'Noto Sans SC', + 'Noto Sans Saurashtra', + 'Noto Sans Sharada', + 'Noto Sans Shavian', + 'Noto Sans Siddham', + 'Noto Sans Sinhala', + 'Noto Sans Sogdian', + 'Noto Sans Sora Sompeng', + 'Noto Sans Soyombo', + 'Noto Sans Sundanese', + 'Noto Sans Syloti Nagri', + 'Noto Sans Syriac', + 'Noto Sans TC', + 'Noto Sans Tagalog', + 'Noto Sans Tagbanwa', + 'Noto Sans Tai Le', + 'Noto Sans Tai Tham', + 'Noto Sans Tai Viet', + 'Noto Sans Takri', + 'Noto Sans Tamil', + 'Noto Sans Tamil Supplement', + 'Noto Sans Telugu', + 'Noto Sans Thaana', + 'Noto Sans Thai', + 'Noto Sans Tifinagh', + 'Noto Sans Tirhuta', + 'Noto Sans Ugaritic', + 'Noto Sans Vai', + 'Noto Sans Wancho', + 'Noto Sans Warang Citi', + 'Noto Sans Yi', + 'Noto Sans Zanabazar Square', })); // Construct random paragraphs out of supported code units. @@ -356,28 +406,45 @@ void testMain() { } } }); - // TODO(hterkelsen): https://github.com/flutter/flutter/issues/60040 }, skip: isSafari); } class TestDownloader extends NotoDownloader { + // Where to redirect downloads to. static final Map mockDownloads = {}; @override Future downloadAsString(String url, {String? debugDescription}) async { if (mockDownloads.containsKey(url)) { - return mockDownloads[url]!; + url = mockDownloads[url]!; + final Uri uri = Uri.parse(url); + expect(uri.isScheme('http'), isFalse); + expect(uri.isScheme('https'), isFalse); + return super.downloadAsString(url); } else { return ''; } } + + @override + Future downloadAsBytes(String url, {String? debugDescription}) { + if (mockDownloads.containsKey(url)) { + url = mockDownloads[url]!; + final Uri uri = Uri.parse(url); + expect(uri.isScheme('http'), isFalse); + expect(uri.isScheme('https'), isFalse); + return super.downloadAsBytes(url); + } else { + return Future.value(Uint8List(0).buffer); + } + } } class LoggingDownloader implements NotoDownloader { - final List log = []; - LoggingDownloader(this.delegate); + final List log = []; + final NotoDownloader delegate; @override diff --git a/lib/web_ui/test/canvaskit/filter_test.dart b/lib/web_ui/test/canvaskit/filter_test.dart index c21684f18d04a..0c3d6b46dcd57 100644 --- a/lib/web_ui/test/canvaskit/filter_test.dart +++ b/lib/web_ui/test/canvaskit/filter_test.dart @@ -46,10 +46,7 @@ void testMain() { ]; } - // TODO(hterkelsen): https://github.com/flutter/flutter/issues/60040 - if (!isIosSafari) { - setUpCanvasKitTest(); - } + setUpCanvasKitTest(); group('ImageFilters', () { test('can be constructed', () { @@ -92,8 +89,7 @@ void testMain() { expect((paint.imageFilter! as ManagedSkiaObject).skiaObject, same(skiaFilter)); }); - // TODO(hterkelsen): https://github.com/flutter/flutter/issues/60040 - }, skip: isIosSafari); + }); group('MaskFilter', () { test('with 0 sigma can be set on a Paint', () { @@ -103,6 +99,5 @@ void testMain() { expect(() => paint.maskFilter = filter, isNot(throwsException)); }); - // TODO(hterkelsen): https://github.com/flutter/flutter/issues/60040 - }, skip: isIosSafari); + }); } diff --git a/lib/web_ui/test/canvaskit/flutter_tester_emulation_golden_test.dart b/lib/web_ui/test/canvaskit/flutter_tester_emulation_golden_test.dart index 455af77659743..add30b29ca95d 100644 --- a/lib/web_ui/test/canvaskit/flutter_tester_emulation_golden_test.dart +++ b/lib/web_ui/test/canvaskit/flutter_tester_emulation_golden_test.dart @@ -112,7 +112,6 @@ void testMain() { region: kDefaultRegion, ); }); - // TODO(yjbanov): https://github.com/flutter/flutter/issues/60040 // TODO(yjbanov): https://github.com/flutter/flutter/issues/71520 }, skip: isSafari || isFirefox); } diff --git a/lib/web_ui/test/canvaskit/frame_timings_test.dart b/lib/web_ui/test/canvaskit/frame_timings_test.dart index ca0ee4df79917..7cab6bc8f0247 100644 --- a/lib/web_ui/test/canvaskit/frame_timings_test.dart +++ b/lib/web_ui/test/canvaskit/frame_timings_test.dart @@ -4,7 +4,6 @@ import 'package:test/bootstrap/browser.dart'; import 'package:test/test.dart'; -import 'package:ui/src/engine/browser_detection.dart'; import '../frame_timings_common.dart'; import 'common.dart'; @@ -20,5 +19,5 @@ void testMain() { test('collects frame timings', () async { await runFrameTimingsTest(); }); - }, skip: isIosSafari); // TODO(hterkelsen): https://github.com/flutter/flutter/issues/60040 + }); } diff --git a/lib/web_ui/test/canvaskit/hot_restart_test.dart b/lib/web_ui/test/canvaskit/hot_restart_test.dart index c99bc57e6963a..b032f532e31b7 100644 --- a/lib/web_ui/test/canvaskit/hot_restart_test.dart +++ b/lib/web_ui/test/canvaskit/hot_restart_test.dart @@ -15,18 +15,16 @@ void testMain() { expect(windowFlutterCanvasKit, isNull); // First initialization should make CanvasKit available through `window`. - await initializeCanvasKit(); + await renderer.initialize(); expect(windowFlutterCanvasKit, isNotNull); // Remember the initial instance. final CanvasKit firstCanvasKitInstance = windowFlutterCanvasKit!; // Try to load CanvasKit again. - await initializeCanvasKit(); + await renderer.initialize(); // Should find the existing instance and reuse it. expect(firstCanvasKitInstance, windowFlutterCanvasKit); - - // TODO(hterkelsen): https://github.com/flutter/flutter/issues/60040 - }, skip: isIosSafari); + }); } diff --git a/lib/web_ui/test/canvaskit/image_golden_test.dart b/lib/web_ui/test/canvaskit/image_golden_test.dart index e7bda2e34465d..215af2fceb165 100644 --- a/lib/web_ui/test/canvaskit/image_golden_test.dart +++ b/lib/web_ui/test/canvaskit/image_golden_test.dart @@ -54,7 +54,6 @@ void testMain() { isTrue, ); }); - // TODO(hterkelsen): https://github.com/flutter/flutter/issues/60040 }, skip: isSafari); } @@ -511,8 +510,7 @@ void _testForImageCodecs({required bool useBrowserImageDecoder}) { canvas.drawImage(snapshot, ui.Offset.zero, CkPaint()); sb.addPicture(ui.Offset.zero, recorder.endRecording()); - final EnginePlatformDispatcher dispatcher = ui.window.platformDispatcher as EnginePlatformDispatcher; - dispatcher.rasterizer!.draw(sb.build().layerTree); + CanvasKitRenderer.instance.rasterizer.draw(sb.build().layerTree); await matchGoldenFile( 'canvaskit_read_back_decoded_image_$mode.png', region: const ui.Rect.fromLTRB(0, 0, 150, 150), @@ -522,7 +520,8 @@ void _testForImageCodecs({required bool useBrowserImageDecoder}) { image.dispose(); codec.dispose(); - }); + // TODO(hterkelsen): https://github.com/flutter/flutter/issues/109265 + }, skip: isFirefox || isSafari); // This is a regression test for the issues with transferring textures from // one GL context to another, such as: @@ -536,9 +535,6 @@ void _testForImageCodecs({required bool useBrowserImageDecoder}) { ); await createPlatformView(0, 'test-platform-view'); - final EnginePlatformDispatcher dispatcher = - ui.window.platformDispatcher as EnginePlatformDispatcher; - final ui.Codec codec = await ui.instantiateImageCodec(k4x4PngImage); final CkImage image = (await codec.getNextFrame()).image as CkImage; @@ -566,7 +562,7 @@ void _testForImageCodecs({required bool useBrowserImageDecoder}) { canvas.drawParagraph(makeSimpleText('2'), const ui.Offset(2, 2)); sb.addPicture(ui.Offset.zero, recorder.endRecording()); } - dispatcher.rasterizer!.draw(sb.build().layerTree); + CanvasKitRenderer.instance.rasterizer.draw(sb.build().layerTree); await matchGoldenFile( 'canvaskit_cross_gl_context_image_$mode.png', region: const ui.Rect.fromLTRB(0, 0, 100, 100), @@ -576,6 +572,52 @@ void _testForImageCodecs({required bool useBrowserImageDecoder}) { await disposePlatformView(0); }); + test('toImageSync with texture-backed image', () async { + final DomResponse imageResponse = await httpFetch('/test_images/mandrill_128.png'); + final Uint8List imageData = (await imageResponse.arrayBuffer() as ByteBuffer).asUint8List(); + final ui.Codec codec = await skiaInstantiateImageCodec(imageData); + final ui.FrameInfo frame = await codec.getNextFrame(); + final CkImage mandrill = frame.image as CkImage; + final ui.PictureRecorder recorder = ui.PictureRecorder(); + final ui.Canvas canvas = ui.Canvas(recorder); + canvas.drawImageRect( + mandrill, + const ui.Rect.fromLTWH(0, 0, 128, 128), + const ui.Rect.fromLTWH(0, 0, 128, 128), + ui.Paint(), + ); + final ui.Picture picture = recorder.endRecording(); + final ui.Image image = picture.toImageSync(50, 50); + + expect(image.width, 50); + expect(image.height, 50); + + final ByteData? data = await image.toByteData(); + expect(data, isNotNull); + expect(data!.lengthInBytes, 50 * 50 * 4); + expect(data.buffer.asUint32List().any((int byte) => byte != 0), isTrue); + + final LayerSceneBuilder sb = LayerSceneBuilder(); + sb.pushOffset(0, 0); + { + final CkPictureRecorder recorder = CkPictureRecorder(); + final CkCanvas canvas = recorder.beginRecording(ui.Rect.largest); + canvas.save(); + canvas.drawImage(image as CkImage, ui.Offset.zero, CkPaint()); + canvas.restore(); + sb.addPicture(ui.Offset.zero, recorder.endRecording()); + } + CanvasKitRenderer.instance.rasterizer.draw(sb.build().layerTree); + await matchGoldenFile( + 'canvaskit_picture_texture_toimage', + region: const ui.Rect.fromLTRB(0, 0, 128, 128), + maxDiffRatePercent: 0, + ); + mandrill.dispose(); + codec.dispose(); + // TODO(hterkelsen): https://github.com/flutter/flutter/issues/109265 + }, skip: isFirefox || isSafari); + test('can detect JPEG from just magic number', () async { expect( detectContentType( diff --git a/lib/web_ui/test/canvaskit/initialization_services_vs_ui_test.dart b/lib/web_ui/test/canvaskit/initialization_services_vs_ui_test.dart index 1268aa12a6b9a..31820129fb49b 100644 --- a/lib/web_ui/test/canvaskit/initialization_services_vs_ui_test.dart +++ b/lib/web_ui/test/canvaskit/initialization_services_vs_ui_test.dart @@ -39,9 +39,7 @@ void testMain() { expect(MouseCursor.instance, isNotNull); expect(KeyboardBinding.instance, isNotNull); expect(PointerBinding.instance, isNotNull); - - // TODO(hterkelsen): https://github.com/flutter/flutter/issues/60040 - }, skip: isIosSafari); + }); } DomElement? findGlassPane() { diff --git a/lib/web_ui/test/canvaskit/initialization_test.dart b/lib/web_ui/test/canvaskit/initialization_test.dart index 202983939e49f..53f3dd34674ec 100644 --- a/lib/web_ui/test/canvaskit/initialization_test.dart +++ b/lib/web_ui/test/canvaskit/initialization_test.dart @@ -22,6 +22,5 @@ void testMain() { 'canvaskit (requested explicitly)'); expect(domDocument.body!.getAttribute('flt-build-mode'), 'debug'); }); - // TODO(hterkelsen): https://github.com/flutter/flutter/issues/60040 - }, skip: isIosSafari); + }); } diff --git a/lib/web_ui/test/canvaskit/layer_test.dart b/lib/web_ui/test/canvaskit/layer_test.dart index dfc4f7ea73349..b4bc7d866b953 100644 --- a/lib/web_ui/test/canvaskit/layer_test.dart +++ b/lib/web_ui/test/canvaskit/layer_test.dart @@ -21,9 +21,6 @@ void testMain() { // Regression test for https://github.com/flutter/flutter/issues/63715 test('TransformLayer prerolls correctly', () async { - final EnginePlatformDispatcher dispatcher = - ui.window.platformDispatcher as EnginePlatformDispatcher; - final CkPicture picture = paintPicture(const ui.Rect.fromLTRB(0, 0, 60, 60), (CkCanvas canvas) { canvas.drawRect(const ui.Rect.fromLTRB(0, 0, 60, 60), @@ -42,7 +39,7 @@ void testMain() { sb.addPicture(ui.Offset.zero, picture); final LayerTree layerTree = sb.build().layerTree; - dispatcher.rasterizer!.draw(layerTree); + CanvasKitRenderer.instance.rasterizer.draw(layerTree); final ClipRectEngineLayer clipRect = layerTree.rootLayer.debugLayers.single as ClipRectEngineLayer; expect(clipRect.paintBounds, const ui.Rect.fromLTRB(15, 15, 30, 30)); @@ -55,7 +52,5 @@ void testMain() { recorder.beginRecording(ui.Rect.zero); LayerSceneBuilder().addPicture(ui.Offset.zero, recorder.endRecording()); }); - - // TODO(hterkelsen): https://github.com/flutter/flutter/issues/60040 - }, skip: isIosSafari); + }); } diff --git a/lib/web_ui/test/canvaskit/linear_gradient_golden_test.dart b/lib/web_ui/test/canvaskit/linear_gradient_golden_test.dart index 465e4030493c8..71d7351a5c6ee 100644 --- a/lib/web_ui/test/canvaskit/linear_gradient_golden_test.dart +++ b/lib/web_ui/test/canvaskit/linear_gradient_golden_test.dart @@ -21,12 +21,10 @@ const ui.Rect region = ui.Rect.fromLTRB(0, 0, 500, 250); Future matchPictureGolden(String goldenFile, CkPicture picture, {bool write = false}) async { - final EnginePlatformDispatcher dispatcher = - ui.window.platformDispatcher as EnginePlatformDispatcher; final LayerSceneBuilder sb = LayerSceneBuilder(); sb.pushOffset(0, 0); sb.addPicture(ui.Offset.zero, picture); - dispatcher.rasterizer!.draw(sb.build().layerTree); + CanvasKitRenderer.instance.rasterizer.draw(sb.build().layerTree); await matchGoldenFile(goldenFile, region: region, write: write); } @@ -101,7 +99,6 @@ void testMain() { recorder.endRecording(), ); }); - // TODO(hterkelsen): https://github.com/flutter/flutter/issues/60040 // TODO(hterkelsen): https://github.com/flutter/flutter/issues/71520 }, skip: isSafari || isFirefox); } diff --git a/lib/web_ui/test/canvaskit/path_test.dart b/lib/web_ui/test/canvaskit/path_test.dart index 5ef031f9aa9f0..c6e3985b75923 100644 --- a/lib/web_ui/test/canvaskit/path_test.dart +++ b/lib/web_ui/test/canvaskit/path_test.dart @@ -19,7 +19,7 @@ void testMain() { setUpCanvasKitTest(); test('Using CanvasKit', () { - expect(useCanvasKit, isTrue); + expect(renderer is CanvasKitRenderer, isTrue); }); test(CkPathMetrics, () { @@ -190,7 +190,5 @@ void testMain() { expect(original.getBounds(), rect1); expect(copy.getBounds(), rect1.expandToInclude(rect2)); }); - }, - skip: - isIosSafari); // TODO(hterkelsen): https://github.com/flutter/flutter/issues/60040 + }); } diff --git a/lib/web_ui/test/canvaskit/picture_test.dart b/lib/web_ui/test/canvaskit/picture_test.dart index b082d13d6e260..213d0dc4bad31 100644 --- a/lib/web_ui/test/canvaskit/picture_test.dart +++ b/lib/web_ui/test/canvaskit/picture_test.dart @@ -101,7 +101,8 @@ void testMain() { expect(data, isNotNull); expect(data!.lengthInBytes, 10 * 15 * 4); expect(data.buffer.asUint32List().first, color.value); - }); - // TODO(hterkelsen): https://github.com/flutter/flutter/issues/60040 + // TODO(hterkelsen): https://github.com/flutter/flutter/issues/109265 + }, skip: isFirefox || isSafari); + // TODO(hterkelsen): https://github.com/flutter/flutter/issues/60040 }, skip: isIosSafari); } diff --git a/lib/web_ui/test/canvaskit/platform_dispatcher_test.dart b/lib/web_ui/test/canvaskit/platform_dispatcher_test.dart index e13eff9584f92..85421d24f70ab 100644 --- a/lib/web_ui/test/canvaskit/platform_dispatcher_test.dart +++ b/lib/web_ui/test/canvaskit/platform_dispatcher_test.dart @@ -40,6 +40,5 @@ void testMain() { [true], ); }); - // TODO(hterkelsen): https://github.com/flutter/flutter/issues/60040 - }, skip: isIosSafari); + }); } diff --git a/lib/web_ui/test/canvaskit/scene_test.dart b/lib/web_ui/test/canvaskit/scene_test.dart index 2dd134a2ca5ec..422e9622f9cac 100644 --- a/lib/web_ui/test/canvaskit/scene_test.dart +++ b/lib/web_ui/test/canvaskit/scene_test.dart @@ -45,7 +45,8 @@ void testMain() { final ui.Image sceneImage = await scene.toImage(100, 100); expect(sceneImage, isA()); - }); + // TODO(hterkelsen): https://github.com/flutter/flutter/issues/109265 + }, skip: isFirefox || isSafari); test('pushColorFilter does not throw', () async { final ui.SceneBuilder builder = ui.SceneBuilder(); @@ -57,6 +58,5 @@ void testMain() { final ui.Scene scene = builder.build(); expect(scene, isNotNull); }); - // TODO(hterkelsen): https://github.com/flutter/flutter/issues/60040 - }, skip: isIosSafari); + }); } diff --git a/lib/web_ui/test/canvaskit/semantics_test.dart b/lib/web_ui/test/canvaskit/semantics_test.dart index 7053c3d7958b2..7df21aeeaac19 100644 --- a/lib/web_ui/test/canvaskit/semantics_test.dart +++ b/lib/web_ui/test/canvaskit/semantics_test.dart @@ -8,7 +8,6 @@ import 'dart:async'; import 'package:test/bootstrap/browser.dart'; import 'package:test/test.dart'; -import 'package:ui/src/engine/browser_detection.dart'; import '../engine/semantics/semantics_test.dart'; import 'common.dart'; @@ -25,6 +24,5 @@ Future testMain() async { setUpCanvasKitTest(); runSemanticsTests(); - // TODO(hterkelsen): https://github.com/flutter/flutter/issues/60040 - }, skip: isIosSafari); + }); } diff --git a/lib/web_ui/test/canvaskit/shader_mask_golden_test.dart b/lib/web_ui/test/canvaskit/shader_mask_golden_test.dart index eb53ed554bc5a..d4effcbdd5c6a 100644 --- a/lib/web_ui/test/canvaskit/shader_mask_golden_test.dart +++ b/lib/web_ui/test/canvaskit/shader_mask_golden_test.dart @@ -20,9 +20,7 @@ const ui.Rect region = ui.Rect.fromLTRB(0, 0, 500, 250); Future matchSceneGolden(String goldenFile, LayerScene scene, {bool write = false}) async { - final EnginePlatformDispatcher dispatcher = - ui.window.platformDispatcher as EnginePlatformDispatcher; - dispatcher.rasterizer!.draw(scene.layerTree); + CanvasKitRenderer.instance.rasterizer.draw(scene.layerTree); await matchGoldenFile(goldenFile, region: region, write: write); } @@ -158,7 +156,6 @@ void testMain() { await matchSceneGolden('canvaskit_shadermask_linear_translated.png', builder.build()); }); - // TODO(hterkelsen): https://github.com/flutter/flutter/issues/60040 // TODO(hterkelsen): https://github.com/flutter/flutter/issues/71520 }, skip: isSafari || isFirefox); } diff --git a/lib/web_ui/test/canvaskit/shader_test.dart b/lib/web_ui/test/canvaskit/shader_test.dart index 2f44ed7e7a837..619cf9dd196c4 100644 --- a/lib/web_ui/test/canvaskit/shader_test.dart +++ b/lib/web_ui/test/canvaskit/shader_test.dart @@ -70,9 +70,12 @@ void testMain() { Float64List.fromList(Matrix4.diagonal3Values(1, 2, 3).storage), ) as CkImageShader; expect(imageShader, isA()); + + expect(imageShader.debugDisposed, false); + imageShader.dispose(); + expect(imageShader.debugDisposed, true); }); - // TODO(hterkelsen): https://github.com/flutter/flutter/issues/60040 - }, skip: isIosSafari); + }); } const List testColors = [ui.Color(0xFFFFFF00), ui.Color(0xFFFFFFFF)]; diff --git a/lib/web_ui/test/canvaskit/skia_font_collection_test.dart b/lib/web_ui/test/canvaskit/skia_font_collection_test.dart index 76a0f56b24f27..9f6aac9a4c926 100644 --- a/lib/web_ui/test/canvaskit/skia_font_collection_test.dart +++ b/lib/web_ui/test/canvaskit/skia_font_collection_test.dart @@ -20,7 +20,7 @@ void testMain() { setUpAll(() async { ensureFlutterViewEmbedderInitialized(); - await initializeCanvasKit(); + await renderer.initialize(); oldPrintWarning = printWarning; printWarning = (String warning) { warnings.add(warning); @@ -124,6 +124,5 @@ void testMain() { // what's specified in the manifest, and the manifest takes precedence. expect(ahem.bytes.length, ahemData.lengthInBytes); }); - // TODO(hterkelsen): https://github.com/flutter/flutter/issues/60040 - }, skip: isIosSafari); + }); } diff --git a/lib/web_ui/test/canvaskit/skia_objects_cache_test.dart b/lib/web_ui/test/canvaskit/skia_objects_cache_test.dart index 0ba042dc2cc98..b58fe26cc8b30 100644 --- a/lib/web_ui/test/canvaskit/skia_objects_cache_test.dart +++ b/lib/web_ui/test/canvaskit/skia_objects_cache_test.dart @@ -21,8 +21,7 @@ void main() { void testMain() { group('skia_objects_cache', () { _tests(); - // TODO(hterkelsen): https://github.com/flutter/flutter/issues/60040 - }, skip: isIosSafari); + }); } void _tests() { @@ -39,7 +38,7 @@ void _tests() { group(ManagedSkiaObject, () { test('implements create, cache, delete, resurrect, delete lifecycle', () { final FakeRasterizer fakeRasterizer = FakeRasterizer(); - EnginePlatformDispatcher.instance.rasterizer = fakeRasterizer; + CanvasKitRenderer.instance.rasterizer = fakeRasterizer; // Trigger first create final TestSkiaObject testObject = TestSkiaObject(); @@ -317,8 +316,6 @@ void _tests() { /// /// Can be [clone]d such that the clones share the same ref counted box. class TestBoxWrapper implements StackTraceDebugger { - static int resurrectCount = 0; - TestBoxWrapper() { if (assertionsEnabled) { _debugStackTrace = StackTrace.current; @@ -337,6 +334,8 @@ class TestBoxWrapper implements StackTraceDebugger { box.ref(this); } + static int resurrectCount = 0; + @override StackTrace get debugStackTrace => _debugStackTrace; late StackTrace _debugStackTrace; @@ -393,14 +392,14 @@ class TestJsConstructor implements JsConstructor { } class TestSkiaObject extends ManagedSkiaObject { + TestSkiaObject({this.isExpensive = false}); + int createDefaultCount = 0; int resurrectCount = 0; int deleteCount = 0; final bool isExpensive; - TestSkiaObject({this.isExpensive = false}); - @override SkPaint createDefault() { createDefaultCount++; diff --git a/lib/web_ui/test/canvaskit/surface_factory_test.dart b/lib/web_ui/test/canvaskit/surface_factory_test.dart index bdefb5a9214fb..9fa98d64f0ba7 100644 --- a/lib/web_ui/test/canvaskit/surface_factory_test.dart +++ b/lib/web_ui/test/canvaskit/surface_factory_test.dart @@ -99,6 +99,5 @@ void testMain() { overlays.forEach(expectDisposed); expect(originalFactory.debugSurfaceCount, 1); }); - // TODO(hterkelsen): https://github.com/flutter/flutter/issues/60040 - }, skip: isIosSafari); + }); } diff --git a/lib/web_ui/test/canvaskit/surface_test.dart b/lib/web_ui/test/canvaskit/surface_test.dart index 1eb2721bf41ee..7253381aafa0d 100644 --- a/lib/web_ui/test/canvaskit/surface_test.dart +++ b/lib/web_ui/test/canvaskit/surface_test.dart @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// ignore_for_file: avoid_dynamic_calls - import 'package:test/bootstrap/browser.dart'; import 'package:test/test.dart'; import 'package:ui/src/engine.dart'; @@ -33,16 +31,18 @@ void testMain() { expect(original.height, 19); expect(original.style.width, '9px'); expect(original.style.height, '19px'); + expect(original.style.transform, _isTranslate(0, 0)); expect(originalSurface.width(), 9); expect(originalSurface.height(), 19); - // Shrinking reuses the existing canvas straight-up. + // Shrinking reuses the existing canvas but translates it so Skia renders into the visible area. final CkSurface shrunkSurface = surface.acquireFrame(const ui.Size(5, 15)).skiaSurface; final DomCanvasElement shrunk = surface.htmlCanvas!; expect(shrunk, same(original)); expect(shrunk.style.width, '9px'); expect(shrunk.style.height, '19px'); + expect(shrunk.style.transform, _isTranslate(0, -4)); expect(shrunkSurface, isNot(same(original))); expect(shrunkSurface.width(), 5); expect(shrunkSurface.height(), 15); @@ -60,6 +60,7 @@ void testMain() { expect(firstIncrease.height, 28); expect(firstIncrease.style.width, '14px'); expect(firstIncrease.style.height, '28px'); + expect(firstIncrease.style.transform, _isTranslate(0, -8)); expect(firstIncreaseSurface.width(), 10); expect(firstIncreaseSurface.height(), 20); @@ -68,6 +69,7 @@ void testMain() { surface.acquireFrame(const ui.Size(11, 22)).skiaSurface; final DomCanvasElement secondIncrease = surface.htmlCanvas!; expect(secondIncrease, same(firstIncrease)); + expect(secondIncrease.style.transform, _isTranslate(0, -6)); expect(secondIncreaseSurface, isNot(same(firstIncreaseSurface))); expect(secondIncreaseSurface.width(), 11); expect(secondIncreaseSurface.height(), 22); @@ -83,6 +85,7 @@ void testMain() { expect(huge.height, 56); expect(huge.style.width, '28px'); expect(huge.style.height, '56px'); + expect(huge.style.transform, _isTranslate(0, -16)); expect(hugeSurface.width(), 20); expect(hugeSurface.height(), 40); @@ -91,15 +94,33 @@ void testMain() { surface.acquireFrame(const ui.Size(5, 15)).skiaSurface; final DomCanvasElement shrunk2 = surface.htmlCanvas!; expect(shrunk2, same(huge)); + expect(shrunk2.style.width, '28px'); + expect(shrunk2.style.height, '56px'); + expect(shrunk2.style.transform, _isTranslate(0, -41)); expect(shrunkSurface2, isNot(same(hugeSurface))); expect(shrunkSurface2.width(), 5); expect(shrunkSurface2.height(), 15); + + // Doubling the DPR should halve the CSS width, height, and translation of the canvas. + // This tests https://github.com/flutter/flutter/issues/77084 + window.debugOverrideDevicePixelRatio(2.0); + final CkSurface dpr2Surface2 = + surface.acquireFrame(const ui.Size(5, 15)).skiaSurface; + final DomCanvasElement dpr2Canvas = surface.htmlCanvas!; + expect(dpr2Canvas, same(huge)); + expect(dpr2Canvas.style.width, '14px'); + expect(dpr2Canvas.style.height, '28px'); + expect(dpr2Canvas.style.transform, _isTranslate(0, -20.5)); + expect(dpr2Surface2, isNot(same(hugeSurface))); + expect(dpr2Surface2.width(), 5); + expect(dpr2Surface2.height(), 15); + // Skipping on Firefox for now since Firefox headless doesn't support WebGL // This causes issues in the test since we create a Canvas-backed surface, // which cannot be a different size from the canvas. // TODO(hterkelsen): See if we can give a custom size for software // surfaces. - }, skip: isFirefox || isIosSafari); + }, skip: isFirefox); test( 'Surface creates new context when WebGL context is restored', @@ -158,6 +179,7 @@ void testMain() { expect(original.height(), 16); expect(surface.htmlCanvas!.style.width, '10px'); expect(surface.htmlCanvas!.style.height, '16px'); + expect(surface.htmlCanvas!.style.transform, _isTranslate(0, 0)); // Increase device-pixel ratio: this makes CSS pixels bigger, so we need // fewer of them to cover the browser window. @@ -168,6 +190,7 @@ void testMain() { expect(highDpr.height(), 16); expect(surface.htmlCanvas!.style.width, '5px'); expect(surface.htmlCanvas!.style.height, '8px'); + expect(surface.htmlCanvas!.style.transform, _isTranslate(0, 0)); // Decrease device-pixel ratio: this makes CSS pixels smaller, so we need // more of them to cover the browser window. @@ -178,6 +201,7 @@ void testMain() { expect(lowDpr.height(), 16); expect(surface.htmlCanvas!.style.width, '20px'); expect(surface.htmlCanvas!.style.height, '32px'); + expect(surface.htmlCanvas!.style.transform, _isTranslate(0, 0)); // See https://github.com/flutter/flutter/issues/77084#issuecomment-1120151172 window.debugOverrideDevicePixelRatio(2.0); @@ -187,6 +211,26 @@ void testMain() { expect(changeRatioAndSize.height(), 16); expect(surface.htmlCanvas!.style.width, '5px'); expect(surface.htmlCanvas!.style.height, '8px'); + expect(surface.htmlCanvas!.style.transform, _isTranslate(0, 0)); }); - }, skip: isIosSafari); + }); +} + +/// Checks that the CSS 'transform' property is a translation in a cross-browser way. +/// +/// Assumes that the `x` and `y` values are round enough for their `toString` values +/// to match the stringified CSS length value. +Matcher _isTranslate(double x, double y) { + // When the y coordinate is zero, Firefox omits it, e.g.: + // Chrome/Safari/Edge: translate(0px, 0px) + // Firefox: translate(0px) + final String fullFormat = 'translate(${x}px, ${y}px)'; + if (y != 0) { + return equals(fullFormat); + } else { + return anyOf( + fullFormat, // Non-Firefox browsers use this format. + 'translate(${x}px)', // Firefox omits y when it's zero. + ); + } } diff --git a/lib/web_ui/test/canvaskit/sweep_gradient_golden_test.dart b/lib/web_ui/test/canvaskit/sweep_gradient_golden_test.dart index 53af0dbc8505e..cfd351dbb8e6f 100644 --- a/lib/web_ui/test/canvaskit/sweep_gradient_golden_test.dart +++ b/lib/web_ui/test/canvaskit/sweep_gradient_golden_test.dart @@ -21,12 +21,10 @@ const ui.Rect region = ui.Rect.fromLTRB(0, 0, 500, 250); Future matchPictureGolden(String goldenFile, CkPicture picture, {bool write = false}) async { - final EnginePlatformDispatcher dispatcher = - ui.window.platformDispatcher as EnginePlatformDispatcher; final LayerSceneBuilder sb = LayerSceneBuilder(); sb.pushOffset(0, 0); sb.addPicture(ui.Offset.zero, picture); - dispatcher.rasterizer!.draw(sb.build().layerTree); + CanvasKitRenderer.instance.rasterizer.draw(sb.build().layerTree); await matchGoldenFile(goldenFile, region: region, write: write); } @@ -68,7 +66,6 @@ void testMain() { recorder.endRecording(), ); }); - // TODO(hterkelsen): https://github.com/flutter/flutter/issues/60040 // TODO(hterkelsen): https://github.com/flutter/flutter/issues/71520 }, skip: isSafari || isFirefox); } diff --git a/lib/web_ui/test/canvaskit/text_test.dart b/lib/web_ui/test/canvaskit/text_test.dart index 4b6bb478bac32..ec5621731a14a 100644 --- a/lib/web_ui/test/canvaskit/text_test.dart +++ b/lib/web_ui/test/canvaskit/text_test.dart @@ -4,7 +4,6 @@ import 'package:test/bootstrap/browser.dart'; import 'package:test/test.dart'; -import 'package:ui/src/engine/browser_detection.dart'; import 'package:ui/ui.dart' as ui; import 'common.dart'; @@ -66,6 +65,5 @@ void testMain() { // because the directionality of the 'h' is LTR. expect(boxes.single.direction, equals(ui.TextDirection.ltr)); }); - // TODO(hterkelsen): https://github.com/flutter/flutter/issues/60040 - }, skip: isIosSafari); + }); } diff --git a/lib/web_ui/test/canvaskit/vertices_test.dart b/lib/web_ui/test/canvaskit/vertices_test.dart index 0db4f8f31fd7b..dc0c0110f3556 100644 --- a/lib/web_ui/test/canvaskit/vertices_test.dart +++ b/lib/web_ui/test/canvaskit/vertices_test.dart @@ -36,8 +36,7 @@ void testMain() { ); vertices.delete(); }); - // TODO(hterkelsen): https://github.com/flutter/flutter/issues/60040 - }, skip: isIosSafari); + }); test('Vertices are not anti-aliased by default', () async { const ui.Rect region = ui.Rect.fromLTRB(0, 0, 500, 500); @@ -60,11 +59,9 @@ void testMain() { final LayerSceneBuilder builder = LayerSceneBuilder(); builder.pushOffset(0, 0); builder.addPicture(ui.Offset.zero, verticesPicture); - EnginePlatformDispatcher.instance.rasterizer! + CanvasKitRenderer.instance.rasterizer .draw(builder.build().layerTree); await matchGoldenFile('canvaskit_vertices_antialiased.png', region: region); - - // TODO(hterkelsen): https://github.com/flutter/flutter/issues/60040 }, skip: isSafari); } diff --git a/lib/web_ui/test/engine/frame_reference_test.dart b/lib/web_ui/test/engine/frame_reference_test.dart index e241d62fd38aa..5e3c37ed1bc17 100644 --- a/lib/web_ui/test/engine/frame_reference_test.dart +++ b/lib/web_ui/test/engine/frame_reference_test.dart @@ -75,6 +75,6 @@ void testMain() { } class TestItem { - final String label; TestItem(this.label); + final String label; } diff --git a/lib/web_ui/test/engine/image_to_byte_data_test.dart b/lib/web_ui/test/engine/image_to_byte_data_test.dart index 56d94e56dca71..991c3e80c8853 100644 --- a/lib/web_ui/test/engine/image_to_byte_data_test.dart +++ b/lib/web_ui/test/engine/image_to_byte_data_test.dart @@ -16,8 +16,8 @@ void main() { Future testMain() async { setUpAll(() async { await webOnlyInitializePlatform(); - fontCollection.debugRegisterTestFonts(); - await fontCollection.ensureFontsLoaded(); + renderer.fontCollection.debugRegisterTestFonts(); + await renderer.fontCollection.ensureFontsLoaded(); }); Future createTestImageByColor(Color color) async { diff --git a/lib/web_ui/test/engine/platform_views/message_handler_test.dart b/lib/web_ui/test/engine/platform_views/message_handler_test.dart index 9698a25502ca8..429779ac09a34 100644 --- a/lib/web_ui/test/engine/platform_views/message_handler_test.dart +++ b/lib/web_ui/test/engine/platform_views/message_handler_test.dart @@ -154,7 +154,7 @@ class _FakePlatformViewManager extends PlatformViewManager { _FakePlatformViewManager(void Function(int) clearFunction) : _clearPlatformView = clearFunction; - void Function(int) _clearPlatformView; + final void Function(int) _clearPlatformView; @override void clearPlatformView(int viewId) { diff --git a/lib/web_ui/test/engine/pointer_binding_test.dart b/lib/web_ui/test/engine/pointer_binding_test.dart index f73dd87419a29..44ff17a0017e9 100644 --- a/lib/web_ui/test/engine/pointer_binding_test.dart +++ b/lib/web_ui/test/engine/pointer_binding_test.dart @@ -36,9 +36,6 @@ void _testEach( } } -/// Some methods in this class are skipped for iOS-Safari. -// TODO(mdebbar): https://github.com/flutter/flutter/issues/60033 - void main() { internalBootstrapBrowserTest(() => testMain); } @@ -54,13 +51,18 @@ void testMain() { }); test('ios workaround', () { + debugEmulateIosSafari = true; + addTearDown(() { + debugEmulateIosSafari = false; + }); + final MockSafariPointerEventWorkaround mockSafariPointer = MockSafariPointerEventWorkaround(); SafariPointerEventWorkaround.instance = mockSafariPointer; final PointerBinding instance = PointerBinding(createDomHTMLDivElement()); expect(mockSafariPointer.workAroundInvoked, isIosSafari); instance.dispose(); - }, skip: !isIosSafari); + }, skip: !isSafari); test('_PointerEventContext generates expected events', () { if (!_PointerEventContext().isSupported) { @@ -2497,7 +2499,7 @@ class _TouchEventContext extends _BasicEventContext @override bool get hasMouseEvents => false; - DomEventTarget _target; + final DomEventTarget _target; DomTouch _createTouch({ int? identifier, diff --git a/lib/web_ui/test/engine/semantics/semantics_test.dart b/lib/web_ui/test/engine/semantics/semantics_test.dart index 907d365a03d7a..84051cc5dd787 100644 --- a/lib/web_ui/test/engine/semantics/semantics_test.dart +++ b/lib/web_ui/test/engine/semantics/semantics_test.dart @@ -1927,11 +1927,7 @@ void _testPlatformView() { expect(shadowRoot.elementFromPoint(10, 50), child3); semantics().semanticsEnabled = false; - // TODO(yjbanov): unable to debug this test on iOS Safari as hacking on a - // Linux machine. iOS Safari returns getBoundingClientRect - // values that are half of desktop browsers, possibly due to - // devicePixelRatio but need to confirm. - }, skip: isIosSafari); + }); } /// A facade in front of [ui.SemanticsUpdateBuilder.updateNode] that diff --git a/lib/web_ui/test/engine/semantics/semantics_tester.dart b/lib/web_ui/test/engine/semantics/semantics_tester.dart index 293785d8da313..04d5d19d1ec41 100644 --- a/lib/web_ui/test/engine/semantics/semantics_tester.dart +++ b/lib/web_ui/test/engine/semantics/semantics_tester.dart @@ -380,17 +380,6 @@ DomElement? findScrollable() { /// Logs semantics actions dispatched to [ui.window]. class SemanticsActionLogger { - late StreamController _idLogController; - late StreamController _actionLogController; - - /// Semantics object ids that dispatched the actions. - Stream get idLog => _idLog; - late Stream _idLog; - - /// The actions that were dispatched to [ui.window]. - Stream get actionLog => _actionLog; - late Stream _actionLog; - SemanticsActionLogger() { _idLogController = StreamController(); _actionLogController = StreamController(); @@ -411,4 +400,15 @@ class SemanticsActionLogger { }); }; } + + late StreamController _idLogController; + late StreamController _actionLogController; + + /// Semantics object ids that dispatched the actions. + Stream get idLog => _idLog; + late Stream _idLog; + + /// The actions that were dispatched to [ui.window]. + Stream get actionLog => _actionLog; + late Stream _actionLog; } diff --git a/lib/web_ui/test/engine/semantics/text_field_test.dart b/lib/web_ui/test/engine/semantics/text_field_test.dart index d57763df97ba4..0f2c2bd713e63 100644 --- a/lib/web_ui/test/engine/semantics/text_field_test.dart +++ b/lib/web_ui/test/engine/semantics/text_field_test.dart @@ -122,7 +122,7 @@ void testMain() { expect(domDocument.activeElement, flutterViewEmbedder.glassPaneElement); expect(appHostNode.activeElement, strategy.domElement); expect(textField.editableElement, strategy.domElement); - expect((textField.editableElement as dynamic).value, 'hello'); // ignore: avoid_dynamic_calls + expect((textField.editableElement as dynamic).value, 'hello'); expect(textField.editableElement.getAttribute('aria-label'), 'greeting'); expect(textField.editableElement.style.width, '10px'); expect(textField.editableElement.style.height, '15px'); @@ -137,7 +137,7 @@ void testMain() { expect(domDocument.activeElement, domDocument.body); expect(appHostNode.activeElement, null); expect(strategy.domElement, null); - expect((textField.editableElement as dynamic).value, 'bye'); // ignore: avoid_dynamic_calls + expect((textField.editableElement as dynamic).value, 'bye'); expect(textField.editableElement.getAttribute('aria-label'), 'farewell'); expect(textField.editableElement.style.width, '12px'); expect(textField.editableElement.style.height, '17px'); diff --git a/lib/web_ui/test/engine/surface/path/path_winding_test.dart b/lib/web_ui/test/engine/surface/path/path_winding_test.dart index a6f788a509ca5..6c1dc06169a11 100644 --- a/lib/web_ui/test/engine/surface/path/path_winding_test.dart +++ b/lib/web_ui/test/engine/surface/path/path_winding_test.dart @@ -442,10 +442,10 @@ void testMain() { } class LineTestCase { + LineTestCase(this.pathContent, this.convexity, this.direction); final String pathContent; final int convexity; final int? direction; - LineTestCase(this.pathContent, this.convexity, this.direction); } /// Parses a string of the format "mx my lx1 ly1 lx2 ly2..." into a path diff --git a/lib/web_ui/test/engine/surface/surface_test.dart b/lib/web_ui/test/engine/surface/surface_test.dart index 189b37041f0bc..702ada6b5db38 100644 --- a/lib/web_ui/test/engine/surface/surface_test.dart +++ b/lib/web_ui/test/engine/surface/surface_test.dart @@ -378,10 +378,10 @@ void testMain() { } class _LoggingTestSurface extends PersistedContainerSurface { - final List log = []; - _LoggingTestSurface() : super(null); + final List log = []; + @override void build() { log.add('build'); diff --git a/lib/web_ui/test/hash_codes_test.dart b/lib/web_ui/test/hash_codes_test.dart index 9d4f24786de4f..7f56a727b6811 100644 --- a/lib/web_ui/test/hash_codes_test.dart +++ b/lib/web_ui/test/hash_codes_test.dart @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// ignore_for_file: deprecated_member_use_from_same_package - import 'package:test/bootstrap/browser.dart'; import 'package:test/test.dart'; import 'package:ui/ui.dart'; diff --git a/lib/web_ui/test/html/canvas_clip_path_golden_test.dart b/lib/web_ui/test/html/canvas_clip_path_golden_test.dart index d31b314908210..e98fde4ec8e66 100644 --- a/lib/web_ui/test/html/canvas_clip_path_golden_test.dart +++ b/lib/web_ui/test/html/canvas_clip_path_golden_test.dart @@ -23,8 +23,8 @@ Future testMain() async { setUpAll(() async { debugEmulateFlutterTesterEnvironment = true; await webOnlyInitializePlatform(); - engine.fontCollection.debugRegisterTestFonts(); - await engine.fontCollection.ensureFontsLoaded(); + engine.renderer.fontCollection.debugRegisterTestFonts(); + await engine.renderer.fontCollection.ensureFontsLoaded(); }); // Regression test for https://github.com/flutter/flutter/issues/48683 diff --git a/lib/web_ui/test/html/canvas_context_golden_test.dart b/lib/web_ui/test/html/canvas_context_golden_test.dart index bb26e08a00d06..6cdf7003564b6 100644 --- a/lib/web_ui/test/html/canvas_context_golden_test.dart +++ b/lib/web_ui/test/html/canvas_context_golden_test.dart @@ -53,8 +53,8 @@ Future testMain() async { setUpAll(() async { debugEmulateFlutterTesterEnvironment = true; await webOnlyInitializePlatform(); - engine.fontCollection.debugRegisterTestFonts(); - await engine.fontCollection.ensureFontsLoaded(); + engine.renderer.fontCollection.debugRegisterTestFonts(); + await engine.renderer.fontCollection.ensureFontsLoaded(); }); // Regression test for https://github.com/flutter/flutter/issues/49429 diff --git a/lib/web_ui/test/html/canvas_reuse_golden_test.dart b/lib/web_ui/test/html/canvas_reuse_golden_test.dart index a14ded8c8a62f..b0b3b6606d81b 100644 --- a/lib/web_ui/test/html/canvas_reuse_golden_test.dart +++ b/lib/web_ui/test/html/canvas_reuse_golden_test.dart @@ -25,8 +25,8 @@ Future testMain() async { setUp(() async { debugEmulateFlutterTesterEnvironment = true; await webOnlyInitializePlatform(); - fontCollection.debugRegisterTestFonts(); - await fontCollection.ensureFontsLoaded(); + renderer.fontCollection.debugRegisterTestFonts(); + await renderer.fontCollection.ensureFontsLoaded(); }); // Regression test for https://github.com/flutter/flutter/issues/51514 diff --git a/lib/web_ui/test/html/compositing/backdrop_filter_golden_test.dart b/lib/web_ui/test/html/compositing/backdrop_filter_golden_test.dart index 36f654dbeceb9..734f26f308221 100644 --- a/lib/web_ui/test/html/compositing/backdrop_filter_golden_test.dart +++ b/lib/web_ui/test/html/compositing/backdrop_filter_golden_test.dart @@ -16,8 +16,8 @@ void main() { Future testMain() async { setUpAll(() async { await webOnlyInitializePlatform(); - fontCollection.debugRegisterTestFonts(); - await fontCollection.ensureFontsLoaded(); + renderer.fontCollection.debugRegisterTestFonts(); + await renderer.fontCollection.ensureFontsLoaded(); }); setUp(() async { diff --git a/lib/web_ui/test/html/compositing/canvas_blend_golden_test.dart b/lib/web_ui/test/html/compositing/canvas_blend_golden_test.dart index f5c2d9a60f3dc..74b54110fff1e 100644 --- a/lib/web_ui/test/html/compositing/canvas_blend_golden_test.dart +++ b/lib/web_ui/test/html/compositing/canvas_blend_golden_test.dart @@ -20,8 +20,8 @@ Future testMain() async { setUpAll(() async { debugEmulateFlutterTesterEnvironment = true; await webOnlyInitializePlatform(); - fontCollection.debugRegisterTestFonts(); - await fontCollection.ensureFontsLoaded(); + renderer.fontCollection.debugRegisterTestFonts(); + await renderer.fontCollection.ensureFontsLoaded(); }); test('Blend circles with difference and color', () async { diff --git a/lib/web_ui/test/html/compositing/canvas_image_blend_mode_golden_test.dart b/lib/web_ui/test/html/compositing/canvas_image_blend_mode_golden_test.dart index d7810f3be3d78..ae2baaea12fbd 100644 --- a/lib/web_ui/test/html/compositing/canvas_image_blend_mode_golden_test.dart +++ b/lib/web_ui/test/html/compositing/canvas_image_blend_mode_golden_test.dart @@ -24,8 +24,8 @@ Future testMain() async { setUpAll(() async { debugEmulateFlutterTesterEnvironment = true; await webOnlyInitializePlatform(); - fontCollection.debugRegisterTestFonts(); - await fontCollection.ensureFontsLoaded(); + renderer.fontCollection.debugRegisterTestFonts(); + await renderer.fontCollection.ensureFontsLoaded(); }); const Color red = Color(0xFFFF0000); diff --git a/lib/web_ui/test/html/compositing/canvas_mask_filter_golden_test.dart b/lib/web_ui/test/html/compositing/canvas_mask_filter_golden_test.dart index 8c593c4fcbf29..24ab2bf8156c6 100644 --- a/lib/web_ui/test/html/compositing/canvas_mask_filter_golden_test.dart +++ b/lib/web_ui/test/html/compositing/canvas_mask_filter_golden_test.dart @@ -20,8 +20,8 @@ Future testMain() async { setUpAll(() async { ui.debugEmulateFlutterTesterEnvironment = true; await ui.webOnlyInitializePlatform(); - fontCollection.debugRegisterTestFonts(); - await fontCollection.ensureFontsLoaded(); + renderer.fontCollection.debugRegisterTestFonts(); + await renderer.fontCollection.ensureFontsLoaded(); }); tearDown(() { diff --git a/lib/web_ui/test/html/compositing/color_filter_golden_test.dart b/lib/web_ui/test/html/compositing/color_filter_golden_test.dart index df768f3f129b7..094d06a768c6a 100644 --- a/lib/web_ui/test/html/compositing/color_filter_golden_test.dart +++ b/lib/web_ui/test/html/compositing/color_filter_golden_test.dart @@ -20,8 +20,8 @@ void main() { Future testMain() async { setUpAll(() async { await webOnlyInitializePlatform(); - fontCollection.debugRegisterTestFonts(); - await fontCollection.ensureFontsLoaded(); + renderer.fontCollection.debugRegisterTestFonts(); + await renderer.fontCollection.ensureFontsLoaded(); }); setUp(() async { diff --git a/lib/web_ui/test/html/compositing/compositing_golden_test.dart b/lib/web_ui/test/html/compositing/compositing_golden_test.dart index 761a1243fba50..5f81799bdf9be 100644 --- a/lib/web_ui/test/html/compositing/compositing_golden_test.dart +++ b/lib/web_ui/test/html/compositing/compositing_golden_test.dart @@ -21,8 +21,8 @@ void main() { Future testMain() async { setUpAll(() async { await ui.webOnlyInitializePlatform(); - fontCollection.debugRegisterTestFonts(); - await fontCollection.ensureFontsLoaded(); + renderer.fontCollection.debugRegisterTestFonts(); + await renderer.fontCollection.ensureFontsLoaded(); }); setUp(() async { diff --git a/lib/web_ui/test/html/compositing/dom_mask_filter_golden_test.dart b/lib/web_ui/test/html/compositing/dom_mask_filter_golden_test.dart index ca29e4efbc42a..fb0ed2b01eedf 100644 --- a/lib/web_ui/test/html/compositing/dom_mask_filter_golden_test.dart +++ b/lib/web_ui/test/html/compositing/dom_mask_filter_golden_test.dart @@ -20,8 +20,8 @@ Future testMain() async { setUp(() async { debugEmulateFlutterTesterEnvironment = true; await webOnlyInitializePlatform(); - fontCollection.debugRegisterTestFonts(); - await fontCollection.ensureFontsLoaded(); + renderer.fontCollection.debugRegisterTestFonts(); + await renderer.fontCollection.ensureFontsLoaded(); }); test('Should blur rectangles based on sigma.', () async { diff --git a/lib/web_ui/test/html/drawing/canvas_draw_color_golden_test.dart b/lib/web_ui/test/html/drawing/canvas_draw_color_golden_test.dart index b79a6dd9bb433..8201c2e808a6f 100644 --- a/lib/web_ui/test/html/drawing/canvas_draw_color_golden_test.dart +++ b/lib/web_ui/test/html/drawing/canvas_draw_color_golden_test.dart @@ -18,8 +18,8 @@ Future testMain() async { debugShowClipLayers = true; SurfaceSceneBuilder.debugForgetFrameScene(); await webOnlyInitializePlatform(); - fontCollection.debugRegisterTestFonts(); - await fontCollection.ensureFontsLoaded(); + renderer.fontCollection.debugRegisterTestFonts(); + await renderer.fontCollection.ensureFontsLoaded(); }); tearDown(() { diff --git a/lib/web_ui/test/html/drawing/canvas_draw_picture_golden_test.dart b/lib/web_ui/test/html/drawing/canvas_draw_picture_golden_test.dart index ae5330f39159b..c304c21c4033f 100644 --- a/lib/web_ui/test/html/drawing/canvas_draw_picture_golden_test.dart +++ b/lib/web_ui/test/html/drawing/canvas_draw_picture_golden_test.dart @@ -21,8 +21,8 @@ Future testMain() async { setUpAll(() async { debugShowClipLayers = true; await webOnlyInitializePlatform(); - fontCollection.debugRegisterTestFonts(); - await fontCollection.ensureFontsLoaded(); + renderer.fontCollection.debugRegisterTestFonts(); + await renderer.fontCollection.ensureFontsLoaded(); }); setUp(() async { diff --git a/lib/web_ui/test/html/drawing/draw_vertices_golden_test.dart b/lib/web_ui/test/html/drawing/draw_vertices_golden_test.dart index bb981854e2f0b..8bb1d8de0d74e 100644 --- a/lib/web_ui/test/html/drawing/draw_vertices_golden_test.dart +++ b/lib/web_ui/test/html/drawing/draw_vertices_golden_test.dart @@ -60,8 +60,8 @@ Future testMain() async { setUpAll(() async { debugEmulateFlutterTesterEnvironment = true; await webOnlyInitializePlatform(); - fontCollection.debugRegisterTestFonts(); - await fontCollection.ensureFontsLoaded(); + renderer.fontCollection.debugRegisterTestFonts(); + await renderer.fontCollection.ensureFontsLoaded(); }); setUp(() { @@ -393,6 +393,10 @@ Future testMain() async { rc.drawVertices(vertices as SurfaceVertices, BlendMode.srcOver, paint); await checkScreenshot(rc, filename, maxDiffRatePercent: 1.0); + + expect(imgShader.debugDisposed, false); + imgShader.dispose(); + expect(imgShader.debugDisposed, true); } test('Should draw triangle with texture and indices', () async { diff --git a/lib/web_ui/test/html/paragraph/text_scuba.dart b/lib/web_ui/test/html/paragraph/text_scuba.dart index 4a0ccaef76a55..bdbaa03822b59 100644 --- a/lib/web_ui/test/html/paragraph/text_scuba.dart +++ b/lib/web_ui/test/html/paragraph/text_scuba.dart @@ -14,11 +14,11 @@ import 'package:web_engine_tester/golden_tester.dart'; /// /// (For Googlers: Not really related with internal Scuba anymore) class EngineScubaTester { + EngineScubaTester(this.viewportSize); + /// The size of the browser window used in this scuba test. final ui.Size viewportSize; - EngineScubaTester(this.viewportSize); - static Future initialize( {ui.Size viewportSize = const ui.Size(2400, 1800)}) async { assert(() { @@ -134,7 +134,7 @@ CanvasParagraph paragraph( void setUpStableTestFonts() { setUpAll(() async { await ui.webOnlyInitializePlatform(); - fontCollection.debugRegisterTestFonts(); - await fontCollection.ensureFontsLoaded(); + renderer.fontCollection.debugRegisterTestFonts(); + await renderer.fontCollection.ensureFontsLoaded(); }); } diff --git a/lib/web_ui/test/html/path_metrics_golden_test.dart b/lib/web_ui/test/html/path_metrics_golden_test.dart index 0ee1464beaf55..7caeb31235315 100644 --- a/lib/web_ui/test/html/path_metrics_golden_test.dart +++ b/lib/web_ui/test/html/path_metrics_golden_test.dart @@ -52,8 +52,8 @@ Future testMain() async { setUpAll(() async { debugEmulateFlutterTesterEnvironment = true; await webOnlyInitializePlatform(); - fontCollection.debugRegisterTestFonts(); - await fontCollection.ensureFontsLoaded(); + renderer.fontCollection.debugRegisterTestFonts(); + await renderer.fontCollection.ensureFontsLoaded(); }); test('Should calculate tangent on line', () async { diff --git a/lib/web_ui/test/html/path_to_svg_golden_test.dart b/lib/web_ui/test/html/path_to_svg_golden_test.dart index 41e02c915c557..5dae71c1984ae 100644 --- a/lib/web_ui/test/html/path_to_svg_golden_test.dart +++ b/lib/web_ui/test/html/path_to_svg_golden_test.dart @@ -210,14 +210,14 @@ DomElement pathToSvgElement(Path path, Paint paint, bool enableFill) { } class ArcSample { + ArcSample(this.offset, + {this.largeArc = false, this.clockwise = false, this.distance = 0}); + final Offset offset; final bool largeArc; final bool clockwise; final double distance; - ArcSample(this.offset, - {this.largeArc = false, this.clockwise = false, this.distance = 0}); - Path createPath() { final Offset startP = Offset(75 - distance + offset.dx, 75 - distance + offset.dy); diff --git a/lib/web_ui/test/html/path_transform_golden_test.dart b/lib/web_ui/test/html/path_transform_golden_test.dart index f0b322b90d4e1..1b173b67b5bce 100644 --- a/lib/web_ui/test/html/path_transform_golden_test.dart +++ b/lib/web_ui/test/html/path_transform_golden_test.dart @@ -51,8 +51,8 @@ Future testMain() async { setUpAll(() async { debugEmulateFlutterTesterEnvironment = true; await webOnlyInitializePlatform(); - fontCollection.debugRegisterTestFonts(); - await fontCollection.ensureFontsLoaded(); + renderer.fontCollection.debugRegisterTestFonts(); + await renderer.fontCollection.ensureFontsLoaded(); }); test('Should draw transformed line.', () async { diff --git a/lib/web_ui/test/html/screenshot.dart b/lib/web_ui/test/html/screenshot.dart index a99bc89f14530..b3c3f294a90f7 100644 --- a/lib/web_ui/test/html/screenshot.dart +++ b/lib/web_ui/test/html/screenshot.dart @@ -70,7 +70,7 @@ Future sceneScreenshot(SurfaceSceneBuilder sceneBuilder, String fileName, void setUpStableTestFonts() { setUpAll(() async { await ui.webOnlyInitializePlatform(); - fontCollection.debugRegisterTestFonts(); - await fontCollection.ensureFontsLoaded(); + renderer.fontCollection.debugRegisterTestFonts(); + await renderer.fontCollection.ensureFontsLoaded(); }); } diff --git a/lib/web_ui/test/html/shaders/image_shader_golden_test.dart b/lib/web_ui/test/html/shaders/image_shader_golden_test.dart index 212f580c51882..55a5276c829de 100644 --- a/lib/web_ui/test/html/shaders/image_shader_golden_test.dart +++ b/lib/web_ui/test/html/shaders/image_shader_golden_test.dart @@ -27,8 +27,8 @@ Future testMain() async { setUpAll(() async { debugEmulateFlutterTesterEnvironment = true; await webOnlyInitializePlatform(); - fontCollection.debugRegisterTestFonts(); - await fontCollection.ensureFontsLoaded(); + renderer.fontCollection.debugRegisterTestFonts(); + await renderer.fontCollection.ensureFontsLoaded(); }); void drawShapes(RecordingCanvas rc, SurfacePaint paint, Rect shaderRect) { diff --git a/lib/web_ui/test/html/shaders/linear_gradient_golden_test.dart b/lib/web_ui/test/html/shaders/linear_gradient_golden_test.dart index d9ab10bc06180..df62522299fa8 100644 --- a/lib/web_ui/test/html/shaders/linear_gradient_golden_test.dart +++ b/lib/web_ui/test/html/shaders/linear_gradient_golden_test.dart @@ -25,8 +25,8 @@ Future testMain() async { setUpAll(() async { debugEmulateFlutterTesterEnvironment = true; await webOnlyInitializePlatform(); - fontCollection.debugRegisterTestFonts(); - await fontCollection.ensureFontsLoaded(); + renderer.fontCollection.debugRegisterTestFonts(); + await renderer.fontCollection.ensureFontsLoaded(); }); test('Should draw linear gradient using rectangle.', () async { diff --git a/lib/web_ui/test/html/shaders/radial_gradient_golden_test.dart b/lib/web_ui/test/html/shaders/radial_gradient_golden_test.dart index c0f18d55e1cad..e8324b12a2ab2 100644 --- a/lib/web_ui/test/html/shaders/radial_gradient_golden_test.dart +++ b/lib/web_ui/test/html/shaders/radial_gradient_golden_test.dart @@ -17,8 +17,8 @@ Future testMain() async { setUpAll(() async { debugEmulateFlutterTesterEnvironment = true; await webOnlyInitializePlatform(); - fontCollection.debugRegisterTestFonts(); - await fontCollection.ensureFontsLoaded(); + renderer.fontCollection.debugRegisterTestFonts(); + await renderer.fontCollection.ensureFontsLoaded(); }); Future testGradient(String fileName, Shader shader, diff --git a/lib/web_ui/test/html/shaders/shader_mask_golden_test.dart b/lib/web_ui/test/html/shaders/shader_mask_golden_test.dart index 717adc1266936..2fcceba1fc939 100644 --- a/lib/web_ui/test/html/shaders/shader_mask_golden_test.dart +++ b/lib/web_ui/test/html/shaders/shader_mask_golden_test.dart @@ -41,8 +41,8 @@ Future testMain() async { scene.remove(); } initWebGl(); - fontCollection.debugRegisterTestFonts(); - await fontCollection.ensureFontsLoaded(); + renderer.fontCollection.debugRegisterTestFonts(); + await renderer.fontCollection.ensureFontsLoaded(); }); /// Should render the picture unmodified. diff --git a/lib/web_ui/test/mock_engine_canvas.dart b/lib/web_ui/test/mock_engine_canvas.dart index 39eb200081981..59c4c81680372 100644 --- a/lib/web_ui/test/mock_engine_canvas.dart +++ b/lib/web_ui/test/mock_engine_canvas.dart @@ -34,7 +34,7 @@ class MockEngineCanvas implements EngineCanvas { @override DomElement get rootElement => _rootElement; - DomElement _rootElement = createDomHTMLDivElement(); + final DomElement _rootElement = createDomHTMLDivElement(); void _called(String methodName, {dynamic arguments}) { methodCallLog.add(MockCanvasCall._( diff --git a/lib/web_ui/test/text/font_loading_test.dart b/lib/web_ui/test/text/font_loading_test.dart index f0d9b97d3dfee..07dc6d933db0d 100644 --- a/lib/web_ui/test/text/font_loading_test.dart +++ b/lib/web_ui/test/text/font_loading_test.dart @@ -97,7 +97,7 @@ Future testMain() async { fontFamily: 'Blehm'); final Completer completer = Completer(); domWindow.requestAnimationFrame(allowInterop((_) { completer.complete();}) ); - await (completer.future); // ignore: unnecessary_parenthesis + await completer.future; window.onPlatformMessage = oldHandler; expect(actualName, 'flutter/system'); expect(message, '{"type":"fontsChange"}'); diff --git a/lib/web_ui/test/text/line_breaker_test.dart b/lib/web_ui/test/text/line_breaker_test.dart index c5c6b6083875e..3b218552b829e 100644 --- a/lib/web_ui/test/text/line_breaker_test.dart +++ b/lib/web_ui/test/text/line_breaker_test.dart @@ -263,7 +263,7 @@ void testMain() { result.index, i, reason: 'Failed at test case number $t:\n' - '${testCase.toString()}\n' + '$testCase\n' '"$text"\n' '\nExpected line break at {$lastLineBreak - $i} but found line break at {$lastLineBreak - ${result.index}}.', ); @@ -283,7 +283,7 @@ void testMain() { result.index, greaterThan(i), reason: 'Failed at test case number $t:\n' - '${testCase.toString()}\n' + '$testCase\n' '"$text"\n' '\nUnexpected line break found at {$lastLineBreak - ${result.index}}.', ); diff --git a/lib/web_ui/test/text_test.dart b/lib/web_ui/test/text_test.dart index d1f8f3fbf6130..442e44bf1b630 100644 --- a/lib/web_ui/test/text_test.dart +++ b/lib/web_ui/test/text_test.dart @@ -352,4 +352,16 @@ Future testMain() async { equals('hello')); }); }); + + test('FontWeights have the correct value', () { + expect(FontWeight.w100.value, 100); + expect(FontWeight.w200.value, 200); + expect(FontWeight.w300.value, 300); + expect(FontWeight.w400.value, 400); + expect(FontWeight.w500.value, 500); + expect(FontWeight.w600.value, 600); + expect(FontWeight.w700.value, 700); + expect(FontWeight.w800.value, 800); + expect(FontWeight.w900.value, 900); + }); } diff --git a/lib/web_ui/test/window_test.dart b/lib/web_ui/test/window_test.dart index f8f8748abfcea..c310f0f9ef125 100644 --- a/lib/web_ui/test/window_test.dart +++ b/lib/web_ui/test/window_test.dart @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// ignore_for_file: avoid_dynamic_calls - import 'dart:async'; import 'dart:js_util' as js_util; @@ -189,7 +187,6 @@ void testMain() { await window.handleNavigationMessage( const JSONMethodCodec().encodeMethodCall(const MethodCall( 'routeInformationUpdated', - // ignore: prefer_const_literals_to_create_immutables { 'location': '/baz', 'state': null, @@ -220,7 +217,6 @@ void testMain() { 'flutter/navigation', const JSONMethodCodec().encodeMethodCall(const MethodCall( 'routeUpdated', - // ignore: prefer_const_literals_to_create_immutables {'routeName': '/bar'}, )), (_) { callback.complete(); }, @@ -235,7 +231,6 @@ void testMain() { 'flutter/navigation', const JSONMethodCodec().encodeMethodCall(const MethodCall( 'routeInformationUpdated', - // ignore: prefer_const_literals_to_create_immutables { 'location': '/baz', 'state': null, @@ -251,7 +246,6 @@ void testMain() { await window.handleNavigationMessage( const JSONMethodCodec().encodeMethodCall(const MethodCall( 'routeUpdated', - // ignore: prefer_const_literals_to_create_immutables {'routeName': '/foo'}, )) ); @@ -273,7 +267,6 @@ void testMain() { 'flutter/navigation', const JSONMethodCodec().encodeMethodCall(const MethodCall( 'routeInformationUpdated', - // ignore: prefer_const_literals_to_create_immutables { 'location': '/baz', 'state': { @@ -313,7 +306,6 @@ void testMain() { 'flutter/navigation', const JSONMethodCodec().encodeMethodCall(const MethodCall( 'routeInformationUpdated', - // ignore: prefer_const_literals_to_create_immutables { 'location': '/baz', 'state': '/state', @@ -330,7 +322,6 @@ void testMain() { 'flutter/navigation', const JSONMethodCodec().encodeMethodCall(const MethodCall( 'routeInformationUpdated', - // ignore: prefer_const_literals_to_create_immutables { 'location': '/baz', 'state': '/state1', @@ -348,7 +339,6 @@ void testMain() { 'flutter/navigation', const JSONMethodCodec().encodeMethodCall(const MethodCall( 'routeInformationUpdated', - // ignore: prefer_const_literals_to_create_immutables { 'location': '/foo', 'state': '/foostate1', @@ -378,7 +368,6 @@ void testMain() { 'flutter/navigation', const JSONMethodCodec().encodeMethodCall(const MethodCall( 'routeUpdated', - // ignore: prefer_const_literals_to_create_immutables {'routeName': '/bar'}, )), (_) { callback.complete(); }, @@ -404,7 +393,6 @@ void testMain() { 'flutter/navigation', const JSONMethodCodec().encodeMethodCall(const MethodCall( 'routeInformationUpdated', - // ignore: prefer_const_literals_to_create_immutables { 'location': '/baz', 'state': null, diff --git a/runtime/runtime_delegate.h b/runtime/runtime_delegate.h index 540e47dd09ba4..ad680677338e0 100644 --- a/runtime/runtime_delegate.h +++ b/runtime/runtime_delegate.h @@ -24,7 +24,7 @@ class RuntimeDelegate { virtual void ScheduleFrame(bool regenerate_layer_tree = true) = 0; - virtual void Render(std::unique_ptr layer_tree) = 0; + virtual void Render(std::shared_ptr layer_tree) = 0; virtual void UpdateSemantics(SemanticsNodeUpdates update, CustomAccessibilityActionUpdates actions) = 0; diff --git a/runtime/service_protocol.cc b/runtime/service_protocol.cc index 3a9c0d6e36bab..0f25565ada9b8 100644 --- a/runtime/service_protocol.cc +++ b/runtime/service_protocol.cc @@ -40,6 +40,8 @@ const std::string_view const std::string_view ServiceProtocol::kRenderFrameWithRasterStatsExtensionName = "_flutter.renderFrameWithRasterStats"; +const std::string_view ServiceProtocol::kReloadAssetFonts = + "_flutter.reloadAssetFonts"; static constexpr std::string_view kViewIdPrefx = "_flutterView/"; static constexpr std::string_view kListViewsExtensionName = @@ -60,6 +62,7 @@ ServiceProtocol::ServiceProtocol() kGetSkSLsExtensionName, kEstimateRasterCacheMemoryExtensionName, kRenderFrameWithRasterStatsExtensionName, + kReloadAssetFonts, }), handlers_mutex_(fml::SharedMutex::Create()) {} diff --git a/runtime/service_protocol.h b/runtime/service_protocol.h index e9df73d4d5648..dc3a4d186e1ca 100644 --- a/runtime/service_protocol.h +++ b/runtime/service_protocol.h @@ -30,6 +30,7 @@ class ServiceProtocol { static const std::string_view kGetSkSLsExtensionName; static const std::string_view kEstimateRasterCacheMemoryExtensionName; static const std::string_view kRenderFrameWithRasterStatsExtensionName; + static const std::string_view kReloadAssetFonts; class Handler { public: diff --git a/shell/common/BUILD.gn b/shell/common/BUILD.gn index 22bde213f94cb..40320c9c76c67 100644 --- a/shell/common/BUILD.gn +++ b/shell/common/BUILD.gn @@ -229,16 +229,13 @@ if (enable_unittests) { ":shell_unittests_gpu_configuration_config", ] - # SwiftShader only supports x86/x86_64 - if (target_cpu == "x86" || target_cpu == "x64") { - if (test_enable_gl) { - sources += [ - "shell_test_platform_view_gl.cc", - "shell_test_platform_view_gl.h", - ] - - public_deps += [ "//flutter/testing:opengl" ] - } + if (test_enable_gl) { + sources += [ + "shell_test_platform_view_gl.cc", + "shell_test_platform_view_gl.h", + ] + + public_deps += [ "//flutter/testing:opengl" ] } if (test_enable_vulkan) { diff --git a/shell/common/animator.cc b/shell/common/animator.cc index 31bd0cdfb5eb5..d8614dc22f69c 100644 --- a/shell/common/animator.cc +++ b/shell/common/animator.cc @@ -144,7 +144,7 @@ void Animator::BeginFrame( } } -void Animator::Render(std::unique_ptr layer_tree) { +void Animator::Render(std::shared_ptr layer_tree) { has_rendered_ = true; last_layer_tree_size_ = layer_tree->frame_size(); diff --git a/shell/common/animator.h b/shell/common/animator.h index d822935307ac8..6550452ba481e 100644 --- a/shell/common/animator.h +++ b/shell/common/animator.h @@ -54,7 +54,7 @@ class Animator final { void RequestFrame(bool regenerate_layer_tree = true); - void Render(std::unique_ptr layer_tree); + void Render(std::shared_ptr layer_tree); const std::weak_ptr GetVsyncWaiter() const; diff --git a/shell/common/animator_unittests.cc b/shell/common/animator_unittests.cc index 628d7eb9afbc0..91c2bad1b033f 100644 --- a/shell/common/animator_unittests.cc +++ b/shell/common/animator_unittests.cc @@ -157,7 +157,7 @@ TEST_F(ShellTest, AnimatorDoesNotNotifyIdleBeforeRender) { [&] { ASSERT_FALSE(delegate.notify_idle_called_); auto layer_tree = - std::make_unique(SkISize::Make(600, 800), 1.0); + std::make_shared(SkISize::Make(600, 800), 1.0); animator->Render(std::move(layer_tree)); task_runners.GetPlatformTaskRunner()->PostTask(flush_vsync_task); }, @@ -240,7 +240,7 @@ TEST_F(ShellTest, AnimatorDoesNotNotifyDelegateIfPipelineIsNotEmpty) { PostTaskSync(task_runners.GetUITaskRunner(), [&] { auto layer_tree = - std::make_unique(SkISize::Make(600, 800), 1.0); + std::make_shared(SkISize::Make(600, 800), 1.0); animator->Render(std::move(layer_tree)); }); } diff --git a/shell/common/engine.cc b/shell/common/engine.cc index 9a0e5d03e4883..c2cbc9f512b98 100644 --- a/shell/common/engine.cc +++ b/shell/common/engine.cc @@ -439,7 +439,7 @@ void Engine::ScheduleFrame(bool regenerate_layer_tree) { animator_->RequestFrame(regenerate_layer_tree); } -void Engine::Render(std::unique_ptr layer_tree) { +void Engine::Render(std::shared_ptr layer_tree) { if (!layer_tree) { return; } diff --git a/shell/common/engine.h b/shell/common/engine.h index 491ec4f0e5fe3..04b968f106c41 100644 --- a/shell/common/engine.h +++ b/shell/common/engine.h @@ -879,7 +879,7 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate { std::string DefaultRouteName() override; // |RuntimeDelegate| - void Render(std::unique_ptr layer_tree) override; + void Render(std::shared_ptr layer_tree) override; // |RuntimeDelegate| void UpdateSemantics(SemanticsNodeUpdates update, diff --git a/shell/common/engine_unittests.cc b/shell/common/engine_unittests.cc index 62674947f9e87..6dfc93c555c36 100644 --- a/shell/common/engine_unittests.cc +++ b/shell/common/engine_unittests.cc @@ -48,7 +48,7 @@ class MockRuntimeDelegate : public RuntimeDelegate { public: MOCK_METHOD0(DefaultRouteName, std::string()); MOCK_METHOD1(ScheduleFrame, void(bool)); - MOCK_METHOD1(Render, void(std::unique_ptr)); + MOCK_METHOD1(Render, void(std::shared_ptr)); MOCK_METHOD2(UpdateSemantics, void(SemanticsNodeUpdates, CustomAccessibilityActionUpdates)); MOCK_METHOD1(HandlePlatformMessage, void(std::unique_ptr)); diff --git a/shell/common/fixtures/shell_test.dart b/shell/common/fixtures/shell_test.dart index b8908bdd24563..a41715add9a68 100644 --- a/shell/common/fixtures/shell_test.dart +++ b/shell/common/fixtures/shell_test.dart @@ -339,8 +339,28 @@ Future toImageSync() async { expect(image.width, 20); expect(image.height, 25); - final ByteData data = (await image.toByteData())!; - expect(data.lengthInBytes, 20 * 25 * 4); - expect(data.buffer.asUint32List().every((int byte) => byte == 0xFFAAAAAA), true); + final ByteData dataBefore = (await image.toByteData())!; + expect(dataBefore.lengthInBytes, 20 * 25 * 4); + for (final int byte in dataBefore.buffer.asUint32List()) { + expect(byte, 0xFFAAAAAA); + } + + // Cause the rasterizer to get torn down. + notifyNative(); + + final ByteData dataAfter = (await image.toByteData())!; + expect(dataAfter.lengthInBytes, 20 * 25 * 4); + for (final int byte in dataAfter.buffer.asUint32List()) { + expect(byte, 0xFFAAAAAA); + } + + // Verify that the image can be drawn successfully. + final PictureRecorder recorder2 = PictureRecorder(); + final Canvas canvas2 = Canvas(recorder2); + canvas2.drawImage(image, Offset.zero, Paint()); + final Picture picture2 = recorder2.endRecording(); + + picture.dispose(); + picture2.dispose(); notifyNative(); } diff --git a/shell/common/pipeline.h b/shell/common/pipeline.h index 35f90f39f2c6a..f9546d0816125 100644 --- a/shell/common/pipeline.h +++ b/shell/common/pipeline.h @@ -222,11 +222,11 @@ class Pipeline { }; struct LayerTreeItem { - LayerTreeItem(std::unique_ptr layer_tree, + LayerTreeItem(std::shared_ptr layer_tree, std::unique_ptr frame_timings_recorder) : layer_tree(std::move(layer_tree)), frame_timings_recorder(std::move(frame_timings_recorder)) {} - std::unique_ptr layer_tree; + std::shared_ptr layer_tree; std::unique_ptr frame_timings_recorder; }; diff --git a/shell/common/rasterizer.cc b/shell/common/rasterizer.cc index d121b90527fab..f9d10fd3e2240 100644 --- a/shell/common/rasterizer.cc +++ b/shell/common/rasterizer.cc @@ -141,8 +141,12 @@ void Rasterizer::NotifyLowMemoryWarning() const { context->performDeferredCleanup(std::chrono::milliseconds(0)); } -flutter::TextureRegistry* Rasterizer::GetTextureRegistry() { - return &compositor_context_->texture_registry(); +std::shared_ptr Rasterizer::GetTextureRegistry() { + return compositor_context_->texture_registry(); +} + +GrDirectContext* Rasterizer::GetGrContext() { + return surface_ ? surface_->GetContext() : nullptr; } flutter::LayerTree* Rasterizer::GetLastLayerTree() { @@ -181,7 +185,7 @@ RasterStatus Rasterizer::Draw(std::shared_ptr pipeline, RasterStatus raster_status = RasterStatus::kFailed; LayerTreePipeline::Consumer consumer = [&](std::unique_ptr item) { - std::unique_ptr layer_tree = std::move(item->layer_tree); + std::shared_ptr layer_tree = std::move(item->layer_tree); std::unique_ptr frame_timings_recorder = std::move(item->frame_timings_recorder); if (discard_callback(*layer_tree.get())) { @@ -248,7 +252,7 @@ bool Rasterizer::ShouldResubmitFrame(const RasterStatus& raster_status) { } namespace { -std::pair, std::string> MakeBitmapImage( +std::unique_ptr MakeBitmapImage( sk_sp display_list, const SkImageInfo& image_info) { FML_DCHECK(display_list); @@ -258,8 +262,10 @@ std::pair, std::string> MakeBitmapImage( // This limit is taken from the Metal specification. D3D, Vulkan, and GL // generally have lower limits. if (image_info.width() > 16384 || image_info.height() > 16384) { - return {nullptr, "unable to create render target at specified size"}; - } + return std::make_unique( + GrBackendTexture(), nullptr, nullptr, + "unable to create render target at specified size"); + }; sk_sp surface = SkSurface::MakeRaster(image_info); SkCanvas* canvas = surface->getCanvas(); @@ -267,18 +273,26 @@ std::pair, std::string> MakeBitmapImage( display_list->RenderTo(canvas); sk_sp image = surface->makeImageSnapshot(); - return {image, image ? "" : "Unable to create image"}; + return std::make_unique( + GrBackendTexture(), nullptr, image, + image ? "" : "Unable to create image"); } } // namespace -std::pair, std::string> Rasterizer::MakeGpuImage( +std::unique_ptr Rasterizer::MakeGpuImage( sk_sp display_list, - SkISize picture_size) { + const SkImageInfo& image_info) { TRACE_EVENT0("flutter", "Rasterizer::MakeGpuImage"); FML_DCHECK(display_list); - const SkImageInfo image_info = SkImageInfo::MakeN32Premul(picture_size); - std::pair, std::string> result; +// TODO(dnfield): the Linux embedding is in a rough state right now and +// I can't seem to get the GPU path working on it. +// https://github.com/flutter/flutter/issues/108835 +#if FML_OS_LINUX + return MakeBitmapImage(std::move(display_list), image_info); +#endif + + std::unique_ptr result; delegate_.GetIsGpuDisabledSyncSwitch()->Execute( fml::SyncSwitch::Handlers() .SetIfTrue([&result, &image_info, &display_list] { @@ -299,11 +313,23 @@ std::pair, std::string> Rasterizer::MakeGpuImage( return; } - sk_sp sk_surface = SkSurface::MakeRenderTarget( - context, SkBudgeted::kYes, image_info); + GrBackendTexture texture = context->createBackendTexture( + image_info.width(), image_info.height(), image_info.colorType(), + GrMipmapped::kNo, GrRenderable::kYes); + if (!texture.isValid()) { + result = std::make_unique( + GrBackendTexture(), nullptr, nullptr, + "unable to create render target at specified size"); + return; + } + + sk_sp sk_surface = SkSurface::MakeFromBackendTexture( + context, texture, kTopLeft_GrSurfaceOrigin, /*sampleCnt=*/0, + image_info.colorType(), image_info.refColorSpace(), nullptr); if (!sk_surface) { - result = {nullptr, - "unable to create render target at specified size"}; + result = std::make_unique( + GrBackendTexture(), nullptr, nullptr, + "unable to create rendering surface for image"); return; } @@ -311,8 +337,8 @@ std::pair, std::string> Rasterizer::MakeGpuImage( canvas->clear(SK_ColorTRANSPARENT); display_list->RenderTo(canvas); - sk_sp image = sk_surface->makeImageSnapshot(); - result = {image, image ? "" : "Unable to create image"}; + result = std::make_unique( + texture, sk_ref_sp(context), nullptr, ""); })); return result; } @@ -466,7 +492,7 @@ fml::Milliseconds Rasterizer::GetFrameBudget() const { RasterStatus Rasterizer::DoDraw( std::unique_ptr frame_timings_recorder, - std::unique_ptr layer_tree) { + std::shared_ptr layer_tree) { TRACE_EVENT_WITH_FRAME_NUMBER(frame_timings_recorder, "flutter", "Rasterizer::DoDraw"); FML_DCHECK(delegate_.GetTaskRunners() @@ -641,7 +667,7 @@ RasterStatus Rasterizer::DrawToSurfaceUnsafe( frame_timings_recorder.RecordRasterStart(fml::TimePoint::Now()); std::unique_ptr damage; - // when leaf layer tracing is enabled we wish to repaing the whole frame for + // when leaf layer tracing is enabled we wish to repaint the whole frame for // accurate performance metrics. if (frame->framebuffer_info().supports_partial_repaint && !layer_tree.is_leaf_layer_tracing_enabled()) { diff --git a/shell/common/rasterizer.h b/shell/common/rasterizer.h index 425b8214e5bd4..7052b664e1791 100644 --- a/shell/common/rasterizer.h +++ b/shell/common/rasterizer.h @@ -26,6 +26,7 @@ #include "flutter/shell/common/pipeline.h" #include "flutter/shell/common/snapshot_surface_producer.h" #include "third_party/skia/include/core/SkImage.h" +#include "third_party/skia/include/gpu/GrDirectContext.h" namespace flutter { @@ -212,16 +213,10 @@ class Rasterizer final : public SnapshotDelegate, void DrawLastLayerTree( std::unique_ptr frame_timings_recorder); - //---------------------------------------------------------------------------- - /// @brief Gets the registry of external textures currently in use by the - /// rasterizer. These textures may be updated at a cadence - /// different from that of the Flutter application. When an - /// external texture is referenced in the Flutter layer tree, that - /// texture is composited within the Flutter layer tree. - /// - /// @return A pointer to the external texture registry. - /// - flutter::TextureRegistry* GetTextureRegistry(); + // |SnapshotDelegate| + GrDirectContext* GetGrContext() override; + + std::shared_ptr GetTextureRegistry() override; using LayerTreeDiscardCallback = std::function; @@ -470,9 +465,9 @@ class Rasterizer final : public SnapshotDelegate, private: // |SnapshotDelegate| - std::pair, std::string> MakeGpuImage( + std::unique_ptr MakeGpuImage( sk_sp display_list, - SkISize picture_size) override; + const SkImageInfo& image_info) override; // |SnapshotDelegate| sk_sp MakeRasterSnapshot( @@ -504,7 +499,7 @@ class Rasterizer final : public SnapshotDelegate, RasterStatus DoDraw( std::unique_ptr frame_timings_recorder, - std::unique_ptr layer_tree); + std::shared_ptr layer_tree); RasterStatus DrawToSurface(FrameTimingsRecorder& frame_timings_recorder, flutter::LayerTree& layer_tree); @@ -523,11 +518,11 @@ class Rasterizer final : public SnapshotDelegate, std::unique_ptr snapshot_surface_producer_; std::unique_ptr compositor_context_; // This is the last successfully rasterized layer tree. - std::unique_ptr last_layer_tree_; + std::shared_ptr last_layer_tree_; // Set when we need attempt to rasterize the layer tree again. This layer_tree // has not successfully rasterized. This can happen due to the change in the // thread configuration. This will be inserted to the front of the pipeline. - std::unique_ptr resubmitted_layer_tree_; + std::shared_ptr resubmitted_layer_tree_; std::unique_ptr resubmitted_recorder_; fml::closure next_frame_callback_; bool user_override_resource_cache_bytes_; diff --git a/shell/common/rasterizer_unittests.cc b/shell/common/rasterizer_unittests.cc index 4ae9beb937396..af14fcc1675cf 100644 --- a/shell/common/rasterizer_unittests.cc +++ b/shell/common/rasterizer_unittests.cc @@ -66,7 +66,8 @@ class MockExternalViewEmbedder : public ExternalViewEmbedder { PostPrerollResult( fml::RefPtr raster_thread_merger)); MOCK_METHOD0(GetCurrentCanvases, std::vector()); - MOCK_METHOD1(CompositeEmbeddedView, SkCanvas*(int view_id)); + MOCK_METHOD0(GetCurrentBuilders, std::vector()); + MOCK_METHOD1(CompositeEmbeddedView, EmbedderPaintContext(int view_id)); MOCK_METHOD2(SubmitFrame, void(GrDirectContext* context, std::unique_ptr frame)); @@ -175,7 +176,7 @@ TEST(RasterizerTest, fml::AutoResetWaitableEvent latch; thread_host.raster_thread->GetTaskRunner()->PostTask([&] { auto pipeline = std::make_shared(/*depth=*/10); - auto layer_tree = std::make_unique(/*frame_size=*/SkISize(), + auto layer_tree = std::make_shared(/*frame_size=*/SkISize(), /*device_pixel_ratio=*/2.0f); auto layer_tree_item = std::make_unique( std::move(layer_tree), CreateFinishedBuildRecorder()); @@ -237,7 +238,7 @@ TEST( fml::AutoResetWaitableEvent latch; thread_host.raster_thread->GetTaskRunner()->PostTask([&] { auto pipeline = std::make_shared(/*depth=*/10); - auto layer_tree = std::make_unique(/*frame_size=*/SkISize(), + auto layer_tree = std::make_shared(/*frame_size=*/SkISize(), /*device_pixel_ratio=*/2.0f); auto layer_tree_item = std::make_unique( std::move(layer_tree), CreateFinishedBuildRecorder()); @@ -305,7 +306,7 @@ TEST( rasterizer->Setup(std::move(surface)); auto pipeline = std::make_shared(/*depth=*/10); - auto layer_tree = std::make_unique(/*frame_size=*/SkISize(), + auto layer_tree = std::make_shared(/*frame_size=*/SkISize(), /*device_pixel_ratio=*/2.0f); auto layer_tree_item = std::make_unique( std::move(layer_tree), CreateFinishedBuildRecorder()); @@ -375,7 +376,7 @@ TEST(RasterizerTest, rasterizer->Setup(std::move(surface)); auto pipeline = std::make_shared(/*depth=*/10); - auto layer_tree = std::make_unique(/*frame_size=*/SkISize(), + auto layer_tree = std::make_shared(/*frame_size=*/SkISize(), /*device_pixel_ratio=*/2.0f); auto layer_tree_item = std::make_unique( std::move(layer_tree), CreateFinishedBuildRecorder()); @@ -422,7 +423,7 @@ TEST(RasterizerTest, externalViewEmbedderDoesntEndFrameWhenNoSurfaceIsSet) { fml::AutoResetWaitableEvent latch; thread_host.raster_thread->GetTaskRunner()->PostTask([&] { auto pipeline = std::make_shared(/*depth=*/10); - auto layer_tree = std::make_unique(/*frame_size=*/SkISize(), + auto layer_tree = std::make_shared(/*frame_size=*/SkISize(), /*device_pixel_ratio=*/2.0f); auto layer_tree_item = std::make_unique( std::move(layer_tree), CreateFinishedBuildRecorder()); @@ -475,7 +476,7 @@ TEST(RasterizerTest, externalViewEmbedderDoesntEndFrameWhenNotUsedThisFrame) { fml::AutoResetWaitableEvent latch; thread_host.raster_thread->GetTaskRunner()->PostTask([&] { auto pipeline = std::make_shared(/*depth=*/10); - auto layer_tree = std::make_unique(/*frame_size=*/SkISize(), + auto layer_tree = std::make_shared(/*frame_size=*/SkISize(), /*device_pixel_ratio=*/2.0f); auto layer_tree_item = std::make_unique( std::move(layer_tree), CreateFinishedBuildRecorder()); @@ -572,7 +573,7 @@ TEST(RasterizerTest, fml::AutoResetWaitableEvent latch; thread_host.raster_thread->GetTaskRunner()->PostTask([&] { auto pipeline = std::make_shared(/*depth=*/10); - auto layer_tree = std::make_unique(/*frame_size=*/SkISize(), + auto layer_tree = std::make_shared(/*frame_size=*/SkISize(), /*device_pixel_ratio=*/2.0f); auto layer_tree_item = std::make_unique( std::move(layer_tree), CreateFinishedBuildRecorder()); @@ -626,7 +627,7 @@ TEST( fml::AutoResetWaitableEvent latch; thread_host.raster_thread->GetTaskRunner()->PostTask([&] { auto pipeline = std::make_shared(/*depth=*/10); - auto layer_tree = std::make_unique(/*frame_size=*/SkISize(), + auto layer_tree = std::make_shared(/*frame_size=*/SkISize(), /*device_pixel_ratio=*/2.0f); auto layer_tree_item = std::make_unique( std::move(layer_tree), CreateFinishedBuildRecorder()); @@ -680,7 +681,7 @@ TEST( fml::AutoResetWaitableEvent latch; thread_host.raster_thread->GetTaskRunner()->PostTask([&] { auto pipeline = std::make_shared(/*depth=*/10); - auto layer_tree = std::make_unique(/*frame_size=*/SkISize(), + auto layer_tree = std::make_shared(/*frame_size=*/SkISize(), /*device_pixel_ratio=*/2.0f); auto layer_tree_item = std::make_unique( std::move(layer_tree), CreateFinishedBuildRecorder()); @@ -733,7 +734,7 @@ TEST( fml::AutoResetWaitableEvent latch; thread_host.raster_thread->GetTaskRunner()->PostTask([&] { auto pipeline = std::make_shared(/*depth=*/10); - auto layer_tree = std::make_unique(/*frame_size=*/SkISize(), + auto layer_tree = std::make_shared(/*frame_size=*/SkISize(), /*device_pixel_ratio=*/2.0f); auto layer_tree_item = std::make_unique( std::move(layer_tree), CreateFinishedBuildRecorder()); @@ -810,7 +811,7 @@ TEST(RasterizerTest, auto pipeline = std::make_shared(/*depth=*/10); for (int i = 0; i < 2; i++) { auto layer_tree = - std::make_unique(/*frame_size=*/SkISize(), + std::make_shared(/*frame_size=*/SkISize(), /*device_pixel_ratio=*/2.0f); auto layer_tree_item = std::make_unique( std::move(layer_tree), CreateFinishedBuildRecorder(timestamps[i])); @@ -971,7 +972,7 @@ TEST(RasterizerTest, presentationTimeSetWhenVsyncTargetInFuture) { auto pipeline = std::make_shared(/*depth=*/10); for (int i = 0; i < 2; i++) { auto layer_tree = - std::make_unique(/*frame_size=*/SkISize(), + std::make_shared(/*frame_size=*/SkISize(), /*device_pixel_ratio=*/2.0f); auto layer_tree_item = std::make_unique( std::move(layer_tree), CreateFinishedBuildRecorder(timestamps[i])); @@ -1044,7 +1045,7 @@ TEST(RasterizerTest, presentationTimeNotSetWhenVsyncTargetInPast) { thread_host.raster_thread->GetTaskRunner()->PostTask([&] { rasterizer->Setup(std::move(surface)); auto pipeline = std::make_shared(/*depth=*/10); - auto layer_tree = std::make_unique(/*frame_size=*/SkISize(), + auto layer_tree = std::make_shared(/*frame_size=*/SkISize(), /*device_pixel_ratio=*/2.0f); auto layer_tree_item = std::make_unique( std::move(layer_tree), CreateFinishedBuildRecorder(first_timestamp)); diff --git a/shell/common/shell.cc b/shell/common/shell.cc index e28a0391a3552..25ad7d7f94332 100644 --- a/shell/common/shell.cc +++ b/shell/common/shell.cc @@ -454,6 +454,10 @@ Shell::Shell(DartVMRef vm, task_runners_.GetRasterTaskRunner(), std::bind(&Shell::OnServiceProtocolRenderFrameWithRasterStats, this, std::placeholders::_1, std::placeholders::_2)}; + service_protocol_handlers_[ServiceProtocol::kReloadAssetFonts] = { + task_runners_.GetPlatformTaskRunner(), + std::bind(&Shell::OnServiceProtocolReloadAssetFonts, this, + std::placeholders::_1, std::placeholders::_2)}; } Shell::~Shell() { @@ -958,6 +962,8 @@ void Shell::OnPlatformViewDispatchPlatformMessage( FML_DCHECK(is_setup_); FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread()); + // The static leak checker gets confused by the use of fml::MakeCopyable. + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) task_runners_.GetUITaskRunner()->PostTask(fml::MakeCopyable( [engine = engine_->GetWeakPtr(), message = std::move(message)]() mutable { if (engine) { @@ -1034,7 +1040,7 @@ void Shell::OnPlatformViewRegisterTexture( task_runners_.GetRasterTaskRunner()->PostTask( [rasterizer = rasterizer_->GetWeakPtr(), texture] { if (rasterizer) { - if (auto* registry = rasterizer->GetTextureRegistry()) { + if (auto registry = rasterizer->GetTextureRegistry()) { registry->RegisterTexture(texture); } } @@ -1049,7 +1055,7 @@ void Shell::OnPlatformViewUnregisterTexture(int64_t texture_id) { task_runners_.GetRasterTaskRunner()->PostTask( [rasterizer = rasterizer_->GetWeakPtr(), texture_id]() { if (rasterizer) { - if (auto* registry = rasterizer->GetTextureRegistry()) { + if (auto registry = rasterizer->GetTextureRegistry()) { registry->UnregisterTexture(texture_id); } } @@ -1064,7 +1070,7 @@ void Shell::OnPlatformViewMarkTextureFrameAvailable(int64_t texture_id) { // Tell the rasterizer that one of its textures has a new frame available. task_runners_.GetRasterTaskRunner()->PostTask( [rasterizer = rasterizer_->GetWeakPtr(), texture_id]() { - auto* registry = rasterizer->GetTextureRegistry(); + auto registry = rasterizer->GetTextureRegistry(); if (!registry) { return; @@ -1230,6 +1236,9 @@ void Shell::OnEngineHandlePlatformMessage( [weak_platform_message_handler = std::weak_ptr(platform_message_handler_), message = std::move(message), ui_task_runner]() mutable { + // The static leak checker gets confused by the use of + // fml::MakeCopyable. + // NOLINTNEXTLINE(clang-analyzer-core.CallAndMessage) ui_task_runner->PostTask(fml::MakeCopyable( [weak_platform_message_handler, message = std::move(message), ui_task_runner]() mutable { @@ -1362,15 +1371,29 @@ void Shell::LoadDartDeferredLibrary( void Shell::LoadDartDeferredLibraryError(intptr_t loading_unit_id, const std::string error_message, bool transient) { - engine_->LoadDartDeferredLibraryError(loading_unit_id, error_message, - transient); + fml::TaskRunner::RunNowOrPostTask( + task_runners_.GetUITaskRunner(), + [engine = weak_engine_, loading_unit_id, error_message, transient] { + if (engine) { + engine->LoadDartDeferredLibraryError(loading_unit_id, error_message, + transient); + } + }); } void Shell::UpdateAssetResolverByType( std::unique_ptr updated_asset_resolver, AssetResolver::AssetResolverType type) { - engine_->GetAssetManager()->UpdateResolverByType( - std::move(updated_asset_resolver), type); + fml::TaskRunner::RunNowOrPostTask( + task_runners_.GetUITaskRunner(), + fml::MakeCopyable( + [engine = weak_engine_, type, + asset_resolver = std::move(updated_asset_resolver)]() mutable { + if (engine) { + engine->GetAssetManager()->UpdateResolverByType( + std::move(asset_resolver), type); + } + })); } // |Engine::Delegate| @@ -1773,10 +1796,15 @@ bool Shell::OnServiceProtocolSetAssetBundlePath( auto asset_manager = std::make_shared(); - asset_manager->PushFront(std::make_unique( - fml::OpenDirectory(params.at("assetDirectory").data(), false, - fml::FilePermission::kRead), - false)); + if (!asset_manager->PushFront(std::make_unique( + fml::OpenDirectory(params.at("assetDirectory").data(), false, + fml::FilePermission::kRead), + false))) { + // The new asset directory path was invalid. + FML_DLOG(ERROR) << "Could not update asset directory."; + ServiceProtocolFailureError(response, "Could not update asset directory."); + return false; + } // Preserve any original asset resolvers to avoid syncing unchanged assets // over the DevFS connection. @@ -1890,6 +1918,45 @@ bool Shell::OnServiceProtocolRenderFrameWithRasterStats( } } +void Shell::SendFontChangeNotification() { + // After system fonts are reloaded, we send a system channel message + // to notify flutter framework. + rapidjson::Document document; + document.SetObject(); + auto& allocator = document.GetAllocator(); + rapidjson::Value message_value; + message_value.SetString(kFontChange, allocator); + document.AddMember(kTypeKey, message_value, allocator); + + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + document.Accept(writer); + std::string message = buffer.GetString(); + std::unique_ptr fontsChangeMessage = + std::make_unique( + kSystemChannel, + fml::MallocMapping::Copy(message.c_str(), message.length()), nullptr); + OnPlatformViewDispatchPlatformMessage(std::move(fontsChangeMessage)); +} + +bool Shell::OnServiceProtocolReloadAssetFonts( + const ServiceProtocol::Handler::ServiceProtocolMap& params, + rapidjson::Document* response) { + FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread()); + if (!engine_) { + return false; + } + engine_->GetFontCollection().RegisterFonts(engine_->GetAssetManager()); + engine_->GetFontCollection().GetFontCollection()->ClearFontFamilyCache(); + SendFontChangeNotification(); + + auto& allocator = response->GetAllocator(); + response->SetObject(); + response->AddMember("type", "Success", allocator); + + return true; +} + Rasterizer::Screenshot Shell::Screenshot( Rasterizer::ScreenshotType screenshot_type, bool base64_encode) { @@ -1952,23 +2019,7 @@ bool Shell::ReloadSystemFonts() { engine_->GetFontCollection().GetFontCollection()->ClearFontFamilyCache(); // After system fonts are reloaded, we send a system channel message // to notify flutter framework. - rapidjson::Document document; - document.SetObject(); - auto& allocator = document.GetAllocator(); - rapidjson::Value message_value; - message_value.SetString(kFontChange, allocator); - document.AddMember(kTypeKey, message_value, allocator); - - rapidjson::StringBuffer buffer; - rapidjson::Writer writer(buffer); - document.Accept(writer); - std::string message = buffer.GetString(); - std::unique_ptr fontsChangeMessage = - std::make_unique( - kSystemChannel, - fml::MallocMapping::Copy(message.c_str(), message.length()), nullptr); - - OnPlatformViewDispatchPlatformMessage(std::move(fontsChangeMessage)); + SendFontChangeNotification(); return true; } diff --git a/shell/common/shell.h b/shell/common/shell.h index 75761b9177488..051e1a9c5d7b6 100644 --- a/shell/common/shell.h +++ b/shell/common/shell.h @@ -714,6 +714,17 @@ class Shell final : public PlatformView::Delegate, const ServiceProtocol::Handler::ServiceProtocolMap& params, rapidjson::Document* response); + // Service protocol handler + // + // Forces the FontCollection to reload the font manifest. Used to support hot + // reload for fonts. + bool OnServiceProtocolReloadAssetFonts( + const ServiceProtocol::Handler::ServiceProtocolMap& params, + rapidjson::Document* response); + + // Send a system font change notification. + void SendFontChangeNotification(); + // |ResourceCacheLimitItem| size_t GetResourceCacheLimit() override { return resource_cache_limit_; }; diff --git a/shell/common/shell_test.cc b/shell/common/shell_test.cc index 27f6b0f65f527..eca345058787f 100644 --- a/shell/common/shell_test.cc +++ b/shell/common/shell_test.cc @@ -197,7 +197,7 @@ void ShellTest::PumpOneFrame(Shell* shell, fml::WeakPtr runtime_delegate = shell->weak_engine_; shell->GetTaskRunners().GetUITaskRunner()->PostTask( [&latch, runtime_delegate, &builder, viewport_metrics]() { - auto layer_tree = std::make_unique( + auto layer_tree = std::make_shared( SkISize::Make(viewport_metrics.physical_width, viewport_metrics.physical_height), static_cast(viewport_metrics.device_pixel_ratio)); diff --git a/shell/common/shell_test_external_view_embedder.cc b/shell/common/shell_test_external_view_embedder.cc index 015709d96f686..a366e6f99075c 100644 --- a/shell/common/shell_test_external_view_embedder.cc +++ b/shell/common/shell_test_external_view_embedder.cc @@ -56,8 +56,15 @@ std::vector ShellTestExternalViewEmbedder::GetCurrentCanvases() { } // |ExternalViewEmbedder| -SkCanvas* ShellTestExternalViewEmbedder::CompositeEmbeddedView(int view_id) { - return nullptr; +std::vector +ShellTestExternalViewEmbedder::GetCurrentBuilders() { + return {}; +} + +// |ExternalViewEmbedder| +EmbedderPaintContext ShellTestExternalViewEmbedder::CompositeEmbeddedView( + int view_id) { + return {nullptr, nullptr}; } // |ExternalViewEmbedder| diff --git a/shell/common/shell_test_external_view_embedder.h b/shell/common/shell_test_external_view_embedder.h index 72c101ed1f4bc..1bb70fd131e07 100644 --- a/shell/common/shell_test_external_view_embedder.h +++ b/shell/common/shell_test_external_view_embedder.h @@ -59,7 +59,10 @@ class ShellTestExternalViewEmbedder final : public ExternalViewEmbedder { std::vector GetCurrentCanvases() override; // |ExternalViewEmbedder| - SkCanvas* CompositeEmbeddedView(int view_id) override; + std::vector GetCurrentBuilders() override; + + // |ExternalViewEmbedder| + EmbedderPaintContext CompositeEmbeddedView(int view_id) override; // |ExternalViewEmbedder| void SubmitFrame(GrDirectContext* context, diff --git a/shell/common/shell_test_platform_view_gl.cc b/shell/common/shell_test_platform_view_gl.cc index 492354230e8dc..ac68ff3971d0b 100644 --- a/shell/common/shell_test_platform_view_gl.cc +++ b/shell/common/shell_test_platform_view_gl.cc @@ -69,8 +69,10 @@ bool ShellTestPlatformViewGL::GLContextPresent( } // |GPUSurfaceGLDelegate| -intptr_t ShellTestPlatformViewGL::GLContextFBO(GLFrameInfo frame_info) const { - return gl_surface_.GetFramebuffer(frame_info.width, frame_info.height); +GLFBOInfo ShellTestPlatformViewGL::GLContextFBO(GLFrameInfo frame_info) const { + return GLFBOInfo{ + .fbo_id = gl_surface_.GetFramebuffer(frame_info.width, frame_info.height), + }; } // |GPUSurfaceGLDelegate| diff --git a/shell/common/shell_test_platform_view_gl.h b/shell/common/shell_test_platform_view_gl.h index c890c1e0fc6ec..b4afc1266975b 100644 --- a/shell/common/shell_test_platform_view_gl.h +++ b/shell/common/shell_test_platform_view_gl.h @@ -61,7 +61,7 @@ class ShellTestPlatformViewGL : public ShellTestPlatformView, bool GLContextPresent(const GLPresentInfo& present_info) override; // |GPUSurfaceGLDelegate| - intptr_t GLContextFBO(GLFrameInfo frame_info) const override; + GLFBOInfo GLContextFBO(GLFrameInfo frame_info) const override; // |GPUSurfaceGLDelegate| GLProcResolver GetGLProcResolver() const override; diff --git a/shell/common/shell_unittests.cc b/shell/common/shell_unittests.cc index e4a86b5775a34..e2193d9be7d24 100644 --- a/shell/common/shell_unittests.cc +++ b/shell/common/shell_unittests.cc @@ -2382,7 +2382,6 @@ TEST_F(ShellTest, OnServiceProtocolEstimateRasterCacheMemoryWorks) { FixedRefreshRateStopwatch raster_time; FixedRefreshRateStopwatch ui_time; MutatorsStack mutators_stack; - TextureRegistry texture_registry; PaintContext paint_context = { // clang-format off .internal_nodes_canvas = nullptr, @@ -2392,7 +2391,7 @@ TEST_F(ShellTest, OnServiceProtocolEstimateRasterCacheMemoryWorks) { .view_embedder = nullptr, .raster_time = raster_time, .ui_time = ui_time, - .texture_registry = texture_registry, + .texture_registry = nullptr, .raster_cache = &raster_cache, .checkerboard_offscreen_layers = false, .frame_device_pixel_ratio = 1.0f, @@ -2411,7 +2410,7 @@ TEST_F(ShellTest, OnServiceProtocolEstimateRasterCacheMemoryWorks) { .surface_needs_readback = false, .raster_time = raster_time, .ui_time = ui_time, - .texture_registry = texture_registry, + .texture_registry = nullptr, .checkerboard_offscreen_layers = false, .frame_device_pixel_ratio = 1.0f, .has_platform_view = false, @@ -3284,9 +3283,9 @@ TEST_F(ShellTest, ImageGeneratorRegistryNotNullAfterParentShellDestroyed) { TEST_F(ShellTest, UpdateAssetResolverByTypeReplaces) { ASSERT_FALSE(DartVMRef::IsInstanceRunning()); Settings settings = CreateSettingsForFixture(); - ThreadHost thread_host("io.flutter.test." + GetCurrentTestName() + ".", - ThreadHost::Type::Platform); - auto task_runner = thread_host.platform_thread->GetTaskRunner(); + + fml::MessageLoop::EnsureInitializedForCurrentThread(); + auto task_runner = fml::MessageLoop::GetCurrent().GetTaskRunner(); TaskRunners task_runners("test", task_runner, task_runner, task_runner, task_runner); auto shell = CreateShell(std::move(settings), task_runners); @@ -3296,7 +3295,10 @@ TEST_F(ShellTest, UpdateAssetResolverByTypeReplaces) { auto configuration = RunConfiguration::InferFromSettings(settings); configuration.SetEntrypoint("emptyMain"); auto asset_manager = configuration.GetAssetManager(); - RunEngine(shell.get(), std::move(configuration)); + + shell->RunEngine(std::move(configuration), [&](auto result) { + ASSERT_EQ(result, Engine::RunStatus::Success); + }); auto platform_view = std::make_unique(*shell.get(), std::move(task_runners)); @@ -3326,9 +3328,9 @@ TEST_F(ShellTest, UpdateAssetResolverByTypeReplaces) { TEST_F(ShellTest, UpdateAssetResolverByTypeAppends) { ASSERT_FALSE(DartVMRef::IsInstanceRunning()); Settings settings = CreateSettingsForFixture(); - ThreadHost thread_host("io.flutter.test." + GetCurrentTestName() + ".", - ThreadHost::Type::Platform); - auto task_runner = thread_host.platform_thread->GetTaskRunner(); + + fml::MessageLoop::EnsureInitializedForCurrentThread(); + auto task_runner = fml::MessageLoop::GetCurrent().GetTaskRunner(); TaskRunners task_runners("test", task_runner, task_runner, task_runner, task_runner); auto shell = CreateShell(std::move(settings), task_runners); @@ -3338,7 +3340,10 @@ TEST_F(ShellTest, UpdateAssetResolverByTypeAppends) { auto configuration = RunConfiguration::InferFromSettings(settings); configuration.SetEntrypoint("emptyMain"); auto asset_manager = configuration.GetAssetManager(); - RunEngine(shell.get(), std::move(configuration)); + + shell->RunEngine(std::move(configuration), [&](auto result) { + ASSERT_EQ(result, Engine::RunStatus::Success); + }); auto platform_view = std::make_unique(*shell.get(), std::move(task_runners)); @@ -3401,10 +3406,9 @@ TEST_F(ShellTest, UpdateAssetResolverByTypeNull) { TEST_F(ShellTest, UpdateAssetResolverByTypeDoesNotReplaceMismatchType) { ASSERT_FALSE(DartVMRef::IsInstanceRunning()); Settings settings = CreateSettingsForFixture(); - ThreadHost thread_host(ThreadHost::ThreadHostConfig( - "io.flutter.test." + GetCurrentTestName() + ".", - ThreadHost::Type::Platform)); - auto task_runner = thread_host.platform_thread->GetTaskRunner(); + + fml::MessageLoop::EnsureInitializedForCurrentThread(); + auto task_runner = fml::MessageLoop::GetCurrent().GetTaskRunner(); TaskRunners task_runners("test", task_runner, task_runner, task_runner, task_runner); auto shell = CreateShell(std::move(settings), task_runners); @@ -3414,7 +3418,10 @@ TEST_F(ShellTest, UpdateAssetResolverByTypeDoesNotReplaceMismatchType) { auto configuration = RunConfiguration::InferFromSettings(settings); configuration.SetEntrypoint("emptyMain"); auto asset_manager = configuration.GetAssetManager(); - RunEngine(shell.get(), std::move(configuration)); + + shell->RunEngine(std::move(configuration), [&](auto result) { + ASSERT_EQ(result, Engine::RunStatus::Success); + }); auto platform_view = std::make_unique(*shell.get(), std::move(task_runners)); @@ -3765,7 +3772,7 @@ TEST_F(ShellTest, SpawnWorksWithOnError) { TEST_F(ShellTest, PictureToImageSync) { #if !SHELL_ENABLE_GL - // GL emulation does not exist on Fuchsia. + // This test uses the GL backend. GTEST_SKIP(); #endif // !SHELL_ENABLE_GL auto settings = CreateSettingsForFixture(); @@ -3778,9 +3785,12 @@ TEST_F(ShellTest, PictureToImageSync) { ShellTestPlatformView::BackendType::kGLBackend // ); - fml::AutoResetWaitableEvent latch; - AddNativeCallback("NotifyNative", CREATE_NATIVE_ENTRY([&latch](auto args) { - latch.Signal(); + fml::CountDownLatch latch(2); + AddNativeCallback("NotifyNative", CREATE_NATIVE_ENTRY([&](auto args) { + // Teardown and set up rasterizer again. + PlatformViewNotifyDestroyed(shell.get()); + PlatformViewNotifyCreated(shell.get()); + latch.CountDown(); })); ASSERT_NE(shell, nullptr); diff --git a/shell/gpu/gpu_surface_gl_delegate.h b/shell/gpu/gpu_surface_gl_delegate.h index f9da6a11120fd..f097cba2d0266 100644 --- a/shell/gpu/gpu_surface_gl_delegate.h +++ b/shell/gpu/gpu_surface_gl_delegate.h @@ -22,17 +22,32 @@ struct GLFrameInfo { uint32_t height; }; +// A structure to represent the frame buffer information which is returned to +// the rendering backend after requesting a frame buffer object. +struct GLFBOInfo { + // The frame buffer's ID. + uint32_t fbo_id; + // This boolean flags whether the returned FBO supports partial repaint. + const bool partial_repaint_enabled; + // The frame buffer's existing damage (i.e. damage since it was last used). + const std::optional existing_damage; +}; + // Information passed during presentation of a frame. struct GLPresentInfo { uint32_t fbo_id; - // Damage is a hint to compositor telling it which parts of front buffer - // need to be updated - const std::optional& damage; + // The frame damage is a hint to compositor telling it which parts of front + // buffer need to be updated. + const std::optional& frame_damage; // Time at which this frame is scheduled to be presented. This is a hint // that can be passed to the platform to drop queued frames. std::optional presentation_time = std::nullopt; + + // The buffer damage refers to the region that needs to be set as damaged + // within the frame buffer. + const std::optional& buffer_damage; }; class GPUSurfaceGLDelegate { @@ -54,8 +69,9 @@ class GPUSurfaceGLDelegate { // context and not any of the contexts dedicated for IO. virtual bool GLContextPresent(const GLPresentInfo& present_info) = 0; - // The ID of the main window bound framebuffer. Typically FBO0. - virtual intptr_t GLContextFBO(GLFrameInfo frame_info) const = 0; + // The information about the main window bound framebuffer. ID is Typically + // FBO0. + virtual GLFBOInfo GLContextFBO(GLFrameInfo frame_info) const = 0; // The rendering subsystem assumes that the ID of the main window bound // framebuffer remains constant throughout. If this assumption in incorrect, diff --git a/shell/gpu/gpu_surface_gl_impeller.cc b/shell/gpu/gpu_surface_gl_impeller.cc index 24413147274a6..3fc7c441b42bb 100644 --- a/shell/gpu/gpu_surface_gl_impeller.cc +++ b/shell/gpu/gpu_surface_gl_impeller.cc @@ -62,10 +62,11 @@ std::unique_ptr GPUSurfaceGLImpeller::AcquireFrame( if (weak) { GLPresentInfo present_info = { .fbo_id = 0, - .damage = std::nullopt, + .frame_damage = std::nullopt, // TODO (https://github.com/flutter/flutter/issues/105597): wire-up // presentation time to impeller backend. .presentation_time = std::nullopt, + .buffer_damage = std::nullopt, }; delegate->GLContextPresent(present_info); } diff --git a/shell/gpu/gpu_surface_gl_skia.cc b/shell/gpu/gpu_surface_gl_skia.cc index 66bb6636d95d8..06b3d5d3c5748 100644 --- a/shell/gpu/gpu_surface_gl_skia.cc +++ b/shell/gpu/gpu_surface_gl_skia.cc @@ -181,10 +181,10 @@ bool GPUSurfaceGLSkia::CreateOrUpdateSurfaces(const SkISize& size) { GLFrameInfo frame_info = {static_cast(size.width()), static_cast(size.height())}; - const uint32_t fbo_id = delegate_->GLContextFBO(frame_info); + const GLFBOInfo fbo_info = delegate_->GLContextFBO(frame_info); onscreen_surface = WrapOnscreenSurface(context_.get(), // GL context size, // root surface size - fbo_id // window FBO ID + fbo_info.fbo_id // window FBO ID ); if (onscreen_surface == nullptr) { @@ -195,7 +195,8 @@ bool GPUSurfaceGLSkia::CreateOrUpdateSurfaces(const SkISize& size) { } onscreen_surface_ = std::move(onscreen_surface); - fbo_id_ = fbo_id; + fbo_id_ = fbo_info.fbo_id; + existing_damage_ = fbo_info.existing_damage; return true; } @@ -248,6 +249,9 @@ std::unique_ptr GPUSurfaceGLSkia::AcquireFrame( }; framebuffer_info = delegate_->GLContextFramebufferInfo(); + if (!framebuffer_info.existing_damage.has_value()) { + framebuffer_info.existing_damage = existing_damage_; + } return std::make_unique(surface, std::move(framebuffer_info), submit_callback, std::move(context_switch)); @@ -268,8 +272,9 @@ bool GPUSurfaceGLSkia::PresentSurface(const SurfaceFrame& frame, GLPresentInfo present_info = { .fbo_id = fbo_id_, - .damage = frame.submit_info().frame_damage, + .frame_damage = frame.submit_info().frame_damage, .presentation_time = frame.submit_info().presentation_time, + .buffer_damage = frame.submit_info().buffer_damage, }; if (!delegate_->GLContextPresent(present_info)) { return false; @@ -284,11 +289,11 @@ bool GPUSurfaceGLSkia::PresentSurface(const SurfaceFrame& frame, // The FBO has changed, ask the delegate for the new FBO and do a surface // re-wrap. - const uint32_t fbo_id = delegate_->GLContextFBO(frame_info); + const GLFBOInfo fbo_info = delegate_->GLContextFBO(frame_info); auto new_onscreen_surface = WrapOnscreenSurface(context_.get(), // GL context current_size, // root surface size - fbo_id // window FBO ID + fbo_info.fbo_id // window FBO ID ); if (!new_onscreen_surface) { @@ -296,7 +301,8 @@ bool GPUSurfaceGLSkia::PresentSurface(const SurfaceFrame& frame, } onscreen_surface_ = std::move(new_onscreen_surface); - fbo_id_ = fbo_id; + fbo_id_ = fbo_info.fbo_id; + existing_damage_ = fbo_info.existing_damage; } return true; diff --git a/shell/gpu/gpu_surface_gl_skia.h b/shell/gpu/gpu_surface_gl_skia.h index 90cad241945fd..35caee497eb6f 100644 --- a/shell/gpu/gpu_surface_gl_skia.h +++ b/shell/gpu/gpu_surface_gl_skia.h @@ -67,6 +67,10 @@ class GPUSurfaceGLSkia : public Surface { sk_sp onscreen_surface_; /// FBO backing the current `onscreen_surface_`. uint32_t fbo_id_ = 0; + // The current FBO's existing damage, as tracked by the GPU surface, delegates + // still have an option of overriding this damage with their own in + // `GLContextFrameBufferInfo`. + std::optional existing_damage_ = std::nullopt; bool context_owner_ = false; // TODO(38466): Refactor GPU surface APIs take into account the fact that an // external view embedder may want to render to the root surface. This is a diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index edb78d40445d9..3a91161dc23ee 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -646,6 +646,70 @@ if (target_cpu != "x86") { } } +if (target_cpu == "x64" || target_cpu == "arm64") { + zip_bundle("analyze_snapshot") { + deps = + [ "//third_party/dart/runtime/bin:analyze_snapshot($host_toolchain)" ] + + analyze_snapshot_bin = "analyze_snapshot" + analyze_snapshot_out_dir = + get_label_info( + "//third_party/dart/runtime/bin:analyze_snapshot($host_toolchain)", + "root_out_dir") + analyze_snapshot_path = + rebase_path("$analyze_snapshot_out_dir/$analyze_snapshot_bin") + + if (host_os == "linux") { + output = "$android_zip_archive_dir/analyze-snapshot-linux-x64.zip" + } else if (host_os == "mac") { + output = "$android_zip_archive_dir/analyze-snapshot-darwin-x64.zip" + } else if (host_os == "win") { + output = "$android_zip_archive_dir/analyze-snapshot-windows-x64.zip" + analyze_snapshot_bin = "analyze-snapshot.exe" + analyze_snapshot_path = rebase_path("$root_out_dir/$analyze_snapshot_bin") + } + + files = [ + { + source = analyze_snapshot_path + destination = analyze_snapshot_bin + }, + ] + } + + # TODO(godofredoc): Remove analyze_snapshot and rename new_analyze_snapshot when v2 migration is complete. + # BUG: https://github.com/flutter/flutter/issues/105351 + zip_bundle("new_analyze_snapshot") { + deps = + [ "//third_party/dart/runtime/bin:analyze_snapshot($host_toolchain)" ] + + analyze_snapshot_bin = "analyze_snapshot" + analyze_snapshot_out_dir = + get_label_info( + "//third_party/dart/runtime/bin:analyze_snapshot($host_toolchain)", + "root_out_dir") + analyze_snapshot_path = + rebase_path("$analyze_snapshot_out_dir/$analyze_snapshot_bin") + + if (host_os == "linux") { + output = "$android_zip_archive_dir/$full_platform_name-$flutter_runtime_mode/analyze-snapshot-linux-x64.zip" + } else if (host_os == "mac") { + output = "$android_zip_archive_dir/$full_platform_name-$flutter_runtime_mode/analyze-snapshot-darwin-x64.zip" + } else if (host_os == "win") { + output = "$android_zip_archive_dir/$full_platform_name-$flutter_runtime_mode/analyze-snapshot-windows-x64.zip" + analyze_snapshot_bin = "analyze-snapshot.exe" + analyze_snapshot_path = rebase_path("$root_out_dir/$analyze_snapshot_bin") + } + + files = [ + { + source = analyze_snapshot_path + destination = analyze_snapshot_bin + }, + ] + } +} + group("android") { deps = [ ":android_javadoc", diff --git a/shell/platform/android/android_external_texture_gl.cc b/shell/platform/android/android_external_texture_gl.cc index f24e7068563aa..599d6190b3306 100644 --- a/shell/platform/android/android_external_texture_gl.cc +++ b/shell/platform/android/android_external_texture_gl.cc @@ -64,23 +64,44 @@ void AndroidExternalTextureGL::Paint(SkCanvas& canvas, kPremul_SkAlphaType, nullptr); if (image) { SkAutoCanvasRestore autoRestore(&canvas, true); - canvas.translate(bounds.x(), bounds.y()); - canvas.scale(bounds.width(), bounds.height()); - if (!transform.isIdentity()) { - SkMatrix transformAroundCenter(transform); - transformAroundCenter.preTranslate(-0.5, -0.5); - transformAroundCenter.postScale(1, -1); - transformAroundCenter.postTranslate(0.5, 0.5); - canvas.concat(transformAroundCenter); + // The incoming texture is vertically flipped, so we flip it + // back. OpenGL's coordinate system has Positive Y equivalent to up, while + // Skia's coordinate system has Negative Y equvalent to up. + canvas.translate(bounds.x(), bounds.y() + bounds.height()); + canvas.scale(bounds.width(), -bounds.height()); + + if (!transform.isIdentity()) { + sk_sp shader = image->makeShader( + SkTileMode::kRepeat, SkTileMode::kRepeat, sampling, transform); + + SkPaint paintWithShader; + if (paint) { + paintWithShader = *paint; + } + paintWithShader.setShader(shader); + canvas.drawRect(SkRect::MakeWH(1, 1), paintWithShader); + } else { + canvas.drawImage(image, 0, 0, sampling, paint); } - canvas.drawImage(image, 0, 0, sampling, paint); } } void AndroidExternalTextureGL::UpdateTransform() { jni_facade_->SurfaceTextureGetTransformMatrix( fml::jni::ScopedJavaLocalRef(surface_texture_), transform); + + // Android's SurfaceTexture transform matrix works on texture coordinate + // lookups in the range 0.0-1.0, while Skia's Shader transform matrix works on + // the image itself, as if it were inscribed inside a clip rect. + // An Android transform that scales lookup by 0.5 (displaying 50% of the + // texture) is the same as a Skia transform by 2.0 (scaling 50% of the image + // outside of the virtual "clip rect"), so we invert the incoming matrix. + SkMatrix inverted; + if (!transform.invert(&inverted)) { + FML_LOG(FATAL) << "Invalid SurfaceTexture transformation matrix"; + } + transform = inverted; } void AndroidExternalTextureGL::OnGrContextDestroyed() { diff --git a/shell/platform/android/android_surface_gl_impeller.cc b/shell/platform/android/android_surface_gl_impeller.cc index 28c1cfe55f19e..635acc77a9502 100644 --- a/shell/platform/android/android_surface_gl_impeller.cc +++ b/shell/platform/android/android_surface_gl_impeller.cc @@ -295,9 +295,11 @@ bool AndroidSurfaceGLImpeller::GLContextPresent( } // |GPUSurfaceGLDelegate| -intptr_t AndroidSurfaceGLImpeller::GLContextFBO(GLFrameInfo frame_info) const { +GLFBOInfo AndroidSurfaceGLImpeller::GLContextFBO(GLFrameInfo frame_info) const { // FBO0 is the default window bound framebuffer in EGL environments. - return 0; + return GLFBOInfo{ + .fbo_id = 0, + }; } // |GPUSurfaceGLDelegate| diff --git a/shell/platform/android/android_surface_gl_impeller.h b/shell/platform/android/android_surface_gl_impeller.h index 8bcf14dbee27b..9d2399868da97 100644 --- a/shell/platform/android/android_surface_gl_impeller.h +++ b/shell/platform/android/android_surface_gl_impeller.h @@ -68,7 +68,7 @@ class AndroidSurfaceGLImpeller final : public GPUSurfaceGLDelegate, bool GLContextPresent(const GLPresentInfo& present_info) override; // |GPUSurfaceGLDelegate| - intptr_t GLContextFBO(GLFrameInfo frame_info) const override; + GLFBOInfo GLContextFBO(GLFrameInfo frame_info) const override; // |GPUSurfaceGLDelegate| sk_sp GetGLInterface() const override; diff --git a/shell/platform/android/android_surface_gl_skia.cc b/shell/platform/android/android_surface_gl_skia.cc index 23242c9b3bad4..f4e3d5acd24c3 100644 --- a/shell/platform/android/android_surface_gl_skia.cc +++ b/shell/platform/android/android_surface_gl_skia.cc @@ -159,13 +159,17 @@ bool AndroidSurfaceGLSkia::GLContextPresent(const GLPresentInfo& present_info) { if (present_info.presentation_time) { onscreen_surface_->SetPresentationTime(*present_info.presentation_time); } - return onscreen_surface_->SwapBuffers(present_info.damage); + return onscreen_surface_->SwapBuffers(present_info.frame_damage); } -intptr_t AndroidSurfaceGLSkia::GLContextFBO(GLFrameInfo frame_info) const { +GLFBOInfo AndroidSurfaceGLSkia::GLContextFBO(GLFrameInfo frame_info) const { FML_DCHECK(IsValid()); // The default window bound framebuffer on Android. - return 0; + return GLFBOInfo{ + .fbo_id = 0, + .partial_repaint_enabled = onscreen_surface_->SupportsPartialRepaint(), + .existing_damage = onscreen_surface_->InitialDamage(), + }; } // |GPUSurfaceGLDelegate| diff --git a/shell/platform/android/android_surface_gl_skia.h b/shell/platform/android/android_surface_gl_skia.h index 99a910b69b76d..73f36945127f8 100644 --- a/shell/platform/android/android_surface_gl_skia.h +++ b/shell/platform/android/android_surface_gl_skia.h @@ -67,7 +67,7 @@ class AndroidSurfaceGLSkia final : public GPUSurfaceGLDelegate, bool GLContextPresent(const GLPresentInfo& present_info) override; // |GPUSurfaceGLDelegate| - intptr_t GLContextFBO(GLFrameInfo frame_info) const override; + GLFBOInfo GLContextFBO(GLFrameInfo frame_info) const override; // |GPUSurfaceGLDelegate| sk_sp GetGLInterface() const override; diff --git a/shell/platform/android/external_view_embedder/external_view_embedder.cc b/shell/platform/android/external_view_embedder/external_view_embedder.cc index a9417ebb16211..3545cb1453dce 100644 --- a/shell/platform/android/external_view_embedder/external_view_embedder.cc +++ b/shell/platform/android/external_view_embedder/external_view_embedder.cc @@ -30,13 +30,15 @@ void AndroidExternalViewEmbedder::PrerollCompositeEmbeddedView( TRACE_EVENT0("flutter", "AndroidExternalViewEmbedder::PrerollCompositeEmbeddedView"); - auto rtree_factory = RTreeFactory(); - view_rtrees_.insert_or_assign(view_id, rtree_factory.getInstance()); - - auto picture_recorder = std::make_unique(); - picture_recorder->beginRecording(SkRect::Make(frame_size_), &rtree_factory); + SkRect view_bounds = SkRect::Make(frame_size_); + std::unique_ptr view; + if (params->display_list_enabled()) { + view = std::make_unique(view_bounds); + } else { + view = std::make_unique(view_bounds); + } + slices_.insert_or_assign(view_id, std::move(view)); - picture_recorders_.insert_or_assign(view_id, std::move(picture_recorder)); composition_order_.push_back(view_id); // Update params only if they changed. if (view_params_.count(view_id) == 1 && @@ -47,11 +49,12 @@ void AndroidExternalViewEmbedder::PrerollCompositeEmbeddedView( } // |ExternalViewEmbedder| -SkCanvas* AndroidExternalViewEmbedder::CompositeEmbeddedView(int view_id) { - if (picture_recorders_.count(view_id) == 1) { - return picture_recorders_.at(view_id)->getRecordingCanvas(); +EmbedderPaintContext AndroidExternalViewEmbedder::CompositeEmbeddedView( + int view_id) { + if (slices_.count(view_id) == 1) { + return {slices_.at(view_id)->canvas(), slices_.at(view_id)->builder()}; } - return nullptr; + return {nullptr, nullptr}; } // |ExternalViewEmbedder| @@ -59,11 +62,26 @@ std::vector AndroidExternalViewEmbedder::GetCurrentCanvases() { std::vector canvases; for (size_t i = 0; i < composition_order_.size(); i++) { int64_t view_id = composition_order_[i]; - canvases.push_back(picture_recorders_.at(view_id)->getRecordingCanvas()); + if (slices_.count(view_id) == 1) { + canvases.push_back(slices_.at(view_id)->canvas()); + } } return canvases; } +// |ExternalViewEmbedder| +std::vector +AndroidExternalViewEmbedder::GetCurrentBuilders() { + std::vector builders; + for (size_t i = 0; i < composition_order_.size(); i++) { + int64_t view_id = composition_order_[i]; + if (slices_.count(view_id) == 1) { + builders.push_back(slices_.at(view_id)->builder()); + } + } + return builders; +} + SkRect AndroidExternalViewEmbedder::GetViewRect(int view_id) const { const EmbeddedViewParams& params = view_params_.at(view_id); // TODO(egarciad): The rect should be computed from the mutator stack. @@ -90,6 +108,7 @@ void AndroidExternalViewEmbedder::SubmitFrame( std::unordered_map overlay_layers; std::unordered_map> pictures; SkCanvas* background_canvas = frame->SkiaCanvas(); + DisplayListBuilder* background_builder = frame->GetDisplayListBuilder().get(); auto current_frame_view_count = composition_order_.size(); // Restore the clip context after exiting this method since it's changed @@ -98,16 +117,13 @@ void AndroidExternalViewEmbedder::SubmitFrame( for (size_t i = 0; i < current_frame_view_count; i++) { int64_t view_id = composition_order_[i]; - if (picture_recorders_.at(view_id)->getRecordingCanvas() == nullptr) { + EmbedderViewSlice* slice = slices_.at(view_id).get(); + if (slice->canvas() == nullptr) { continue; } - sk_sp picture = - picture_recorders_.at(view_id)->finishRecordingAsPicture(); - FML_CHECK(picture); - pictures.insert({view_id, picture}); + slice->end_recording(); - sk_sp rtree = view_rtrees_.at(view_id); SkRect joined_rect = SkRect::MakeEmpty(); // Determinate if Flutter UI intersects with any of the previous @@ -121,7 +137,7 @@ void AndroidExternalViewEmbedder::SubmitFrame( SkRect current_view_rect = GetViewRect(current_view_id); // Each rect corresponds to a native view that renders Flutter UI. std::list intersection_rects = - rtree->searchNonOverlappingDrawnRects(current_view_rect); + slice->searchNonOverlappingDrawnRects(current_view_rect); // Limit the number of native views, so it doesn't grow forever. // @@ -144,8 +160,16 @@ void AndroidExternalViewEmbedder::SubmitFrame( // drawn on the overlay layer. background_canvas->clipRect(joined_rect, SkClipOp::kDifference); } - background_canvas->drawPicture(pictures.at(view_id)); + if (background_builder) { + slice->render_into(background_builder); + } else { + slice->render_into(background_canvas); + } } + + // Manually trigger the SkAutoCanvasRestore before we submit the frame + save.restore(); + // Submit the background canvas frame before switching the GL context to // the overlay surfaces. // @@ -177,10 +201,10 @@ void AndroidExternalViewEmbedder::SubmitFrame( continue; } std::unique_ptr frame = - CreateSurfaceIfNeeded(context, // - view_id, // - pictures.at(view_id), // - overlay->second // + CreateSurfaceIfNeeded(context, // + view_id, // + slices_.at(view_id).get(), // + overlay->second // ); if (should_submit_current_frame) { frame->Submit(); @@ -192,7 +216,7 @@ void AndroidExternalViewEmbedder::SubmitFrame( std::unique_ptr AndroidExternalViewEmbedder::CreateSurfaceIfNeeded(GrDirectContext* context, int64_t view_id, - sk_sp picture, + EmbedderViewSlice* slice, const SkRect& rect) { std::shared_ptr layer = surface_pool_->GetLayer( context, android_context_, jni_facade_, surface_factory_); @@ -212,7 +236,11 @@ AndroidExternalViewEmbedder::CreateSurfaceIfNeeded(GrDirectContext* context, // Offset the picture since its absolute position on the scene is determined // by the position of the overlay view. overlay_canvas->translate(-rect.x(), -rect.y()); - overlay_canvas->drawPicture(picture); + if (frame->GetDisplayListBuilder()) { + slice->render_into(frame->GetDisplayListBuilder().get()); + } else { + slice->render_into(overlay_canvas); + } return frame; } @@ -258,7 +286,7 @@ void AndroidExternalViewEmbedder::Reset() { previous_frame_view_count_ = composition_order_.size(); composition_order_.clear(); - picture_recorders_.clear(); + slices_.clear(); } // |ExternalViewEmbedder| diff --git a/shell/platform/android/external_view_embedder/external_view_embedder.h b/shell/platform/android/external_view_embedder/external_view_embedder.h index 2ec13297bb9ad..5bae7a4e1c9e7 100644 --- a/shell/platform/android/external_view_embedder/external_view_embedder.h +++ b/shell/platform/android/external_view_embedder/external_view_embedder.h @@ -42,11 +42,17 @@ class AndroidExternalViewEmbedder final : public ExternalViewEmbedder { std::unique_ptr params) override; // |ExternalViewEmbedder| - SkCanvas* CompositeEmbeddedView(int view_id) override; + EmbedderPaintContext CompositeEmbeddedView(int view_id) override; // |ExternalViewEmbedder| std::vector GetCurrentCanvases() override; + // |ExternalViewEmbedder| + // Similar call to GetCurrentCanvases but will return the array of + // builders being used by PlatformViews on platforms that provide + // optional DisplayListBuilder objects for rendering. + std::vector GetCurrentBuilders() override; + // |ExternalViewEmbedder| void SubmitFrame(GrDirectContext* context, std::unique_ptr frame) override; @@ -115,19 +121,15 @@ class AndroidExternalViewEmbedder final : public ExternalViewEmbedder { // view. std::vector composition_order_; - // The platform view's picture recorder keyed off the platform view id, which - // contains any subsequent operation until the next platform view or the end - // of the last leaf node in the layer tree. - std::unordered_map> - picture_recorders_; + // The |EmbedderViewSlice| implementation keyed off the platform view id, + // which contains any subsequent operations until the next platform view or + // the end of the last leaf node in the layer tree. + std::unordered_map> slices_; // The params for a platform view, which contains the size, position and // mutation stack. std::unordered_map view_params_; - // The r-tree that captures the operations for the picture recorders. - std::unordered_map> view_rtrees_; - // The number of platform views in the previous frame. int64_t previous_frame_view_count_; @@ -146,7 +148,7 @@ class AndroidExternalViewEmbedder final : public ExternalViewEmbedder { // Finally, draws the picture on the frame's canvas. std::unique_ptr CreateSurfaceIfNeeded(GrDirectContext* context, int64_t view_id, - sk_sp picture, + EmbedderViewSlice* slice, const SkRect& rect); }; diff --git a/shell/platform/android/external_view_embedder/external_view_embedder_unittests.cc b/shell/platform/android/external_view_embedder/external_view_embedder_unittests.cc index 7ff2a86592455..3005024bb9fe3 100644 --- a/shell/platform/android/external_view_embedder/external_view_embedder_unittests.cc +++ b/shell/platform/android/external_view_embedder/external_view_embedder_unittests.cc @@ -124,6 +124,9 @@ TEST(AndroidExternalViewEmbedder, GetCurrentCanvases) { ASSERT_EQ(2UL, canvases.size()); ASSERT_EQ(SkISize::Make(10, 20), canvases[0]->getBaseLayerSize()); ASSERT_EQ(SkISize::Make(10, 20), canvases[1]->getBaseLayerSize()); + + auto builders = embedder->GetCurrentBuilders(); + ASSERT_EQ(2UL, builders.size()); } TEST(AndroidExternalViewEmbedder, GetCurrentCanvasesCompositeOrder) { @@ -147,8 +150,11 @@ TEST(AndroidExternalViewEmbedder, GetCurrentCanvasesCompositeOrder) { auto canvases = embedder->GetCurrentCanvases(); ASSERT_EQ(2UL, canvases.size()); - ASSERT_EQ(embedder->CompositeEmbeddedView(0), canvases[0]); - ASSERT_EQ(embedder->CompositeEmbeddedView(1), canvases[1]); + ASSERT_EQ(embedder->CompositeEmbeddedView(0).canvas, canvases[0]); + ASSERT_EQ(embedder->CompositeEmbeddedView(1).canvas, canvases[1]); + + auto builders = embedder->GetCurrentBuilders(); + ASSERT_EQ(2UL, builders.size()); } TEST(AndroidExternalViewEmbedder, CompositeEmbeddedView) { @@ -156,15 +162,15 @@ TEST(AndroidExternalViewEmbedder, CompositeEmbeddedView) { auto embedder = std::make_unique( android_context, nullptr, nullptr, GetTaskRunnersForFixture()); - ASSERT_EQ(nullptr, embedder->CompositeEmbeddedView(0)); + ASSERT_EQ(nullptr, embedder->CompositeEmbeddedView(0).canvas); embedder->PrerollCompositeEmbeddedView( 0, std::make_unique()); - ASSERT_NE(nullptr, embedder->CompositeEmbeddedView(0)); + ASSERT_NE(nullptr, embedder->CompositeEmbeddedView(0).canvas); - ASSERT_EQ(nullptr, embedder->CompositeEmbeddedView(1)); + ASSERT_EQ(nullptr, embedder->CompositeEmbeddedView(1).canvas); embedder->PrerollCompositeEmbeddedView( 1, std::make_unique()); - ASSERT_NE(nullptr, embedder->CompositeEmbeddedView(1)); + ASSERT_NE(nullptr, embedder->CompositeEmbeddedView(1).canvas); } TEST(AndroidExternalViewEmbedder, CancelFrame) { @@ -178,6 +184,9 @@ TEST(AndroidExternalViewEmbedder, CancelFrame) { auto canvases = embedder->GetCurrentCanvases(); ASSERT_EQ(0UL, canvases.size()); + + auto builders = embedder->GetCurrentBuilders(); + ASSERT_EQ(0UL, builders.size()); } TEST(AndroidExternalViewEmbedder, RasterizerRunsOnPlatformThread) { @@ -391,7 +400,7 @@ TEST(AndroidExternalViewEmbedder, SubmitFrame) { embedder->PrerollCompositeEmbeddedView(0, std::move(view_params_1)); // This is the recording canvas flow writes to. - auto canvas_1 = embedder->CompositeEmbeddedView(0); + auto canvas_1 = embedder->CompositeEmbeddedView(0).canvas; auto rect_paint = SkPaint(); rect_paint.setColor(SkColors::kCyan); @@ -458,7 +467,7 @@ TEST(AndroidExternalViewEmbedder, SubmitFrame) { embedder->PrerollCompositeEmbeddedView(0, std::move(view_params_1)); // This is the recording canvas flow writes to. - auto canvas_1 = embedder->CompositeEmbeddedView(0); + auto canvas_1 = embedder->CompositeEmbeddedView(0).canvas; auto rect_paint = SkPaint(); rect_paint.setColor(SkColors::kCyan); @@ -560,7 +569,7 @@ TEST(AndroidExternalViewEmbedder, SubmitFrameOverlayComposition) { rect_paint.setStyle(SkPaint::Style::kFill_Style); // This simulates Flutter UI that intersects with the first Android view. - embedder->CompositeEmbeddedView(0)->drawRect( + embedder->CompositeEmbeddedView(0).canvas->drawRect( SkRect::MakeXYWH(25, 25, 80, 150), rect_paint); { @@ -577,10 +586,10 @@ TEST(AndroidExternalViewEmbedder, SubmitFrameOverlayComposition) { } // This simulates Flutter UI that intersects with the first and second Android // views. - embedder->CompositeEmbeddedView(1)->drawRect(SkRect::MakeXYWH(25, 25, 80, 50), - rect_paint); + embedder->CompositeEmbeddedView(1).canvas->drawRect( + SkRect::MakeXYWH(25, 25, 80, 50), rect_paint); - embedder->CompositeEmbeddedView(1)->drawRect( + embedder->CompositeEmbeddedView(1).canvas->drawRect( SkRect::MakeXYWH(75, 75, 30, 100), rect_paint); EXPECT_CALL(*jni_mock, FlutterViewCreateOverlaySurface()) @@ -745,7 +754,7 @@ TEST(AndroidExternalViewEmbedder, DestroyOverlayLayersOnSizeChange) { embedder->PrerollCompositeEmbeddedView(0, std::move(view_params_1)); // This simulates Flutter UI that intersects with the Android view. - embedder->CompositeEmbeddedView(0)->drawRect( + embedder->CompositeEmbeddedView(0).canvas->drawRect( SkRect::MakeXYWH(50, 50, 200, 200), SkPaint()); // Create a new overlay surface. @@ -832,7 +841,7 @@ TEST(AndroidExternalViewEmbedder, DoesNotDestroyOverlayLayersOnSizeChange) { embedder->PrerollCompositeEmbeddedView(0, std::move(view_params_1)); // This simulates Flutter UI that intersects with the Android view. - embedder->CompositeEmbeddedView(0)->drawRect( + embedder->CompositeEmbeddedView(0).canvas->drawRect( SkRect::MakeXYWH(50, 50, 200, 200), SkPaint()); // Create a new overlay surface. @@ -948,7 +957,7 @@ TEST(AndroidExternalViewEmbedder, Teardown) { embedder->PrerollCompositeEmbeddedView(0, std::move(view_params)); // This simulates Flutter UI that intersects with the Android view. - embedder->CompositeEmbeddedView(0)->drawRect( + embedder->CompositeEmbeddedView(0).canvas->drawRect( SkRect::MakeXYWH(50, 50, 200, 200), SkPaint()); // Create a new overlay surface. diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java index 4d16a2ddab38d..13137ebf7dd7b 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java @@ -164,12 +164,14 @@ * *

Launch Screen and Splash Screen * - *

{@code FlutterActivity} supports the display of an Android "launch screen" as well as a - * Flutter-specific "splash screen". The launch screen is displayed while the Android application - * loads. It is only applicable if {@code FlutterActivity} is the first {@code Activity} displayed - * upon loading the app. After the launch screen passes, a splash screen is optionally displayed. - * The splash screen is displayed for as long as it takes Flutter to initialize and render its first - * frame. + *

{@code FlutterActivity} supports the display of an Android "launch screen", which is displayed + * while the Android application loads. It is only applicable if {@code FlutterActivity} is the + * first {@code Activity} displayed upon loading the app. + * + *

Prior to Flutter 2.5, {@code FlutterActivity} supported the display of a Flutter-specific + * "splash screen" that would be displayed after the launch screen passes. This has since been + * deprecated. If a launch screen is specified, it will automatically persist for as long as it + * takes Flutter to initialize and render its first frame. * *

Use Android themes to display a launch screen. Create two themes: a launch theme and a normal * theme. In the launch theme, set {@code windowBackground} to the desired {@code Drawable} for the @@ -198,13 +200,6 @@ * initialization, subclass {@code FlutterActivity} and override {@link #provideSplashScreen()}. See * {@link SplashScreen} for details on implementing a splash screen. * - *

Flutter ships with a splash screen that automatically displays the exact same {@code - * windowBackground} as the launch theme discussed previously. To use that splash screen, include - * the following metadata in AndroidManifest.xml for this {@code FlutterActivity}: - * - *

{@code } - * *

Alternative Activity {@link FlutterFragmentActivity} is also available, which * is similar to {@code FlutterActivity} but it extends {@code FragmentActivity}. You should use * {@code FlutterActivity}, if possible, but if you need a {@code FragmentActivity} then you should diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java index 708e3f4a8c241..8fb3bf2a6a37c 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -393,7 +393,10 @@ private boolean isAttachedToJni() { @NonNull Context context, @NonNull DartEntrypoint dartEntrypoint, @Nullable String initialRoute, - @Nullable List dartEntrypointArgs) { + @Nullable List dartEntrypointArgs, + @Nullable PlatformViewsController platformViewsController, + boolean automaticallyRegisterPlugins, + boolean waitForRestorationData) { if (!isAttachedToJni()) { throw new IllegalStateException( "Spawn can only be called on a fully constructed FlutterEngine"); @@ -409,7 +412,11 @@ private boolean isAttachedToJni() { context, // Context. null, // FlutterLoader. A null value passed here causes the constructor to get it from the // FlutterInjector. - newFlutterJNI); // FlutterJNI. + newFlutterJNI, // FlutterJNI. + platformViewsController, // PlatformViewsController. + null, // String[]. The Dart VM has already started, this arguments will have no effect. + automaticallyRegisterPlugins, // boolean. + waitForRestorationData); // boolean } /** diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngineConnectionRegistry.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngineConnectionRegistry.java index b3203179de19f..80aab73e04959 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngineConnectionRegistry.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngineConnectionRegistry.java @@ -331,9 +331,11 @@ private void attachToActivityInternal(@NonNull Activity activity, @NonNull Lifec this.activityPluginBinding = new FlutterEngineActivityPluginBinding(activity, lifecycle); final boolean useSoftwareRendering = - activity - .getIntent() - .getBooleanExtra(FlutterShellArgs.ARG_KEY_ENABLE_SOFTWARE_RENDERING, false); + activity.getIntent() != null + ? activity + .getIntent() + .getBooleanExtra(FlutterShellArgs.ARG_KEY_ENABLE_SOFTWARE_RENDERING, false) + : false; flutterEngine.getPlatformViewsController().setSoftwareRendering(useSoftwareRendering); // Activate the PlatformViewsController. This must happen before any plugins attempt diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java index bb1a9ba2995cf..fc9a95c2dd7bf 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java @@ -11,6 +11,7 @@ import io.flutter.FlutterInjector; import io.flutter.embedding.engine.dart.DartExecutor.DartEntrypoint; import io.flutter.embedding.engine.loader.FlutterLoader; +import io.flutter.plugin.platform.PlatformViewsController; import java.util.ArrayList; import java.util.List; @@ -142,20 +143,39 @@ public FlutterEngine createAndRunEngine(@NonNull Options options) { DartEntrypoint dartEntrypoint = options.getDartEntrypoint(); String initialRoute = options.getInitialRoute(); List dartEntrypointArgs = options.getDartEntrypointArgs(); + PlatformViewsController platformViewsController = options.getPlatformViewsController(); + platformViewsController = + platformViewsController != null ? platformViewsController : new PlatformViewsController(); + boolean automaticallyRegisterPlugins = options.getAutomaticallyRegisterPlugins(); + boolean waitForRestorationData = options.getWaitForRestorationData(); if (dartEntrypoint == null) { dartEntrypoint = DartEntrypoint.createDefault(); } if (activeEngines.size() == 0) { - engine = createEngine(context); + engine = + createEngine( + context, + platformViewsController, + automaticallyRegisterPlugins, + waitForRestorationData); if (initialRoute != null) { engine.getNavigationChannel().setInitialRoute(initialRoute); } engine.getDartExecutor().executeDartEntrypoint(dartEntrypoint, dartEntrypointArgs); } else { engine = - activeEngines.get(0).spawn(context, dartEntrypoint, initialRoute, dartEntrypointArgs); + activeEngines + .get(0) + .spawn( + context, + dartEntrypoint, + initialRoute, + dartEntrypointArgs, + platformViewsController, + automaticallyRegisterPlugins, + waitForRestorationData); } activeEngines.add(engine); @@ -178,8 +198,19 @@ public void onEngineWillDestroy() { } @VisibleForTesting - /* package */ FlutterEngine createEngine(Context context) { - return new FlutterEngine(context); + /* package */ FlutterEngine createEngine( + Context context, + @NonNull PlatformViewsController platformViewsController, + boolean automaticallyRegisterPlugins, + boolean waitForRestorationData) { + return new FlutterEngine( + context, // Context. + null, // FlutterLoader. + null, // FlutterJNI. + platformViewsController, // PlatformViewsController. + null, // String[]. The Dart VM has already started, this arguments will have no effect. + automaticallyRegisterPlugins, // boolean. + waitForRestorationData); // boolean. } /** Options that control how a FlutterEngine should be created. */ @@ -188,6 +219,9 @@ public static class Options { @Nullable private DartEntrypoint dartEntrypoint; @Nullable private String initialRoute; @Nullable private List dartEntrypointArgs; + @NonNull private PlatformViewsController platformViewsController; + private boolean automaticallyRegisterPlugins = true; + private boolean waitForRestorationData = false; public Options(@NonNull Context context) { this.context = context; @@ -219,6 +253,28 @@ public List getDartEntrypointArgs() { return dartEntrypointArgs; } + /** Manages platform views. */ + public PlatformViewsController getPlatformViewsController() { + return platformViewsController; + } + + /** + * If plugins are automatically registered, then they are registered during the {@link + * io.flutter.embedding.engine.FlutterEngine}'s constructor. + */ + public boolean getAutomaticallyRegisterPlugins() { + return automaticallyRegisterPlugins; + } + + /** + * The waitForRestorationData flag controls whether the engine delays responding to requests + * from the framework for restoration data until that data has been provided to the engine via + * {@code RestorationChannel.setRestorationData(byte[] data)}. + */ + public boolean getWaitForRestorationData() { + return waitForRestorationData; + } + /** * Setter for `dartEntrypoint` property. * @@ -251,5 +307,41 @@ public Options setDartEntrypointArgs(List dartEntrypointArgs) { this.dartEntrypointArgs = dartEntrypointArgs; return this; } + + /** + * Setter for `platformViewsController` property. + * + * @param platformViewsController Manages platform views. + */ + public Options setPlatformViewsController( + @NonNull PlatformViewsController platformViewsController) { + this.platformViewsController = platformViewsController; + return this; + } + + /** + * Setter for `automaticallyRegisterPlugins` property. + * + * @param automaticallyRegisterPlugins If plugins are automatically registered, then they are + * registered during the execution of {@link io.flutter.embedding.engine.FlutterEngine}'s + * constructor. + */ + public Options setAutomaticallyRegisterPlugins(boolean automaticallyRegisterPlugins) { + this.automaticallyRegisterPlugins = automaticallyRegisterPlugins; + return this; + } + + /** + * Setter for `waitForRestorationData` property. + * + * @param waitForRestorationData The waitForRestorationData flag controls whether the engine + * delays responding to requests from the framework for restoration data until that data has + * been provided to the engine via {@code RestorationChannel.setRestorationData(byte[] + * data)}. + */ + public Options setWaitForRestorationData(boolean waitForRestorationData) { + this.waitForRestorationData = waitForRestorationData; + return this; + } } } diff --git a/shell/platform/android/io/flutter/plugin/common/MethodChannel.java b/shell/platform/android/io/flutter/plugin/common/MethodChannel.java index c13f82d2beb1f..9949167a7ce03 100644 --- a/shell/platform/android/io/flutter/plugin/common/MethodChannel.java +++ b/shell/platform/android/io/flutter/plugin/common/MethodChannel.java @@ -173,8 +173,10 @@ public interface MethodCallHandler { *

Any uncaught exception thrown by this method will be caught by the channel implementation * and logged, and an error result will be sent back to Flutter. * - *

The handler is called on the platform thread (Android main thread). For more details see - * Threading in + *

The handler is called on the platform thread (Android main thread) by default, or + * otherwise on the thread specified by the {@link BinaryMessenger.TaskQueue} provided to the + * associated {@link MethodChannel} when it was created. See also Threading in * the Flutter Engine. * * @param call A {@link MethodCall}. @@ -190,10 +192,7 @@ public interface MethodCallHandler { * Flutter methods provide implementations of this interface for handling results received from * Flutter. * - *

All methods of this class must be called on the platform thread (Android main thread). For - * more details see Threading in the - * Flutter Engine. + *

All methods of this class can be invoked on any thread. */ public interface Result { /** diff --git a/shell/platform/android/io/flutter/plugin/localization/LocalizationPlugin.java b/shell/platform/android/io/flutter/plugin/localization/LocalizationPlugin.java index 38dc780ed79fe..acb1e904ca629 100644 --- a/shell/platform/android/io/flutter/plugin/localization/LocalizationPlugin.java +++ b/shell/platform/android/io/flutter/plugin/localization/LocalizationPlugin.java @@ -23,7 +23,10 @@ public class LocalizationPlugin { @NonNull private final LocalizationChannel localizationChannel; @NonNull private final Context context; - @SuppressLint("AppBundleLocaleChanges") // This is optionally turned on by apps. + @SuppressLint({ + "AppBundleLocaleChanges", + "DiscouragedApi" + }) // This is optionally turned on by apps. @VisibleForTesting final LocalizationChannel.LocalizationMessageHandler localizationMessageHandler = new LocalizationChannel.LocalizationMessageHandler() { diff --git a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java index 4f78a732d1fe1..794d339c79452 100644 --- a/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java +++ b/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java @@ -626,7 +626,7 @@ public MotionEvent toMotionEvent( return MotionEvent.obtain( trackedEvent.getDownTime(), trackedEvent.getEventTime(), - trackedEvent.getAction(), + touch.action, touch.pointerCount, pointerProperties, pointerCoords, diff --git a/shell/platform/android/io/flutter/view/AccessibilityBridge.java b/shell/platform/android/io/flutter/view/AccessibilityBridge.java index 28b09507214b3..8b05faa1600b6 100644 --- a/shell/platform/android/io/flutter/view/AccessibilityBridge.java +++ b/shell/platform/android/io/flutter/view/AccessibilityBridge.java @@ -9,6 +9,7 @@ import android.app.Activity; import android.content.ContentResolver; import android.content.Context; +import android.content.res.Configuration; import android.database.ContentObserver; import android.graphics.Rect; import android.net.Uri; @@ -120,6 +121,9 @@ public class AccessibilityBridge extends AccessibilityNodeProvider { // and the most significant 16 bits are used for engine generated IDs. private static final int MIN_ENGINE_GENERATED_NODE_ID = 1 << 16; + // Font weight adjustment for bold text. FontWeight.Bold - FontWeight.Normal = w700 - w400 = 300. + private static final int BOLD_TEXT_WEIGHT_ADJUSTMENT = 300; + /// Value is derived from ACTION_TYPE_MASK in AccessibilityNodeInfo.java private static int FIRST_RESOURCE_ID = 267386881; @@ -257,6 +261,30 @@ public int getHoveredObjectId() { @Nullable private OnAccessibilityChangeListener onAccessibilityChangeListener; + // Whether the users are using assistive technologies to interact with the devices. + // + // The getter returns true when at least one of the assistive technologies is running: + // TalkBack, SwitchAccess, or VoiceAccess. + @VisibleForTesting + public boolean getAccessibleNavigation() { + return accessibleNavigation; + } + + private boolean accessibleNavigation = false; + + private void setAccessibleNavigation(boolean value) { + if (accessibleNavigation == value) { + return; + } + accessibleNavigation = value; + if (accessibleNavigation) { + accessibilityFeatureFlags |= AccessibilityFeature.ACCESSIBLE_NAVIGATION.value; + } else { + accessibilityFeatureFlags &= ~AccessibilityFeature.ACCESSIBLE_NAVIGATION.value; + } + sendLatestAccessibilityFlagsToFlutter(); + } + // Set to true after {@code release} has been invoked. private boolean isReleased = false; @@ -331,6 +359,7 @@ public void onAccessibilityStateChanged(boolean accessibilityEnabled) { accessibilityChannel.setAccessibilityMessageHandler(accessibilityMessageHandler); accessibilityChannel.onAndroidAccessibilityEnabled(); } else { + setAccessibleNavigation(false); accessibilityChannel.setAccessibilityMessageHandler(null); accessibilityChannel.onAndroidAccessibilityDisabled(); } @@ -409,7 +438,6 @@ public AccessibilityBridge( this.contentResolver = contentResolver; this.accessibilityViewEmbedder = accessibilityViewEmbedder; this.platformViewsAccessibilityDelegate = platformViewsAccessibilityDelegate; - // Tell Flutter whether accessibility is initially active or not. Then register a listener // to be notified of changes in the future. accessibilityStateChangeListener.onAccessibilityStateChanged(accessibilityManager.isEnabled()); @@ -425,13 +453,10 @@ public void onTouchExplorationStateChanged(boolean isTouchExplorationEnabled) { if (isReleased) { return; } - if (isTouchExplorationEnabled) { - accessibilityFeatureFlags |= AccessibilityFeature.ACCESSIBLE_NAVIGATION.value; - } else { + if (!isTouchExplorationEnabled) { + setAccessibleNavigation(false); onTouchExplorationExit(); - accessibilityFeatureFlags &= ~AccessibilityFeature.ACCESSIBLE_NAVIGATION.value; } - sendLatestAccessibilityFlagsToFlutter(); if (onAccessibilityChangeListener != null) { onAccessibilityChangeListener.onAccessibilityChanged( @@ -455,6 +480,12 @@ public void onTouchExplorationStateChanged(boolean isTouchExplorationEnabled) { this.contentResolver.registerContentObserver(transitionUri, false, animationScaleObserver); } + // Tells Flutter whether the text should be bolded or not. If the user changes bold text + // setting, the configuration will change and trigger a re-build of the accesibiltyBridge. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + setBoldTextFlag(); + } + platformViewsAccessibilityDelegate.attachAccessibilityBridge(this); } @@ -518,6 +549,26 @@ private boolean shouldSetCollectionInfo(final SemanticsNode semanticsNode) { accessibilityFocusedSemanticsNode, o -> o.hasFlag(Flag.HAS_IMPLICIT_SCROLLING))); } + @TargetApi(31) + @RequiresApi(31) + private void setBoldTextFlag() { + if (rootAccessibilityView == null || rootAccessibilityView.getResources() == null) { + return; + } + int fontWeightAdjustment = + rootAccessibilityView.getResources().getConfiguration().fontWeightAdjustment; + boolean shouldBold = + fontWeightAdjustment != Configuration.FONT_WEIGHT_ADJUSTMENT_UNDEFINED + && fontWeightAdjustment >= BOLD_TEXT_WEIGHT_ADJUSTMENT; + + if (shouldBold) { + accessibilityFeatureFlags |= AccessibilityFeature.BOLD_TEXT.value; + } else { + accessibilityFeatureFlags &= AccessibilityFeature.BOLD_TEXT.value; + } + sendLatestAccessibilityFlagsToFlutter(); + } + @VisibleForTesting public AccessibilityNodeInfo obtainAccessibilityNodeInfo(View rootView, int virtualViewId) { return AccessibilityNodeInfo.obtain(rootView, virtualViewId); @@ -551,6 +602,7 @@ public AccessibilityNodeInfo obtainAccessibilityNodeInfo(View rootView, int virt // Suppressing Lint warning for new API, as we are version guarding all calls to newer APIs @SuppressLint("NewApi") public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) { + setAccessibleNavigation(true); if (virtualViewId >= MIN_ENGINE_GENERATED_NODE_ID) { // The node is in the engine generated range, and is provided by the accessibility view // embedder. diff --git a/shell/platform/android/io/flutter/view/VsyncWaiter.java b/shell/platform/android/io/flutter/view/VsyncWaiter.java index c5021460da89b..80f5658dea569 100644 --- a/shell/platform/android/io/flutter/view/VsyncWaiter.java +++ b/shell/platform/android/io/flutter/view/VsyncWaiter.java @@ -47,6 +47,7 @@ public void onDisplayChanged(int displayId) { private static DisplayListener listener; private long refreshPeriodNanos = -1; private FlutterJNI flutterJNI; + private FrameCallback frameCallback = new FrameCallback(0); @NonNull public static VsyncWaiter getInstance(float fps, @NonNull FlutterJNI flutterJNI) { @@ -85,22 +86,41 @@ public static void reset() { listener = null; } + private class FrameCallback implements Choreographer.FrameCallback { + + private long cookie; + + FrameCallback(long cookie) { + this.cookie = cookie; + } + + @Override + public void doFrame(long frameTimeNanos) { + long delay = System.nanoTime() - frameTimeNanos; + if (delay < 0) { + delay = 0; + } + flutterJNI.onVsync(delay, refreshPeriodNanos, cookie); + frameCallback = this; + } + } + private final FlutterJNI.AsyncWaitForVsyncDelegate asyncWaitForVsyncDelegate = new FlutterJNI.AsyncWaitForVsyncDelegate() { + + private Choreographer.FrameCallback obtainFrameCallback(final long cookie) { + if (frameCallback != null) { + frameCallback.cookie = cookie; + FrameCallback ret = frameCallback; + frameCallback = null; + return ret; + } + return new FrameCallback(cookie); + } + @Override public void asyncWaitForVsync(long cookie) { - Choreographer.getInstance() - .postFrameCallback( - new Choreographer.FrameCallback() { - @Override - public void doFrame(long frameTimeNanos) { - long delay = System.nanoTime() - frameTimeNanos; - if (delay < 0) { - delay = 0; - } - flutterJNI.onVsync(delay, refreshPeriodNanos, cookie); - } - }); + Choreographer.getInstance().postFrameCallback(obtainFrameCallback(cookie)); } }; diff --git a/shell/platform/android/platform_view_android.cc b/shell/platform/android/platform_view_android.cc index 305948de8a093..5e0ab4be7f2e7 100644 --- a/shell/platform/android/platform_view_android.cc +++ b/shell/platform/android/platform_view_android.cc @@ -10,14 +10,10 @@ #include "flutter/fml/synchronization/waitable_event.h" #include "flutter/shell/common/shell_io_manager.h" #include "flutter/shell/gpu/gpu_surface_gl_delegate.h" -#if IMPELLER_SUPPORTS_PLATFORM #include "flutter/shell/platform/android/android_context_gl_impeller.h" -#endif #include "flutter/shell/platform/android/android_context_gl_skia.h" #include "flutter/shell/platform/android/android_external_texture_gl.h" -#if IMPELLER_SUPPORTS_PLATFORM #include "flutter/shell/platform/android/android_surface_gl_impeller.h" -#endif #include "flutter/shell/platform/android/android_surface_gl_skia.h" #include "flutter/shell/platform/android/android_surface_software.h" #include "flutter/shell/platform/android/context/android_context.h" @@ -46,17 +42,13 @@ std::unique_ptr AndroidSurfaceFactoryImpl::CreateSurface() { return std::make_unique(android_context_, jni_facade_); case AndroidRenderingAPI::kOpenGLES: -#if IMPELLER_SUPPORTS_PLATFORM if (enable_impeller_) { return std::make_unique(android_context_, jni_facade_); } else { -#endif return std::make_unique(android_context_, jni_facade_); -#if IMPELLER_SUPPORTS_PLATFORM } -#endif default: FML_DCHECK(false); return nullptr; @@ -71,11 +63,9 @@ static std::shared_ptr CreateAndroidContext( if (use_software_rendering) { return std::make_shared(AndroidRenderingAPI::kSoftware); } -#if IMPELLER_SUPPORTS_PLATFORM if (enable_impeller) { return std::make_unique(); } -#endif return std::make_unique( AndroidRenderingAPI::kOpenGLES, // fml::MakeRefCounted(), // diff --git a/shell/platform/android/platform_view_android_jni_impl.cc b/shell/platform/android/platform_view_android_jni_impl.cc index 92070dc0f52e6..2d02fcbfd3fb2 100644 --- a/shell/platform/android/platform_view_android_jni_impl.cc +++ b/shell/platform/android/platform_view_android_jni_impl.cc @@ -1319,18 +1319,6 @@ void PlatformViewAndroidJNIImpl::SurfaceTextureUpdateTexImage( FML_CHECK(fml::jni::CheckException(env)); } -// The bounds we set for the canvas are post composition. -// To fill the canvas we need to ensure that the transformation matrix -// on the `SurfaceTexture` will be scaled to fill. We rescale and preseve -// the scaled aspect ratio. -SkSize ScaleToFill(float scaleX, float scaleY) { - const double epsilon = std::numeric_limits::epsilon(); - // scaleY is negative. - const double minScale = fmin(scaleX, fabs(scaleY)); - const double rescale = 1.0f / (minScale + epsilon); - return SkSize::Make(scaleX * rescale, scaleY * rescale); -} - void PlatformViewAndroidJNIImpl::SurfaceTextureGetTransformMatrix( JavaLocalRef surface_texture, SkMatrix& transform) { @@ -1355,12 +1343,34 @@ void PlatformViewAndroidJNIImpl::SurfaceTextureGetTransformMatrix( FML_CHECK(fml::jni::CheckException(env)); float* m = env->GetFloatArrayElements(transformMatrix.obj(), nullptr); - float scaleX = m[0], scaleY = m[5]; - const SkSize scaled = ScaleToFill(scaleX, scaleY); + + // SurfaceTexture 4x4 Column Major -> Skia 3x3 Row Major + + // SurfaceTexture 4x4 (Column Major): + // | m[0] m[4] m[ 8] m[12] | + // | m[1] m[5] m[ 9] m[13] | + // | m[2] m[6] m[10] m[14] | + // | m[3] m[7] m[11] m[15] | + + // According to Android documentation, the 4x4 matrix returned should be used + // with texture coordinates in the form (s, t, 0, 1). Since the z component is + // always 0.0, we are free to ignore any element that multiplies with the z + // component. Converting this to a 3x3 matrix is easy: + + // SurfaceTexture 3x3 (Column Major): + // | m[0] m[4] m[12] | + // | m[1] m[5] m[13] | + // | m[3] m[7] m[15] | + + // Skia (Row Major): + // | m[0] m[1] m[2] | + // | m[3] m[4] m[5] | + // | m[6] m[7] m[8] | + SkScalar matrix3[] = { - scaled.fWidth, m[1], m[2], // - m[4], scaled.fHeight, m[6], // - m[8], m[9], m[10], // + m[0], m[4], m[12], // + m[1], m[5], m[13], // + m[3], m[7], m[15], // }; env->ReleaseFloatArrayElements(transformMatrix.obj(), m, JNI_ABORT); transform.set9(matrix3); diff --git a/shell/platform/android/surface/android_surface_mock.cc b/shell/platform/android/surface/android_surface_mock.cc index c3a9f38fc8f0c..8f718fcae667a 100644 --- a/shell/platform/android/surface/android_surface_mock.cc +++ b/shell/platform/android/surface/android_surface_mock.cc @@ -22,8 +22,10 @@ bool AndroidSurfaceMock::GLContextPresent(const GLPresentInfo& present_info) { return true; } -intptr_t AndroidSurfaceMock::GLContextFBO(GLFrameInfo frame_info) const { - return 0; +GLFBOInfo AndroidSurfaceMock::GLContextFBO(GLFrameInfo frame_info) const { + return GLFBOInfo{ + .fbo_id = 0, + }; } } // namespace flutter diff --git a/shell/platform/android/surface/android_surface_mock.h b/shell/platform/android/surface/android_surface_mock.h index d7363873a9a31..3f326a21920b7 100644 --- a/shell/platform/android/surface/android_surface_mock.h +++ b/shell/platform/android/surface/android_surface_mock.h @@ -51,7 +51,7 @@ class AndroidSurfaceMock final : public GPUSurfaceGLDelegate, bool GLContextPresent(const GLPresentInfo& present_info) override; // |GPUSurfaceGLDelegate| - intptr_t GLContextFBO(GLFrameInfo frame_info) const override; + GLFBOInfo GLContextFBO(GLFrameInfo frame_info) const override; }; } // namespace flutter diff --git a/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineConnectionRegistryTest.java b/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineConnectionRegistryTest.java index 8bbc729d8b3a1..975d6d052a5b1 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineConnectionRegistryTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineConnectionRegistryTest.java @@ -127,15 +127,18 @@ public void softwareRendering() { Activity activity = mock(Activity.class); when(appComponent.getAppComponent()).thenReturn(activity); + // Test attachToActivity with an Activity that has no Intent. + FlutterEngineConnectionRegistry registry = + new FlutterEngineConnectionRegistry(context, flutterEngine, flutterLoader); + registry.attachToActivity(appComponent, mock(Lifecycle.class)); + verify(platformViewsController).setSoftwareRendering(false); + Intent intent = mock(Intent.class); when(intent.getBooleanExtra("enable-software-rendering", false)).thenReturn(false); when(activity.getIntent()).thenReturn(intent); - FlutterEngineConnectionRegistry registry = - new FlutterEngineConnectionRegistry(context, flutterEngine, flutterLoader); - registry.attachToActivity(appComponent, mock(Lifecycle.class)); - verify(platformViewsController).setSoftwareRendering(false); + verify(platformViewsController, times(2)).setSoftwareRendering(false); when(intent.getBooleanExtra("enable-software-rendering", false)).thenReturn(true); diff --git a/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineGroupComponentTest.java b/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineGroupComponentTest.java index 223ec86f3d342..4e91e7788ca64 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineGroupComponentTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineGroupComponentTest.java @@ -5,6 +5,7 @@ package io.flutter.embedding.engine; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; @@ -25,6 +26,7 @@ import io.flutter.embedding.engine.dart.DartExecutor.DartEntrypoint; import io.flutter.embedding.engine.loader.FlutterLoader; import io.flutter.embedding.engine.systemchannels.NavigationChannel; +import io.flutter.plugin.platform.PlatformViewsController; import io.flutter.plugins.GeneratedPluginRegistrant; import java.util.ArrayList; import java.util.List; @@ -42,7 +44,7 @@ @RunWith(AndroidJUnit4.class) public class FlutterEngineGroupComponentTest { private final Context ctx = ApplicationProvider.getApplicationContext(); - @Mock FlutterJNI mockflutterJNI; + @Mock FlutterJNI mockFlutterJNI; @Mock FlutterLoader mockFlutterLoader; FlutterEngineGroup engineGroupUnderTest; FlutterEngine firstEngineUnderTest; @@ -54,26 +56,46 @@ public void setUp() { MockitoAnnotations.openMocks(this); jniAttached = false; - when(mockflutterJNI.isAttached()).thenAnswer(invocation -> jniAttached); - doAnswer(invocation -> jniAttached = true).when(mockflutterJNI).attachToNative(); + when(mockFlutterJNI.isAttached()).thenAnswer(invocation -> jniAttached); + doAnswer(invocation -> jniAttached = true).when(mockFlutterJNI).attachToNative(); GeneratedPluginRegistrant.clearRegisteredEngines(); when(mockFlutterLoader.findAppBundlePath()).thenReturn("some/path/to/flutter_assets"); + + FlutterJNI.Factory jniFactory = + new FlutterJNI.Factory() { + @Override + public FlutterJNI provideFlutterJNI() { + // The default implementation is that `new FlutterJNI()` will report errors when + // creating the engine later, + // Change mockFlutterJNI to the default so that we can create the engine directly in + // later tests + return mockFlutterJNI; + } + }; + FlutterInjector.setInstance( - new FlutterInjector.Builder().setFlutterLoader(mockFlutterLoader).build()); + new FlutterInjector.Builder() + .setFlutterLoader(mockFlutterLoader) + .setFlutterJNIFactory(jniFactory) + .build()); firstEngineUnderTest = spy( new FlutterEngine( ctx, mock(FlutterLoader.class), - mockflutterJNI, + mockFlutterJNI, /*dartVmArgs=*/ new String[] {}, /*automaticallyRegisterPlugins=*/ false)); engineGroupUnderTest = new FlutterEngineGroup(ctx) { @Override - FlutterEngine createEngine(Context context) { + FlutterEngine createEngine( + Context context, + PlatformViewsController platformViewsController, + boolean automaticallyRegisterPlugins, + boolean waitForRestorationData) { return firstEngineUnderTest; } }; @@ -124,7 +146,10 @@ public void canSpawnMoreEngines() { any(Context.class), any(DartEntrypoint.class), nullable(String.class), - nullable(List.class)); + nullable(List.class), + any(PlatformViewsController.class), + any(Boolean.class), + any(Boolean.class)); FlutterEngine secondEngine = engineGroupUnderTest.createAndRunEngine(ctx, mock(DartEntrypoint.class)); @@ -139,7 +164,10 @@ public void canSpawnMoreEngines() { any(Context.class), any(DartEntrypoint.class), nullable(String.class), - nullable(List.class))) + nullable(List.class), + any(PlatformViewsController.class), + any(Boolean.class), + any(Boolean.class))) .thenReturn(mock(FlutterEngine.class)); FlutterEngine thirdEngine = @@ -156,7 +184,7 @@ public void canCreateAndRunCustomEntrypoints() { FlutterInjector.instance().flutterLoader().findAppBundlePath(), "other entrypoint")); assertEquals(1, engineGroupUnderTest.activeEngines.size()); - verify(mockflutterJNI, times(1)) + verify(mockFlutterJNI, times(1)) .runBundleAndSnapshotFromLibrary( eq("some/path/to/flutter_assets"), eq("other entrypoint"), @@ -174,13 +202,13 @@ public void canCreateAndRunWithCustomInitialRoute() { assertEquals(1, engineGroupUnderTest.activeEngines.size()); verify(firstEngine.getNavigationChannel(), times(1)).setInitialRoute("/foo"); - when(mockflutterJNI.isAttached()).thenReturn(true); + when(mockFlutterJNI.isAttached()).thenReturn(true); jniAttached = false; - FlutterJNI secondMockflutterJNI = mock(FlutterJNI.class); - when(secondMockflutterJNI.isAttached()).thenAnswer(invocation -> jniAttached); - doAnswer(invocation -> jniAttached = true).when(secondMockflutterJNI).attachToNative(); - doReturn(secondMockflutterJNI) - .when(mockflutterJNI) + FlutterJNI secondMockFlutterJNI = mock(FlutterJNI.class); + when(secondMockFlutterJNI.isAttached()).thenAnswer(invocation -> jniAttached); + doAnswer(invocation -> jniAttached = true).when(secondMockFlutterJNI).attachToNative(); + doReturn(secondMockFlutterJNI) + .when(mockFlutterJNI) .spawn( nullable(String.class), nullable(String.class), @@ -191,7 +219,7 @@ public void canCreateAndRunWithCustomInitialRoute() { engineGroupUnderTest.createAndRunEngine(ctx, mock(DartEntrypoint.class), "/bar"); assertEquals(2, engineGroupUnderTest.activeEngines.size()); - verify(mockflutterJNI, times(1)) + verify(mockFlutterJNI, times(1)) .spawn(nullable(String.class), nullable(String.class), eq("/bar"), nullable(List.class)); } @@ -204,7 +232,7 @@ public void canCreateAndRunWithCustomEntrypointArgs() { .setDartEntrypoint(mock(DartEntrypoint.class)) .setDartEntrypointArgs(firstDartEntrypointArgs)); assertEquals(1, engineGroupUnderTest.activeEngines.size()); - verify(mockflutterJNI, times(1)) + verify(mockFlutterJNI, times(1)) .runBundleAndSnapshotFromLibrary( nullable(String.class), nullable(String.class), @@ -212,13 +240,13 @@ public void canCreateAndRunWithCustomEntrypointArgs() { any(AssetManager.class), eq(firstDartEntrypointArgs)); - when(mockflutterJNI.isAttached()).thenReturn(true); + when(mockFlutterJNI.isAttached()).thenReturn(true); jniAttached = false; - FlutterJNI secondMockflutterJNI = mock(FlutterJNI.class); - when(secondMockflutterJNI.isAttached()).thenAnswer(invocation -> jniAttached); - doAnswer(invocation -> jniAttached = true).when(secondMockflutterJNI).attachToNative(); - doReturn(secondMockflutterJNI) - .when(mockflutterJNI) + FlutterJNI secondMockFlutterJNI = mock(FlutterJNI.class); + when(secondMockFlutterJNI.isAttached()).thenAnswer(invocation -> jniAttached); + doAnswer(invocation -> jniAttached = true).when(secondMockFlutterJNI).attachToNative(); + doReturn(secondMockFlutterJNI) + .when(mockFlutterJNI) .spawn( nullable(String.class), nullable(String.class), @@ -232,11 +260,90 @@ public void canCreateAndRunWithCustomEntrypointArgs() { .setDartEntrypointArgs(secondDartEntrypointArgs)); assertEquals(2, engineGroupUnderTest.activeEngines.size()); - verify(mockflutterJNI, times(1)) + verify(mockFlutterJNI, times(1)) .spawn( nullable(String.class), nullable(String.class), nullable(String.class), eq(secondDartEntrypointArgs)); } + + @Test + public void createEngineSupportMoreParams() { + // Create a new FlutterEngineGroup because the first engine created in engineGroupUnderTest was + // changed to firstEngineUnderTest in `setUp()`, so can't use it to validate params. + FlutterEngineGroup engineGroup = new FlutterEngineGroup(ctx); + + PlatformViewsController controller = new PlatformViewsController(); + boolean waitForRestorationData = true; + boolean automaticallyRegisterPlugins = true; + + when(FlutterInjector.instance().flutterLoader().automaticallyRegisterPlugins()) + .thenReturn(true); + assertTrue(FlutterInjector.instance().flutterLoader().automaticallyRegisterPlugins()); + assertEquals(0, GeneratedPluginRegistrant.getRegisteredEngines().size()); + + FlutterEngine firstEngine = + engineGroup.createAndRunEngine( + new FlutterEngineGroup.Options(ctx) + .setDartEntrypoint(mock(DartEntrypoint.class)) + .setPlatformViewsController(controller) + .setWaitForRestorationData(waitForRestorationData) + .setAutomaticallyRegisterPlugins(automaticallyRegisterPlugins)); + + assertEquals(1, GeneratedPluginRegistrant.getRegisteredEngines().size()); + assertEquals(controller, firstEngine.getPlatformViewsController()); + assertEquals( + waitForRestorationData, firstEngine.getRestorationChannel().waitForRestorationData); + } + + @Test + public void spawnEngineSupportMoreParams() { + FlutterEngine firstEngine = + engineGroupUnderTest.createAndRunEngine( + new FlutterEngineGroup.Options(ctx).setDartEntrypoint(mock(DartEntrypoint.class))); + assertEquals(1, engineGroupUnderTest.activeEngines.size()); + verify(mockFlutterJNI, times(1)) + .runBundleAndSnapshotFromLibrary( + nullable(String.class), + nullable(String.class), + isNull(), + any(AssetManager.class), + nullable(List.class)); + + when(mockFlutterJNI.isAttached()).thenReturn(true); + jniAttached = false; + FlutterJNI secondMockFlutterJNI = mock(FlutterJNI.class); + when(secondMockFlutterJNI.isAttached()).thenAnswer(invocation -> jniAttached); + doAnswer(invocation -> jniAttached = true).when(secondMockFlutterJNI).attachToNative(); + doReturn(secondMockFlutterJNI) + .when(mockFlutterJNI) + .spawn( + nullable(String.class), + nullable(String.class), + nullable(String.class), + nullable(List.class)); + + PlatformViewsController controller = new PlatformViewsController(); + boolean waitForRestorationData = false; + boolean automaticallyRegisterPlugins = false; + + when(FlutterInjector.instance().flutterLoader().automaticallyRegisterPlugins()) + .thenReturn(true); + assertTrue(FlutterInjector.instance().flutterLoader().automaticallyRegisterPlugins()); + assertEquals(0, GeneratedPluginRegistrant.getRegisteredEngines().size()); + + FlutterEngine secondEngine = + engineGroupUnderTest.createAndRunEngine( + new FlutterEngineGroup.Options(ctx) + .setDartEntrypoint(mock(DartEntrypoint.class)) + .setWaitForRestorationData(waitForRestorationData) + .setPlatformViewsController(controller) + .setAutomaticallyRegisterPlugins(automaticallyRegisterPlugins)); + + assertEquals( + waitForRestorationData, secondEngine.getRestorationChannel().waitForRestorationData); + assertEquals(controller, secondEngine.getPlatformViewsController()); + assertEquals(0, GeneratedPluginRegistrant.getRegisteredEngines().size()); + } } diff --git a/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java b/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java index d3fb9a8659efe..824c148a70086 100644 --- a/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java +++ b/shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java @@ -172,6 +172,51 @@ public void itUsesActionEventTypeFromFrameworkEventForVirtualDisplays() { assertNotEquals(resolvedEvent.getAction(), original.getAction()); } + @Test + public void itUsesActionEventTypeFromFrameworkEventAsActionChanged() { + MotionEventTracker motionEventTracker = MotionEventTracker.getInstance(); + PlatformViewsController platformViewsController = new PlatformViewsController(); + + MotionEvent original = + MotionEvent.obtain( + 10, // downTime + 10, // eventTime + 261, // action + 0, // x + 0, // y + 0 // metaState + ); + + MotionEventTracker.MotionEventId motionEventId = motionEventTracker.track(original); + + PlatformViewTouch frameWorkTouch = + new PlatformViewTouch( + 0, // viewId + original.getDownTime(), + original.getEventTime(), + 0, // action + 1, // pointerCount + Arrays.asList(Arrays.asList(0, 0)), // pointer properties + Arrays.asList(Arrays.asList(0., 1., 2., 3., 4., 5., 6., 7., 8.)), // pointer coords + original.getMetaState(), + original.getButtonState(), + original.getXPrecision(), + original.getYPrecision(), + original.getDeviceId(), + original.getEdgeFlags(), + original.getSource(), + original.getFlags(), + motionEventId.getId()); + MotionEvent resolvedEvent = + platformViewsController.toMotionEvent( + 1, // density + frameWorkTouch, + false // usingVirtualDisplays + ); + assertEquals(resolvedEvent.getAction(), frameWorkTouch.action); + assertNotEquals(resolvedEvent.getAction(), original.getAction()); + } + @Ignore @Test public void itUsesActionEventTypeFromMotionEventForHybridPlatformViews() { @@ -214,8 +259,7 @@ public void itUsesActionEventTypeFromMotionEventForHybridPlatformViews() { platformViewsController.toMotionEvent( /*density=*/ 1, frameWorkTouch, /*usingVirtualDisplay=*/ false); - assertNotEquals(resolvedEvent.getAction(), frameWorkTouch.action); - assertEquals(resolvedEvent.getAction(), original.getAction()); + assertEquals(resolvedEvent.getAction(), frameWorkTouch.action); } @Test diff --git a/shell/platform/android/test/io/flutter/view/AccessibilityBridgeTest.java b/shell/platform/android/test/io/flutter/view/AccessibilityBridgeTest.java index 324331d9d1817..a88c0323a7b33 100644 --- a/shell/platform/android/test/io/flutter/view/AccessibilityBridgeTest.java +++ b/shell/platform/android/test/io/flutter/view/AccessibilityBridgeTest.java @@ -25,6 +25,8 @@ import android.app.Activity; import android.content.ContentResolver; import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; import android.graphics.Rect; import android.os.Bundle; import android.text.SpannableString; @@ -124,6 +126,44 @@ public void itTakesGlobalCoordinatesOfFlutterViewIntoAccount() { assertEquals(position, outBoundsInScreen.top); } + @Test + public void itSetsAccessibleNavigation() { + AccessibilityChannel mockChannel = mock(AccessibilityChannel.class); + AccessibilityViewEmbedder mockViewEmbedder = mock(AccessibilityViewEmbedder.class); + AccessibilityManager mockManager = mock(AccessibilityManager.class); + View mockRootView = mock(View.class); + Context context = mock(Context.class); + when(mockRootView.getContext()).thenReturn(context); + when(context.getPackageName()).thenReturn("test"); + when(mockManager.isTouchExplorationEnabled()).thenReturn(false); + AccessibilityBridge accessibilityBridge = + setUpBridge( + /*rootAccessibilityView=*/ mockRootView, + /*accessibilityChannel=*/ mockChannel, + /*accessibilityManager=*/ mockManager, + /*contentResolver=*/ null, + /*accessibilityViewEmbedder=*/ mockViewEmbedder, + /*platformViewsAccessibilityDelegate=*/ null); + ArgumentCaptor listenerCaptor = + ArgumentCaptor.forClass(AccessibilityManager.TouchExplorationStateChangeListener.class); + verify(mockManager).addTouchExplorationStateChangeListener(listenerCaptor.capture()); + + assertEquals(accessibilityBridge.getAccessibleNavigation(), false); + verify(mockChannel).setAccessibilityFeatures(0); + reset(mockChannel); + + // Simulate assistive technology accessing accessibility tree. + accessibilityBridge.createAccessibilityNodeInfo(0); + verify(mockChannel).setAccessibilityFeatures(1); + assertEquals(accessibilityBridge.getAccessibleNavigation(), true); + + // Simulate turning off TalkBack. + reset(mockChannel); + listenerCaptor.getValue().onTouchExplorationStateChanged(false); + verify(mockChannel).setAccessibilityFeatures(0); + assertEquals(accessibilityBridge.getAccessibleNavigation(), false); + } + @Test public void itDoesNotContainADescriptionIfScopesRoute() { AccessibilityBridge accessibilityBridge = setUpBridge(); @@ -993,6 +1033,34 @@ public void itSetsFocusabilityBasedOnFlagsCorrectly() { assertTrue(node2Info.isFocusable()); } + @TargetApi(31) + @Test + public void itSetsBoldTextFlagCorrectly() { + AccessibilityChannel mockChannel = mock(AccessibilityChannel.class); + AccessibilityViewEmbedder mockViewEmbedder = mock(AccessibilityViewEmbedder.class); + AccessibilityManager mockManager = mock(AccessibilityManager.class); + View mockRootView = mock(View.class); + Context context = mock(Context.class); + Resources resource = mock(Resources.class); + Configuration config = new Configuration(); + config.fontWeightAdjustment = 300; + + when(mockRootView.getContext()).thenReturn(context); + when(mockRootView.getResources()).thenReturn(resource); + when(resource.getConfiguration()).thenReturn(config); + + AccessibilityBridge accessibilityBridge = + setUpBridge( + /*rootAccessibilityView=*/ mockRootView, + /*accessibilityChannel=*/ mockChannel, + /*accessibilityManager=*/ mockManager, + /*contentResolver=*/ null, + /*accessibilityViewEmbedder=*/ mockViewEmbedder, + /*platformViewsAccessibilityDelegate=*/ null); + + verify(mockChannel).setAccessibilityFeatures(1 << 3); + } + @Test public void itSetsFocusedNodeBeforeSendingEvent() { AccessibilityChannel mockChannel = mock(AccessibilityChannel.class); diff --git a/shell/platform/android/test_runner/build.gradle b/shell/platform/android/test_runner/build.gradle index 5d6aca1b8dfe5..dfdf361aeeae0 100644 --- a/shell/platform/android/test_runner/build.gradle +++ b/shell/platform/android/test_runner/build.gradle @@ -33,7 +33,7 @@ println "AVAILABLE PROCESSORS: $availableProcessors" println "==========================================" android { - compileSdkVersion 32 + compileSdkVersion 33 defaultConfig { minSdkVersion 16 diff --git a/shell/platform/common/BUILD.gn b/shell/platform/common/BUILD.gn index 24d450cdb9497..a1a1817939202 100644 --- a/shell/platform/common/BUILD.gn +++ b/shell/platform/common/BUILD.gn @@ -11,6 +11,7 @@ config("desktop_library_implementation") { _public_headers = [ "public/flutter_export.h", + "public/flutter_macros.h", "public/flutter_messenger.h", "public/flutter_plugin_registrar.h", "public/flutter_texture_registrar.h", @@ -173,6 +174,7 @@ if (enable_unittests) { sources = [ "engine_switches_unittests.cc", "geometry_unittests.cc", + "incoming_message_dispatcher_unittests.cc", "json_message_codec_unittests.cc", "json_method_codec_unittests.cc", "text_editing_delta_unittests.cc", diff --git a/shell/platform/common/accessibility_bridge.cc b/shell/platform/common/accessibility_bridge.cc index ab2468e331cfa..576edfac6e6eb 100644 --- a/shell/platform/common/accessibility_bridge.cc +++ b/shell/platform/common/accessibility_bridge.cc @@ -375,7 +375,8 @@ void AccessibilityBridge::SetIntAttributesFromFlutterUpdate( node_data.AddIntAttribute(ax::mojom::IntAttribute::kTextSelStart, sel_start); node_data.AddIntAttribute(ax::mojom::IntAttribute::kTextSelEnd, sel_end); - if (node_data.role == ax::mojom::Role::kRadioButton) { + if (node_data.role == ax::mojom::Role::kRadioButton || + node_data.role == ax::mojom::Role::kCheckBox) { node_data.AddIntAttribute( ax::mojom::IntAttribute::kCheckedState, static_cast( diff --git a/shell/platform/common/accessibility_bridge_unittests.cc b/shell/platform/common/accessibility_bridge_unittests.cc index 38e038266628d..41834b107a44b 100644 --- a/shell/platform/common/accessibility_bridge_unittests.cc +++ b/shell/platform/common/accessibility_bridge_unittests.cc @@ -337,5 +337,36 @@ TEST(AccessibilityBridgeTest, SliderHasSliderRole) { EXPECT_EQ(root_node->GetData().role, ax::mojom::Role::kSlider); } +// Ensure that checkboxes have their checked status set apropriately +// Previously, only Radios could have this flag updated +// Resulted in the issue seen at +// https://github.com/flutter/flutter/issues/96218 +// As this fix involved code run on all platforms, it is included here. +TEST(AccessibilityBridgeTest, CanSetCheckboxChecked) { + std::shared_ptr bridge = + std::make_shared( + std::make_unique()); + FlutterSemanticsNode root; + root.id = 0; + root.label = "root"; + root.hint = ""; + root.value = ""; + root.increased_value = ""; + root.decreased_value = ""; + root.child_count = 0; + root.custom_accessibility_actions_count = 0; + root.flags = static_cast( + FlutterSemanticsFlag::kFlutterSemanticsFlagHasCheckedState | + FlutterSemanticsFlag::kFlutterSemanticsFlagIsChecked); + bridge->AddFlutterSemanticsNodeUpdate(&root); + + bridge->CommitUpdates(); + + auto root_node = bridge->GetFlutterPlatformNodeDelegateFromID(0).lock(); + EXPECT_EQ(root_node->GetData().role, ax::mojom::Role::kCheckBox); + EXPECT_EQ(root_node->GetData().GetCheckedState(), + ax::mojom::CheckedState::kTrue); +} + } // namespace testing } // namespace flutter diff --git a/shell/platform/common/incoming_message_dispatcher_unittests.cc b/shell/platform/common/incoming_message_dispatcher_unittests.cc new file mode 100644 index 0000000000000..55d69ed369218 --- /dev/null +++ b/shell/platform/common/incoming_message_dispatcher_unittests.cc @@ -0,0 +1,91 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/shell/platform/common/incoming_message_dispatcher.h" + +#include "gtest/gtest.h" + +namespace flutter { +TEST(IncomingMessageDispatcher, SetHandle) { + FlutterDesktopMessengerRef messenger = + reinterpret_cast(0xfeedface); + const uint8_t* message_data = reinterpret_cast(0xcafebabe); + auto dispatcher = std::make_unique(messenger); + bool did_call = false; + dispatcher->SetMessageCallback( + "hello", + [](FlutterDesktopMessengerRef messenger, + const FlutterDesktopMessage* message, void* user_data) { + EXPECT_EQ(messenger, + reinterpret_cast(0xfeedface)); + EXPECT_EQ(message->message, + reinterpret_cast(0xcafebabe)); + EXPECT_EQ(message->message_size, 123u); + *reinterpret_cast(user_data) = true; + }, + &did_call); + FlutterDesktopMessage message = { + .struct_size = sizeof(FlutterDesktopMessage), + .channel = "hello", + .message = message_data, + .message_size = 123, + .response_handle = nullptr, + }; + dispatcher->HandleMessage(message); + EXPECT_TRUE(did_call); +} + +TEST(IncomingMessageDispatcher, BlockInputFalse) { + FlutterDesktopMessengerRef messenger = nullptr; + auto dispatcher = std::make_unique(messenger); + bool did_call[3] = {false, false, false}; + dispatcher->SetMessageCallback( + "hello", + [](FlutterDesktopMessengerRef messenger, + const FlutterDesktopMessage* message, + void* user_data) { reinterpret_cast(user_data)[0] = true; }, + &did_call); + FlutterDesktopMessage message = { + .struct_size = sizeof(FlutterDesktopMessage), + .channel = "hello", + .message = nullptr, + .message_size = 0, + .response_handle = nullptr, + }; + dispatcher->HandleMessage( + message, [&did_call] { did_call[1] = true; }, + [&did_call] { did_call[2] = true; }); + EXPECT_TRUE(did_call[0]); + EXPECT_FALSE(did_call[1]); + EXPECT_FALSE(did_call[2]); +} + +TEST(IncomingMessageDispatcher, BlockInputTrue) { + FlutterDesktopMessengerRef messenger = nullptr; + auto dispatcher = std::make_unique(messenger); + static int counter = 0; + int did_call[3] = {-1, -1, -1}; + dispatcher->EnableInputBlockingForChannel("hello"); + dispatcher->SetMessageCallback( + "hello", + [](FlutterDesktopMessengerRef messenger, + const FlutterDesktopMessage* message, + void* user_data) { reinterpret_cast(user_data)[counter++] = 1; }, + &did_call); + FlutterDesktopMessage message = { + .struct_size = sizeof(FlutterDesktopMessage), + .channel = "hello", + .message = nullptr, + .message_size = 0, + .response_handle = nullptr, + }; + dispatcher->HandleMessage( + message, [&did_call] { did_call[counter++] = 0; }, + [&did_call] { did_call[counter++] = 2; }); + EXPECT_EQ(did_call[0], 0); + EXPECT_EQ(did_call[1], 1); + EXPECT_EQ(did_call[2], 2); +} + +} // namespace flutter diff --git a/shell/platform/common/public/flutter_export.h b/shell/platform/common/public/flutter_export.h index 38cac85b7ba2e..d71b3fb5607aa 100644 --- a/shell/platform/common/public/flutter_export.h +++ b/shell/platform/common/public/flutter_export.h @@ -6,8 +6,8 @@ #define FLUTTER_SHELL_PLATFORM_COMMON_PUBLIC_FLUTTER_EXPORT_H_ #ifdef FLUTTER_DESKTOP_LIBRARY -// Add visibility/export annotations when building the library. +// Add visibility/export annotations when building the library. #ifdef _WIN32 #define FLUTTER_EXPORT __declspec(dllexport) #else diff --git a/shell/platform/common/public/flutter_macros.h b/shell/platform/common/public/flutter_macros.h new file mode 100644 index 0000000000000..7b60cb488b0b2 --- /dev/null +++ b/shell/platform/common/public/flutter_macros.h @@ -0,0 +1,24 @@ +// 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_SHELL_PLATFORM_COMMON_PUBLIC_FLUTTER_MACROS_H_ +#define FLUTTER_SHELL_PLATFORM_COMMON_PUBLIC_FLUTTER_MACROS_H_ + +#ifdef FLUTTER_DESKTOP_LIBRARY + +// Do not add deprecation annotations when building the library. +#define FLUTTER_DEPRECATED(message) + +#else // FLUTTER_DESKTOP_LIBRARY + +// Add deprecation warning for users of the library. +#ifdef _WIN32 +#define FLUTTER_DEPRECATED(message) __declspec(deprecated(message)) +#else +#define FLUTTER_DEPRECATED(message) __attribute__((deprecated(message))) +#endif + +#endif // FLUTTER_DESKTOP_LIBRARY + +#endif // FLUTTER_SHELL_PLATFORM_COMMON_PUBLIC_FLUTTER_MACROS_H_ diff --git a/shell/platform/darwin/ios/BUILD.gn b/shell/platform/darwin/ios/BUILD.gn index 1ee7c5cadbd11..e3a8339e22033 100644 --- a/shell/platform/darwin/ios/BUILD.gn +++ b/shell/platform/darwin/ios/BUILD.gn @@ -201,6 +201,7 @@ source_set("ios_test_flutter_mrc") { "framework/Source/FlutterEngineTest_mrc.mm", "framework/Source/FlutterPlatformPluginTest.mm", "framework/Source/FlutterPlatformViewsTest.mm", + "framework/Source/FlutterViewControllerTest_mrc.mm", "framework/Source/FlutterViewTest.mm", "framework/Source/VsyncWaiterIosTest.mm", "framework/Source/accessibility_bridge_test.mm", @@ -381,11 +382,7 @@ copy("copy_license") { shared_library("copy_and_verify_framework_module") { framework_search_path = rebase_path("$root_out_dir") visibility = [ ":*" ] - cflags_objc = [ - "-F$framework_search_path", - "-fmodules", - "-Wnon-modular-include-in-framework-module", - ] + cflags_objc = [ "-F$framework_search_path" ] sources = [ "framework/Source/FlutterUmbrellaImport.m" ] deps = [ diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm index e7cca9c817e5d..bea122558e458 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm @@ -324,11 +324,15 @@ - (BOOL)flt_hasFirstResponderInViewHierarchySubtree { // All the CATransactions should be committed by the end of the last frame, // so catransaction_added_ must be false. FML_DCHECK(!catransaction_added_); - picture_recorders_[view_id] = std::make_unique(); - auto rtree_factory = RTreeFactory(); - platform_view_rtrees_[view_id] = rtree_factory.getInstance(); - picture_recorders_[view_id]->beginRecording(SkRect::Make(frame_size_), &rtree_factory); + SkRect view_bounds = SkRect::Make(frame_size_); + std::unique_ptr view; + if (params->display_list_enabled()) { + view = std::make_unique(view_bounds); + } else { + view = std::make_unique(view_bounds); + } + slices_.insert_or_assign(view_id, std::move(view)); composition_order_.push_back(view_id); @@ -361,11 +365,20 @@ - (BOOL)flt_hasFirstResponderInViewHierarchySubtree { std::vector canvases; for (size_t i = 0; i < composition_order_.size(); i++) { int64_t view_id = composition_order_[i]; - canvases.push_back(picture_recorders_[view_id]->getRecordingCanvas()); + canvases.push_back(slices_[view_id]->canvas()); } return canvases; } +std::vector FlutterPlatformViewsController::GetCurrentBuilders() { + std::vector builders; + for (size_t i = 0; i < composition_order_.size(); i++) { + int64_t view_id = composition_order_[i]; + builders.push_back(slices_[view_id]->builder()); + } + return builders; +} + int FlutterPlatformViewsController::CountClips(const MutatorsStack& mutators_stack) { std::vector>::const_reverse_iterator iter = mutators_stack.Bottom(); int clipCount = 0; @@ -459,16 +472,16 @@ - (BOOL)flt_hasFirstResponderInViewHierarchySubtree { ApplyMutators(mutatorStack, touchInterceptor); } -SkCanvas* FlutterPlatformViewsController::CompositeEmbeddedView(int view_id) { +EmbedderPaintContext FlutterPlatformViewsController::CompositeEmbeddedView(int view_id) { // Any UIKit related code has to run on main thread. FML_DCHECK([[NSThread currentThread] isMainThread]); // Do nothing if the view doesn't need to be composited. if (views_to_recomposite_.count(view_id) == 0) { - return picture_recorders_[view_id]->getRecordingCanvas(); + return {slices_[view_id]->canvas(), slices_[view_id]->builder()}; } CompositeWithParams(view_id, current_composition_params_[view_id]); views_to_recomposite_.erase(view_id); - return picture_recorders_[view_id]->getRecordingCanvas(); + return {slices_[view_id]->canvas(), slices_[view_id]->builder()}; } void FlutterPlatformViewsController::Reset() { @@ -481,8 +494,7 @@ - (BOOL)flt_hasFirstResponderInViewHierarchySubtree { views_.clear(); composition_order_.clear(); active_composition_order_.clear(); - picture_recorders_.clear(); - platform_view_rtrees_.clear(); + slices_.clear(); current_composition_params_.clear(); clip_count_.clear(); views_to_recomposite_.clear(); @@ -513,12 +525,15 @@ - (BOOL)flt_hasFirstResponderInViewHierarchySubtree { DisposeViews(); SkCanvas* background_canvas = frame->SkiaCanvas(); + DisplayListBuilder* background_builder = frame->GetDisplayListBuilder().get(); // Resolve all pending GPU operations before allocating a new surface. background_canvas->flush(); + // Clipping the background canvas before drawing the picture recorders requires to // save and restore the clip context. SkAutoCanvasRestore save(background_canvas, /*doSave=*/true); + // Maps a platform view id to a vector of `FlutterPlatformViewLayer`. LayersMap platform_view_layers; @@ -527,8 +542,8 @@ - (BOOL)flt_hasFirstResponderInViewHierarchySubtree { for (size_t i = 0; i < num_platform_views; i++) { int64_t platform_view_id = composition_order_[i]; - sk_sp rtree = platform_view_rtrees_[platform_view_id]; - sk_sp picture = picture_recorders_[platform_view_id]->finishRecordingAsPicture(); + EmbedderViewSlice* slice = slices_[platform_view_id].get(); + slice->end_recording(); // Check if the current picture contains overlays that intersect with the // current platform view or any of the previous platform views. @@ -536,7 +551,7 @@ - (BOOL)flt_hasFirstResponderInViewHierarchySubtree { int64_t current_platform_view_id = composition_order_[j - 1]; SkRect platform_view_rect = GetPlatformViewRect(current_platform_view_id); std::list intersection_rects = - rtree->searchNonOverlappingDrawnRects(platform_view_rect); + slice->searchNonOverlappingDrawnRects(platform_view_rect); auto allocation_size = intersection_rects.size(); // For testing purposes, the overlay id is used to find the overlay view. @@ -573,7 +588,7 @@ - (BOOL)flt_hasFirstResponderInViewHierarchySubtree { // Get a new host layer. std::shared_ptr layer = GetLayer(gr_context, // ios_context, // - picture, // + slice, // joined_rect, // current_platform_view_id, // overlay_id // @@ -583,8 +598,16 @@ - (BOOL)flt_hasFirstResponderInViewHierarchySubtree { overlay_id++; } } - background_canvas->drawPicture(picture); + if (background_builder) { + slice->render_into(background_builder); + } else { + slice->render_into(background_canvas); + } } + + // Manually trigger the SkAutoCanvasRestore before we submit the frame + save.restore(); + // If a layer was allocated in the previous frame, but it's not used in the current frame, // then it can be removed from the scene. RemoveUnusedLayers(); @@ -636,7 +659,7 @@ - (BOOL)flt_hasFirstResponderInViewHierarchySubtree { std::shared_ptr FlutterPlatformViewsController::GetLayer( GrDirectContext* gr_context, std::shared_ptr ios_context, - sk_sp picture, + EmbedderViewSlice* slice, SkRect rect, int64_t view_id, int64_t overlay_id) { @@ -669,7 +692,11 @@ - (BOOL)flt_hasFirstResponderInViewHierarchySubtree { } SkCanvas* overlay_canvas = frame->SkiaCanvas(); overlay_canvas->clear(SK_ColorTRANSPARENT); - overlay_canvas->drawPicture(picture); + if (frame->GetDisplayListBuilder()) { + slice->render_into(frame->GetDisplayListBuilder().get()); + } else { + slice->render_into(overlay_canvas); + } layer->did_submit_last_frame = frame->Submit(); return layer; @@ -730,7 +757,7 @@ - (BOOL)flt_hasFirstResponderInViewHierarchySubtree { } void FlutterPlatformViewsController::ResetFrameState() { - picture_recorders_.clear(); + slices_.clear(); composition_order_.clear(); } diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm index b91880b3a4d09..cc205e91bd3f6 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm @@ -1076,16 +1076,19 @@ - (void)testFlutterPlatformViewControllerBeginFrameShouldResetCompisitionOrder { flutterPlatformViewsController->PrerollCompositeEmbeddedView(0, std::move(embeddedViewParams1)); flutterPlatformViewsController->CompositeEmbeddedView(0); XCTAssertEqual(flutterPlatformViewsController->GetCurrentCanvases().size(), 1UL); + XCTAssertEqual(flutterPlatformViewsController->GetCurrentBuilders().size(), 1UL); // Second frame, |GetCurrentCanvases| should be empty at the start flutterPlatformViewsController->BeginFrame(SkISize::Make(300, 300)); XCTAssertTrue(flutterPlatformViewsController->GetCurrentCanvases().empty()); + XCTAssertTrue(flutterPlatformViewsController->GetCurrentBuilders().empty()); auto embeddedViewParams2 = std::make_unique(finalMatrix, SkSize::Make(300, 300), stack); flutterPlatformViewsController->PrerollCompositeEmbeddedView(0, std::move(embeddedViewParams2)); flutterPlatformViewsController->CompositeEmbeddedView(0); XCTAssertEqual(flutterPlatformViewsController->GetCurrentCanvases().size(), 1UL); + XCTAssertEqual(flutterPlatformViewsController->GetCurrentBuilders().size(), 1UL); } - (void)testThreadMergeAtEndFrame { diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h index fde21d23e2e20..0a2e134f876dd 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h @@ -165,7 +165,9 @@ class FlutterPlatformViewsController { std::vector GetCurrentCanvases(); - SkCanvas* CompositeEmbeddedView(int view_id); + std::vector GetCurrentBuilders(); + + EmbedderPaintContext CompositeEmbeddedView(int view_id); // The rect of the platform view at index view_id. This rect has been translated into the // host view coordinate system. Units are device screen pixels. @@ -227,7 +229,7 @@ class FlutterPlatformViewsController { // the picture on the layer's canvas. std::shared_ptr GetLayer(GrDirectContext* gr_context, std::shared_ptr ios_context, - sk_sp picture, + EmbedderViewSlice* slice, SkRect rect, int64_t view_id, int64_t overlay_id); @@ -251,15 +253,11 @@ class FlutterPlatformViewsController { // The pool of reusable view layers. The pool allows to recycle layer in each frame. std::unique_ptr layer_pool_; - // The platform view's R-tree keyed off the view id, which contains any subsequent - // draw operation until the next platform view or the last leaf node in the layer tree. - // - // The R-trees are deleted by the FlutterPlatformViewsController.reset(). - std::map> platform_view_rtrees_; - - // The platform view's picture recorder keyed off the view id, which contains any subsequent + // The platform view's |EmbedderViewSlice| keyed off the view id, which contains any subsequent // operation until the next platform view or the end of the last leaf node in the layer tree. - std::map> picture_recorders_; + // + // The Slices are deleted by the FlutterPlatformViewsController.reset(). + std::map> slices_; fml::scoped_nsobject channel_; fml::scoped_nsobject flutter_view_; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm index 41ed193411a84..aec571a812510 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm @@ -1137,6 +1137,7 @@ - (void)testInputViewsHasNonNilInputDelegate { @"composingExtent" : @3 }]; OCMVerify([mockInputView setInputDelegate:[OCMArg isNotNil]]); + [inputView removeFromSuperview]; } } diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 315611072f9cd..cc7947421f8f3 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -26,6 +26,7 @@ #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterView.h" #import "flutter/shell/platform/darwin/ios/framework/Source/platform_message_response_darwin.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.h" #import "flutter/shell/platform/darwin/ios/platform_view_ios.h" #import "flutter/shell/platform/embedder/embedder.h" @@ -62,7 +63,7 @@ @interface FlutterViewController () recorder) { + if (!weakSelf) { + return; + } + fml::scoped_nsobject flutterViewController( + [(FlutterViewController*)weakSelf.get() retain]); + if (!flutterViewController) { + return; + } + + if ([flutterViewController keyboardAnimationView].superview == nil) { + // Ensure the keyboardAnimationView is in view hierarchy when animation running. + [flutterViewController.get().view addSubview:[flutterViewController keyboardAnimationView]]; + } + if ([flutterViewController keyboardAnimationView].layer.presentationLayer) { + CGFloat value = + [flutterViewController keyboardAnimationView].layer.presentationLayer.frame.origin.y; + flutterViewController.get()->_viewportMetrics.physical_view_inset_bottom = value; + [flutterViewController updateViewportMetrics]; + } + }; + flutter::Shell& shell = [_engine.get() shell]; + NSAssert(_keyboardAnimationVSyncClient == nil, + @"_keyboardAnimationVSyncClient must be nil when setup"); + _keyboardAnimationVSyncClient = + [[VSyncClient alloc] initWithTaskRunner:shell.GetTaskRunners().GetPlatformTaskRunner() + callback:callback]; + _keyboardAnimationVSyncClient.allowPauseAfterVsync = NO; + [_keyboardAnimationVSyncClient await]; +} + +- (void)invalidateKeyboardAnimationVSyncClient { + [_keyboardAnimationVSyncClient invalidate]; + [_keyboardAnimationVSyncClient release]; + _keyboardAnimationVSyncClient = nil; } - (void)removeKeyboardAnimationView { @@ -1318,18 +1351,6 @@ - (void)ensureViewportMetricsIsCorrect { } } -- (void)onDisplayLink { - if ([self keyboardAnimationView].superview == nil) { - // Ensure the keyboardAnimationView is in view hierarchy when animation running. - [self.view addSubview:[self keyboardAnimationView]]; - } - if ([self keyboardAnimationView].layer.presentationLayer) { - CGFloat value = [self keyboardAnimationView].layer.presentationLayer.frame.origin.y; - _viewportMetrics.physical_view_inset_bottom = value; - [self updateViewportMetrics]; - } -} - - (void)handlePressEvent:(FlutterUIPressProxy*)press nextAction:(void (^)())next API_AVAILABLE(ios(13.4)) { if (@available(iOS 13.4, *)) { diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm index 556d87f584ecb..d3f4de66f8710 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm @@ -126,8 +126,9 @@ - (void)goToApplicationLifecycle:(nonnull NSString*)state; - (void)keyboardWillChangeFrame:(NSNotification*)notification; - (void)keyboardWillBeHidden:(NSNotification*)notification; - (void)startKeyBoardAnimation:(NSTimeInterval)duration; +- (void)setupKeyboardAnimationVsyncClient; - (void)ensureViewportMetricsIsCorrect; -- (void)invalidateDisplayLink; +- (void)invalidateKeyboardAnimationVSyncClient; - (void)addInternalPlugins; - (flutter::PointerData)generatePointerDataForFake; - (void)sharedSetupWithProject:(nullable FlutterDartProject*)project @@ -159,6 +160,18 @@ - (void)tearDown { self.messageSent = nil; } +- (void)testStartKeyboardAnimationWillInvokeSetupKeyboardAnimationVsyncClient { + FlutterEngine* engine = [[FlutterEngine alloc] init]; + [engine runWithEntrypoint:nil]; + FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine + nibName:nil + bundle:nil]; + FlutterViewController* viewControllerMock = OCMPartialMock(viewController); + viewControllerMock.targetViewInsetBottom = 100; + [viewControllerMock startKeyBoardAnimation:0.25]; + OCMVerify([viewControllerMock setupKeyboardAnimationVsyncClient]); +} + - (void)testkeyboardWillChangeFrameWillStartKeyboardAnimation { FlutterEngine* mockEngine = OCMPartialMock([[FlutterEngine alloc] init]); [mockEngine createShell:@"" libraryURI:@"" initialRoute:nil]; @@ -223,7 +236,7 @@ - (void)testEnsureViewportMetricsWillInvokeAndDisplayLinkWillInvalidateInViewDid id viewControllerMock = OCMPartialMock(viewController); [viewControllerMock viewDidDisappear:YES]; OCMVerify([viewControllerMock ensureViewportMetricsIsCorrect]); - OCMVerify([viewControllerMock invalidateDisplayLink]); + OCMVerify([viewControllerMock invalidateKeyboardAnimationVSyncClient]); } - (void)testViewDidDisappearDoesntPauseEngineWhenNotTheViewController { diff --git a/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest_mrc.mm b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest_mrc.mm new file mode 100644 index 0000000000000..f4cc21706ef47 --- /dev/null +++ b/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest_mrc.mm @@ -0,0 +1,59 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import +#import + +#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h" +#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.h" + +FLUTTER_ASSERT_NOT_ARC + +@interface VSyncClient (Testing) + +- (CADisplayLink*)getDisplayLink; + +@end + +@interface FlutterViewController (Testing) + +@property(nonatomic, assign) double targetViewInsetBottom; +@property(nonatomic, retain) VSyncClient* keyboardAnimationVSyncClient; + +- (void)setupKeyboardAnimationVsyncClient; + +@end + +@interface FlutterViewControllerTest_mrc : XCTestCase +@end + +@implementation FlutterViewControllerTest_mrc + +- (void)testSetupKeyboardAnimationVsyncClientWillCreateNewVsyncClientForFlutterViewController { + id bundleMock = OCMPartialMock([NSBundle mainBundle]); + OCMStub([bundleMock objectForInfoDictionaryKey:@"CADisableMinimumFrameDurationOnPhone"]) + .andReturn(@YES); + id mockDisplayLinkManager = [OCMockObject mockForClass:[DisplayLinkManager class]]; + double maxFrameRate = 120; + [[[mockDisplayLinkManager stub] andReturnValue:@(maxFrameRate)] displayRefreshRate]; + FlutterEngine* engine = [[FlutterEngine alloc] init]; + [engine runWithEntrypoint:nil]; + FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine + nibName:nil + bundle:nil]; + [viewController setupKeyboardAnimationVsyncClient]; + XCTAssertNotNil(viewController.keyboardAnimationVSyncClient); + CADisplayLink* link = [viewController.keyboardAnimationVSyncClient getDisplayLink]; + XCTAssertNotNil(link); + if (@available(iOS 15.0, *)) { + XCTAssertEqual(link.preferredFrameRateRange.maximum, maxFrameRate); + XCTAssertEqual(link.preferredFrameRateRange.preferred, maxFrameRate); + XCTAssertEqual(link.preferredFrameRateRange.minimum, maxFrameRate / 2); + } else { + XCTAssertEqual(link.preferredFramesPerSecond, maxFrameRate); + } +} + +@end diff --git a/shell/platform/darwin/ios/framework/Source/VsyncWaiterIosTest.mm b/shell/platform/darwin/ios/framework/Source/VsyncWaiterIosTest.mm index 124bdee2e8441..4ac0529808ed7 100644 --- a/shell/platform/darwin/ios/framework/Source/VsyncWaiterIosTest.mm +++ b/shell/platform/darwin/ios/framework/Source/VsyncWaiterIosTest.mm @@ -22,6 +22,7 @@ @interface VSyncClient (Testing) - (CADisplayLink*)getDisplayLink; +- (void)onDisplayLink:(CADisplayLink*)link; @end @@ -30,6 +31,26 @@ @interface VsyncWaiterIosTest : XCTestCase @implementation VsyncWaiterIosTest +- (void)testSetAllowPauseAfterVsyncCorrect { + auto thread_task_runner = CreateNewThread("VsyncWaiterIosTest"); + VSyncClient* vsyncClient = [[[VSyncClient alloc] + initWithTaskRunner:thread_task_runner + callback:[](std::unique_ptr recorder) {}] + autorelease]; + CADisplayLink* link = [vsyncClient getDisplayLink]; + vsyncClient.allowPauseAfterVsync = NO; + [vsyncClient await]; + [vsyncClient onDisplayLink:link]; + XCTAssertFalse(link.isPaused); + + vsyncClient.allowPauseAfterVsync = YES; + [vsyncClient await]; + [vsyncClient onDisplayLink:link]; + XCTAssertTrue(link.isPaused); + + [vsyncClient release]; +} + - (void)testSetCorrectVariableRefreshRates { auto thread_task_runner = CreateNewThread("VsyncWaiterIosTest"); auto callback = [](std::unique_ptr recorder) {}; diff --git a/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.h b/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.h index 9b5d5aa519bd9..9185fb00978d9 100644 --- a/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.h +++ b/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.h @@ -30,6 +30,16 @@ @interface VSyncClient : NSObject +//------------------------------------------------------------------------------ +/// @brief Default value is YES. Vsync client will pause vsync callback after receiving +/// a vsync signal. Setting this property to NO can avoid this and vsync client +/// will trigger vsync callback continuously. +/// +/// +/// @param allowPauseAfterVsync Allow vsync client to pause after receiving a vsync signal. +/// +@property(nonatomic, assign) BOOL allowPauseAfterVsync; + - (instancetype)initWithTaskRunner:(fml::RefPtr)task_runner callback:(flutter::VsyncWaiter::Callback)callback; diff --git a/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.mm b/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.mm index 0e6b2f5e68dad..439ba868ff8b5 100644 --- a/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.mm +++ b/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.mm @@ -57,6 +57,7 @@ - (instancetype)initWithTaskRunner:(fml::RefPtr)task_runner if (self) { current_refresh_rate_ = [DisplayLinkManager displayRefreshRate]; + _allowPauseAfterVsync = YES; callback_ = std::move(callback); display_link_ = fml::scoped_nsobject { [[CADisplayLink displayLinkWithTarget:self selector:@selector(onDisplayLink:)] retain] @@ -111,7 +112,9 @@ - (void)onDisplayLink:(CADisplayLink*)link { current_refresh_rate_ = round(1 / (frame_target_time - frame_start_time).ToSecondsF()); recorder->RecordVsync(frame_start_time, frame_target_time); - display_link_.get().paused = YES; + if (_allowPauseAfterVsync) { + display_link_.get().paused = YES; + } callback_(std::move(recorder)); } diff --git a/shell/platform/darwin/ios/ios_external_view_embedder.h b/shell/platform/darwin/ios/ios_external_view_embedder.h index 6c023d1b793e1..3a5a0b3c17613 100644 --- a/shell/platform/darwin/ios/ios_external_view_embedder.h +++ b/shell/platform/darwin/ios/ios_external_view_embedder.h @@ -50,7 +50,10 @@ class IOSExternalViewEmbedder : public ExternalViewEmbedder { std::vector GetCurrentCanvases() override; // |ExternalViewEmbedder| - SkCanvas* CompositeEmbeddedView(int view_id) override; + std::vector GetCurrentBuilders() override; + + // |ExternalViewEmbedder| + EmbedderPaintContext CompositeEmbeddedView(int view_id) override; // |ExternalViewEmbedder| void SubmitFrame(GrDirectContext* context, diff --git a/shell/platform/darwin/ios/ios_external_view_embedder.mm b/shell/platform/darwin/ios/ios_external_view_embedder.mm index 1323fa4c2f487..e67983321c770 100644 --- a/shell/platform/darwin/ios/ios_external_view_embedder.mm +++ b/shell/platform/darwin/ios/ios_external_view_embedder.mm @@ -65,7 +65,13 @@ } // |ExternalViewEmbedder| -SkCanvas* IOSExternalViewEmbedder::CompositeEmbeddedView(int view_id) { +std::vector IOSExternalViewEmbedder::GetCurrentBuilders() { + FML_CHECK(platform_views_controller_); + return platform_views_controller_->GetCurrentBuilders(); +} + +// |ExternalViewEmbedder| +EmbedderPaintContext IOSExternalViewEmbedder::CompositeEmbeddedView(int view_id) { TRACE_EVENT0("flutter", "IOSExternalViewEmbedder::CompositeEmbeddedView"); FML_CHECK(platform_views_controller_); return platform_views_controller_->CompositeEmbeddedView(view_id); diff --git a/shell/platform/darwin/macos/BUILD.gn b/shell/platform/darwin/macos/BUILD.gn index 640e340b3ff3a..d2f5b3df28619 100644 --- a/shell/platform/darwin/macos/BUILD.gn +++ b/shell/platform/darwin/macos/BUILD.gn @@ -306,6 +306,10 @@ action("_generate_symlinks") { ":copy_framework_module_map", ":copy_license", ] + metadata = { + macos_framework_without_entitlement = + [ "FlutterMacOS.framework.zip/Versions/A/FlutterMacOS" ] + } } group("flutter_framework") { @@ -321,28 +325,28 @@ if (build_glfw_shell) { } zip_bundle("zip_macos_flutter_framework") { - deps = [ ":_generate_symlinks" ] - output = "tmp/FlutterMacOS.framework.zip" - visibility = [ ":*" ] - files = [ - { - source = "$root_out_dir/FlutterMacOS.framework" - destination = "FlutterMacOS.framework" - }, + deps = [ + ":_generate_symlinks", + ":macos_framework_without_entitlement_config", ] -} - -zip_bundle("macos_flutter_framework_archive") { - deps = [ ":zip_macos_flutter_framework" ] prefix = "$full_platform_name-$flutter_runtime_mode/" if (flutter_runtime_mode == "debug") { prefix = "$full_platform_name/" } output = "${prefix}FlutterMacOS.framework.zip" + visibility = [ ":*" ] files = [ { - source = "$root_out_dir/zip_archives/tmp/FlutterMacOS.framework.zip" - destination = "FlutterMacOS.framework.zip" + source = "$root_out_dir/FlutterMacOS.framework" + destination = "FlutterMacOS.framework" }, ] } + +generated_file("macos_framework_without_entitlement_config") { + outputs = [ "$target_gen_dir/framework_without_entitlements.txt" ] + + data_keys = [ "macos_framework_without_entitlement" ] + + deps = [ ":_generate_symlinks" ] +} diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm index 6f6706305223f..116c94537d9be 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm @@ -269,9 +269,9 @@ - (void)buildLayout { } // Derive key mapping for each key code based on their layout clues. - // Max key code is 127 for ADB keyboards. - // https://developer.apple.com/documentation/coreservices/1390584-uckeytranslate?language=objc#parameters - const uint16_t kMaxKeyCode = 127; + // Key code 0x00 - 0x32 are typewriter keys (letters, digits, and symbols.) + // See keyCodeToPhysicalKey. + const uint16_t kMaxKeyCode = 0x32; #ifdef DEBUG_PRINT_LAYOUT NSString* debugLayoutData = @""; #endif @@ -303,8 +303,10 @@ - (void)buildLayout { } bool hasAnyEascii = isEascii(thisKeyClues[0]) || isEascii(thisKeyClues[1]); // See if any produced char meets the requirement as a logical key. - if (_layoutMap[@(keyCode)] == nil && !hasAnyEascii) { - _layoutMap[@(keyCode)] = @(usLayoutGoalsByKeyCode[keyCode].keyChar); + auto foundUsLayoutGoal = usLayoutGoalsByKeyCode.find(keyCode); + if (foundUsLayoutGoal != usLayoutGoalsByKeyCode.end() && _layoutMap[@(keyCode)] == nil && + !hasAnyEascii) { + _layoutMap[@(keyCode)] = @(foundUsLayoutGoal->second.keyChar); } } #ifdef DEBUG_PRINT_LAYOUT diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManagerUnittests.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManagerUnittests.mm index e257e94a9a031..b5fe0e65626f6 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManagerUnittests.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManagerUnittests.mm @@ -17,6 +17,7 @@ using flutter::testing::keycodes::kLogicalBracketLeft; using flutter::testing::keycodes::kLogicalDigit1; +using flutter::testing::keycodes::kLogicalDigit2; using flutter::testing::keycodes::kLogicalKeyA; using flutter::testing::keycodes::kLogicalKeyM; using flutter::testing::keycodes::kLogicalKeyQ; @@ -67,26 +68,7 @@ typedef void (^AsyncEmbedderCallbackHandler)(const FlutterKeyEvent* event, /* 0x24 */ 0x00000, 0x00000, 0x0006c, 0x0004c, 0x0006a, 0x0004a, 0x00027, 0x00022, /* 0x28 */ 0x0006b, 0x0004b, 0x0003b, 0x0003a, 0x0005c, 0x0007c, 0x0002c, 0x0003c, /* 0x2c */ 0x0002f, 0x0003f, 0x0006e, 0x0004e, 0x0006d, 0x0004d, 0x0002e, 0x0003e, - /* 0x30 */ 0x00000, 0x00000, 0x00020, 0x00020, 0x00060, 0x0007e, 0x00000, 0x00000, - /* 0x34 */ 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, - /* 0x38 */ 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, - /* 0x3c */ 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, - /* 0x40 */ 0x00000, 0x00000, 0x0002e, 0x0002e, 0x00000, 0x0002a, 0x0002a, 0x0002a, - /* 0x44 */ 0x00000, 0x00000, 0x0002b, 0x0002b, 0x00000, 0x0002b, 0x00000, 0x00000, - /* 0x48 */ 0x00000, 0x0003d, 0x00000, 0x00000, 0x00000, 0x00000, 0x0002f, 0x0002f, - /* 0x4c */ 0x00000, 0x00000, 0x00000, 0x0002f, 0x0002d, 0x0002d, 0x00000, 0x00000, - /* 0x50 */ 0x00000, 0x00000, 0x0003d, 0x0003d, 0x00030, 0x00030, 0x00031, 0x00031, - /* 0x54 */ 0x00032, 0x00032, 0x00033, 0x00033, 0x00034, 0x00034, 0x00035, 0x00035, - /* 0x58 */ 0x00036, 0x00036, 0x00037, 0x00037, 0x00000, 0x00000, 0x00038, 0x00038, - /* 0x5c */ 0x00039, 0x00039, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, - /* 0x60 */ 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, - /* 0x64 */ 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, - /* 0x68 */ 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, - /* 0x6c */ 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, - /* 0x70 */ 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, - /* 0x74 */ 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, - /* 0x78 */ 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, - /* 0x7c */ 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + /* 0x30 */ 0x00000, 0x00000, 0x00020, 0x00020, 0x00060, 0x0007e, }; MockLayoutData kFrenchLayout = { @@ -103,26 +85,7 @@ typedef void (^AsyncEmbedderCallbackHandler)(const FlutterKeyEvent* event, /* 0x24 */ 0x00000, 0x00000, 0x0006c, 0x0004c, 0x0006a, 0x0004a, 0x000f9, 0x00025, /* 0x28 */ 0x0006b, 0x0004b, 0x0006d, 0x0004d, 0x10060, 0x000a3, 0x0003b, 0x0002e, /* 0x2c */ 0x0003d, 0x0002b, 0x0006e, 0x0004e, 0x0002c, 0x0003f, 0x0003a, 0x0002f, - /* 0x30 */ 0x00000, 0x00000, 0x00020, 0x00020, 0x0003c, 0x0003e, 0x00000, 0x00000, - /* 0x34 */ 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, - /* 0x38 */ 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, - /* 0x3c */ 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, - /* 0x40 */ 0x00000, 0x00000, 0x0002c, 0x0002e, 0x00000, 0x0002a, 0x0002a, 0x0002a, - /* 0x44 */ 0x00000, 0x00000, 0x0002b, 0x0002b, 0x00000, 0x0002b, 0x00000, 0x00000, - /* 0x48 */ 0x00000, 0x0003d, 0x00000, 0x00000, 0x00000, 0x00000, 0x0002f, 0x0002f, - /* 0x4c */ 0x00000, 0x00000, 0x00000, 0x0002f, 0x0002d, 0x0002d, 0x00000, 0x00000, - /* 0x50 */ 0x00000, 0x00000, 0x0003d, 0x0003d, 0x00030, 0x00030, 0x00031, 0x00031, - /* 0x54 */ 0x00032, 0x00032, 0x00033, 0x00033, 0x00034, 0x00034, 0x00035, 0x00035, - /* 0x58 */ 0x00036, 0x00036, 0x00037, 0x00037, 0x00000, 0x00000, 0x00038, 0x00038, - /* 0x5c */ 0x00039, 0x00039, 0x00040, 0x00023, 0x0003c, 0x0003e, 0x00000, 0x00000, - /* 0x60 */ 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, - /* 0x64 */ 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, - /* 0x68 */ 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, - /* 0x6c */ 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, - /* 0x70 */ 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, - /* 0x74 */ 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, - /* 0x78 */ 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, - /* 0x7c */ 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + /* 0x30 */ 0x00000, 0x00000, 0x00020, 0x00020, 0x0003c, 0x0003e, }; MockLayoutData kRussianLayout = { @@ -139,26 +102,24 @@ typedef void (^AsyncEmbedderCallbackHandler)(const FlutterKeyEvent* event, /* 0x24 */ 0x00000, 0x00000, 0x00434, 0x00414, 0x0043e, 0x0041e, 0x0044d, 0x0042d, /* 0x28 */ 0x0043b, 0x0041b, 0x00436, 0x00416, 0x00451, 0x00401, 0x00431, 0x00411, /* 0x2c */ 0x0002f, 0x0003f, 0x00442, 0x00422, 0x0044c, 0x0042c, 0x0044e, 0x0042e, - /* 0x30 */ 0x00000, 0x00000, 0x00020, 0x00020, 0x0005d, 0x0005b, 0x00000, 0x00000, - /* 0x34 */ 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, - /* 0x38 */ 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, - /* 0x3c */ 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, - /* 0x40 */ 0x00000, 0x00000, 0x0002c, 0x0002e, 0x00000, 0x0002a, 0x0002a, 0x0002a, - /* 0x44 */ 0x00000, 0x00000, 0x0002b, 0x0002b, 0x00000, 0x0002b, 0x00000, 0x00000, - /* 0x48 */ 0x00000, 0x0003d, 0x00000, 0x00000, 0x00000, 0x00000, 0x0002f, 0x0002f, - /* 0x4c */ 0x00000, 0x00000, 0x00000, 0x0002f, 0x0002d, 0x0002d, 0x00000, 0x00000, - /* 0x50 */ 0x00000, 0x00000, 0x0003d, 0x0003d, 0x00030, 0x00030, 0x00031, 0x00031, - /* 0x54 */ 0x00032, 0x00032, 0x00033, 0x00033, 0x00034, 0x00034, 0x00035, 0x00035, - /* 0x58 */ 0x00036, 0x00036, 0x00037, 0x00037, 0x00000, 0x00000, 0x00038, 0x00038, - /* 0x5c */ 0x00039, 0x00039, 0x0003e, 0x0003c, 0x0005d, 0x0005b, 0x00000, 0x00000, - /* 0x60 */ 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, - /* 0x64 */ 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, - /* 0x68 */ 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, - /* 0x6c */ 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, - /* 0x70 */ 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, - /* 0x74 */ 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, - /* 0x78 */ 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, - /* 0x7c */ 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, 0x00000, + /* 0x30 */ 0x00000, 0x00000, 0x00020, 0x00020, 0x0005d, 0x0005b, +}; + +MockLayoutData kKhmerLayout = { + // +0x0 Shift +0x1 Shift +0x2 Shift +0x3 Shift + /* 0x00 */ 0x017b6, 0x017ab, 0x0179f, 0x017c3, 0x0178a, 0x0178c, 0x01790, 0x01792, + /* 0x04 */ 0x017a0, 0x017c7, 0x01784, 0x017a2, 0x0178b, 0x0178d, 0x01781, 0x01783, + /* 0x08 */ 0x01785, 0x01787, 0x0179c, 0x017c8, 0x00000, 0x00000, 0x01794, 0x01796, + /* 0x0c */ 0x01786, 0x01788, 0x017b9, 0x017ba, 0x017c1, 0x017c2, 0x0179a, 0x017ac, + /* 0x10 */ 0x01799, 0x017bd, 0x0178f, 0x01791, 0x017e1, 0x00021, 0x017e2, 0x017d7, + /* 0x14 */ 0x017e3, 0x00022, 0x017e4, 0x017db, 0x017e6, 0x017cd, 0x017e5, 0x00025, + /* 0x18 */ 0x017b2, 0x017ce, 0x017e9, 0x017b0, 0x017e7, 0x017d0, 0x017a5, 0x017cc, + /* 0x1c */ 0x017e8, 0x017cf, 0x017e0, 0x017b3, 0x017aa, 0x017a7, 0x017c4, 0x017c5, + /* 0x20 */ 0x017bb, 0x017bc, 0x017c0, 0x017bf, 0x017b7, 0x017b8, 0x01795, 0x01797, + /* 0x24 */ 0x00000, 0x00000, 0x0179b, 0x017a1, 0x017d2, 0x01789, 0x017cb, 0x017c9, + /* 0x28 */ 0x01780, 0x01782, 0x017be, 0x017d6, 0x017ad, 0x017ae, 0x017a6, 0x017b1, + /* 0x2c */ 0x017ca, 0x017af, 0x01793, 0x0178e, 0x01798, 0x017c6, 0x017d4, 0x017d5, + /* 0x30 */ 0x00000, 0x00000, 0x00020, 0x0200b, 0x000ab, 0x000bb, }; NSEvent* keyDownEvent(unsigned short keyCode, NSString* chars = @"", NSString* charsUnmod = @"") { @@ -757,6 +718,13 @@ - (bool)correctLogicalKeyForLayouts { sendTap(kVK_ANSI_LeftBracket, @"х", @"х"); VERIFY_DOWN(kLogicalBracketLeft, "х"); + /* Khmer keyboard layout */ + // Regression test for https://github.com/flutter/flutter/issues/108729 + [tester setLayout:kKhmerLayout]; + + sendTap(kVK_ANSI_2, @"២", @"២"); // Digit2 + VERIFY_DOWN(kLogicalDigit2, "២"); + return TRUE; } diff --git a/shell/platform/embedder/embedder.cc b/shell/platform/embedder/embedder.cc index c733c1a44ec92..97fced2ab3001 100644 --- a/shell/platform/embedder/embedder.cc +++ b/shell/platform/embedder/embedder.cc @@ -132,6 +132,12 @@ static bool IsOpenGLRendererConfigValid(const FlutterRendererConfig* config) { return false; } + if (!SAFE_EXISTS(open_gl_config, populate_existing_damage)) { + FML_LOG(INFO) << "populate_existing_damage was not defined, disabling " + "partial repaint. If you wish to enable partial repaint, " + "please define this callback."; + } + return true; } @@ -222,7 +228,29 @@ static void* DefaultGLProcResolver(const char* name) { } #endif // FML_OS_LINUX || FML_OS_WIN -static flutter::Shell::CreateCallback +#ifdef SHELL_ENABLE_GL +// Auxiliary function used to translate rectangles of type SkIRect to +// FlutterRect. +static FlutterRect SkIRectToFlutterRect(const SkIRect sk_rect) { + FlutterRect flutter_rect = {static_cast(sk_rect.fLeft), + static_cast(sk_rect.fTop), + static_cast(sk_rect.fRight), + static_cast(sk_rect.fBottom)}; + return flutter_rect; +} + +// Auxiliary function used to translate rectangles of type FlutterRect to +// SkIRect. +static const SkIRect FlutterRectToSkIRect(FlutterRect flutter_rect) { + SkIRect rect = {static_cast(flutter_rect.left), + static_cast(flutter_rect.top), + static_cast(flutter_rect.right), + static_cast(flutter_rect.bottom)}; + return rect; +} +#endif + +static inline flutter::Shell::CreateCallback InferOpenGLPlatformViewCreationCallback( const FlutterRendererConfig* config, void* user_data, @@ -241,15 +269,45 @@ InferOpenGLPlatformViewCreationCallback( auto gl_clear_current = [ptr = config->open_gl.clear_current, user_data]() -> bool { return ptr(user_data); }; - auto gl_present = [present = config->open_gl.present, - present_with_info = config->open_gl.present_with_info, - user_data](uint32_t fbo_id) -> bool { + auto gl_present = + [present = config->open_gl.present, + present_with_info = config->open_gl.present_with_info, + user_data](flutter::GLPresentInfo gl_present_info) -> bool { if (present) { return present(user_data); } else { - FlutterPresentInfo present_info = {}; - present_info.struct_size = sizeof(FlutterPresentInfo); - present_info.fbo_id = fbo_id; + // Format the frame and buffer damages accordingly. Note that, since the + // current compute damage algorithm only returns one rectangle for damage + // we are assuming the number of rectangles provided in frame and buffer + // damage are always 1. Once the function that computes damage implements + // support for multiple damage rectangles, GLPresentInfo should also + // contain the number of damage rectangles. + const size_t num_rects = 1; + + std::array frame_damage_rect = { + SkIRectToFlutterRect(*(gl_present_info.frame_damage))}; + std::array buffer_damage_rect = { + SkIRectToFlutterRect(*(gl_present_info.buffer_damage))}; + + FlutterDamage frame_damage{ + .struct_size = sizeof(FlutterDamage), + .num_rects = frame_damage_rect.size(), + .damage = frame_damage_rect.data(), + }; + FlutterDamage buffer_damage{ + .struct_size = sizeof(FlutterDamage), + .num_rects = buffer_damage_rect.size(), + .damage = buffer_damage_rect.data(), + }; + + // Construct the present information concerning the frame being rendered. + FlutterPresentInfo present_info = { + .struct_size = sizeof(FlutterPresentInfo), + .fbo_id = gl_present_info.fbo_id, + .frame_damage = frame_damage, + .buffer_damage = buffer_damage, + }; + return present_with_info(user_data, &present_info); } }; @@ -269,6 +327,50 @@ InferOpenGLPlatformViewCreationCallback( } }; + auto gl_populate_existing_damage = + [populate_existing_damage = config->open_gl.populate_existing_damage, + user_data](intptr_t id) -> flutter::GLFBOInfo { + // If no populate_existing_damage was provided, disable partial + // repaint. + if (!populate_existing_damage) { + return flutter::GLFBOInfo{ + .fbo_id = static_cast(id), + .partial_repaint_enabled = false, + .existing_damage = SkIRect::MakeEmpty(), + }; + } + + // Given the FBO's ID, get its existing damage. + FlutterDamage existing_damage; + populate_existing_damage(user_data, id, &existing_damage); + + bool partial_repaint_enabled = true; + SkIRect existing_damage_rect; + + // Verify that at least one damage rectangle was provided. + if (existing_damage.num_rects <= 0 || existing_damage.damage == nullptr) { + FML_LOG(INFO) << "No damage was provided. Forcing full repaint."; + existing_damage_rect = SkIRect::MakeEmpty(); + partial_repaint_enabled = false; + } else if (existing_damage.num_rects > 1) { + // Log message notifying users that multi-damage is not yet available in + // case they try to make use of it. + FML_LOG(INFO) << "Damage with multiple rectangles not yet supported. " + "Repainting the whole frame."; + existing_damage_rect = SkIRect::MakeEmpty(); + partial_repaint_enabled = false; + } else { + existing_damage_rect = FlutterRectToSkIRect(*(existing_damage.damage)); + } + + // Pass the information about this FBO to the rendering backend. + return flutter::GLFBOInfo{ + .fbo_id = static_cast(id), + .partial_repaint_enabled = partial_repaint_enabled, + .existing_damage = existing_damage_rect, + }; + }; + const FlutterOpenGLRendererConfig* open_gl_config = &config->open_gl; std::function gl_make_resource_current_callback = nullptr; if (SAFE_ACCESS(open_gl_config, make_resource_current, nullptr) != nullptr) { @@ -326,6 +428,7 @@ InferOpenGLPlatformViewCreationCallback( gl_make_resource_current_callback, // gl_make_resource_current_callback gl_surface_transformation_callback, // gl_surface_transformation_callback gl_proc_resolver, // gl_proc_resolver + gl_populate_existing_damage, // gl_populate_existing_damage }; return fml::MakeCopyable( @@ -398,6 +501,8 @@ InferMetalPlatformViewCreationCallback( config->metal.present_command_queue), metal_dispatch_table, view_embedder); + // The static leak checker gets confused by the use of fml::MakeCopyable. + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) return fml::MakeCopyable( [embedder_surface = std::move(embedder_surface), platform_dispatch_table, external_view_embedder = view_embedder](flutter::Shell& shell) mutable { @@ -2681,6 +2786,36 @@ FlutterEngineResult FlutterEngineScheduleFrame(FLUTTER_API_SYMBOL(FlutterEngine) "Could not schedule frame."); } +FlutterEngineResult FlutterEngineSetNextFrameCallback( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + VoidCallback callback, + void* user_data) { + if (engine == nullptr) { + return LOG_EMBEDDER_ERROR(kInvalidArguments, "Invalid engine handle."); + } + + if (callback == nullptr) { + return LOG_EMBEDDER_ERROR(kInvalidArguments, + "Next frame callback was null."); + } + + flutter::EmbedderEngine* embedder_engine = + reinterpret_cast(engine); + + fml::WeakPtr weak_platform_view = + embedder_engine->GetShell().GetPlatformView(); + + if (!weak_platform_view) { + return LOG_EMBEDDER_ERROR(kInternalInconsistency, + "Platform view unavailable."); + } + + weak_platform_view->SetNextFrameCallback( + [callback, user_data]() { callback(user_data); }); + + return kSuccess; +} + FlutterEngineResult FlutterEngineGetProcAddresses( FlutterEngineProcTable* table) { if (!table) { @@ -2732,6 +2867,7 @@ FlutterEngineResult FlutterEngineGetProcAddresses( FlutterEnginePostCallbackOnAllNativeThreads); SET_PROC(NotifyDisplayUpdate, FlutterEngineNotifyDisplayUpdate); SET_PROC(ScheduleFrame, FlutterEngineScheduleFrame); + SET_PROC(SetNextFrameCallback, FlutterEngineSetNextFrameCallback); #undef SET_PROC return kSuccess; diff --git a/shell/platform/embedder/embedder.h b/shell/platform/embedder/embedder.h index 43fd5bd5b1cde..93e92b7d4c6fa 100644 --- a/shell/platform/embedder/embedder.h +++ b/shell/platform/embedder/embedder.h @@ -433,6 +433,16 @@ typedef struct { FlutterSize lower_left_corner_radius; } FlutterRoundedRect; +/// A structure to represent a damage region. +typedef struct { + /// The size of this struct. Must be sizeof(FlutterDamage). + size_t struct_size; + /// The number of rectangles within the damage region. + size_t num_rects; + /// The actual damage region(s) in question. + FlutterRect* damage; +} FlutterDamage; + /// This information is passed to the embedder when requesting a frame buffer /// object. /// @@ -449,6 +459,13 @@ typedef uint32_t (*UIntFrameInfoCallback)( void* /* user data */, const FlutterFrameInfo* /* frame info */); +/// Callback for when a frame buffer object is requested with necessary +/// information for partial repaint. +typedef void (*FlutterFrameBufferWithDamageCallback)( + void* /* user data */, + const intptr_t /* fbo id */, + FlutterDamage* /* existing damage */); + /// This information is passed to the embedder when a surface is presented. /// /// See: \ref FlutterOpenGLRendererConfig.present_with_info. @@ -457,6 +474,10 @@ typedef struct { size_t struct_size; /// Id of the fbo backing the surface that was presented. uint32_t fbo_id; + /// Damage representing the area that the compositor needs to render. + FlutterDamage frame_damage; + /// Damage used to set the buffer's damage region. + FlutterDamage buffer_damage; } FlutterPresentInfo; /// Callback for when a surface is presented. @@ -471,7 +492,10 @@ typedef struct { BoolCallback clear_current; /// Specifying one (and only one) of `present` or `present_with_info` is /// required. Specifying both is an error and engine initialization will be - /// terminated. The return value indicates success of the present call. + /// terminated. The return value indicates success of the present call. If + /// the intent is to use dirty region management, present_with_info must be + /// defined as present will not succeed in communicating information about + /// damage. BoolCallback present; /// Specifying one (and only one) of the `fbo_callback` or /// `fbo_with_frame_info_callback` is required. Specifying both is an error @@ -520,8 +544,27 @@ typedef struct { /// required. Specifying both is an error and engine initialization will be /// terminated. When using this variant, the embedder is passed a /// `FlutterPresentInfo` struct that the embedder can use to release any - /// resources. The return value indicates success of the present call. + /// resources. The return value indicates success of the present call. This + /// callback is essential for dirty region management. If not defined, all the + /// pixels on the screen will be rendered at every frame (regardless of + /// whether damage is actually being computed or not). This is because the + /// information that is passed along to the callback contains the frame and + /// buffer damage that are essential for dirty region management. BoolPresentInfoCallback present_with_info; + /// Specifying this callback is a requirement for dirty region management. + /// Dirty region management will only render the areas of the screen that have + /// changed in between frames, greatly reducing rendering times and energy + /// consumption. To take advantage of these benefits, it is necessary to + /// define populate_existing_damage as a callback that takes user + /// data, an FBO ID, and an existing damage FlutterDamage. The callback should + /// use the given FBO ID to identify the FBO's exisiting damage (i.e. areas + /// that have changed since the FBO was last used) and use it to populate the + /// given existing damage variable. This callback is dependent on either + /// fbo_callback or fbo_with_frame_info_callback being defined as they are + /// responsible for providing populate_existing_damage with the FBO's + /// ID. Not specifying populate_existing_damage will result in full + /// repaint (i.e. rendering all the pixels on the screen at every frame). + FlutterFrameBufferWithDamageCallback populate_existing_damage; } FlutterOpenGLRendererConfig; /// Alias for id. @@ -2510,6 +2553,26 @@ FLUTTER_EXPORT FlutterEngineResult FlutterEngineScheduleFrame(FLUTTER_API_SYMBOL(FlutterEngine) engine); +//------------------------------------------------------------------------------ +/// @brief Schedule a callback to be called after the next frame is drawn. +/// This must be called from the platform thread. The callback is +/// executed only once from the raster thread; embedders must +/// re-thread if necessary. Performing blocking calls +/// in this callback may introduce application jank. +/// +/// @param[in] engine A running engine instance. +/// @param[in] callback The callback to execute. +/// @param[in] user_data A baton passed by the engine to the callback. This +/// baton is not interpreted by the engine in any way. +/// +/// @return The result of the call. +/// +FLUTTER_EXPORT +FlutterEngineResult FlutterEngineSetNextFrameCallback( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + VoidCallback callback, + void* user_data); + #endif // !FLUTTER_ENGINE_NO_PROTOTYPES // Typedefs for the function pointers in FlutterEngineProcTable. @@ -2628,6 +2691,10 @@ typedef FlutterEngineResult (*FlutterEngineNotifyDisplayUpdateFnPtr)( size_t display_count); typedef FlutterEngineResult (*FlutterEngineScheduleFrameFnPtr)( FLUTTER_API_SYMBOL(FlutterEngine) engine); +typedef FlutterEngineResult (*FlutterEngineSetNextFrameCallbackFnPtr)( + FLUTTER_API_SYMBOL(FlutterEngine) engine, + VoidCallback callback, + void* user_data); /// Function-pointer-based versions of the APIs above. typedef struct { @@ -2673,6 +2740,7 @@ typedef struct { PostCallbackOnAllNativeThreads; FlutterEngineNotifyDisplayUpdateFnPtr NotifyDisplayUpdate; FlutterEngineScheduleFrameFnPtr ScheduleFrame; + FlutterEngineSetNextFrameCallbackFnPtr SetNextFrameCallback; } FlutterEngineProcTable; //------------------------------------------------------------------------------ diff --git a/shell/platform/embedder/embedder_external_view_embedder.cc b/shell/platform/embedder/embedder_external_view_embedder.cc index 14927f4833be5..026d1c5d48dd3 100644 --- a/shell/platform/embedder/embedder_external_view_embedder.cc +++ b/shell/platform/embedder/embedder_external_view_embedder.cc @@ -111,15 +111,22 @@ std::vector EmbedderExternalViewEmbedder::GetCurrentCanvases() { } // |ExternalViewEmbedder| -SkCanvas* EmbedderExternalViewEmbedder::CompositeEmbeddedView(int view_id) { +std::vector +EmbedderExternalViewEmbedder::GetCurrentBuilders() { + return std::vector({}); +} + +// |ExternalViewEmbedder| +EmbedderPaintContext EmbedderExternalViewEmbedder::CompositeEmbeddedView( + int view_id) { auto vid = EmbedderExternalView::ViewIdentifier(view_id); auto found = pending_views_.find(vid); if (found == pending_views_.end()) { FML_DCHECK(false) << "Attempted to composite a view that was not " "pre-rolled."; - return nullptr; + return {nullptr, nullptr}; } - return found->second->GetCanvas(); + return {found->second->GetCanvas(), nullptr}; } static FlutterBackingStoreConfig MakeBackingStoreConfig( diff --git a/shell/platform/embedder/embedder_external_view_embedder.h b/shell/platform/embedder/embedder_external_view_embedder.h index 98fe8b2a56639..7921cb8b78255 100644 --- a/shell/platform/embedder/embedder_external_view_embedder.h +++ b/shell/platform/embedder/embedder_external_view_embedder.h @@ -94,7 +94,10 @@ class EmbedderExternalViewEmbedder final : public ExternalViewEmbedder { std::vector GetCurrentCanvases() override; // |ExternalViewEmbedder| - SkCanvas* CompositeEmbeddedView(int view_id) override; + std::vector GetCurrentBuilders() override; + + // |ExternalViewEmbedder| + EmbedderPaintContext CompositeEmbeddedView(int view_id) override; // |ExternalViewEmbedder| void SubmitFrame(GrDirectContext* context, diff --git a/shell/platform/embedder/embedder_platform_message_response.cc b/shell/platform/embedder/embedder_platform_message_response.cc index 633e1c4b0971f..6da7262a78f84 100644 --- a/shell/platform/embedder/embedder_platform_message_response.cc +++ b/shell/platform/embedder/embedder_platform_message_response.cc @@ -24,6 +24,8 @@ void EmbedderPlatformMessageResponse::Complete( } runner_->PostTask( + // The static leak checker gets confused by the use of fml::MakeCopyable. + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) fml::MakeCopyable([data = std::move(data), callback = callback_]() { callback(data->GetMapping(), data->GetSize()); })); diff --git a/shell/platform/embedder/embedder_surface_gl.cc b/shell/platform/embedder/embedder_surface_gl.cc index e406ee9ed10fa..f08793429c4d5 100644 --- a/shell/platform/embedder/embedder_surface_gl.cc +++ b/shell/platform/embedder/embedder_surface_gl.cc @@ -19,7 +19,8 @@ EmbedderSurfaceGL::EmbedderSurfaceGL( if (!gl_dispatch_table_.gl_make_current_callback || !gl_dispatch_table_.gl_clear_current_callback || !gl_dispatch_table_.gl_present_callback || - !gl_dispatch_table_.gl_fbo_callback) { + !gl_dispatch_table_.gl_fbo_callback || + !gl_dispatch_table_.gl_populate_existing_damage) { return; } @@ -46,12 +47,16 @@ bool EmbedderSurfaceGL::GLContextClearCurrent() { // |GPUSurfaceGLDelegate| bool EmbedderSurfaceGL::GLContextPresent(const GLPresentInfo& present_info) { - return gl_dispatch_table_.gl_present_callback(present_info.fbo_id); + // Pass the present information to the embedder present callback. + return gl_dispatch_table_.gl_present_callback(present_info); } // |GPUSurfaceGLDelegate| -intptr_t EmbedderSurfaceGL::GLContextFBO(GLFrameInfo frame_info) const { - return gl_dispatch_table_.gl_fbo_callback(frame_info); +GLFBOInfo EmbedderSurfaceGL::GLContextFBO(GLFrameInfo frame_info) const { + // Get the FBO ID using the gl_fbo_callback and then get exiting damage by + // passing that ID to the gl_populate_existing_damage. + return gl_dispatch_table_.gl_populate_existing_damage( + gl_dispatch_table_.gl_fbo_callback(frame_info)); } // |GPUSurfaceGLDelegate| @@ -75,6 +80,17 @@ EmbedderSurfaceGL::GLProcResolver EmbedderSurfaceGL::GetGLProcResolver() const { return gl_dispatch_table_.gl_proc_resolver; } +// |GPUSurfaceGLDelegate| +SurfaceFrame::FramebufferInfo EmbedderSurfaceGL::GLContextFramebufferInfo() + const { + // Enable partial repaint by default on the embedders. + auto info = SurfaceFrame::FramebufferInfo{}; + info.supports_readback = true; + info.supports_partial_repaint = + gl_dispatch_table_.gl_populate_existing_damage != nullptr; + return info; +} + // |EmbedderSurface| std::unique_ptr EmbedderSurfaceGL::CreateGPUSurface() { const bool render_to_surface = !external_view_embedder_; diff --git a/shell/platform/embedder/embedder_surface_gl.h b/shell/platform/embedder/embedder_surface_gl.h index c0a2b6a55d98f..695aeea84b860 100644 --- a/shell/platform/embedder/embedder_surface_gl.h +++ b/shell/platform/embedder/embedder_surface_gl.h @@ -18,12 +18,13 @@ class EmbedderSurfaceGL final : public EmbedderSurface, struct GLDispatchTable { std::function gl_make_current_callback; // required std::function gl_clear_current_callback; // required - std::function gl_present_callback; // required + std::function gl_present_callback; // required std::function gl_fbo_callback; // required std::function gl_make_resource_current_callback; // optional std::function - gl_surface_transformation_callback; // optional - std::function gl_proc_resolver; // optional + gl_surface_transformation_callback; // optional + std::function gl_proc_resolver; // optional + std::function gl_populate_existing_damage; // required }; EmbedderSurfaceGL( @@ -59,7 +60,7 @@ class EmbedderSurfaceGL final : public EmbedderSurface, bool GLContextPresent(const GLPresentInfo& present_info) override; // |GPUSurfaceGLDelegate| - intptr_t GLContextFBO(GLFrameInfo frame_info) const override; + GLFBOInfo GLContextFBO(GLFrameInfo frame_info) const override; // |GPUSurfaceGLDelegate| bool GLContextFBOResetAfterPresent() const override; @@ -70,6 +71,9 @@ class EmbedderSurfaceGL final : public EmbedderSurface, // |GPUSurfaceGLDelegate| GLProcResolver GetGLProcResolver() const override; + // |GPUSurfaceGLDelegate| + SurfaceFrame::FramebufferInfo GLContextFramebufferInfo() const override; + FML_DISALLOW_COPY_AND_ASSIGN(EmbedderSurfaceGL); }; diff --git a/shell/platform/embedder/tests/embedder_config_builder.cc b/shell/platform/embedder/tests/embedder_config_builder.cc index 918abff4e261e..58e48c4b594d8 100644 --- a/shell/platform/embedder/tests/embedder_config_builder.cc +++ b/shell/platform/embedder/tests/embedder_config_builder.cc @@ -52,13 +52,14 @@ EmbedderConfigBuilder::EmbedderConfigBuilder( opengl_renderer_config_.present_with_info = [](void* context, const FlutterPresentInfo* present_info) -> bool { return reinterpret_cast(context)->GLPresent( - present_info->fbo_id); + *present_info); }; opengl_renderer_config_.fbo_with_frame_info_callback = [](void* context, const FlutterFrameInfo* frame_info) -> uint32_t { return reinterpret_cast(context)->GLGetFramebuffer( *frame_info); }; + opengl_renderer_config_.populate_existing_damage = nullptr; opengl_renderer_config_.make_resource_current = [](void* context) -> bool { return reinterpret_cast(context) ->GLMakeResourceCurrent(); @@ -160,7 +161,10 @@ void EmbedderConfigBuilder::SetOpenGLPresentCallBack() { FML_CHECK(renderer_config_.type == FlutterRendererType::kOpenGL); renderer_config_.open_gl.present = [](void* context) -> bool { // passing a placeholder fbo_id. - return reinterpret_cast(context)->GLPresent(0); + return reinterpret_cast(context)->GLPresent( + FlutterPresentInfo{ + .fbo_id = 0, + }); }; #endif } @@ -312,6 +316,10 @@ void EmbedderConfigBuilder::SetupVsyncCallback() { }; } +FlutterRendererConfig& EmbedderConfigBuilder::GetRendererConfig() { + return renderer_config_; +} + void EmbedderConfigBuilder::SetRenderTaskRunner( const FlutterTaskRunnerDescription* runner) { if (runner == nullptr) { diff --git a/shell/platform/embedder/tests/embedder_config_builder.h b/shell/platform/embedder/tests/embedder_config_builder.h index 3ce230c0146b8..2727c85fdadbd 100644 --- a/shell/platform/embedder/tests/embedder_config_builder.h +++ b/shell/platform/embedder/tests/embedder_config_builder.h @@ -104,6 +104,8 @@ class EmbedderConfigBuilder { FlutterCompositor& GetCompositor(); + FlutterRendererConfig& GetRendererConfig(); + void SetRenderTargetType( EmbedderTestBackingStoreProducer::RenderTargetType type, FlutterSoftwarePixelFormat software_pixfmt = kNative32); diff --git a/shell/platform/embedder/tests/embedder_test_context_gl.cc b/shell/platform/embedder/tests/embedder_test_context_gl.cc index 9a7bc9bcf1c26..0215999814a0d 100644 --- a/shell/platform/embedder/tests/embedder_test_context_gl.cc +++ b/shell/platform/embedder/tests/embedder_test_context_gl.cc @@ -39,7 +39,7 @@ bool EmbedderTestContextGL::GLClearCurrent() { return gl_surface_->ClearCurrent(); } -bool EmbedderTestContextGL::GLPresent(uint32_t fbo_id) { +bool EmbedderTestContextGL::GLPresent(FlutterPresentInfo present_info) { FML_CHECK(gl_surface_) << "GL surface must be initialized."; gl_surface_present_count_++; @@ -50,7 +50,7 @@ bool EmbedderTestContextGL::GLPresent(uint32_t fbo_id) { } if (callback) { - callback(fbo_id); + callback(present_info); } FireRootSurfacePresentCallbackIfPresent( @@ -64,6 +64,12 @@ void EmbedderTestContextGL::SetGLGetFBOCallback(GLGetFBOCallback callback) { gl_get_fbo_callback_ = callback; } +void EmbedderTestContextGL::SetGLPopulateExistingDamageCallback( + GLPopulateExistingDamageCallback callback) { + std::scoped_lock lock(gl_callback_mutex_); + gl_populate_existing_damage_callback_ = callback; +} + void EmbedderTestContextGL::SetGLPresentCallback(GLPresentCallback callback) { std::scoped_lock lock(gl_callback_mutex_); gl_present_callback_ = callback; @@ -86,6 +92,22 @@ uint32_t EmbedderTestContextGL::GLGetFramebuffer(FlutterFrameInfo frame_info) { return gl_surface_->GetFramebuffer(size.width, size.height); } +void EmbedderTestContextGL::GLPopulateExistingDamage( + const intptr_t id, + FlutterDamage* existing_damage) { + FML_CHECK(gl_surface_) << "GL surface must be initialized."; + + GLPopulateExistingDamageCallback callback; + { + std::scoped_lock lock(gl_callback_mutex_); + callback = gl_populate_existing_damage_callback_; + } + + if (callback) { + callback(id, existing_damage); + } +} + bool EmbedderTestContextGL::GLMakeResourceCurrent() { FML_CHECK(gl_surface_) << "GL surface must be initialized."; return gl_surface_->MakeResourceCurrent(); diff --git a/shell/platform/embedder/tests/embedder_test_context_gl.h b/shell/platform/embedder/tests/embedder_test_context_gl.h index 65e2c34c355c2..9b91ef5868868 100644 --- a/shell/platform/embedder/tests/embedder_test_context_gl.h +++ b/shell/platform/embedder/tests/embedder_test_context_gl.h @@ -14,7 +14,10 @@ namespace testing { class EmbedderTestContextGL : public EmbedderTestContext { public: using GLGetFBOCallback = std::function; - using GLPresentCallback = std::function; + using GLPopulateExistingDamageCallback = + std::function; + using GLPresentCallback = + std::function; explicit EmbedderTestContextGL(std::string assets_path = ""); @@ -38,6 +41,9 @@ class EmbedderTestContextGL : public EmbedderTestContext { /// void SetGLGetFBOCallback(GLGetFBOCallback callback); + void SetGLPopulateExistingDamageCallback( + GLPopulateExistingDamageCallback callback); + uint32_t GetWindowFBOId() const; //---------------------------------------------------------------------------- @@ -53,6 +59,9 @@ class EmbedderTestContextGL : public EmbedderTestContext { /// void SetGLPresentCallback(GLPresentCallback callback); + void GLPopulateExistingDamage(const intptr_t id, + FlutterDamage* existing_damage); + protected: virtual void SetupCompositor() override; @@ -65,6 +74,7 @@ class EmbedderTestContextGL : public EmbedderTestContext { std::mutex gl_callback_mutex_; GLGetFBOCallback gl_get_fbo_callback_; GLPresentCallback gl_present_callback_; + GLPopulateExistingDamageCallback gl_populate_existing_damage_callback_; void SetupSurface(SkISize surface_size) override; @@ -72,7 +82,7 @@ class EmbedderTestContextGL : public EmbedderTestContext { bool GLClearCurrent(); - bool GLPresent(uint32_t fbo_id); + bool GLPresent(FlutterPresentInfo present_info); uint32_t GLGetFramebuffer(FlutterFrameInfo frame_info); diff --git a/shell/platform/embedder/tests/embedder_unittests.cc b/shell/platform/embedder/tests/embedder_unittests.cc index d2b0c96b3c99b..74c3782c037fc 100644 --- a/shell/platform/embedder/tests/embedder_unittests.cc +++ b/shell/platform/embedder/tests/embedder_unittests.cc @@ -2126,6 +2126,44 @@ TEST_F(EmbedderTest, CanScheduleFrame) { check_latch.Wait(); } +TEST_F(EmbedderTest, CanSetNextFrameCallback) { + auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); + EmbedderConfigBuilder builder(context); + builder.SetSoftwareRendererConfig(); + builder.SetDartEntrypoint("draw_solid_red"); + + auto engine = builder.LaunchEngine(); + ASSERT_TRUE(engine.is_valid()); + + // Register the callback that is executed once the next frame is drawn. + fml::AutoResetWaitableEvent callback_latch; + VoidCallback callback = [](void* user_data) { + fml::AutoResetWaitableEvent* callback_latch = + static_cast(user_data); + + callback_latch->Signal(); + }; + + auto result = FlutterEngineSetNextFrameCallback(engine.get(), callback, + &callback_latch); + ASSERT_EQ(result, kSuccess); + + // Send a window metrics events so frames may be scheduled. + FlutterWindowMetricsEvent event = {}; + event.struct_size = sizeof(event); + event.width = 800; + event.height = 600; + event.pixel_ratio = 1.0; + event.physical_view_inset_top = 0.0; + event.physical_view_inset_right = 0.0; + event.physical_view_inset_bottom = 0.0; + event.physical_view_inset_left = 0.0; + ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), + kSuccess); + + callback_latch.Wait(); +} + #if defined(FML_OS_MACOSX) static void MockThreadConfigSetter(const fml::Thread::ThreadConfig& config) { diff --git a/shell/platform/embedder/tests/embedder_unittests_gl.cc b/shell/platform/embedder/tests/embedder_unittests_gl.cc index 9174461529058..08f653495f379 100644 --- a/shell/platform/embedder/tests/embedder_unittests_gl.cc +++ b/shell/platform/embedder/tests/embedder_unittests_gl.cc @@ -5,6 +5,7 @@ #include "tests/embedder_test_context.h" #define FML_USED_ON_EMBEDDER +#include #include #include @@ -2030,8 +2031,7 @@ TEST_F(EmbedderTest, constexpr size_t frames_expected = 10; fml::CountDownLatch frame_latch(frames_expected); - static size_t frames_seen; - frames_seen = 0; + std::atomic_size_t frames_seen = 0; context.AddNativeCallback("SignalNativeTest", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { frames_seen++; @@ -2039,7 +2039,7 @@ TEST_F(EmbedderTest, })); frame_latch.Wait(); - ASSERT_EQ(frames_expected, frames_seen); + ASSERT_GE(frames_seen, frames_expected); FlutterEngineShutdown(engine.release()); } @@ -2071,8 +2071,7 @@ TEST_F(EmbedderTest, constexpr size_t frames_expected = 10; fml::CountDownLatch frame_latch(frames_expected); - static size_t frames_seen; - frames_seen = 0; + std::atomic_size_t frames_seen = 0; context.AddNativeCallback("SignalNativeTest", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { frames_seen++; @@ -2080,7 +2079,7 @@ TEST_F(EmbedderTest, })); frame_latch.Wait(); - ASSERT_EQ(frames_expected, frames_seen); + ASSERT_GE(frames_seen, frames_expected); FlutterEngineShutdown(engine.release()); } @@ -3106,6 +3105,72 @@ TEST_F(EmbedderTest, MustNotRunWithBothPresentCallbacksSet) { ASSERT_FALSE(engine.is_valid()); } +TEST_F(EmbedderTest, MustStillRunWhenPopulateExistingDamageIsNotProvided) { + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + + EmbedderConfigBuilder builder(context); + builder.SetOpenGLRendererConfig(SkISize::Make(1, 1)); + builder.GetRendererConfig().open_gl.populate_existing_damage = nullptr; + + auto engine = builder.LaunchEngine(); + ASSERT_TRUE(engine.is_valid()); +} + +TEST_F(EmbedderTest, MustRunWhenPopulateExistingDamageIsProvided) { + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + + EmbedderConfigBuilder builder(context); + builder.SetOpenGLRendererConfig(SkISize::Make(1, 1)); + + builder.GetRendererConfig().open_gl.populate_existing_damage = + [](void* context, const intptr_t id, + FlutterDamage* existing_damage) -> void { + return reinterpret_cast(context) + ->GLPopulateExistingDamage(id, existing_damage); + }; + + auto engine = builder.LaunchEngine(); + ASSERT_TRUE(engine.is_valid()); +} + +TEST_F(EmbedderTest, MustRunWithPopulateExistingDamageAndFBOCallback) { + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + + EmbedderConfigBuilder builder(context); + builder.SetOpenGLRendererConfig(SkISize::Make(1, 1)); + builder.GetRendererConfig().open_gl.fbo_callback = + [](void* context) -> uint32_t { return 0; }; + builder.GetRendererConfig().open_gl.fbo_with_frame_info_callback = nullptr; + builder.GetRendererConfig().open_gl.populate_existing_damage = + [](void* context, const intptr_t id, + FlutterDamage* existing_damage) -> void { + return reinterpret_cast(context) + ->GLPopulateExistingDamage(id, existing_damage); + }; + + auto engine = builder.LaunchEngine(); + ASSERT_TRUE(engine.is_valid()); +} + +TEST_F(EmbedderTest, + MustNotRunWhenPopulateExistingDamageButNoOtherFBOCallback) { + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + + EmbedderConfigBuilder builder(context); + builder.SetOpenGLRendererConfig(SkISize::Make(1, 1)); + builder.GetRendererConfig().open_gl.fbo_callback = nullptr; + builder.GetRendererConfig().open_gl.fbo_with_frame_info_callback = nullptr; + builder.GetRendererConfig().open_gl.populate_existing_damage = + [](void* context, const intptr_t id, + FlutterDamage* existing_damage) -> void { + return reinterpret_cast(context) + ->GLPopulateExistingDamage(id, existing_damage); + }; + + auto engine = builder.LaunchEngine(); + ASSERT_FALSE(engine.is_valid()); +} + TEST_F(EmbedderTest, PresentInfoContainsValidFBOId) { auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); @@ -3140,8 +3205,8 @@ TEST_F(EmbedderTest, PresentInfoContainsValidFBOId) { const uint32_t window_fbo_id = static_cast(context).GetWindowFBOId(); static_cast(context).SetGLPresentCallback( - [window_fbo_id = window_fbo_id](uint32_t fbo_id) { - ASSERT_EQ(fbo_id, window_fbo_id); + [window_fbo_id = window_fbo_id](FlutterPresentInfo present_info) { + ASSERT_EQ(present_info.fbo_id, window_fbo_id); frame_latch.CountDown(); }); @@ -3149,6 +3214,315 @@ TEST_F(EmbedderTest, PresentInfoContainsValidFBOId) { frame_latch.Wait(); } +TEST_F(EmbedderTest, + PresentInfoReceivesFullDamageWhenExistingDamageIsWholeScreen) { + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + + EmbedderConfigBuilder builder(context); + builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetDartEntrypoint("render_gradient"); + builder.GetRendererConfig().open_gl.populate_existing_damage = + [](void* context, const intptr_t id, + FlutterDamage* existing_damage) -> void { + return reinterpret_cast(context) + ->GLPopulateExistingDamage(id, existing_damage); + }; + + // Return existing damage as the entire screen on purpose. + static_cast(context) + .SetGLPopulateExistingDamageCallback( + [](const intptr_t id, FlutterDamage* existing_damage_ptr) { + const size_t num_rects = 1; + FlutterRect existing_damage_rects[num_rects] = { + FlutterRect{0, 0, 800, 600}}; + existing_damage_ptr->num_rects = num_rects; + existing_damage_ptr->damage = existing_damage_rects; + }); + + auto engine = builder.LaunchEngine(); + ASSERT_TRUE(engine.is_valid()); + + // First frame should be entirely rerendered. + static_cast(context).SetGLPresentCallback( + [](FlutterPresentInfo present_info) { + const size_t num_rects = 1; + ASSERT_EQ(present_info.frame_damage.num_rects, num_rects); + ASSERT_EQ(present_info.frame_damage.damage->left, 0); + ASSERT_EQ(present_info.frame_damage.damage->top, 0); + ASSERT_EQ(present_info.frame_damage.damage->right, 800); + ASSERT_EQ(present_info.frame_damage.damage->bottom, 600); + + ASSERT_EQ(present_info.buffer_damage.num_rects, num_rects); + ASSERT_EQ(present_info.buffer_damage.damage->left, 0); + ASSERT_EQ(present_info.buffer_damage.damage->top, 0); + ASSERT_EQ(present_info.buffer_damage.damage->right, 800); + ASSERT_EQ(present_info.buffer_damage.damage->bottom, 600); + }); + + // Send a window metrics events so frames may be scheduled. + FlutterWindowMetricsEvent event = {}; + event.struct_size = sizeof(event); + event.width = 800; + event.height = 600; + event.pixel_ratio = 1.0; + ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), + kSuccess); + + // Because it's the same as the first frame, the second frame damage should + // be empty but, because there was a full existing buffer damage, the buffer + // damage should be the entire screen. + static_cast(context).SetGLPresentCallback( + [](FlutterPresentInfo present_info) { + const size_t num_rects = 1; + ASSERT_EQ(present_info.frame_damage.num_rects, num_rects); + ASSERT_EQ(present_info.frame_damage.damage->left, 0); + ASSERT_EQ(present_info.frame_damage.damage->top, 0); + ASSERT_EQ(present_info.frame_damage.damage->right, 0); + ASSERT_EQ(present_info.frame_damage.damage->bottom, 0); + + ASSERT_EQ(present_info.buffer_damage.num_rects, num_rects); + ASSERT_EQ(present_info.buffer_damage.damage->left, 0); + ASSERT_EQ(present_info.buffer_damage.damage->top, 0); + ASSERT_EQ(present_info.buffer_damage.damage->right, 800); + ASSERT_EQ(present_info.buffer_damage.damage->bottom, 600); + }); + + ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), + kSuccess); +} + +TEST_F(EmbedderTest, PresentInfoReceivesEmptyDamage) { + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + + EmbedderConfigBuilder builder(context); + builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetDartEntrypoint("render_gradient"); + builder.GetRendererConfig().open_gl.populate_existing_damage = + [](void* context, const intptr_t id, + FlutterDamage* existing_damage) -> void { + return reinterpret_cast(context) + ->GLPopulateExistingDamage(id, existing_damage); + }; + + // Return no existing damage on purpose. + static_cast(context) + .SetGLPopulateExistingDamageCallback( + [](const intptr_t id, FlutterDamage* existing_damage_ptr) { + const size_t num_rects = 1; + FlutterRect existing_damage_rects[num_rects] = { + FlutterRect{0, 0, 0, 0}}; + existing_damage_ptr->num_rects = num_rects; + existing_damage_ptr->damage = existing_damage_rects; + }); + + auto engine = builder.LaunchEngine(); + ASSERT_TRUE(engine.is_valid()); + + // First frame should be entirely rerendered. + static_cast(context).SetGLPresentCallback( + [](FlutterPresentInfo present_info) { + const size_t num_rects = 1; + ASSERT_EQ(present_info.frame_damage.num_rects, num_rects); + ASSERT_EQ(present_info.frame_damage.damage->left, 0); + ASSERT_EQ(present_info.frame_damage.damage->top, 0); + ASSERT_EQ(present_info.frame_damage.damage->right, 800); + ASSERT_EQ(present_info.frame_damage.damage->bottom, 600); + + ASSERT_EQ(present_info.buffer_damage.num_rects, num_rects); + ASSERT_EQ(present_info.buffer_damage.damage->left, 0); + ASSERT_EQ(present_info.buffer_damage.damage->top, 0); + ASSERT_EQ(present_info.buffer_damage.damage->right, 800); + ASSERT_EQ(present_info.buffer_damage.damage->bottom, 600); + }); + + // Send a window metrics events so frames may be scheduled. + FlutterWindowMetricsEvent event = {}; + event.struct_size = sizeof(event); + event.width = 800; + event.height = 600; + event.pixel_ratio = 1.0; + ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), + kSuccess); + + // Because it's the same as the first frame, the second frame should not be + // rerendered assuming there is no existing damage. + static_cast(context).SetGLPresentCallback( + [](FlutterPresentInfo present_info) { + const size_t num_rects = 1; + ASSERT_EQ(present_info.frame_damage.num_rects, num_rects); + ASSERT_EQ(present_info.frame_damage.damage->left, 0); + ASSERT_EQ(present_info.frame_damage.damage->top, 0); + ASSERT_EQ(present_info.frame_damage.damage->right, 0); + ASSERT_EQ(present_info.frame_damage.damage->bottom, 0); + + ASSERT_EQ(present_info.buffer_damage.num_rects, num_rects); + ASSERT_EQ(present_info.buffer_damage.damage->left, 0); + ASSERT_EQ(present_info.buffer_damage.damage->top, 0); + ASSERT_EQ(present_info.buffer_damage.damage->right, 0); + ASSERT_EQ(present_info.buffer_damage.damage->bottom, 0); + }); + + ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), + kSuccess); +} + +TEST_F(EmbedderTest, PresentInfoReceivesPartialDamage) { + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + + EmbedderConfigBuilder builder(context); + builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetDartEntrypoint("render_gradient"); + builder.GetRendererConfig().open_gl.populate_existing_damage = + [](void* context, const intptr_t id, + FlutterDamage* existing_damage) -> void { + return reinterpret_cast(context) + ->GLPopulateExistingDamage(id, existing_damage); + }; + + // Return existing damage as only part of the screen on purpose. + static_cast(context) + .SetGLPopulateExistingDamageCallback( + [](const intptr_t id, FlutterDamage* existing_damage_ptr) { + const size_t num_rects = 1; + FlutterRect existing_damage_rects[num_rects] = { + FlutterRect{200, 150, 400, 300}}; + existing_damage_ptr->num_rects = num_rects; + existing_damage_ptr->damage = existing_damage_rects; + }); + + auto engine = builder.LaunchEngine(); + ASSERT_TRUE(engine.is_valid()); + + // First frame should be entirely rerendered. + static_cast(context).SetGLPresentCallback( + [](FlutterPresentInfo present_info) { + const size_t num_rects = 1; + ASSERT_EQ(present_info.frame_damage.num_rects, num_rects); + ASSERT_EQ(present_info.frame_damage.damage->left, 0); + ASSERT_EQ(present_info.frame_damage.damage->top, 0); + ASSERT_EQ(present_info.frame_damage.damage->right, 800); + ASSERT_EQ(present_info.frame_damage.damage->bottom, 600); + + ASSERT_EQ(present_info.buffer_damage.num_rects, num_rects); + ASSERT_EQ(present_info.buffer_damage.damage->left, 0); + ASSERT_EQ(present_info.buffer_damage.damage->top, 0); + ASSERT_EQ(present_info.buffer_damage.damage->right, 800); + ASSERT_EQ(present_info.buffer_damage.damage->bottom, 600); + }); + + // Send a window metrics events so frames may be scheduled. + FlutterWindowMetricsEvent event = {}; + event.struct_size = sizeof(event); + event.width = 800; + event.height = 600; + event.pixel_ratio = 1.0; + ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), + kSuccess); + + // Because it's the same as the first frame, the second frame damage should be + // empty but, because there was a partial existing damage, the buffer damage + // should represent that partial damage area. + static_cast(context).SetGLPresentCallback( + [](FlutterPresentInfo present_info) { + const size_t num_rects = 1; + ASSERT_EQ(present_info.frame_damage.num_rects, num_rects); + ASSERT_EQ(present_info.frame_damage.damage->left, 0); + ASSERT_EQ(present_info.frame_damage.damage->top, 0); + ASSERT_EQ(present_info.frame_damage.damage->right, 0); + ASSERT_EQ(present_info.frame_damage.damage->bottom, 0); + + ASSERT_EQ(present_info.buffer_damage.num_rects, num_rects); + ASSERT_EQ(present_info.buffer_damage.damage->left, 200); + ASSERT_EQ(present_info.buffer_damage.damage->top, 150); + ASSERT_EQ(present_info.buffer_damage.damage->right, 400); + ASSERT_EQ(present_info.buffer_damage.damage->bottom, 300); + }); + + ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), + kSuccess); +} + +TEST_F(EmbedderTest, PopulateExistingDamageReceivesValidID) { + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + + EmbedderConfigBuilder builder(context); + builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetDartEntrypoint("render_gradient"); + builder.GetRendererConfig().open_gl.populate_existing_damage = + [](void* context, const intptr_t id, + FlutterDamage* existing_damage) -> void { + return reinterpret_cast(context) + ->GLPopulateExistingDamage(id, existing_damage); + }; + + auto engine = builder.LaunchEngine(); + ASSERT_TRUE(engine.is_valid()); + + const uint32_t window_fbo_id = + static_cast(context).GetWindowFBOId(); + static_cast(context) + .SetGLPopulateExistingDamageCallback( + [window_fbo_id = window_fbo_id](intptr_t id, + FlutterDamage* existing_damage) { + ASSERT_EQ(id, window_fbo_id); + }); + + // Send a window metrics events so frames may be scheduled. + FlutterWindowMetricsEvent event = {}; + event.struct_size = sizeof(event); + event.width = 800; + event.height = 600; + event.pixel_ratio = 1.0; + ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), + kSuccess); +} + +TEST_F(EmbedderTest, PopulateExistingDamageReceivesInvalidID) { + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + + EmbedderConfigBuilder builder(context); + builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetDartEntrypoint("render_gradient"); + builder.GetRendererConfig().open_gl.populate_existing_damage = + [](void* context, const intptr_t id, + FlutterDamage* existing_damage) -> void { + return reinterpret_cast(context) + ->GLPopulateExistingDamage(id, existing_damage); + }; + + // Return a bad FBO ID on purpose. + builder.GetRendererConfig().open_gl.fbo_with_frame_info_callback = + [](void* context, const FlutterFrameInfo* frame_info) -> uint32_t { + return 123; + }; + + auto engine = builder.LaunchEngine(); + ASSERT_TRUE(engine.is_valid()); + + context.AddNativeCallback("SignalNativeTest", + CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { + /* Nothing to do. */ + })); + + const uint32_t window_fbo_id = + static_cast(context).GetWindowFBOId(); + static_cast(context) + .SetGLPopulateExistingDamageCallback( + [window_fbo_id = window_fbo_id](intptr_t id, + FlutterDamage* existing_damage) { + ASSERT_NE(id, window_fbo_id); + }); + + // Send a window metrics events so frames may be scheduled. + FlutterWindowMetricsEvent event = {}; + event.struct_size = sizeof(event); + event.width = 800; + event.height = 600; + event.pixel_ratio = 1.0; + ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), + kSuccess); +} + TEST_F(EmbedderTest, SetSingleDisplayConfigurationWithDisplayId) { auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); @@ -3691,6 +4065,26 @@ TEST_F(EmbedderTest, ExternalTextureGLRefreshedTooOften) { EXPECT_TRUE(resolve_called); } +TEST_F(EmbedderTest, + PresentInfoReceivesNoDamageWhenPopulateExistingDamageIsUndefined) { + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); + + EmbedderConfigBuilder builder(context); + builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); + builder.SetDartEntrypoint("render_gradient"); + builder.GetRendererConfig().open_gl.populate_existing_damage = nullptr; + + auto engine = builder.LaunchEngine(); + ASSERT_TRUE(engine.is_valid()); + + // No damage should be passed. + static_cast(context).SetGLPresentCallback( + [](FlutterPresentInfo present_info) { + ASSERT_EQ(present_info.frame_damage.damage, nullptr); + ASSERT_EQ(present_info.buffer_damage.damage, nullptr); + }); +} + INSTANTIATE_TEST_SUITE_P( EmbedderTestGlVk, EmbedderTestMultiBackend, diff --git a/shell/platform/fuchsia/flutter/BUILD.gn b/shell/platform/fuchsia/flutter/BUILD.gn index 0680f42913021..01a1f2e3ce106 100644 --- a/shell/platform/fuchsia/flutter/BUILD.gn +++ b/shell/platform/fuchsia/flutter/BUILD.gn @@ -457,6 +457,34 @@ jit_runner("flutter_jit_product_runner") { product = true } +# "OOT" copy of the runner used by tests, to avoid conflicting with the runner +# in the base fuchsia image. +# TODO(fxbug.dev/106575): Fix this with subpackages. +aot_runner("oot_flutter_aot_runner") { + product = false +} + +# "OOT" copy of the runner used by tests, to avoid conflicting with the runner +# in the base fuchsia image. +# TODO(fxbug.dev/106575): Fix this with subpackages. +aot_runner("oot_flutter_aot_product_runner") { + product = true +} + +# "OOT" copy of the runner used by tests, to avoid conflicting with the runner +# in the base fuchsia image. +# TODO(fxbug.dev/106575): Fix this with subpackages. +jit_runner("oot_flutter_jit_runner") { + product = false +} + +# "OOT" copy of the runner used by tests, to avoid conflicting with the runner +# in the base fuchsia image. +# TODO(fxbug.dev/106575): Fix this with subpackages. +jit_runner("oot_flutter_jit_product_runner") { + product = true +} + test_fixtures("flutter_runner_fixtures") { fixtures = [] } @@ -885,7 +913,7 @@ if (enable_unittests) { ":testing_tests", ":txt_tests", ":ui_tests", - "integration_flutter_tests", + "tests/integration", ] } } diff --git a/shell/platform/fuchsia/flutter/engine.cc b/shell/platform/fuchsia/flutter/engine.cc index ab94955d6776a..1d491d393302c 100644 --- a/shell/platform/fuchsia/flutter/engine.cc +++ b/shell/platform/fuchsia/flutter/engine.cc @@ -166,8 +166,8 @@ void Engine::Initialize( } else { gfx_protocols.set_view_focuser(focuser.NewRequest()); gfx_protocols.set_view_ref_focused(view_ref_focused.NewRequest()); - // TODO(fxbug.dev/85125): Enable TouchSource for GFX. - // gfx_protocols.set_touch_source(touch_source.NewRequest()); + gfx_protocols.set_touch_source(touch_source.NewRequest()); + // GFX used only on products without a mouse. } scenic->CreateSessionT(std::move(gfx_protocols), [] {}); diff --git a/shell/platform/fuchsia/flutter/flatland_external_view_embedder.cc b/shell/platform/fuchsia/flutter/flatland_external_view_embedder.cc index 8eca3629343d5..41049c39fa839 100644 --- a/shell/platform/fuchsia/flutter/flatland_external_view_embedder.cc +++ b/shell/platform/fuchsia/flutter/flatland_external_view_embedder.cc @@ -56,6 +56,11 @@ std::vector FlatlandExternalViewEmbedder::GetCurrentCanvases() { return canvases; } +std::vector +FlatlandExternalViewEmbedder::GetCurrentBuilders() { + return std::vector(); +} + void FlatlandExternalViewEmbedder::PrerollCompositeEmbeddedView( int view_id, std::unique_ptr params) { @@ -67,12 +72,13 @@ void FlatlandExternalViewEmbedder::PrerollCompositeEmbeddedView( frame_composition_order_.push_back(handle); } -SkCanvas* FlatlandExternalViewEmbedder::CompositeEmbeddedView(int view_id) { +flutter::EmbedderPaintContext +FlatlandExternalViewEmbedder::CompositeEmbeddedView(int view_id) { zx_handle_t handle = static_cast(view_id); auto found = frame_layers_.find(handle); FML_CHECK(found != frame_layers_.end()); - return found->second.canvas_spy->GetSpyingCanvas(); + return {found->second.canvas_spy->GetSpyingCanvas(), nullptr}; } flutter::PostPrerollResult FlatlandExternalViewEmbedder::PostPrerollAction( @@ -178,7 +184,14 @@ void FlatlandExternalViewEmbedder::SubmitFrame( // Get the FlatlandView structure corresponding to the platform view. auto found = flatland_views_.find(layer_id.value()); - FML_CHECK(found != flatland_views_.end()); + FML_CHECK(found != flatland_views_.end()) + << "No FlatlandView for layer_id = " << layer_id.value() + << ". This typically indicates that the Dart code in " + "Fuchsia's fuchsia_scenic_flutter library failed to create " + "the platform view, leading to a crash later down the road in " + "the Flutter Engine code that tries to find that platform view. " + "Check the Flutter Framework for changes to PlatformView that " + "might have caused a regression."; auto& viewport = found->second; // Compute mutators, and size for the platform view. diff --git a/shell/platform/fuchsia/flutter/flatland_external_view_embedder.h b/shell/platform/fuchsia/flutter/flatland_external_view_embedder.h index a47cd42055965..360e068699def 100644 --- a/shell/platform/fuchsia/flutter/flatland_external_view_embedder.h +++ b/shell/platform/fuchsia/flutter/flatland_external_view_embedder.h @@ -64,13 +64,16 @@ class FlatlandExternalViewEmbedder final // |ExternalViewEmbedder| std::vector GetCurrentCanvases() override; + // |ExternalViewEmbedder| + std::vector GetCurrentBuilders() override; + // |ExternalViewEmbedder| void PrerollCompositeEmbeddedView( int view_id, std::unique_ptr params) override; // |ExternalViewEmbedder| - SkCanvas* CompositeEmbeddedView(int view_id) override; + flutter::EmbedderPaintContext CompositeEmbeddedView(int view_id) override; // |ExternalViewEmbedder| flutter::PostPrerollResult PostPrerollAction( diff --git a/shell/platform/fuchsia/flutter/gfx_external_view_embedder.cc b/shell/platform/fuchsia/flutter/gfx_external_view_embedder.cc index 388671808f001..1cd292764efa3 100644 --- a/shell/platform/fuchsia/flutter/gfx_external_view_embedder.cc +++ b/shell/platform/fuchsia/flutter/gfx_external_view_embedder.cc @@ -164,6 +164,11 @@ std::vector GfxExternalViewEmbedder::GetCurrentCanvases() { return canvases; } +std::vector +GfxExternalViewEmbedder::GetCurrentBuilders() { + return std::vector({}); +} + void GfxExternalViewEmbedder::PrerollCompositeEmbeddedView( int view_id, std::unique_ptr params) { @@ -176,12 +181,13 @@ void GfxExternalViewEmbedder::PrerollCompositeEmbeddedView( frame_composition_order_.push_back(handle); } -SkCanvas* GfxExternalViewEmbedder::CompositeEmbeddedView(int view_id) { +flutter::EmbedderPaintContext GfxExternalViewEmbedder::CompositeEmbeddedView( + int view_id) { zx_handle_t handle = static_cast(view_id); auto found = frame_layers_.find(handle); FML_CHECK(found != frame_layers_.end()); - return found->second.canvas_spy->GetSpyingCanvas(); + return {found->second.canvas_spy->GetSpyingCanvas(), nullptr}; } flutter::PostPrerollResult GfxExternalViewEmbedder::PostPrerollAction( diff --git a/shell/platform/fuchsia/flutter/gfx_external_view_embedder.h b/shell/platform/fuchsia/flutter/gfx_external_view_embedder.h index 81b061776973b..c2e67e34a2845 100644 --- a/shell/platform/fuchsia/flutter/gfx_external_view_embedder.h +++ b/shell/platform/fuchsia/flutter/gfx_external_view_embedder.h @@ -87,13 +87,16 @@ class GfxExternalViewEmbedder final : public flutter::ExternalViewEmbedder { // |ExternalViewEmbedder| std::vector GetCurrentCanvases() override; + // |ExternalViewEmbedder| + std::vector GetCurrentBuilders() override; + // |ExternalViewEmbedder| void PrerollCompositeEmbeddedView( int view_id, std::unique_ptr params) override; // |ExternalViewEmbedder| - SkCanvas* CompositeEmbeddedView(int view_id) override; + flutter::EmbedderPaintContext CompositeEmbeddedView(int view_id) override; // |ExternalViewEmbedder| flutter::PostPrerollResult PostPrerollAction( diff --git a/shell/platform/fuchsia/flutter/integration_flutter_tests/OWNERS b/shell/platform/fuchsia/flutter/integration_flutter_tests/OWNERS deleted file mode 100644 index aa90637fa8ff3..0000000000000 --- a/shell/platform/fuchsia/flutter/integration_flutter_tests/OWNERS +++ /dev/null @@ -1,5 +0,0 @@ -dworsham@google.com -sanjayc@google.com -richkadel@google.com - -# COMPONENT: FlutteronFuchsia diff --git a/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/BUILD.gn b/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/BUILD.gn deleted file mode 100644 index bba0da5c26dff..0000000000000 --- a/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/BUILD.gn +++ /dev/null @@ -1,67 +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. - -assert(is_fuchsia) - -import("//build/fuchsia/sdk.gni") -import("//flutter/tools/fuchsia/fuchsia_archive.gni") - -group("tests") { - testonly = true - deps = [ - ":flutter-embedder-test2", - "//flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/child-view2", - "//flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/parent-view2", - ] -} - -executable("flutter-embedder-test2-bin") { - testonly = true - - output_name = "flutter-embedder-test2" - - sources = [ - "flutter-embedder-test2.cc", - "flutter-embedder-test2.h", - ] - - # This is needed for //third_party/googletest for linking zircon symbols. - libs = [ "$fuchsia_sdk_path/arch/$target_cpu/sysroot/lib/libzircon.so" ] - - include_dirs = [ "//flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing" ] - - public_deps = [ "//third_party/googletest:gtest" ] - - deps = [ - "$fuchsia_sdk_root/fidl:fuchsia.sys", - "$fuchsia_sdk_root/fidl:fuchsia.ui.app", - "$fuchsia_sdk_root/fidl:fuchsia.ui.input", - "$fuchsia_sdk_root/fidl:fuchsia.ui.lifecycle", - "$fuchsia_sdk_root/fidl:fuchsia.ui.policy", - "$fuchsia_sdk_root/fidl:fuchsia.ui.scenic", - "$fuchsia_sdk_root/pkg:async-loop-cpp", - "$fuchsia_sdk_root/pkg:async-loop-default", - "$fuchsia_sdk_root/pkg:fit", - "$fuchsia_sdk_root/pkg:scenic_cpp", - "$fuchsia_sdk_root/pkg:sys_cpp", - "$fuchsia_sdk_root/pkg:sys_cpp_testing", - "$fuchsia_sdk_root/pkg:zx", - "//flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing/src/lib/ui/base_view", - "//flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing/src/ui/testing/views", - "//third_party/dart/runtime:libdart_jit", - "//third_party/googletest:gtest_main", - ] -} - -fuchsia_test_archive("flutter-embedder-test2") { - deps = [ - ":flutter-embedder-test2-bin", - "//flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/child-view2:package", - "//flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/parent-view2:package", - ] - - binary = "$target_name" - - cmx_file = rebase_path("meta/$target_name.cmx") -} diff --git a/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/README.md b/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/README.md deleted file mode 100644 index 6b5190b9af804..0000000000000 --- a/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/README.md +++ /dev/null @@ -1,173 +0,0 @@ -# `flutter scenic embedder tests` - -## Configure and build fuchsia - -For tests that require scenic, for example, run `fx set` with the required -targets; for example: - -```shell -$ cd "$FUCHSIA_DIR" -$ fx set core.x64 \ - --with //src/ui/scenic \ - --with //src/ui/bin/root_presenter \ - --with //src/ui/bin/hardware_display_controller_provider -$ fx build -``` - -Note 1: You could use `--with-base` here, instead of `--with`, but if so, you -would also need to add `--with-base //garnet/bin/run_test_component`. More on -this below, under [Start the package servers](#start-the-package-servers). - -Note 2: The `fx set` flags, above, offer a minimized fuchsia platform -configuration to successfully execute the test, but some optional services may -be missing. Be aware that the Fuchsia system logs may include multiple -occurrences `WARNING: error resolving ...` messages, such as the following, -which can be ignored: - -``` -[pkg-resolver] WARNING: error resolving fuchsia-pkg://fuchsia.com/fonts/0 ... -[pkg-resolver] WARNING: error resolving fuchsia-pkg://fuchsia.com/ime_service/0 ... -[pkg-resolver] WARNING: error resolving fuchsia-pkg://fuchsia.com/intl_property_manager/0 ... -``` - -## Restart and reboot your device - -_(Optional)_ If developing with the emulator, launch (or shutdown and relaunch) -the emulator. - -```shell -fx vdl start -N -``` - -NOTE: Do _not_ run the default package server. The instructions below describe -how to launch a flutter-specific package server. - -Or if you've rebuilt fuchsia for a device that is already running a version of -fuchsia, you may be able to reboot without restarting the device: - -```shell -$ fx reboot -r -``` - -If you are building a device that launches the UI at startup, you will likely -need to kill Scenic before running the test. - -```shell -$ fx shell killall scenic.cmx -``` - -## Build the test - -You can specify the test's package target to build only the test package, with -its dependencies. This will also build the required runner. - -```shell -$ cd "$FLUTTER_ENGINE_DIR/src" -$ ./flutter/tools/gn --fuchsia \ - # for example: --goma --fuchsia-cpu=x64 --runtime-mode=debug -$ ninja -C out/fuchsia_debug_x64 \ - flutter/shell/platform/fuchsia/flutter/integration_flutter_tests -``` - -## Publish the test packages to the Fuchsia package server - -The tests currently specify the Fuchsia package server's standard domain, -`fuchsia.com`, as the server to use to resolve (locate and load) the test -packages. So, before running the test, the most recently built `.far` files -need to be published to the Fuchsia package repo: - -```shell -$ fx pm publish -a -repo "$(cat $FUCHSIA_DIR/.fx-build-dir)/amber-files/" \ - -f "$FLUTTER_ENGINE_DIR"/src/out/fuchsia_*64/flutter-embedder-test2-0.far -$ fx pm publish -a -repo "$(cat $FUCHSIA_DIR/.fx-build-dir)/amber-files/" \ - -f $(find "$FLUTTER_ENGINE_DIR"/src/out/fuchsia_*64 -name parent-view2.far) -$ fx pm publish -a -repo "$(cat $FUCHSIA_DIR/.fx-build-dir)/amber-files/" \ - -f $(find "$FLUTTER_ENGINE_DIR"/src/out/fuchsia_*64 -name child-view2.far) -``` - -## Run the test (using the package server at `fuchsia.com`) - -```shell -$ fx test flutter-embedder-test2 -``` - -## Make a change and re-run the test - -If, for example, you only make a change to the Dart code in `parent-view2`, you -can rebuild only the parent-view2 package target, republish it, and then re-run -the test, with: - -```shell -$ ninja -C out/fuchsia_debug_x64 \ - flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/parent-view2:package -$ fx pm publish -a -repo "$(cat $FUCHSIA_DIR/.fx-build-dir)/amber-files/" \ - -f $(find "$FLUTTER_ENGINE_DIR"/src/out/fuchsia_*64 -name parent-view2.far) -$ fx test flutter-embedder-test2 -``` - -From here, you can modify the Flutter test, rebuild flutter, and usually rerun -the test without rebooting, by repeating the commands above. - -The embedder tests must be run on a product without a graphical base shell, -such as `core` because it starts and stops Scenic. - -## (Alternative) Serving flutter packages from a custom package server - -If you want to use a custom package server, you will need to edit these sources: - - * `//flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/flutter-embedder-test2.cc` - * `//flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/parent-view2/parent_view2.dart` - -Search for the component URLs with `fuchsia.com`, and change it to `engine`, -which is the domain currently registered with the custom package server in -`//tools/fuchsia/devshell/serve.sh`. - -WARNING: Be careful not to check in that change because CI requires using the -`fuchsia.com` domain/package server. - -The default fuchsia package server (launched via `fx serve`) is normally -required, unless all of your test's package dependencies are included in the -fuchsia system image. You can force additional packages into the system image -with `fx set ... --with-base ` (instead of using `--with`). For -example, in the `fx set` command above, using, `--with-base scenic`, and so on. -Note, however, that the default `core.x64` configuration bundles the test -runner as if it was included via -`--with //garnet/bin/run_test_component`, so to include the test runner in the -system image requires adding that package as well, via `--with-base`, instead. - -In order to serve fuchsia package dependencies (like `scenic`, `root_presenter`, -and `hardware-display-controller-provider`), without forcing them into the -system image, you will need to run the fuchsia default package server, via `fx -serve`. - -The `flutter/engine` packages (tests and flutter runners, for dart-based tests) -are served from a separate package server. The `flutter/engine` repo's -`serve.sh` script launches this secondary package server, and configures -package URL rewrite rules to redirect fuchsia's requests for flutter- and -dart-runner packages from `fuchsia.com` to flutter's package server instead. - -**IMPORTANT:** _The flutter package server must be launched **after** the -default package server, because both `fx serve` and flutter's `serve.sh` set -package URL rewrite rules, and only the last one wins._ - -Launch each package server in a separate window or shell: - -```shell -$ cd "${FUCHSIA_DIR}" -$ fx serve -``` - -From the flutter engine `src` directory, run the following script to launch the -`engine` package server, to serve the flutter runner and test components. - -```shell -$ flutter/tools/fuchsia/devshell/serve.sh --out out/fuchsia_debug_x64 --only-serve-runners -``` - -## Run the test, using `engine` as the package server domain - -```shell -$ fx test flutter-embedder-test2 -``` - -You can recompile and run the test without needing to re-publish the `.far`. diff --git a/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/child-view2/BUILD.gn b/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/child-view2/BUILD.gn deleted file mode 100644 index 93e836386931b..0000000000000 --- a/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/child-view2/BUILD.gn +++ /dev/null @@ -1,38 +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. - -import("//build/fuchsia/sdk.gni") -import("//flutter/tools/fuchsia/dart/dart_library.gni") -import("//flutter/tools/fuchsia/flutter/flutter_component.gni") -import("//flutter/tools/fuchsia/gn-sdk/package.gni") - -dart_library("child-view2_dart_library") { - package_name = "child-view2" - - source_dir = "." - sources = [ "child_view2.dart" ] - - deps = [] -} - -flutter_component("child-view2_flutter_component") { - main_package = "child-view2" - component_name = "child-view2" - main_dart = "child_view2.dart" - manifest = rebase_path("meta/child-view2.cmx") - deps = [ ":child-view2_dart_library" ] -} - -# TODO(richkadel): The target name is set differently compared to fuchsia.git's flutter_app(). -# Unlike in fuchsia.git's version of fuchsia_component, the Fuchsia GN SDK -# version passes the component name to fuchsia_component via it's target_name only. -# GN SDK's fuchsia_component doesn't have a `component_name` argument! So I'm forced to set -# the component name via "target_name". This is a problem in fuchsia_package, which uses -# the target_name to name the fuchsia_pm_tool target, creating duplicate target IDs! -# So I have to change the fuchsia_package name to something that is NOT the component name, -# and then set the package_name (which fuchsia_package does support). -fuchsia_package("package") { - package_name = "child-view2" - deps = [ ":child-view2_flutter_component" ] -} diff --git a/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/child-view2/meta/child-view2.cmx b/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/child-view2/meta/child-view2.cmx deleted file mode 100644 index 2157ca2190988..0000000000000 --- a/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/child-view2/meta/child-view2.cmx +++ /dev/null @@ -1,13 +0,0 @@ -{ - "program": { - "data": "data/child-view2" - }, - "sandbox": { - "services": [ - "fuchsia.fonts.Provider", - "fuchsia.sys.Environment", - "fuchsia.ui.input.ImeService", - "fuchsia.ui.scenic.Scenic" - ] - } -} diff --git a/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/flutter-embedder-test2.cc b/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/flutter-embedder-test2.cc deleted file mode 100644 index 63a5cd197ab30..0000000000000 --- a/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/flutter-embedder-test2.cc +++ /dev/null @@ -1,218 +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. - -#include "flutter-embedder-test2.h" - -namespace flutter_embedder_test2 { - -// TODO(richkadel): To run the test serving the runner and test packages from -// the flutter/engine package server (via -// `//flutter/tools/fuchsia/devshell/serve.sh`), change `fuchsia.com` to -// `engine`. -constexpr char kParentViewUrl[] = - "fuchsia-pkg://fuchsia.com/parent-view2#meta/parent-view2.cmx"; - -constexpr scenic::Color kParentBackgroundColor = {0x00, 0x00, 0xFF, - 0xFF}; // Blue -constexpr scenic::Color kParentTappedColor = {0x00, 0x00, 0x00, 0xFF}; // Black -constexpr scenic::Color kChildBackgroundColor = {0xFF, 0x00, 0xFF, - 0xFF}; // Pink -constexpr scenic::Color kChildTappedColor = {0xFF, 0xFF, 0x00, 0xFF}; // Yellow - -// TODO(fxb/94000): The new flutter renderer draws overlays as a single, large -// layer. Some parts of this layer are fully transparent, so we want the -// compositor to treat the layer as transparent and blend it with the contents -// below. -// -// The gfx Scenic API only provides one way to mark this layer as transparent -// which is to set an opacity < 1.0 for the entire layer. In practice, we use -// 0.9961 (254 / 255) as an opacity value to force transparency. Unfortunately -// this causes the overlay to blend very slightly and it looks wrong. -// -// Flatland allows marking a layer as transparent while still using a 1.0 -// opacity value when blending, so migrating flutter to Flatland will fix this -// issue. For now we just hard-code the broken, blended values. -constexpr scenic::Color kOverlayBackgroundColor1 = { - 0x00, 0xFF, 0x0E, 0xFF}; // Green, blended with blue (FEMU local) -constexpr scenic::Color kOverlayBackgroundColor2 = { - 0x0E, 0xFF, 0x0E, 0xFF}; // Green, blended with pink (FEMU local) -constexpr scenic::Color kOverlayBackgroundColor3 = { - 0x00, 0xFF, 0x0D, 0xFF}; // Green, blended with blue (AEMU infra) -constexpr scenic::Color kOverlayBackgroundColor4 = { - 0x0D, 0xFF, 0x0D, 0xFF}; // Green, blended with pink (AEMU infra) -constexpr scenic::Color kOverlayBackgroundColor5 = { - 0x00, 0xFE, 0x0D, 0xFF}; // Green, blended with blue (NUC) -constexpr scenic::Color kOverlayBackgroundColor6 = { - 0x0D, 0xFF, 0x00, 0xFF}; // Green, blended with pink (NUC) - -static size_t OverlayPixelCount(std::map& histogram) { - return histogram[kOverlayBackgroundColor1] + - histogram[kOverlayBackgroundColor2] + - histogram[kOverlayBackgroundColor3] + - histogram[kOverlayBackgroundColor4] + - histogram[kOverlayBackgroundColor5] + - histogram[kOverlayBackgroundColor6]; -} - -/// Defines a list of services that are injected into the test environment. -/// Unlike the injected-services in CMX which are injected per test package, -/// these are injected per test and result in a more hermetic test environment. -const std::vector> GetInjectedServices() { - std::vector> injected_services = {{ - {"fuchsia.accessibility.semantics.SemanticsManager", - "fuchsia-pkg://fuchsia.com/a11y-manager#meta/a11y-manager.cmx"}, - {"fuchsia.fonts.Provider", - "fuchsia-pkg://fuchsia.com/fonts#meta/fonts.cmx"}, - {"fuchsia.hardware.display.Provider", - "fuchsia-pkg://fuchsia.com/" - "fake-hardware-display-controller-provider#meta/hdcp.cmx"}, - {"fuchsia.intl.PropertyProvider", - "fuchsia-pkg://fuchsia.com/intl_property_manager#meta/" - "intl_property_manager.cmx"}, - {"fuchsia.netstack.Netstack", - "fuchsia-pkg://fuchsia.com/network-legacy-deprecated#meta/netstack.cmx"}, - {"fuchsia.posix.socket.Provider", - "fuchsia-pkg://fuchsia.com/network-legacy-deprecated#meta/netstack.cmx"}, - {"fuchsia.tracing.provider.Registry", - "fuchsia-pkg://fuchsia.com/trace_manager#meta/trace_manager.cmx"}, - {"fuchsia.ui.input.ImeService", - "fuchsia-pkg://fuchsia.com/text_manager#meta/text_manager.cmx"}, - {"fuchsia.ui.input.ImeVisibilityService", - "fuchsia-pkg://fuchsia.com/text_manager#meta/text_manager.cmx"}, - {"fuchsia.ui.scenic.Scenic", - "fuchsia-pkg://fuchsia.com/scenic#meta/scenic.cmx"}, - {"fuchsia.ui.pointerinjector.Registry", - "fuchsia-pkg://fuchsia.com/scenic#meta/scenic.cmx"}, // For - // root_presenter - // TODO(fxbug.dev/82655): Remove this after migrating to RealmBuilder. - {"fuchsia.ui.lifecycle.LifecycleController", - "fuchsia-pkg://fuchsia.com/scenic#meta/scenic.cmx"}, - {"fuchsia.ui.policy.Presenter", - "fuchsia-pkg://fuchsia.com/root_presenter#meta/root_presenter.cmx"}, - {"fuchsia.ui.input.InputDeviceRegistry", - "fuchsia-pkg://fuchsia.com/root_presenter#meta/root_presenter.cmx"}, - }}; - return injected_services; -} - -TEST_F(FlutterScenicEmbedderTests, Embedding) { - RunAppWithArgs(kParentViewUrl); - - // Take screenshot until we see the child-view2's embedded color. - ASSERT_TRUE(TakeScreenshotUntil( - kChildBackgroundColor, [](scenic::Screenshot screenshot, - std::map histogram) { - // Expect parent and child background colors, with parent color > child - // color. - EXPECT_GT(histogram[kParentBackgroundColor], 0u); - EXPECT_GT(histogram[kChildBackgroundColor], 0u); - EXPECT_GT(histogram[kParentBackgroundColor], - histogram[kChildBackgroundColor]); - - // Expect all corners to be the parent-view2 background color - EXPECT_EQ(kParentBackgroundColor, screenshot.ColorAtPixelXY(10, 10)); - EXPECT_EQ(kParentBackgroundColor, - screenshot.ColorAtPixelXY(screenshot.width() - 10, 0)); - EXPECT_EQ(kParentBackgroundColor, - screenshot.ColorAtPixelXY(0, screenshot.height() - 10)); - EXPECT_EQ(kParentBackgroundColor, - screenshot.ColorAtPixelXY(screenshot.width() - 10, - screenshot.height() - 10)); - })); -} - -TEST_F(FlutterScenicEmbedderTests, HittestEmbedding) { - RunAppWithArgs(kParentViewUrl); - - // Take screenshot until we see the child-view2's embedded color. - ASSERT_TRUE(TakeScreenshotUntil(kChildBackgroundColor)); - - // Tap the center of child view2. - InjectInput(); - - // Take screenshot until we see the child-view2's tapped color. - ASSERT_TRUE(TakeScreenshotUntil( - kChildTappedColor, [](scenic::Screenshot screenshot, - std::map histogram) { - // Expect parent and child background colors, with parent color > child - // color. - EXPECT_GT(histogram[kParentBackgroundColor], 0u); - EXPECT_EQ(histogram[kChildBackgroundColor], 0u); - EXPECT_GT(histogram[kChildTappedColor], 0u); - EXPECT_GT(histogram[kParentBackgroundColor], - histogram[kChildTappedColor]); - })); -} - -TEST_F(FlutterScenicEmbedderTests, HittestDisabledEmbedding) { - RunAppWithArgs(kParentViewUrl, {"--no-hitTestable"}); - - // Take screenshots until we see the child-view2's embedded color. - ASSERT_TRUE(TakeScreenshotUntil(kChildBackgroundColor)); - - // Tap the center of child view2. Since it's not hit-testable, the tap should - // go to the parent. - InjectInput(); - - // The parent-view2 should change color. - ASSERT_TRUE(TakeScreenshotUntil( - kParentTappedColor, [](scenic::Screenshot screenshot, - std::map histogram) { - // Expect parent and child background colors, with parent color > child - // color. - EXPECT_EQ(histogram[kParentBackgroundColor], 0u); - EXPECT_GT(histogram[kParentTappedColor], 0u); - EXPECT_GT(histogram[kChildBackgroundColor], 0u); - EXPECT_EQ(histogram[kChildTappedColor], 0u); - EXPECT_GT(histogram[kParentTappedColor], - histogram[kChildBackgroundColor]); - })); -} - -TEST_F(FlutterScenicEmbedderTests, EmbeddingWithOverlay) { - RunAppWithArgs(kParentViewUrl, {"--showOverlay"}); - - // Take screenshot until we see the child-view2's embedded color. - ASSERT_TRUE(TakeScreenshotUntil( - kChildBackgroundColor, [](scenic::Screenshot screenshot, - std::map histogram) { - // Expect parent, overlay and child background colors. - // With parent color > child color and overlay color > child color. - const size_t overlay_pixel_count = OverlayPixelCount(histogram); - EXPECT_GT(histogram[kParentBackgroundColor], 0u); - EXPECT_GT(overlay_pixel_count, 0u); - EXPECT_GT(histogram[kChildBackgroundColor], 0u); - EXPECT_GT(histogram[kParentBackgroundColor], - histogram[kChildBackgroundColor]); - EXPECT_GT(overlay_pixel_count, histogram[kChildBackgroundColor]); - })); -} - -TEST_F(FlutterScenicEmbedderTests, HittestEmbeddingWithOverlay) { - RunAppWithArgs(kParentViewUrl, {"--showOverlay"}); - - // Take screenshot until we see the child-view2's embedded color. - ASSERT_TRUE(TakeScreenshotUntil(kChildBackgroundColor)); - - // Tap the center of child view2. - InjectInput(); - - // Take screenshot until we see the child-view2's tapped color. - ASSERT_TRUE(TakeScreenshotUntil( - kChildTappedColor, [](scenic::Screenshot screenshot, - std::map histogram) { - // Expect parent, overlay and child background colors. - // With parent color > child color and overlay color > child color. - const size_t overlay_pixel_count = OverlayPixelCount(histogram); - EXPECT_GT(histogram[kParentBackgroundColor], 0u); - EXPECT_GT(overlay_pixel_count, 0u); - EXPECT_EQ(histogram[kChildBackgroundColor], 0u); - EXPECT_GT(histogram[kChildTappedColor], 0u); - EXPECT_GT(histogram[kParentBackgroundColor], - histogram[kChildTappedColor]); - EXPECT_GT(overlay_pixel_count, histogram[kChildTappedColor]); - })); -} - -} // namespace flutter_embedder_test2 diff --git a/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/flutter-embedder-test2.h b/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/flutter-embedder-test2.h deleted file mode 100644 index 1a8cbf54e4b07..0000000000000 --- a/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/flutter-embedder-test2.h +++ /dev/null @@ -1,234 +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. - -#ifndef SRC_UI_TESTS_INTEGRATION_FLUTTER_TESTS_EMBEDDER_flutter_embedder_test2_H_ -#define SRC_UI_TESTS_INTEGRATION_FLUTTER_TESTS_EMBEDDER_flutter_embedder_test2_H_ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "flutter/fml/logging.h" - -#include - -#include "src/lib/ui/base_view/embedded_view_utils.h" -#include "src/ui/testing/views/color.h" -#include "src/ui/testing/views/embedder_view.h" - -namespace flutter_embedder_test2 { - -/// Defines a list of services that are injected into the test environment. -/// Unlike the injected-services in CMX which are injected per test package, -/// these are injected per test and result in a more hermetic test environment. -const std::vector> GetInjectedServices(); - -// Timeout when waiting on Scenic API calls like |GetDisplayInfo|. -constexpr zx::duration kCallTimeout = zx::sec(5); -// Timeout for Scenic's |TakeScreenshot| FIDL call. -constexpr zx::duration kScreenshotTimeout = zx::sec(10); -// Timeout to fail the test if it goes beyond this duration. -constexpr zx::duration kTestTimeout = zx::min(1); - -class FlutterScenicEmbedderTests : public sys::testing::TestWithEnvironment, - public ::testing::Test { - public: - // |testing::Test| - void SetUp() override { - Test::SetUp(); - - // Create test-specific launchable services. - auto services = TestWithEnvironment::CreateServices(); - for (const auto& service_info : GetInjectedServices()) { - zx_status_t status = services->AddServiceWithLaunchInfo( - {.url = service_info.second}, service_info.first); - FML_CHECK(status == ZX_OK) - << "Failed to add service " << service_info.first; - } - - environment_ = CreateNewEnclosingEnvironment( - "flutter-embedder-test2s", std::move(services), - {.inherit_parent_services = true}); - WaitForEnclosingEnvToStart(environment()); - - FML_VLOG(fml::LOG_INFO) << "Created test environment."; - - // Connects to scenic lifecycle controller in order to shutdown scenic at - // the end of the test. This ensures the correct ordering of shutdown under - // CFv1: first scenic, then the fake display controller. - // - // TODO(fxbug.dev/82655): Remove this after migrating to RealmBuilder. - environment_->ConnectToService( - scenic_lifecycle_controller_.NewRequest()); - - environment_->ConnectToService(scenic_.NewRequest()); - scenic_.set_error_handler([](zx_status_t status) { - FAIL() << "Lost connection to Scenic: " << zx_status_get_string(status); - }); - - // Post a "just in case" quit task, if the test hangs. - async::PostDelayedTask( - dispatcher(), - [] { - FML_LOG(FATAL) - << "\n\n>> Test did not complete in time, terminating. <<\n\n"; - }, - kTestTimeout); - } - - // |testing::Test| - void TearDown() override { - // Avoid spurious errors since we are about to kill scenic. - // - // TODO(fxbug.dev/82655): Remove this after migrating to RealmBuilder. - scenic_.set_error_handler(nullptr); - - zx_status_t terminate_status = scenic_lifecycle_controller_->Terminate(); - FML_CHECK(terminate_status == ZX_OK) - << "Failed to terminate Scenic with status: " - << zx_status_get_string(terminate_status); - } - - sys::testing::EnclosingEnvironment* environment() { - return environment_.get(); - } - - fuchsia::ui::views::ViewToken CreatePresentationViewToken() { - auto [view_token, view_holder_token] = scenic::ViewTokenPair::New(); - - auto presenter = - environment()->ConnectToService(); - presenter.set_error_handler([](zx_status_t status) { - FAIL() << "presenter: " << zx_status_get_string(status); - }); - presenter->PresentView(std::move(view_holder_token), nullptr); - - return std::move(view_token); - } - - void RunAppWithArgs(const std::string& component_url, - const std::vector& component_args = {}) { - scenic::EmbeddedViewInfo flutter_runner = - scenic::LaunchComponentAndCreateView(environment()->launcher_ptr(), - component_url, component_args); - flutter_runner.controller.events().OnTerminated = [](auto...) { FAIL(); }; - - // Present the view. - embedder_view_.emplace(scenic::ViewContext{ - .session_and_listener_request = - scenic::CreateScenicSessionPtrAndListenerRequest(scenic_.get()), - .view_token = CreatePresentationViewToken(), - }); - - // Embed the view. - bool is_rendering = false; - embedder_view_->EmbedView( - std::move(flutter_runner), - [&is_rendering](fuchsia::ui::gfx::ViewState view_state) { - is_rendering = view_state.is_rendering; - }); - RunLoopUntil([&is_rendering] { return is_rendering; }); - FML_LOG(INFO) << "Launched component: " << component_url; - } - - scenic::Screenshot TakeScreenshot() { - FML_LOG(INFO) << "Taking screenshot... "; - fuchsia::ui::scenic::ScreenshotData screenshot_out; - scenic_->TakeScreenshot( - [this, &screenshot_out](fuchsia::ui::scenic::ScreenshotData screenshot, - bool status) { - EXPECT_TRUE(status) << "Failed to take screenshot"; - screenshot_out = std::move(screenshot); - QuitLoop(); - }); - EXPECT_FALSE(RunLoopWithTimeout(kScreenshotTimeout)) - << "Timed out waiting for screenshot."; - FML_LOG(INFO) << "Screenshot captured."; - - return scenic::Screenshot(screenshot_out); - } - - bool TakeScreenshotUntil( - scenic::Color color, - fit::function)> - callback = nullptr, - zx::duration timeout = kTestTimeout) { - return RunLoopWithTimeoutOrUntil( - [this, &callback, &color] { - auto screenshot = TakeScreenshot(); - auto histogram = screenshot.Histogram(); - - bool color_found = histogram[color] > 0; - if (color_found && callback != nullptr) { - callback(std::move(screenshot), std::move(histogram)); - } - return color_found; - }, - timeout); - } - - // Inject directly into Root Presenter, using fuchsia.ui.input FIDLs. - void InjectInput() { - using fuchsia::ui::input::InputReport; - // Device parameters - auto parameters = fuchsia::ui::input::TouchscreenDescriptor::New(); - *parameters = {.x = {.range = {.min = -1000, .max = 1000}}, - .y = {.range = {.min = -1000, .max = 1000}}, - .max_finger_id = 10}; - - FML_LOG(INFO) << "Injecting input... "; - // Register it against Root Presenter. - fuchsia::ui::input::DeviceDescriptor device{.touchscreen = - std::move(parameters)}; - auto registry = - environment() - ->ConnectToService(); - fuchsia::ui::input::InputDevicePtr connection; - registry->RegisterDevice(std::move(device), connection.NewRequest()); - - { - // Inject one input report, then a conclusion (empty) report. - auto touch = fuchsia::ui::input::TouchscreenReport::New(); - *touch = { - .touches = {{.finger_id = 1, .x = 0, .y = 0}}}; // center of display - InputReport report{ - .event_time = static_cast(zx::clock::get_monotonic().get()), - .touchscreen = std::move(touch)}; - connection->DispatchReport(std::move(report)); - } - - { - auto touch = fuchsia::ui::input::TouchscreenReport::New(); - InputReport report{ - .event_time = static_cast(zx::clock::get_monotonic().get()), - .touchscreen = std::move(touch)}; - connection->DispatchReport(std::move(report)); - } - FML_LOG(INFO) << "Input dispatched."; - } - - private: - const std::unique_ptr component_context_; - std::unique_ptr environment_; - - fuchsia::ui::lifecycle::LifecycleControllerSyncPtr - scenic_lifecycle_controller_; - fuchsia::ui::scenic::ScenicPtr scenic_; - - // Wrapped in optional since the view is not created until the middle of SetUp - std::optional embedder_view_; -}; - -} // namespace flutter_embedder_test2 - -#endif // SRC_UI_TESTS_INTEGRATION_FLUTTER_TESTS_EMBEDDER_flutter_embedder_test2_H_ diff --git a/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/meta/flutter-embedder-test2.cmx b/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/meta/flutter-embedder-test2.cmx deleted file mode 100644 index 0e21e6dc387a4..0000000000000 --- a/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/meta/flutter-embedder-test2.cmx +++ /dev/null @@ -1,25 +0,0 @@ -{ - "facets": { - "fuchsia.test": { - "system-services": [ - "fuchsia.scheduler.ProfileProvider", - "fuchsia.sysmem.Allocator", - "fuchsia.vulkan.loader.Loader" - ] - } - }, - "program": { - "binary": "bin/app" - }, - "sandbox": { - "services": [ - "fuchsia.logger.LogSink", - "fuchsia.sys.Environment", - "fuchsia.sys.Launcher", - "fuchsia.sys.Loader", - "fuchsia.sysmem.Allocator", - "fuchsia.tracing.provider.Registry", - "fuchsia.vulkan.loader.Loader" - ] - } -} \ No newline at end of file diff --git a/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/parent-view2/BUILD.gn b/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/parent-view2/BUILD.gn deleted file mode 100644 index 0b2ec390b39ce..0000000000000 --- a/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/parent-view2/BUILD.gn +++ /dev/null @@ -1,46 +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. - -import("//build/fuchsia/sdk.gni") -import("//flutter/tools/fuchsia/dart/dart_library.gni") -import("//flutter/tools/fuchsia/flutter/flutter_component.gni") -import("//flutter/tools/fuchsia/gn-sdk/package.gni") - -dart_library("parent-view2_dart_library") { - package_name = "parent-view2" - - source_dir = "." - sources = [ "parent_view2.dart" ] - - deps = [ - "//flutter/shell/platform/fuchsia/dart:args", - "//flutter/shell/platform/fuchsia/dart:vector_math", - "//flutter/tools/fuchsia/dart:fuchsia_services", - "//flutter/tools/fuchsia/dart:zircon", - "//flutter/tools/fuchsia/fidl:fuchsia.sys", - "//flutter/tools/fuchsia/fidl:fuchsia.ui.app", - "//flutter/tools/fuchsia/fidl:fuchsia.ui.views", - ] -} - -flutter_component("parent-view2_flutter_component") { - main_package = "parent-view2" - component_name = "parent-view2" - main_dart = "parent_view2.dart" - manifest = rebase_path("meta/parent-view2.cmx") - deps = [ ":parent-view2_dart_library" ] -} - -# TODO(richkadel): The target name is set differently compared to fuchsia.git's flutter_app(). -# Unlike in fuchsia.git's version of fuchsia_component, the Fuchsia GN SDK -# version passes the component name to fuchsia_component via it's target_name only. -# GN SDK's fuchsia_component doesn't have a `component_name` argument! So I'm forced to set -# the component name via "target_name". This is a problem in fuchsia_package, which uses -# the target_name to name the fuchsia_pm_tool target, creating duplicate target IDs! -# So I have to change the fuchsia_package name to something that is NOT the component name, -# and then set the package_name (which fuchsia_package does support). -fuchsia_package("package") { - package_name = "parent-view2" - deps = [ ":parent-view2_flutter_component" ] -} diff --git a/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/parent-view2/meta/parent-view2.cmx b/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/parent-view2/meta/parent-view2.cmx deleted file mode 100644 index b839053be4c13..0000000000000 --- a/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/parent-view2/meta/parent-view2.cmx +++ /dev/null @@ -1,14 +0,0 @@ -{ - "program": { - "data": "data/parent-view2" - }, - "sandbox": { - "services": [ - "fuchsia.fonts.Provider", - "fuchsia.sys.Environment", - "fuchsia.sys.Launcher", - "fuchsia.ui.input.ImeService", - "fuchsia.ui.scenic.Scenic" - ] - } -} diff --git a/shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing/src/lib/ui/base_view/BUILD.gn b/shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing/src/lib/ui/base_view/BUILD.gn deleted file mode 100644 index 36d862c7136dd..0000000000000 --- a/shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing/src/lib/ui/base_view/BUILD.gn +++ /dev/null @@ -1,30 +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. - -import("//build/fuchsia/sdk.gni") - -source_set("base_view") { - sources = [ - "base_view.cc", - "base_view.h", - "embedded_view_utils.cc", - "embedded_view_utils.h", - "math.h", - ] - - include_dirs = [ "//flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing" ] - - public_deps = [ - "$fuchsia_sdk_root/fidl:fuchsia.sys", - "$fuchsia_sdk_root/fidl:fuchsia.ui.app", - "$fuchsia_sdk_root/fidl:fuchsia.ui.gfx", - "$fuchsia_sdk_root/fidl:fuchsia.ui.input", - "$fuchsia_sdk_root/fidl:fuchsia.ui.views", - "$fuchsia_sdk_root/pkg:scenic_cpp", - "$fuchsia_sdk_root/pkg:sys_cpp", - "//flutter/fml", - ] - - deps = [ "$fuchsia_sdk_root/pkg:trace" ] -} diff --git a/shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing/src/lib/ui/base_view/base_view.cc b/shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing/src/lib/ui/base_view/base_view.cc deleted file mode 100644 index e4756f0bb8c7e..0000000000000 --- a/shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing/src/lib/ui/base_view/base_view.cc +++ /dev/null @@ -1,240 +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. - -#include "src/lib/ui/base_view/base_view.h" - -#include -#include -#include -#include -#include "flutter/fml/logging.h" - -namespace scenic { - -BaseView::BaseView(ViewContext context, const std::string& debug_name) - : component_context_(context.component_context), - listener_binding_(this, - std::move(context.session_and_listener_request.second)), - session_(std::move(context.session_and_listener_request.first)), - root_node_(&session_), - ime_client_(this), - enable_ime_(context.enable_ime) { - if (!context.view_ref_pair) { - context.view_ref_pair = scenic::ViewRefPair::New(); - } - view_.emplace(&session_, std::move(context.view_token), - std::move(context.view_ref_pair->control_ref), - std::move(context.view_ref_pair->view_ref), debug_name); - FML_DCHECK(view_); - - session_.SetDebugName(debug_name); - - // Listen for metrics events on our top node. - root_node_.SetEventMask(fuchsia::ui::gfx::kMetricsEventMask); - view_->AddChild(root_node_); - - if (enable_ime_) { - ime_manager_ = - component_context_->svc()->Connect(); - - ime_.set_error_handler([](zx_status_t status) { - FML_LOG(ERROR) << "Interface error on: Input Method Editor " - << zx_status_get_string(status); - }); - ime_manager_.set_error_handler([](zx_status_t status) { - FML_LOG(ERROR) << "Interface error on: Text Sync Service " - << zx_status_get_string(status); - }); - } - - // We must immediately invalidate the scene, otherwise we wouldn't ever hook - // the View up to the ViewHolder. An alternative would be to require - // subclasses to call an Init() method to set up the initial connection. - InvalidateScene(); -} - -void BaseView::SetReleaseHandler(fit::function callback) { - listener_binding_.set_error_handler(std::move(callback)); -} - -void BaseView::InvalidateScene(PresentCallback present_callback) { - TRACE_DURATION("view", "BaseView::InvalidateScene"); - if (present_callback) { - callbacks_for_next_present_.push_back(std::move(present_callback)); - } - if (invalidate_pending_) - return; - - invalidate_pending_ = true; - - // Present the scene ASAP. Pass in the last presentation time; otherwise, if - // presentation_time argument is less than the previous time passed to - // PresentScene, the Session will be closed. - // (We cannot use the current time because the last requested presentation - // time, |last_presentation_time_|, could still be in the future. This is - // because Session.Present() returns after it _begins_ preparing the given - // frame, not after it is presented.) - if (!present_pending_) - PresentScene(last_presentation_time_); -} - -void BaseView::PresentScene() { - PresentScene(last_presentation_time_); -} - -void BaseView::OnScenicEvent(std::vector events) { - TRACE_DURATION("view", "BaseView::OnScenicEvent"); - for (auto& event : events) { - switch (event.Which()) { - case ::fuchsia::ui::scenic::Event::Tag::kGfx: - switch (event.gfx().Which()) { - case ::fuchsia::ui::gfx::Event::Tag::kViewPropertiesChanged: { - auto& evt = event.gfx().view_properties_changed(); - FML_DCHECK(view_->id() == evt.view_id); - auto old_props = view_properties_; - view_properties_ = evt.properties; - - ::fuchsia::ui::gfx::BoundingBox layout_box = - ViewPropertiesLayoutBox(view_properties_); - - logical_size_ = scenic::Max(layout_box.max - layout_box.min, 0.f); - physical_size_.x = logical_size_.x * metrics_.scale_x; - physical_size_.y = logical_size_.y * metrics_.scale_y; - physical_size_.z = logical_size_.z * metrics_.scale_z; - - OnPropertiesChanged(std::move(old_props)); - InvalidateScene(); - break; - } - case fuchsia::ui::gfx::Event::Tag::kMetrics: { - auto& evt = event.gfx().metrics(); - if (evt.node_id == root_node_.id()) { - auto old_metrics = metrics_; - metrics_ = std::move(evt.metrics); - physical_size_.x = logical_size_.x * metrics_.scale_x; - physical_size_.y = logical_size_.y * metrics_.scale_y; - physical_size_.z = logical_size_.z * metrics_.scale_z; - OnMetricsChanged(std::move(old_metrics)); - InvalidateScene(); - } - break; - } - default: { - OnScenicEvent(std::move(event)); - } - } - break; - case ::fuchsia::ui::scenic::Event::Tag::kInput: { - if (event.input().Which() == - fuchsia::ui::input::InputEvent::Tag::kFocus && - enable_ime_) { - OnHandleFocusEvent(event.input().focus()); - } - OnInputEvent(std::move(event.input())); - break; - } - case ::fuchsia::ui::scenic::Event::Tag::kUnhandled: { - OnUnhandledCommand(std::move(event.unhandled())); - break; - } - default: { - OnScenicEvent(std::move(event)); - } - } - } -} - -void BaseView::PresentScene(zx_time_t presentation_time) { - TRACE_DURATION("view", "BaseView::PresentScene"); - // TODO(fxbug.dev/24406): Remove this when BaseView::PresentScene() is - // deprecated, see fxbug.dev/24573. - if (present_pending_) - return; - - present_pending_ = true; - - // Keep track of the most recent presentation time we've passed to - // Session.Present(), for use in InvalidateScene(). - last_presentation_time_ = presentation_time; - - TRACE_FLOW_BEGIN("gfx", "Session::Present", session_present_count_); - ++session_present_count_; - - session()->Present( - presentation_time, - [this, present_callbacks = std::move(callbacks_for_next_present_)]( - fuchsia::images::PresentationInfo info) mutable { - TRACE_DURATION("view", "BaseView::PresentationCallback"); - TRACE_FLOW_END("gfx", "present_callback", info.presentation_time); - - FML_DCHECK(present_pending_); - - zx_time_t next_presentation_time = - info.presentation_time + info.presentation_interval; - - bool present_needed = false; - if (invalidate_pending_) { - invalidate_pending_ = false; - OnSceneInvalidated(std::move(info)); - present_needed = true; - } - - for (auto& callback : present_callbacks) { - callback(info); - } - - present_pending_ = false; - if (present_needed) - PresentScene(next_presentation_time); - }); - callbacks_for_next_present_.clear(); -} - -// |fuchsia::ui::input::InputMethodEditorClient| -void BaseView::DidUpdateState( - fuchsia::ui::input::TextInputState state, - std::unique_ptr input_event) { - if (input_event) { - const fuchsia::ui::input::InputEvent& input = *input_event; - fuchsia::ui::input::InputEvent input_event_copy; - fidl::Clone(input, &input_event_copy); - OnInputEvent(std::move(input_event_copy)); - } -} - -// |fuchsia::ui::input::InputMethodEditorClient| -void BaseView::OnAction(fuchsia::ui::input::InputMethodAction action) {} - -bool BaseView::OnHandleFocusEvent(const fuchsia::ui::input::FocusEvent& focus) { - if (focus.focused) { - ActivateIme(); - return true; - } else if (!focus.focused) { - DeactivateIme(); - return true; - } - return false; -} - -void BaseView::ActivateIme() { - ime_manager_->GetInputMethodEditor( - fuchsia::ui::input::KeyboardType::TEXT, // keyboard type - fuchsia::ui::input::InputMethodAction::DONE, // input method action - fuchsia::ui::input::TextInputState{}, // initial state - ime_client_.NewBinding(), // client - ime_.NewRequest() // editor - ); -} - -void BaseView::DeactivateIme() { - if (ime_) { - ime_manager_->HideKeyboard(); - ime_ = nullptr; - } - if (ime_client_.is_bound()) { - ime_client_.Unbind(); - } -} - -} // namespace scenic diff --git a/shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing/src/lib/ui/base_view/base_view.h b/shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing/src/lib/ui/base_view/base_view.h deleted file mode 100644 index 6ad0257ca5547..0000000000000 --- a/shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing/src/lib/ui/base_view/base_view.h +++ /dev/null @@ -1,210 +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. - -#ifndef SRC_LIB_UI_BASE_VIEW_BASE_VIEW_H_ -#define SRC_LIB_UI_BASE_VIEW_BASE_VIEW_H_ - -#include -#include -#include -#include -#include -#include -#include - -#include "src/lib/ui/base_view/embedded_view_utils.h" -#include "src/lib/ui/base_view/math.h" - -namespace scenic { - -// Parameters for creating a BaseView. -struct ViewContext { - scenic::SessionPtrAndListenerRequest session_and_listener_request; - fuchsia::ui::views::ViewToken view_token; - std::optional view_ref_pair; - sys::ComponentContext* component_context; - bool enable_ime = false; -}; - -// Abstract base implementation of a view for simple applications. -// Subclasses must handle layout and provide content for the scene by -// overriding the virtual methods defined in this class. -// -// It is not necessary to use this class to implement all Views. -// This class is merely intended to make the simple apps easier to write. -class BaseView : private fuchsia::ui::scenic::SessionListener, - private fuchsia::ui::input::InputMethodEditorClient { - public: - using PresentCallback = - fit::function; - - // Subclasses are typically created by ViewProviderService::CreateView(), - // which provides the necessary args to pass down to this base class. - BaseView(ViewContext context, const std::string& debug_name); - - BaseView(const BaseView&) = delete; - - // |root_node| is the node directly under our View; i.e. it's the top-most - // node within the tree under our View. Use it to attach any resources for - // your UI. - scenic::EntityNode& root_node() { return root_node_; } - Session* session() { return &session_; } - sys::ComponentContext* component_context() { return component_context_; } - - fuchsia::ui::gfx::ViewProperties view_properties() const { - return view_properties_; - } - - // Returns true if the view has a non-empty size in logical pixels. - bool has_logical_size() const { - auto& sz = logical_size(); - return sz.x > 0.f && sz.y > 0.f && sz.z > 0.f; - } - - // Gets the size of the view in logical pixels. - // This value is zero until the view receives a layout from its parent. - const fuchsia::ui::gfx::vec3& logical_size() const { return logical_size_; } - - // Returns true if the view has a non-empty size in physical pixels. - bool has_physical_size() const { - auto& sz = physical_size(); - return sz.x > 0.f && sz.y > 0.f && sz.z > 0.f; - } - - // Gets the size of the view in physical pixels. - // This value is zero until the view receives a layout from its parent - // and metrics from its session. - const fuchsia::ui::gfx::vec3& physical_size() const { return physical_size_; } - - // Returns true if the view has received metrics from its session. - bool has_metrics() const { - return metrics_.scale_x > 0.f && metrics_.scale_y > 0.f && - metrics_.scale_z > 0.f; - } - - // Gets the view's metrics. - // This value is zero until the view receives metrics from its session. - const fuchsia::ui::gfx::Metrics& metrics() const { return metrics_; } - - // Sets a callback which is invoked when the view's owner releases the - // view causing the view manager to unregister it. - // - // This should be used to implement cleanup policies to release resources - // associated with the view (including the object itself). - void SetReleaseHandler(fit::function callback); - - // Invalidates the scene, causing |OnSceneInvalidated()| to be invoked - // during the next frame. When the Present() callback corresponding to this - // invalidate is invoked, the optional |present_callback| will also be - // invoked. - void InvalidateScene(PresentCallback present_callback = nullptr); - - // Called when it's time for the view to update its scene contents due to - // invalidation. The new contents are presented once this function returns. - // - // The default implementation does nothing. - virtual void OnSceneInvalidated( - fuchsia::images::PresentationInfo presentation_info) {} - - // Called when the view's properties have changed. - // - // The subclass should compare the old and new properties and make note of - // whether these property changes will affect the layout or content of - // the view then update accordingly. - // - // The default implementation does nothing. - virtual void OnPropertiesChanged( - fuchsia::ui::gfx::ViewProperties old_properties) {} - - // Called when the view's metrics have changed. - // - // The subclass should compare the old and new metrics and make note of - // whether this change will affect the layout or content of the view then - // update accordingly. - // - // The default implementation does nothing. - virtual void OnMetricsChanged(fuchsia::ui::gfx::Metrics old_metrics){}; - - // Called to handle an input event. - // - // The default implementation does nothing. - virtual void OnInputEvent(fuchsia::ui::input::InputEvent event) {} - - // Called when a command sent by the client was not handled by Scenic. - // - // The default implementation does nothing. - virtual void OnUnhandledCommand(fuchsia::ui::scenic::Command unhandled) {} - - // Called when an event that is not handled directly by BaseView is received. - // For example, BaseView handles fuchsia::ui::gfx::ViewPropertiesChangedEvent, - // and notifies the subclass via OnPropertiesChanged(); not all events are - // handled in this way. - // - // The default implementation does nothing. - virtual void OnScenicEvent(fuchsia::ui::scenic::Event) {} - - protected: - // An alternative way to update the scene. Provide a faster way to cause a - // present in comparison to InvalidateScene(). Caller should update the - // scene contents before calling this method. - void PresentScene(); - - private: - // |scenic::SessionListener| - // - // Iterates over the received events and either handles them in a sensible way - // (e.g. fuchsia::ui::gfx::ViewPropertiesChangedEvent is handled by invoking - // the virtual method OnPropertiesChanged()), or delegates handling to the - // subclass via the single-event version of OnEvent() above. - // - // Subclasses should not override this. - void OnScenicEvent(std::vector events) override; - - // |fuchsia::ui::input::InputMethodEditorClient| - void DidUpdateState( - fuchsia::ui::input::TextInputState state, - std::unique_ptr event) override; - - // |fuchsia::ui::input::InputMethodEditorClient| - void OnAction(fuchsia::ui::input::InputMethodAction action) override; - - void PresentScene(zx_time_t presentation_time); - - // Handles focus event when IME is enabled. This event is used to activate - // or deactivate the IME client. - bool OnHandleFocusEvent(const fuchsia::ui::input::FocusEvent& focus); - - // Gets a new input method editor from the IME manager. - void ActivateIme(); - - // Detaches the input method editor connection, ending the edit session and - // closing the onscreen keyboard. - void DeactivateIme(); - - sys::ComponentContext* const component_context_; - fidl::Binding listener_binding_; - Session session_; - std::optional view_; - scenic::EntityNode root_node_; - - fidl::Binding ime_client_; - fuchsia::ui::input::InputMethodEditorPtr ime_; - fuchsia::ui::input::ImeServicePtr ime_manager_; - - fuchsia::ui::gfx::vec3 logical_size_; - fuchsia::ui::gfx::vec3 physical_size_; - fuchsia::ui::gfx::ViewProperties view_properties_; - fuchsia::ui::gfx::Metrics metrics_; - - zx_time_t last_presentation_time_ = 0; - size_t session_present_count_ = 0; - bool invalidate_pending_ = false; - std::vector callbacks_for_next_present_; - bool present_pending_ = false; - bool enable_ime_ = false; -}; - -} // namespace scenic - -#endif // SRC_LIB_UI_BASE_VIEW_BASE_VIEW_H_ diff --git a/shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing/src/lib/ui/base_view/embedded_view_utils.cc b/shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing/src/lib/ui/base_view/embedded_view_utils.cc deleted file mode 100644 index 6235b8d29c1e3..0000000000000 --- a/shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing/src/lib/ui/base_view/embedded_view_utils.cc +++ /dev/null @@ -1,48 +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. - -#include "src/lib/ui/base_view/embedded_view_utils.h" - -#include -#include -#include "flutter/fml/logging.h" - -namespace scenic { - -EmbeddedViewInfo LaunchComponentAndCreateView( - const fuchsia::sys::LauncherPtr& launcher, - const std::string& component_url, - const std::vector& component_args) { - FML_DCHECK(launcher); - - EmbeddedViewInfo info; - - // Configure the information to launch the component with. - fuchsia::sys::LaunchInfo launch_info; - info.app_services = - sys::ServiceDirectory::CreateWithRequest(&launch_info.directory_request); - launch_info.url = component_url; - launch_info.arguments = fidl::VectorPtr( - std::vector(component_args.begin(), component_args.end())); - - launcher->CreateComponent(std::move(launch_info), - info.controller.NewRequest()); - - info.view_provider = - info.app_services->Connect(); - - auto [view_token, view_holder_token] = scenic::ViewTokenPair::New(); - info.view_holder_token = std::move(view_holder_token); - - auto [view_ref_control, view_ref] = scenic::ViewRefPair::New(); - fidl::Clone(view_ref, &info.view_ref); - - info.view_provider->CreateViewWithViewRef(std::move(view_token.value), - std::move(view_ref_control), - std::move(view_ref)); - - return info; -} - -} // namespace scenic diff --git a/shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing/src/lib/ui/base_view/embedded_view_utils.h b/shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing/src/lib/ui/base_view/embedded_view_utils.h deleted file mode 100644 index ff087d4a03c6e..0000000000000 --- a/shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing/src/lib/ui/base_view/embedded_view_utils.h +++ /dev/null @@ -1,58 +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. - -#ifndef SRC_LIB_UI_BASE_VIEW_EMBEDDED_VIEW_UTILS_H_ -#define SRC_LIB_UI_BASE_VIEW_EMBEDDED_VIEW_UTILS_H_ - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace scenic { - -// Serves as the return value for LaunchAppAndCreateView(), below. -struct EmbeddedViewInfo { - // Controls the launched app. The app will be destroyed if this connection - // is closed. - fuchsia::sys::ComponentControllerPtr controller; - - // Services provided by the launched app. Must not be destroyed - // immediately, otherwise the |view_provider| connection may not be - // established. - std::shared_ptr app_services; - - // ViewProvider service obtained from the app via |app_services|. Must not - // be destroyed immediately, otherwise the call to CreateView() might not be - // processed. - fuchsia::ui::app::ViewProviderPtr view_provider; - - // A token that can be used to create a ViewHolder; the corresponding token - // was provided to |view_provider| via ViewProvider.CreateView(). The - // launched app is expected to create a View, which will be connected to the - // ViewHolder created with this token. - fuchsia::ui::views::ViewHolderToken view_holder_token; - - // The ViewRef of the embedded View. - fuchsia::ui::views::ViewRef view_ref; -}; - -// Launch a component and connect to its ViewProvider service, passing it the -// necessary information to attach itself as a child view2. Populates the -// returned EmbeddedViewInfo, which the caller can use to embed the child. -// For example, an interface to a ViewProvider is obtained, a pair of -// zx::eventpairs is created, CreateView is called, etc. This encapsulates -// the boilerplate the client would otherwise write themselves. -EmbeddedViewInfo LaunchComponentAndCreateView( - const fuchsia::sys::LauncherPtr& launcher, - const std::string& component_url, - const std::vector& component_args = {}); - -} // namespace scenic - -#endif // SRC_LIB_UI_BASE_VIEW_EMBEDDED_VIEW_UTILS_H_ diff --git a/shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing/src/lib/ui/base_view/math.h b/shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing/src/lib/ui/base_view/math.h deleted file mode 100644 index 9cf99ce8276bd..0000000000000 --- a/shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing/src/lib/ui/base_view/math.h +++ /dev/null @@ -1,92 +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. - -#ifndef SRC_LIB_UI_BASE_VIEW_MATH_H_ -#define SRC_LIB_UI_BASE_VIEW_MATH_H_ - -#include - -namespace scenic { - -// Return a vec3 consisting of the component-wise sum of the two arguments. -inline fuchsia::ui::gfx::vec3 operator+(const fuchsia::ui::gfx::vec3& a, - const fuchsia::ui::gfx::vec3& b) { - return {.x = a.x + b.x, .y = a.y + b.y, .z = a.z + b.z}; -} - -// Return a vec3 consisting of the component-wise difference of the two args. -inline fuchsia::ui::gfx::vec3 operator-(const fuchsia::ui::gfx::vec3& a, - const fuchsia::ui::gfx::vec3& b) { - return {.x = a.x - b.x, .y = a.y - b.y, .z = a.z - b.z}; -} - -// Return true if |point| is contained by |box|, including when it is on the -// box boundary, and false otherwise. -inline bool ContainsPoint(const fuchsia::ui::gfx::BoundingBox& box, - const fuchsia::ui::gfx::vec3& point) { - return point.x >= box.min.x && point.y >= box.min.y && point.z >= box.min.z && - point.x <= box.max.x && point.y <= box.max.y && point.z <= box.max.z; -} - -// Similar to fuchsia::ui::gfx::ViewProperties: adds the inset to box.min, and -// subtracts it from box.max. -inline fuchsia::ui::gfx::BoundingBox InsetBy( - const fuchsia::ui::gfx::BoundingBox& box, - const fuchsia::ui::gfx::vec3& inset) { - return {.min = box.min + inset, .max = box.max - inset}; -} - -// Similar to fuchsia::ui::gfx::ViewProperties: adds the inset to box.min, and -// subtracts it from box.max. -inline fuchsia::ui::gfx::BoundingBox InsetBy( - const fuchsia::ui::gfx::BoundingBox& box, - const fuchsia::ui::gfx::vec3& inset_from_min, - const fuchsia::ui::gfx::vec3& inset_from_max) { - return {.min = box.min + inset_from_min, .max = box.max - inset_from_max}; -} - -// Inset the view properties' outer box by its insets. -inline fuchsia::ui::gfx::BoundingBox ViewPropertiesLayoutBox( - const fuchsia::ui::gfx::ViewProperties& view_properties) { - return InsetBy(view_properties.bounding_box, view_properties.inset_from_min, - view_properties.inset_from_max); -} - -// Return a vec3 consisting of the maximum x/y/z from the two arguments. -inline fuchsia::ui::gfx::vec3 Max(const fuchsia::ui::gfx::vec3& a, - const fuchsia::ui::gfx::vec3& b) { - return {.x = std::max(a.x, b.x), - .y = std::max(a.y, b.y), - .z = std::max(a.z, b.z)}; -} - -// Return a vec3 consisting of the maximum of the x/y/z components of |v|, -// compared with |min_val|. -inline fuchsia::ui::gfx::vec3 Max(const fuchsia::ui::gfx::vec3& v, - float min_val) { - return {.x = std::max(v.x, min_val), - .y = std::max(v.y, min_val), - .z = std::max(v.z, min_val)}; -} - -// Return a vec3 consisting of the minimum x/y/z from the two arguments. -inline fuchsia::ui::gfx::vec3 Min(const fuchsia::ui::gfx::vec3& a, - const fuchsia::ui::gfx::vec3& b) { - return {.x = std::min(a.x, b.x), - .y = std::min(a.y, b.y), - .z = std::min(a.z, b.z)}; -} - -// Return a vec3 consisting of the minimum of the x/y/z components of |v|, -// compared with |max_val|. -inline fuchsia::ui::gfx::vec3 Min(const fuchsia::ui::gfx::vec3& v, - float max_val) { - return {.x = std::min(v.x, max_val), - .y = std::min(v.y, max_val), - .z = std::min(v.z, max_val)}; -} - -} // namespace scenic - -#endif // SRC_LIB_UI_BASE_VIEW_MATH_H_ diff --git a/shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing/src/ui/testing/views/BUILD.gn b/shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing/src/ui/testing/views/BUILD.gn deleted file mode 100644 index f97e927c2dc77..0000000000000 --- a/shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing/src/ui/testing/views/BUILD.gn +++ /dev/null @@ -1,24 +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. - -import("//build/fuchsia/sdk.gni") - -source_set("views") { - testonly = true - - sources = [ - "color.cc", - "color.h", - "embedder_view.cc", - "embedder_view.h", - ] - - include_dirs = [ "//flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing" ] - - public_deps = [ - "$fuchsia_sdk_root/pkg:scenic_cpp", - "//flutter/fml", - "//flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing/src/lib/ui/base_view", - ] -} diff --git a/shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing/src/ui/testing/views/embedder_view.cc b/shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing/src/ui/testing/views/embedder_view.cc deleted file mode 100644 index 0d1128256bece..0000000000000 --- a/shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing/src/ui/testing/views/embedder_view.cc +++ /dev/null @@ -1,88 +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. - -#include - -#include "flutter/fml/logging.h" -#include "src/ui/testing/views/embedder_view.h" - -namespace scenic { - -EmbedderView::EmbedderView(ViewContext context, const std::string& debug_name) - : binding_(this, std::move(context.session_and_listener_request.second)), - session_(std::move(context.session_and_listener_request.first)), - view_(&session_, std::move(context.view_token), debug_name), - top_node_(&session_) { - binding_.set_error_handler([](zx_status_t status) { - FML_LOG(FATAL) << "Session listener binding: " - << zx_status_get_string(status); - }); - view_.AddChild(top_node_); - // Call |Session::Present| in order to flush events having to do with - // creation of |view_| and |top_node_|. - session_.Present(0, [](auto) {}); -} - -// Sets the EmbeddedViewInfo and attaches the embedded View to the scene. Any -// callbacks for the embedded View's ViewState are delivered to the supplied -// callback. -void EmbedderView::EmbedView(EmbeddedViewInfo info, - std::function - view_state_changed_callback) { - // Only one EmbeddedView is currently supported. - FML_CHECK(!embedded_view_); - embedded_view_ = std::make_unique( - std::move(info), &session_, std::move(view_state_changed_callback)); - - // Attach the embedded view to the scene. - top_node_.Attach(embedded_view_->view_holder); - - // Call |Session::Present| to apply the embedded view to the scene graph. - session_.Present(0, [](auto) {}); -} - -void EmbedderView::OnScenicEvent( - std::vector events) { - for (const auto& event : events) { - if (event.Which() == fuchsia::ui::scenic::Event::Tag::kGfx && - event.gfx().Which() == - fuchsia::ui::gfx::Event::Tag::kViewPropertiesChanged) { - const auto& evt = event.gfx().view_properties_changed(); - // Naively apply the parent's ViewProperties to any EmbeddedViews. - if (embedded_view_) { - embedded_view_->view_holder.SetViewProperties( - std::move(evt.properties)); - session_.Present(0, [](auto) {}); - } - } else if (event.Which() == fuchsia::ui::scenic::Event::Tag::kGfx && - event.gfx().Which() == - fuchsia::ui::gfx::Event::Tag::kViewStateChanged) { - const auto& evt = event.gfx().view_state_changed(); - if (embedded_view_ && - evt.view_holder_id == embedded_view_->view_holder.id()) { - // Clients of |EmbedderView| *must* set a view state changed - // callback. Failure to do so is a usage error. - FML_CHECK(embedded_view_->view_state_changed_callback); - embedded_view_->view_state_changed_callback(evt.state); - } - } - } -} - -void EmbedderView::OnScenicError(std::string error) { - FML_LOG(FATAL) << "OnScenicError: " << error; -} - -EmbedderView::EmbeddedView::EmbeddedView( - EmbeddedViewInfo info, - Session* session, - std::function view_state_callback, - const std::string& debug_name) - : embedded_info(std::move(info)), - view_holder(session, - std::move(embedded_info.view_holder_token), - debug_name + " ViewHolder"), - view_state_changed_callback(std::move(view_state_callback)) {} - -} // namespace scenic diff --git a/shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing/src/ui/testing/views/embedder_view.h b/shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing/src/ui/testing/views/embedder_view.h deleted file mode 100644 index 4514d0f850fd3..0000000000000 --- a/shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing/src/ui/testing/views/embedder_view.h +++ /dev/null @@ -1,59 +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. - -#ifndef SRC_UI_TESTING_VIEWS_EMBEDDER_VIEW_H_ -#define SRC_UI_TESTING_VIEWS_EMBEDDER_VIEW_H_ - -#include -#include -#include - -#include "src/lib/ui/base_view/base_view.h" - -namespace scenic { - -// This is a simplified |BaseView| that exposes view state events. -// -// See also lib/ui/base_view. -class EmbedderView : public fuchsia::ui::scenic::SessionListener { - public: - EmbedderView(ViewContext context, - const std::string& debug_name = "EmbedderView"); - - // Sets the EmbeddedViewInfo and attaches the embedded View to the scene. Any - // callbacks for the embedded View's ViewState are delivered to the supplied - // callback. - void EmbedView(EmbeddedViewInfo info, - std::function - view_state_changed_callback); - - private: - // |fuchsia::ui::scenic::SessionListener| - void OnScenicEvent(std::vector events) override; - // |fuchsia::ui::scenic::SessionListener| - void OnScenicError(std::string error) override; - - struct EmbeddedView { - EmbeddedView( - EmbeddedViewInfo info, - Session* session, - std::function view_state_callback, - const std::string& debug_name = "EmbedderView"); - - EmbeddedViewInfo embedded_info; - ViewHolder view_holder; - std::function - view_state_changed_callback; - }; - - fidl::Binding binding_; - Session session_; - View view_; - EntityNode top_node_; - std::optional embedded_view_properties_; - std::unique_ptr embedded_view_; -}; - -} // namespace scenic -#endif // SRC_UI_TESTING_VIEWS_EMBEDDER_VIEW_H_ diff --git a/shell/platform/fuchsia/flutter/platform_view.cc b/shell/platform/fuchsia/flutter/platform_view.cc index 6ae402b4ef163..85d671c1e773f 100644 --- a/shell/platform/fuchsia/flutter/platform_view.cc +++ b/shell/platform/fuchsia/flutter/platform_view.cc @@ -109,33 +109,31 @@ PlatformView::PlatformView( }); // Begin watching for pointer events. - if (is_flatland) { // TODO(fxbug.dev/85125): make unconditional - pointer_delegate_->WatchLoop([weak = weak_factory_.GetWeakPtr()]( - std::vector events) { - if (!weak) { - FML_LOG(WARNING) << "PlatformView use-after-free attempted. Ignoring."; - return; - } + pointer_delegate_->WatchLoop([weak = weak_factory_.GetWeakPtr()]( + std::vector events) { + if (!weak) { + FML_LOG(WARNING) << "PlatformView use-after-free attempted. Ignoring."; + return; + } - if (events.size() == 0) { - return; // No work, bounce out. - } + if (events.empty()) { + return; // No work, bounce out. + } - // If pixel ratio hasn't been set, use a default value of 1. - const float pixel_ratio = weak->view_pixel_ratio_.value_or(1.f); - auto packet = std::make_unique(events.size()); - for (size_t i = 0; i < events.size(); ++i) { - auto& event = events[i]; - // Translate logical to physical coordinates, as per - // flutter::PointerData contract. Done here because pixel ratio comes - // from the graphics API. - event.physical_x = event.physical_x * pixel_ratio; - event.physical_y = event.physical_y * pixel_ratio; - packet->SetPointerData(i, event); - } - weak->DispatchPointerDataPacket(std::move(packet)); - }); - } + // If pixel ratio hasn't been set, use a default value of 1. + const float pixel_ratio = weak->view_pixel_ratio_.value_or(1.f); + auto packet = std::make_unique(events.size()); + for (size_t i = 0; i < events.size(); ++i) { + auto& event = events[i]; + // Translate logical to physical coordinates, as per + // flutter::PointerData contract. Done here because pixel ratio comes + // from the graphics API. + event.physical_x = event.physical_x * pixel_ratio; + event.physical_y = event.physical_y * pixel_ratio; + packet->SetPointerData(i, event); + } + weak->DispatchPointerDataPacket(std::move(packet)); + }); // Configure the pointer injector delegate. pointer_injector_delegate_ = std::make_unique( diff --git a/shell/platform/fuchsia/flutter/platform_view_unittest.cc b/shell/platform/fuchsia/flutter/platform_view_unittest.cc index b39fdb60edde6..3d7eb7977f5c0 100644 --- a/shell/platform/fuchsia/flutter/platform_view_unittest.cc +++ b/shell/platform/fuchsia/flutter/platform_view_unittest.cc @@ -48,6 +48,9 @@ class MockExternalViewEmbedder : public flutter::ExternalViewEmbedder { std::vector GetCurrentCanvases() override { return std::vector(); } + std::vector GetCurrentBuilders() override { + return std::vector(); + } void CancelFrame() override {} void BeginFrame( @@ -63,7 +66,9 @@ class MockExternalViewEmbedder : public flutter::ExternalViewEmbedder { void PrerollCompositeEmbeddedView( int view_id, std::unique_ptr params) override {} - SkCanvas* CompositeEmbeddedView(int view_id) override { return nullptr; } + flutter::EmbedderPaintContext CompositeEmbeddedView(int view_id) override { + return {nullptr, nullptr}; + } }; class MockPlatformViewDelegate : public flutter::PlatformView::Delegate { @@ -316,7 +321,7 @@ class PlatformViewBuilder { // Once Build is called, the instance is no longer usable. GfxPlatformView Build() { EXPECT_FALSE(std::exchange(built_, true)) - << "Build() was already called, this buider is good for one use only."; + << "Build() was already called, this builder is good for one use only."; return GfxPlatformView( delegate_, task_runners_, std::move(view_ref_pair_.view_ref), external_external_view_embedder_, std::move(ime_service_), @@ -1394,8 +1399,7 @@ TEST_F(PlatformViewTests, OnShaderWarmup) { EXPECT_EQ(expected_result_string, response->result_string); } -// TODO(fxbug.dev/85125): Enable when GFX converts to TouchSource. -TEST_F(PlatformViewTests, DISABLED_TouchSourceLogicalToPhysicalConversion) { +TEST_F(PlatformViewTests, TouchSourceLogicalToPhysicalConversion) { constexpr std::array, 2> kRect = {{{0, 0}, {20, 20}}}; constexpr std::array kIdentity = {1, 0, 0, 0, 1, 0, 0, 0, 1}; constexpr fuchsia::ui::pointer::TouchInteractionId kIxnOne = { diff --git a/shell/platform/fuchsia/flutter/pointer_delegate.cc b/shell/platform/fuchsia/flutter/pointer_delegate.cc index 71cdb63d73eb1..1977521468a77 100644 --- a/shell/platform/fuchsia/flutter/pointer_delegate.cc +++ b/shell/platform/fuchsia/flutter/pointer_delegate.cc @@ -447,7 +447,9 @@ void PointerDelegate::WatchLoop( // Start watching both channels. touch_source_->Watch(std::move(touch_responses_), /*copy*/ touch_responder_); touch_responses_.clear(); - mouse_source_->Watch(/*copy*/ mouse_responder_); + if (mouse_source_) { + mouse_source_->Watch(/*copy*/ mouse_responder_); + } } } // namespace flutter_runner diff --git a/shell/platform/fuchsia/flutter/pointer_injector_delegate.cc b/shell/platform/fuchsia/flutter/pointer_injector_delegate.cc index 0119f28e8a2b0..0c682bef6ff6b 100644 --- a/shell/platform/fuchsia/flutter/pointer_injector_delegate.cc +++ b/shell/platform/fuchsia/flutter/pointer_injector_delegate.cc @@ -305,4 +305,10 @@ void PointerInjectorDelegate::PointerInjectorEndpoint::RegisterInjector( registered_ = true; } +void PointerInjectorDelegate::PointerInjectorEndpoint::Reset() { + injection_in_flight_ = false; + registered_ = false; + injector_events_ = {}; +} + } // namespace flutter_runner diff --git a/shell/platform/fuchsia/flutter/pointer_injector_delegate.h b/shell/platform/fuchsia/flutter/pointer_injector_delegate.h index 7c041fa346a1a..00063c1af32e5 100644 --- a/shell/platform/fuchsia/flutter/pointer_injector_delegate.h +++ b/shell/platform/fuchsia/flutter/pointer_injector_delegate.h @@ -106,7 +106,11 @@ class PointerInjectorDelegate { if (!weak) { return; } - weak->registered_ = false; + + // Clear all the stale pointer events in |injector_events_| and + // reset the state of |weak| so that any future calls do not inject + // any stale pointer events. + weak->Reset(); }); } @@ -129,6 +133,12 @@ class PointerInjectorDelegate { void EnqueueEvent(fuchsia::ui::pointerinjector::Event event); + // Resets |registered_|, |injection_in_flight_| and |injector_events_| so + // that |device_| can be re-registered and future calls to + // |fuchsia.ui.pointerinjector.Device.Inject| do not include any stale + // pointer events. + void Reset(); + // Set to true if there is a |fuchsia.ui.pointerinjector.Device.Inject| call // in progress. If true, the |fuchsia.ui.pointerinjector.Event| is buffered // in |injector_events_|. diff --git a/shell/platform/fuchsia/flutter/pointer_injector_delegate_unittest.cc b/shell/platform/fuchsia/flutter/pointer_injector_delegate_unittest.cc index 1005671e39225..cee930a428f35 100644 --- a/shell/platform/fuchsia/flutter/pointer_injector_delegate_unittest.cc +++ b/shell/platform/fuchsia/flutter/pointer_injector_delegate_unittest.cc @@ -681,6 +681,72 @@ TEST_P(PointerInjectorDelegateTest, ViewsGetPointerEventsInFIFO) { } } +TEST_P(PointerInjectorDelegateTest, DeviceRetriesRegisterWhenClosed) { + const uint64_t view_id = 1; + const int pointer_id = 1; + auto view_ref_pair = scenic::ViewRefPair::New(); + + auto response = FakePlatformMessageResponse::Create(); + auto response_2 = FakePlatformMessageResponse::Create(); + + std::optional view_ref_clone; + std::optional view_ref_clone_2; + + // Create the view. + if (!is_flatland_) { + CreateView(view_id); + fuv_ViewRef temp_ref; + fuv_ViewRef temp_ref_2; + fidl::Clone(view_ref_pair.view_ref, &temp_ref); + fidl::Clone(view_ref_pair.view_ref, &temp_ref_2); + view_ref_clone = std::move(temp_ref); + view_ref_clone_2 = std::move(temp_ref_2); + } else { + fuv_ViewRef view_ref; + fidl::Clone(view_ref_pair.view_ref, &view_ref); + CreateView(view_id, std::move(view_ref)); + } + + EXPECT_TRUE(pointer_injector_delegate_->HandlePlatformMessage( + PlatformMessageBuilder() + .SetViewId(view_id) + .SetPointerId(pointer_id) + .SetViewRefMaybe(std::move(view_ref_clone)) + .Build(), + response)); + + response->ExpectCompleted("[0]"); + + // The mock Pointerinjector registry server receives a pointer event from + // |f.u.p.Device.Inject| call for the view. + RunLoopUntil([this] { return registry_->num_events_received() == 1; }); + + // The mock Pointerinjector registry server receives a + // |f.u.p.Registry.Register| call for the view. + ASSERT_TRUE(registry_->num_register_calls() == 1); + + // Close the device channel. + registry_->ClearBindings(); + RunLoopUntilIdle(); + + EXPECT_TRUE(pointer_injector_delegate_->HandlePlatformMessage( + PlatformMessageBuilder() + .SetViewId(view_id) + .SetPointerId(pointer_id) + .SetViewRefMaybe(std::move(view_ref_clone_2)) + .Build(), + response_2)); + + response_2->ExpectCompleted("[0]"); + + // The mock Pointerinjector registry server receives a pointer event from + // |f.u.p.Device.Inject| call for the view. + RunLoopUntil([this] { return registry_->num_events_received() == 2; }); + + // The device tries to register again as the channel got closed. + ASSERT_TRUE(registry_->num_register_calls() == 2); +} + INSTANTIATE_TEST_SUITE_P(PointerInjectorDelegateParameterizedTest, PointerInjectorDelegateTest, ::testing::Bool()); diff --git a/shell/platform/fuchsia/flutter/tests/fakes/focuser.h b/shell/platform/fuchsia/flutter/tests/fakes/focuser.h index 275af5671f87a..aeaf8755d5661 100644 --- a/shell/platform/fuchsia/flutter/tests/fakes/focuser.h +++ b/shell/platform/fuchsia/flutter/tests/fakes/focuser.h @@ -6,12 +6,17 @@ #define FLUTTER_SHELL_PLATFORM_FUCHSIA_TESTS_FAKES_FOCUSER_H_ #include +#include + +#include + +#include "flutter/fml/logging.h" using Focuser = fuchsia::ui::views::Focuser; namespace flutter_runner::testing { -class FakeFocuser : public Focuser { +class FakeFocuser : public fuchsia::ui::views::testing::Focuser_TestBase { public: bool request_focus_called() const { return request_focus_called_; } @@ -32,6 +37,11 @@ class FakeFocuser : public Focuser { callback(std::move(result)); } + void NotImplemented_(const std::string& name) { + FML_LOG(FATAL) << "flutter_runner::Testing::FakeFocuser does not implement " + << name; + } + bool request_focus_called_ = false; bool fail_request_focus_ = false; }; diff --git a/shell/platform/fuchsia/flutter/tests/fakes/mock_injector_registry.h b/shell/platform/fuchsia/flutter/tests/fakes/mock_injector_registry.h index 467cd9da43d12..cd7d14b7f7d90 100644 --- a/shell/platform/fuchsia/flutter/tests/fakes/mock_injector_registry.h +++ b/shell/platform/fuchsia/flutter/tests/fakes/mock_injector_registry.h @@ -51,6 +51,8 @@ class MockInjectorRegistry : public fuchsia::ui::pointerinjector::Registry, callback(); } + void ClearBindings() { bindings_.clear(); } + // Returns the |fuchsia::ui::pointerinjector::Config| received in the last // |Register(...)| call. const fuchsia::ui::pointerinjector::Config& config() const { return config_; } diff --git a/shell/platform/fuchsia/flutter/tests/flatland_external_view_embedder_unittests.cc b/shell/platform/fuchsia/flutter/tests/flatland_external_view_embedder_unittests.cc index 0c88990e41e07..5ac7705ada835 100644 --- a/shell/platform/fuchsia/flutter/tests/flatland_external_view_embedder_unittests.cc +++ b/shell/platform/fuchsia/flutter/tests/flatland_external_view_embedder_unittests.cc @@ -306,7 +306,7 @@ void DrawFrameWithView(FlatlandExternalViewEmbedder& external_view_embedder, external_view_embedder.PostPrerollAction(nullptr); background_draw_callback(root_canvas); SkCanvas* overlay_canvas = - external_view_embedder.CompositeEmbeddedView(view_id); + external_view_embedder.CompositeEmbeddedView(view_id).canvas; overlay_draw_callback(overlay_canvas); } external_view_embedder.EndFrame(false, nullptr); diff --git a/shell/platform/fuchsia/flutter/tests/flatland_platform_view_unittest.cc b/shell/platform/fuchsia/flutter/tests/flatland_platform_view_unittest.cc index 0e804e1f68890..f2565ac69c9c4 100644 --- a/shell/platform/fuchsia/flutter/tests/flatland_platform_view_unittest.cc +++ b/shell/platform/fuchsia/flutter/tests/flatland_platform_view_unittest.cc @@ -47,6 +47,9 @@ class MockExternalViewEmbedder : public flutter::ExternalViewEmbedder { std::vector GetCurrentCanvases() override { return std::vector(); } + std::vector GetCurrentBuilders() override { + return std::vector(); + } void CancelFrame() override {} void BeginFrame( @@ -62,7 +65,9 @@ class MockExternalViewEmbedder : public flutter::ExternalViewEmbedder { void PrerollCompositeEmbeddedView( int view_id, std::unique_ptr params) override {} - SkCanvas* CompositeEmbeddedView(int view_id) override { return nullptr; } + flutter::EmbedderPaintContext CompositeEmbeddedView(int view_id) override { + return {nullptr, nullptr}; + } }; class MockPlatformViewDelegate : public flutter::PlatformView::Delegate { @@ -421,7 +426,7 @@ class PlatformViewBuilder { // Once Build is called, the instance is no longer usable. FlatlandPlatformView Build() { EXPECT_FALSE(std::exchange(built_, true)) - << "Build() was already called, this buider is good for one use only."; + << "Build() was already called, this builder is good for one use only."; return FlatlandPlatformView( delegate_, task_runners_, std::move(view_ref_pair_.view_ref), external_external_view_embedder_, std::move(ime_service_), diff --git a/shell/platform/fuchsia/flutter/tests/gfx_external_view_embedder_unittests.cc b/shell/platform/fuchsia/flutter/tests/gfx_external_view_embedder_unittests.cc index f99c54fb1904a..adbcc643e1af1 100644 --- a/shell/platform/fuchsia/flutter/tests/gfx_external_view_embedder_unittests.cc +++ b/shell/platform/fuchsia/flutter/tests/gfx_external_view_embedder_unittests.cc @@ -480,7 +480,7 @@ void DrawFrameWithView(GfxExternalViewEmbedder& external_view_embedder, external_view_embedder.PostPrerollAction(nullptr); background_draw_callback(root_canvas); SkCanvas* overlay_canvas = - external_view_embedder.CompositeEmbeddedView(view_id); + external_view_embedder.CompositeEmbeddedView(view_id).canvas; overlay_draw_callback(overlay_canvas); } external_view_embedder.EndFrame(false, nullptr); diff --git a/shell/platform/fuchsia/flutter/integration_flutter_tests/BUILD.gn b/shell/platform/fuchsia/flutter/tests/integration/BUILD.gn similarity index 73% rename from shell/platform/fuchsia/flutter/integration_flutter_tests/BUILD.gn rename to shell/platform/fuchsia/flutter/tests/integration/BUILD.gn index 6e5e43e0b05ea..c25f18aaa8b70 100644 --- a/shell/platform/fuchsia/flutter/integration_flutter_tests/BUILD.gn +++ b/shell/platform/fuchsia/flutter/tests/integration/BUILD.gn @@ -2,7 +2,11 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -group("integration_flutter_tests") { +assert(is_fuchsia) + +import("//build/fuchsia/sdk.gni") + +group("integration") { testonly = true deps = [ "embedder:tests" ] } diff --git a/shell/platform/fuchsia/flutter/tests/integration/README.md b/shell/platform/fuchsia/flutter/tests/integration/README.md new file mode 100644 index 0000000000000..2e86aa702868a --- /dev/null +++ b/shell/platform/fuchsia/flutter/tests/integration/README.md @@ -0,0 +1,84 @@ +# `flutter integration tests` + +## Configure and build fuchsia + +```shell +$ cd "$FUCHSIA_DIR" +$ fx set terminal.x64 +$ fx build +``` + +## Build the test + +You can specify the test's package target to build only the test package, with +its dependencies. This will also build the required runner. + +```shell +$ cd "$ENGINE_DIR/src" +$ ./flutter/tools/gn --fuchsia \ + # for example: --goma --fuchsia-cpu=x64 --runtime-mode=debug +$ ninja -C out/fuchsia_debug_x64 \ + flutter/shell/platform/fuchsia/flutter/tests/integration +``` + + +## Start an emulator + +```shell +ffx emu start --net tap +``` + +NOTE: Do _not_ run the default package server. The instructions below describe +how to launch a flutter-specific package server. + +## Publish the test packages to the Fuchsia package server + +The tests currently specify the Fuchsia package server's standard domain, +`fuchsia.com`, as the server to use to resolve (locate and load) the test +packages. So, before running the test, the most recently built `.far` files +need to be published to the Fuchsia package repo: + +```shell +$ fx pm publish -a -repo "$(cat $FUCHSIA_DIR/.fx-build-dir)/amber-files/" \ + -f "$FLUTTER_ENGINE_DIR"/src/out/fuchsia_*64/oot_flutter_jit_runner-0.far +$ fx pm publish -a -repo "$(cat $FUCHSIA_DIR/.fx-build-dir)/amber-files/" \ + -f "$FLUTTER_ENGINE_DIR"/src/out/fuchsia_*64/flutter-embedder-test-0.far +$ fx pm publish -a -repo "$(cat $FUCHSIA_DIR/.fx-build-dir)/amber-files/" \ + -f $(find "$FLUTTER_ENGINE_DIR"/src/out/fuchsia_*64 -name parent-view.far) +$ fx pm publish -a -repo "$(cat $FUCHSIA_DIR/.fx-build-dir)/amber-files/" \ + -f $(find "$FLUTTER_ENGINE_DIR"/src/out/fuchsia_*64 -name child-view.far) +``` + +## Run the test + +```shell +$ ffx test run fuchsia-pkg:://fuchsia.com/flutter-embedder-test#meta/flutter-embedder-test.cm +``` + +If, for example, you only make a change to the Dart code in `parent-view`, you +can rebuild only the parent-view package target, and republish it. + +```shell +$ ninja -C out/fuchsia_debug_x64 \ + flutter/shell/platform/fuchsia/flutter/tests/integration/embedder/parent-view:package +$ fx pm publish -a -repo "$(cat $FUCHSIA_DIR/.fx-build-dir)/amber-files/" \ + -f $(find "$FLUTTER_ENGINE_DIR"/src/out/fuchsia_*64 -name parent-view.far) +``` + +Then re-run the test as above. + +The tests use a flutter runner with "oot_" prefixed to its package name, to +avoid conflicting with any flutter_runner package in the base fuchsia image. +After making a change to the flutter_runner you can re-deploy it with: + +```shell +$ ninja -C out/fuchsia_debug_x64 \ + flutter/shell/platform/fuchsia/flutter:oot_flutter_jit_runner +$ fx pm publish -a -repo "$(cat $FUCHSIA_DIR/.fx-build-dir)/amber-files/" \ + -f $(find "$FLUTTER_ENGINE_DIR"/src/out/fuchsia_*64 -name oot_flutter_jit_runner.far) +``` + +Then re-run the test as above. + +From here, you can modify the Flutter test, rebuild flutter, and usually rerun +the test without rebooting, by repeating the commands above. diff --git a/shell/platform/fuchsia/flutter/tests/integration/embedder/BUILD.gn b/shell/platform/fuchsia/flutter/tests/integration/embedder/BUILD.gn new file mode 100644 index 0000000000000..ce295f31107f7 --- /dev/null +++ b/shell/platform/fuchsia/flutter/tests/integration/embedder/BUILD.gn @@ -0,0 +1,65 @@ +# 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. + +assert(is_fuchsia) + +import("//build/fuchsia/sdk.gni") +import("//flutter/tools/fuchsia/fuchsia_archive.gni") + +group("tests") { + testonly = true + deps = [ ":flutter-embedder-test" ] +} + +executable("flutter-embedder-test-bin") { + testonly = true + + output_name = "flutter-embedder-test" + + sources = [ + "color.cc", + "color.h", + "flutter-embedder-test.cc", + ] + + # This is needed for //third_party/googletest for linking zircon symbols. + libs = [ "$fuchsia_sdk_path/arch/$target_cpu/sysroot/lib/libzircon.so" ] + + deps = [ + "$fuchsia_sdk_root/fidl:fuchsia.logger", + "$fuchsia_sdk_root/fidl:fuchsia.tracing.provider", + "$fuchsia_sdk_root/fidl:fuchsia.ui.app", + "$fuchsia_sdk_root/fidl:fuchsia.ui.composition", + "$fuchsia_sdk_root/fidl:fuchsia.ui.observation.geometry", + "$fuchsia_sdk_root/fidl:fuchsia.ui.scenic", + "$fuchsia_sdk_root/fidl:fuchsia.ui.test.input", + "$fuchsia_sdk_root/fidl:fuchsia.ui.test.scene", + "$fuchsia_sdk_root/pkg:async", + "$fuchsia_sdk_root/pkg:async-loop-testing", + "$fuchsia_sdk_root/pkg:fidl_cpp", + "$fuchsia_sdk_root/pkg:scenic_cpp", + "$fuchsia_sdk_root/pkg:sys_component_cpp_testing", + "$fuchsia_sdk_root/pkg:zx", + "//flutter/fml", + "//third_party/googletest:gtest", + "//third_party/googletest:gtest_main", + ] +} + +fuchsia_test_archive("flutter-embedder-test") { + deps = [ + ":flutter-embedder-test-bin", + "child-view:package", + "parent-view:package", + + # "OOT" copies of the runners used by tests, to avoid conflicting with the + # runners in the base fuchsia image. + # TODO(fxbug.dev/106575): Fix this with subpackages. + "//flutter/shell/platform/fuchsia/flutter:oot_flutter_jit_runner", + ] + + binary = "$target_name" + + cml_file = rebase_path("meta/$target_name.cml") +} diff --git a/shell/platform/fuchsia/flutter/tests/integration/embedder/child-view/BUILD.gn b/shell/platform/fuchsia/flutter/tests/integration/embedder/child-view/BUILD.gn new file mode 100644 index 0000000000000..dedf77c3eb950 --- /dev/null +++ b/shell/platform/fuchsia/flutter/tests/integration/embedder/child-view/BUILD.gn @@ -0,0 +1,26 @@ +# 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("//build/fuchsia/sdk.gni") +import("//flutter/tools/fuchsia/dart/dart_library.gni") +import("//flutter/tools/fuchsia/flutter/flutter_component.gni") +import("//flutter/tools/fuchsia/gn-sdk/package.gni") + +dart_library("lib") { + package_name = "child-view" + sources = [ "child_view.dart" ] +} + +flutter_component("component") { + main_package = "child-view" + component_name = "child-view" + main_dart = "child_view.dart" + manifest = rebase_path("meta/child-view.cml") + deps = [ ":lib" ] +} + +fuchsia_package("package") { + package_name = "child-view" + deps = [ ":component" ] +} diff --git a/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/child-view2/child_view2.dart b/shell/platform/fuchsia/flutter/tests/integration/embedder/child-view/lib/child_view.dart similarity index 85% rename from shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/child-view2/child_view2.dart rename to shell/platform/fuchsia/flutter/tests/integration/embedder/child-view/lib/child_view.dart index a36195526d497..5b71294b700d5 100644 --- a/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/child-view2/child_view2.dart +++ b/shell/platform/fuchsia/flutter/tests/integration/embedder/child-view/lib/child_view.dart @@ -4,10 +4,10 @@ import 'dart:ui'; -TestApp app; - void main(List args) { - app = TestApp(); + print('child-view: starting'); + + TestApp app = TestApp(); app.run(); } @@ -19,11 +19,15 @@ class TestApp { void run() { window.onPointerDataPacket = (PointerDataPacket packet) { - app.pointerDataPacket(packet); + this.pointerDataPacket(packet); + }; + window.onMetricsChanged = () { + window.scheduleFrame(); }; window.onBeginFrame = (Duration duration) { - app.beginFrame(duration); + this.beginFrame(duration); }; + window.scheduleFrame(); } @@ -46,7 +50,7 @@ class TestApp { void pointerDataPacket(PointerDataPacket packet) { for (final data in packet.data) { - if (data.change == PointerChange.up) { + if (data.change == PointerChange.down) { this._backgroundColor = _yellow; } } diff --git a/shell/platform/fuchsia/flutter/tests/integration/embedder/child-view/meta/child-view.cml b/shell/platform/fuchsia/flutter/tests/integration/embedder/child-view/meta/child-view.cml new file mode 100644 index 0000000000000..9e33d6a3effe9 --- /dev/null +++ b/shell/platform/fuchsia/flutter/tests/integration/embedder/child-view/meta/child-view.cml @@ -0,0 +1,21 @@ +{ + include: [ "syslog/client.shard.cml" ], + program: { + data: "data/child-view", + + // Always use the jit runner for now. + // TODO(fxbug.dev/106577): Implement manifest merging build rules for V2 components. + runner: "flutter_jit_runner", + }, + capabilities: [ + { + protocol: [ "fuchsia.ui.app.ViewProvider" ], + }, + ], + expose: [ + { + protocol: [ "fuchsia.ui.app.ViewProvider" ], + from: "self", + }, + ], +} diff --git a/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/child-view2/pubspec.yaml b/shell/platform/fuchsia/flutter/tests/integration/embedder/child-view/pubspec.yaml similarity index 100% rename from shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/child-view2/pubspec.yaml rename to shell/platform/fuchsia/flutter/tests/integration/embedder/child-view/pubspec.yaml diff --git a/shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing/src/ui/testing/views/color.cc b/shell/platform/fuchsia/flutter/tests/integration/embedder/color.cc similarity index 98% rename from shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing/src/ui/testing/views/color.cc rename to shell/platform/fuchsia/flutter/tests/integration/embedder/color.cc index d723734f1a3ec..24fd675375247 100644 --- a/shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing/src/ui/testing/views/color.cc +++ b/shell/platform/fuchsia/flutter/tests/integration/embedder/color.cc @@ -2,10 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "color.h" + #include #include "flutter/fml/logging.h" -#include "src/ui/testing/views/color.h" namespace scenic { diff --git a/shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing/src/ui/testing/views/color.h b/shell/platform/fuchsia/flutter/tests/integration/embedder/color.h similarity index 100% rename from shell/platform/fuchsia/flutter/integration_flutter_tests/fuchsia_testing/src/ui/testing/views/color.h rename to shell/platform/fuchsia/flutter/tests/integration/embedder/color.h diff --git a/shell/platform/fuchsia/flutter/tests/integration/embedder/flutter-embedder-test.cc b/shell/platform/fuchsia/flutter/tests/integration/embedder/flutter-embedder-test.cc new file mode 100644 index 0000000000000..73f36e85ede8c --- /dev/null +++ b/shell/platform/fuchsia/flutter/tests/integration/embedder/flutter-embedder-test.cc @@ -0,0 +1,602 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "flutter/fml/logging.h" +#include "gtest/gtest.h" + +#include "color.h" + +namespace flutter_embedder_test { +namespace { + +// Types imported for the realm_builder library. +using component_testing::ChildOptions; +using component_testing::ChildRef; +using component_testing::DirectoryContents; +using component_testing::ParentRef; +using component_testing::Protocol; +using component_testing::RealmRoot; +using component_testing::Route; +using component_testing::StartupMode; + +// The FIDL bindings for this service are not exposed in the Fuchsia SDK, so we +// must encode the name manually here. +constexpr auto kVulkanLoaderServiceName = "fuchsia.vulkan.loader.Loader"; + +constexpr auto kFlutterJitRunnerUrl = + "fuchsia-pkg://fuchsia.com/oot_flutter_jit_runner#meta/" + "flutter_jit_runner.cm"; +constexpr auto kFlutterJitProductRunnerUrl = + "fuchsia-pkg://fuchsia.com/oot_flutter_jit_product_runner#meta/" + "flutter_jit_product_runner.cm"; +constexpr auto kFlutterAotRunnerUrl = + "fuchsia-pkg://fuchsia.com/oot_flutter_aot_runner#meta/" + "flutter_aot_runner.cm"; +constexpr auto kFlutterAotProductRunnerUrl = + "fuchsia-pkg://fuchsia.com/oot_flutter_aot_product_runner#meta/" + "flutter_aot_product_runner.cm"; +constexpr char kChildViewUrl[] = + "fuchsia-pkg://fuchsia.com/child-view#meta/child-view.cm"; +constexpr char kParentViewUrl[] = + "fuchsia-pkg://fuchsia.com/parent-view#meta/parent-view.cm"; +static constexpr auto kTestUIStackUrl = + "fuchsia-pkg://fuchsia.com/test-ui-stack#meta/test-ui-stack.cm"; + +constexpr auto kFlutterRunnerEnvironment = "flutter_runner_env"; +constexpr auto kFlutterJitRunner = "flutter_jit_runner"; +constexpr auto kFlutterJitRunnerRef = ChildRef{kFlutterJitRunner}; +constexpr auto kFlutterJitProductRunner = "flutter_jit_product_runner"; +constexpr auto kFlutterJitProductRunnerRef = ChildRef{kFlutterJitProductRunner}; +constexpr auto kFlutterAotRunner = "flutter_aot_runner"; +constexpr auto kFlutterAotRunnerRef = ChildRef{kFlutterAotRunner}; +constexpr auto kFlutterAotProductRunner = "flutter_aot_product_runner"; +constexpr auto kFlutterAotProductRunnerRef = ChildRef{kFlutterAotProductRunner}; +constexpr auto kChildView = "child_view"; +constexpr auto kChildViewRef = ChildRef{kChildView}; +constexpr auto kParentView = "parent_view"; +constexpr auto kParentViewRef = ChildRef{kParentView}; +constexpr auto kTestUIStack = "ui"; +constexpr auto kTestUIStackRef = ChildRef{kTestUIStack}; + +constexpr scenic::Color kParentBackgroundColor = {0x00, 0x00, 0xFF, + 0xFF}; // Blue +constexpr scenic::Color kParentTappedColor = {0x00, 0x00, 0x00, 0xFF}; // Black +constexpr scenic::Color kChildBackgroundColor = {0xFF, 0x00, 0xFF, + 0xFF}; // Pink +constexpr scenic::Color kChildTappedColor = {0xFF, 0xFF, 0x00, 0xFF}; // Yellow + +// TODO(fxb/64201): Remove forced opacity colors when Flatland is enabled. +constexpr scenic::Color kOverlayBackgroundColor1 = { + 0x00, 0xFF, 0x0E, 0xFF}; // Green, blended with blue (FEMU local) +constexpr scenic::Color kOverlayBackgroundColor2 = { + 0x0E, 0xFF, 0x0E, 0xFF}; // Green, blended with pink (FEMU local) +constexpr scenic::Color kOverlayBackgroundColor3 = { + 0x00, 0xFF, 0x0D, 0xFF}; // Green, blended with blue (AEMU infra) +constexpr scenic::Color kOverlayBackgroundColor4 = { + 0x0D, 0xFF, 0x0D, 0xFF}; // Green, blended with pink (AEMU infra) +constexpr scenic::Color kOverlayBackgroundColor5 = { + 0x00, 0xFE, 0x0D, 0xFF}; // Green, blended with blue (NUC) +constexpr scenic::Color kOverlayBackgroundColor6 = { + 0x0D, 0xFF, 0x00, 0xFF}; // Green, blended with pink (NUC) + +static size_t OverlayPixelCount(std::map& histogram) { + return histogram[kOverlayBackgroundColor1] + + histogram[kOverlayBackgroundColor2] + + histogram[kOverlayBackgroundColor3] + + histogram[kOverlayBackgroundColor4] + + histogram[kOverlayBackgroundColor5] + + histogram[kOverlayBackgroundColor6]; +} + +// Timeout for Scenic's |TakeScreenshot| FIDL call. +constexpr zx::duration kScreenshotTimeout = zx::sec(10); +// Timeout to fail the test if it goes beyond this duration. +constexpr zx::duration kTestTimeout = zx::min(1); + +bool CheckViewExistsInSnapshot( + const fuchsia::ui::observation::geometry::ViewTreeSnapshot& snapshot, + zx_koid_t view_ref_koid) { + if (!snapshot.has_views()) { + return false; + } + + auto snapshot_count = + std::count_if(snapshot.views().begin(), snapshot.views().end(), + [view_ref_koid](const auto& view) { + return view.view_ref_koid() == view_ref_koid; + }); + + return snapshot_count > 0; +} + +bool CheckViewExistsInUpdates( + const std::vector& + updates, + zx_koid_t view_ref_koid) { + auto update_count = std::count_if( + updates.begin(), updates.end(), [view_ref_koid](auto& snapshot) { + return CheckViewExistsInSnapshot(snapshot, view_ref_koid); + }); + + return update_count > 0; +} + +} // namespace + +class FlutterEmbedderTest : public ::loop_fixture::RealLoop, + public ::testing::Test { + public: + FlutterEmbedderTest() + : realm_builder_(component_testing::RealmBuilder::Create()) { + FML_VLOG(-1) << "Setting up base realm"; + SetUpRealmBase(); + + // Post a "just in case" quit task, if the test hangs. + async::PostDelayedTask( + dispatcher(), + [] { + FML_LOG(FATAL) + << "\n\n>> Test did not complete in time, terminating. <<\n\n"; + }, + kTestTimeout); + } + + bool HasViewConnected( + const fuchsia::ui::observation::geometry::ViewTreeWatcherPtr& + view_tree_watcher, + std::optional& + watch_response, + zx_koid_t view_ref_koid); + + void LaunchParentViewInRealm( + const std::vector& component_args = {}); + + scenic::Screenshot TakeScreenshot(); + + bool TakeScreenshotUntil( + scenic::Color color, + fit::function)> callback = nullptr, + zx::duration timeout = kTestTimeout); + + // Simulates a tap at location (x, y). + void InjectTap(int32_t x, int32_t y); + + // Injects an input event, and posts a task to retry after + // `kTapRetryInterval`. + // + // We post the retry task because the first input event we send to Flutter may + // be lost. The reason the first event may be lost is that there is a race + // condition as the scene owner starts up. + // + // More specifically: in order for our app + // to receive the injected input, two things must be true before we inject + // touch input: + // * The Scenic root view must have been installed, and + // * The Input Pipeline must have received a viewport to inject touch into. + // + // The problem we have is that the `is_rendering` signal that we monitor only + // guarantees us the view is ready. If the viewport is not ready in Input + // Pipeline at that time, it will drop the touch event. + // + // TODO(fxbug.dev/96986): Improve synchronization and remove retry logic. + void TryInject(int32_t x, int32_t y); + + private: + fuchsia::ui::scenic::Scenic* scenic() { return scenic_.get(); } + + void SetUpRealmBase(); + + // Registers a fake touch screen device with an injection coordinate space + // spanning [-1000, 1000] on both axes. + void RegisterTouchScreen(); + + fuchsia::ui::scenic::ScenicPtr scenic_; + fuchsia::ui::test::input::RegistryPtr input_registry_; + fuchsia::ui::test::input::TouchScreenPtr fake_touchscreen_; + fuchsia::ui::test::scene::ControllerPtr scene_provider_; + fuchsia::ui::observation::geometry::ViewTreeWatcherPtr view_tree_watcher_; + + // Wrapped in optional since the view is not created until the middle of SetUp + component_testing::RealmBuilder realm_builder_; + std::unique_ptr realm_; + + // The typical latency on devices we've tested is ~60 msec. The retry interval + // is chosen to be a) Long enough that it's unlikely that we send a new tap + // while a previous tap is still being + // processed. That is, it should be far more likely that a new tap is sent + // because the first tap was lost, than because the system is just running + // slowly. + // b) Short enough that we don't slow down tryjobs. + // + // The first property is important to avoid skewing the latency metrics that + // we collect. For an explanation of why a tap might be lost, see the + // documentation for TryInject(). + static constexpr auto kTapRetryInterval = zx::sec(1); +}; + +void FlutterEmbedderTest::SetUpRealmBase() { + FML_LOG(INFO) << "Setting up realm base."; + + // First, add the flutter runner(s) as children. + realm_builder_.AddChild(kFlutterJitRunner, kFlutterJitRunnerUrl); + realm_builder_.AddChild(kFlutterJitProductRunner, + kFlutterJitProductRunnerUrl); + realm_builder_.AddChild(kFlutterAotRunner, kFlutterAotRunnerUrl); + realm_builder_.AddChild(kFlutterAotProductRunner, + kFlutterAotProductRunnerUrl); + + // Then, add an environment providing them. + fuchsia::component::decl::Environment flutter_runner_environment; + flutter_runner_environment.set_name(kFlutterRunnerEnvironment); + flutter_runner_environment.set_extends( + fuchsia::component::decl::EnvironmentExtends::REALM); + flutter_runner_environment.set_runners({}); + auto environment_runners = flutter_runner_environment.mutable_runners(); + fuchsia::component::decl::RunnerRegistration flutter_jit_runner_reg; + flutter_jit_runner_reg.set_source(fuchsia::component::decl::Ref::WithChild( + fuchsia::component::decl::ChildRef{.name = kFlutterJitRunner})); + flutter_jit_runner_reg.set_source_name(kFlutterJitRunner); + flutter_jit_runner_reg.set_target_name(kFlutterJitRunner); + environment_runners->push_back(std::move(flutter_jit_runner_reg)); + fuchsia::component::decl::RunnerRegistration flutter_jit_product_runner_reg; + flutter_jit_product_runner_reg.set_source( + fuchsia::component::decl::Ref::WithChild( + fuchsia::component::decl::ChildRef{.name = + kFlutterJitProductRunner})); + flutter_jit_product_runner_reg.set_source_name(kFlutterJitProductRunner); + flutter_jit_product_runner_reg.set_target_name(kFlutterJitProductRunner); + environment_runners->push_back(std::move(flutter_jit_product_runner_reg)); + fuchsia::component::decl::RunnerRegistration flutter_aot_runner_reg; + flutter_aot_runner_reg.set_source(fuchsia::component::decl::Ref::WithChild( + fuchsia::component::decl::ChildRef{.name = kFlutterAotRunner})); + flutter_aot_runner_reg.set_source_name(kFlutterAotRunner); + flutter_aot_runner_reg.set_target_name(kFlutterAotRunner); + environment_runners->push_back(std::move(flutter_aot_runner_reg)); + fuchsia::component::decl::RunnerRegistration flutter_aot_product_runner_reg; + flutter_aot_product_runner_reg.set_source( + fuchsia::component::decl::Ref::WithChild( + fuchsia::component::decl::ChildRef{.name = + kFlutterAotProductRunner})); + flutter_aot_product_runner_reg.set_source_name(kFlutterAotProductRunner); + flutter_aot_product_runner_reg.set_target_name(kFlutterAotProductRunner); + environment_runners->push_back(std::move(flutter_aot_product_runner_reg)); + auto realm_decl = realm_builder_.GetRealmDecl(); + if (!realm_decl.has_environments()) { + realm_decl.set_environments({}); + } + auto realm_environments = realm_decl.mutable_environments(); + realm_environments->push_back(std::move(flutter_runner_environment)); + realm_builder_.ReplaceRealmDecl(std::move(realm_decl)); + + // Add test UI stack component. + realm_builder_.AddChild(kTestUIStack, kTestUIStackUrl); + + // Add embedded parent and child components. + realm_builder_.AddChild(kChildView, kChildViewUrl, + ChildOptions{ + .environment = kFlutterRunnerEnvironment, + }); + realm_builder_.AddChild(kParentView, kParentViewUrl, + ChildOptions{ + .environment = kFlutterRunnerEnvironment, + }); + + // Route base system services to flutter runners. + realm_builder_.AddRoute( + Route{.capabilities = + { + Protocol{fuchsia::logger::LogSink::Name_}, + Protocol{fuchsia::sysmem::Allocator::Name_}, + Protocol{fuchsia::tracing::provider::Registry::Name_}, + Protocol{kVulkanLoaderServiceName}, + }, + .source = ParentRef{}, + .targets = {kFlutterJitRunnerRef, kFlutterJitProductRunnerRef, + kFlutterAotRunnerRef, kFlutterAotProductRunnerRef}}); + + // Route base system services to the test UI stack. + realm_builder_.AddRoute(Route{ + .capabilities = {Protocol{fuchsia::logger::LogSink::Name_}, + Protocol{fuchsia::sysmem::Allocator::Name_}, + Protocol{fuchsia::tracing::provider::Registry::Name_}, + Protocol{kVulkanLoaderServiceName}}, + .source = ParentRef{}, + .targets = {kTestUIStackRef}}); + + // Route UI capabilities from test UI stack to flutter runners. + realm_builder_.AddRoute(Route{ + .capabilities = {Protocol{fuchsia::ui::composition::Flatland::Name_}, + Protocol{fuchsia::ui::scenic::Scenic::Name_}}, + .source = kTestUIStackRef, + .targets = {kFlutterJitRunnerRef, kFlutterJitProductRunnerRef, + kFlutterAotRunnerRef, kFlutterAotProductRunnerRef}}); + + // Route test capabilities from test UI stack to test driver. + realm_builder_.AddRoute(Route{ + .capabilities = {Protocol{fuchsia::ui::test::input::Registry::Name_}, + Protocol{fuchsia::ui::test::scene::Controller::Name_}, + Protocol{fuchsia::ui::scenic::Scenic::Name_}}, + .source = kTestUIStackRef, + .targets = {ParentRef{}}}); + + // Route ViewProvider from child to parent, and parent to test. + realm_builder_.AddRoute( + Route{.capabilities = {Protocol{fuchsia::ui::app::ViewProvider::Name_}}, + .source = kParentViewRef, + .targets = {ParentRef()}}); + realm_builder_.AddRoute( + Route{.capabilities = {Protocol{fuchsia::ui::app::ViewProvider::Name_}}, + .source = kChildViewRef, + .targets = {kParentViewRef}}); +} + +// Checks whether the view with |view_ref_koid| has connected to the view tree. +// The response of a f.u.o.g.Provider.Watch call is stored in |watch_response| +// if it contains |view_ref_koid|. +bool FlutterEmbedderTest::HasViewConnected( + const fuchsia::ui::observation::geometry::ViewTreeWatcherPtr& + view_tree_watcher, + std::optional& + watch_response, + zx_koid_t view_ref_koid) { + std::optional watch_result; + view_tree_watcher->Watch( + [&watch_result](auto response) { watch_result = std::move(response); }); + FML_LOG(INFO) << "Waiting for view tree watch result"; + RunLoopUntil([&watch_result] { return watch_result.has_value(); }); + FML_LOG(INFO) << "Received for view tree watch result"; + if (CheckViewExistsInUpdates(watch_result->updates(), view_ref_koid)) { + watch_response = std::move(watch_result); + }; + return watch_response.has_value(); +} + +void FlutterEmbedderTest::LaunchParentViewInRealm( + const std::vector& component_args) { + FML_LOG(INFO) << "Launching parent-view"; + + if (!component_args.empty()) { + // Construct a args.csv file containing the specified comma-separated + // component args. + std::string csv; + for (const auto& arg : component_args) { + csv += arg + ','; + } + // Remove last comma. + csv.pop_back(); + + auto config_directory_contents = DirectoryContents(); + config_directory_contents.AddFile("args.csv", csv); + realm_builder_.RouteReadOnlyDirectory("config-data", {kParentViewRef}, + std::move(config_directory_contents)); + } + realm_ = std::make_unique(realm_builder_.Build()); + + // Register fake touch screen device. + RegisterTouchScreen(); + + // Instruct Test UI Stack to present parent-view's View. + std::optional view_ref_koid; + scene_provider_ = realm_->Connect(); + scene_provider_.set_error_handler( + [](auto) { FML_LOG(ERROR) << "Error from test scene provider"; }); + fuchsia::ui::test::scene::ControllerAttachClientViewRequest request; + request.set_view_provider(realm_->Connect()); + scene_provider_->RegisterViewTreeWatcher(view_tree_watcher_.NewRequest(), + []() {}); + scene_provider_->AttachClientView( + std::move(request), [&view_ref_koid](auto client_view_ref_koid) { + view_ref_koid = client_view_ref_koid; + }); + + FML_LOG(INFO) << "Waiting for client view ref koid"; + RunLoopUntil([&view_ref_koid] { return view_ref_koid.has_value(); }); + + // Wait for the client view to get attached to the view tree. + std::optional + watch_response; + FML_LOG(INFO) << "Waiting for client view to render; koid is " + << (view_ref_koid.has_value() ? view_ref_koid.value() : 0); + RunLoopUntil([this, &watch_response, &view_ref_koid] { + return HasViewConnected(view_tree_watcher_, watch_response, *view_ref_koid); + }); + FML_LOG(INFO) << "Client view has rendered"; + + scenic_ = realm_->Connect(); + FML_LOG(INFO) << "Launched parent-view"; +} + +scenic::Screenshot FlutterEmbedderTest::TakeScreenshot() { + FML_LOG(INFO) << "Taking screenshot... "; + fuchsia::ui::scenic::ScreenshotData screenshot_out; + scenic_->TakeScreenshot( + [this, &screenshot_out](fuchsia::ui::scenic::ScreenshotData screenshot, + bool status) { + EXPECT_TRUE(status) << "Failed to take screenshot"; + screenshot_out = std::move(screenshot); + QuitLoop(); + }); + EXPECT_FALSE(RunLoopWithTimeout(kScreenshotTimeout)) + << "Timed out waiting for screenshot."; + FML_LOG(INFO) << "Screenshot captured."; + + return scenic::Screenshot(screenshot_out); +} + +bool FlutterEmbedderTest::TakeScreenshotUntil( + scenic::Color color, + fit::function)> callback, + zx::duration timeout) { + return RunLoopWithTimeoutOrUntil( + [this, &callback, &color] { + auto screenshot = TakeScreenshot(); + auto histogram = screenshot.Histogram(); + + bool color_found = histogram[color] > 0; + if (color_found && callback != nullptr) { + callback(std::move(histogram)); + } + return color_found; + }, + timeout); +} + +void FlutterEmbedderTest::RegisterTouchScreen() { + FML_LOG(INFO) << "Registering fake touch screen"; + input_registry_ = realm_->Connect(); + input_registry_.set_error_handler( + [](auto) { FML_LOG(ERROR) << "Error from input helper"; }); + bool touchscreen_registered = false; + fuchsia::ui::test::input::RegistryRegisterTouchScreenRequest request; + request.set_device(fake_touchscreen_.NewRequest()); + input_registry_->RegisterTouchScreen( + std::move(request), + [&touchscreen_registered]() { touchscreen_registered = true; }); + RunLoopUntil([&touchscreen_registered] { return touchscreen_registered; }); + FML_LOG(INFO) << "Touchscreen registered"; +} + +void FlutterEmbedderTest::InjectTap(int32_t x, int32_t y) { + fuchsia::ui::test::input::TouchScreenSimulateTapRequest tap_request; + tap_request.mutable_tap_location()->x = x; + tap_request.mutable_tap_location()->y = y; + fake_touchscreen_->SimulateTap(std::move(tap_request), [x, y]() { + FML_LOG(INFO) << "Tap injected at (" << x << ", " << y << ")"; + }); +} + +void FlutterEmbedderTest::TryInject(int32_t x, int32_t y) { + InjectTap(x, y); + async::PostDelayedTask( + dispatcher(), [this, x, y] { TryInject(x, y); }, kTapRetryInterval); +} + +TEST_F(FlutterEmbedderTest, Embedding) { + LaunchParentViewInRealm(); + + // Take screenshot until we see the child-view's embedded color. + ASSERT_TRUE(TakeScreenshotUntil( + kChildBackgroundColor, [](std::map histogram) { + // Expect parent and child background colors, with parent color > child + // color. + EXPECT_GT(histogram[kParentBackgroundColor], 0u); + EXPECT_GT(histogram[kChildBackgroundColor], 0u); + EXPECT_GT(histogram[kParentBackgroundColor], + histogram[kChildBackgroundColor]); + })); +} + +TEST_F(FlutterEmbedderTest, HittestEmbedding) { + LaunchParentViewInRealm(); + + // Take screenshot until we see the child-view's embedded color. + ASSERT_TRUE(TakeScreenshotUntil(kChildBackgroundColor)); + + // Simulate a tap at the center of the child view. + TryInject(/* x = */ 0, /* y = */ 0); + + // Take screenshot until we see the child-view's tapped color. + ASSERT_TRUE(TakeScreenshotUntil( + kChildTappedColor, [](std::map histogram) { + // Expect parent and child background colors, with parent color > child + // color. + EXPECT_GT(histogram[kParentBackgroundColor], 0u); + EXPECT_EQ(histogram[kChildBackgroundColor], 0u); + EXPECT_GT(histogram[kChildTappedColor], 0u); + EXPECT_GT(histogram[kParentBackgroundColor], + histogram[kChildTappedColor]); + })); +} + +TEST_F(FlutterEmbedderTest, HittestDisabledEmbedding) { + LaunchParentViewInRealm({"--no-hitTestable"}); + + // Take screenshots until we see the child-view's embedded color. + ASSERT_TRUE(TakeScreenshotUntil(kChildBackgroundColor)); + + // Simulate a tap at the center of the child view. + TryInject(/* x = */ 0, /* y = */ 0); + + // The parent-view should change color. + ASSERT_TRUE(TakeScreenshotUntil( + kParentTappedColor, [](std::map histogram) { + // Expect parent and child background colors, with parent color > child + // color. + EXPECT_EQ(histogram[kParentBackgroundColor], 0u); + EXPECT_GT(histogram[kParentTappedColor], 0u); + EXPECT_GT(histogram[kChildBackgroundColor], 0u); + EXPECT_EQ(histogram[kChildTappedColor], 0u); + EXPECT_GT(histogram[kParentTappedColor], + histogram[kChildBackgroundColor]); + })); +} + +TEST_F(FlutterEmbedderTest, EmbeddingWithOverlay) { + LaunchParentViewInRealm({"--showOverlay"}); + + // Take screenshot until we see the child-view's embedded color. + ASSERT_TRUE(TakeScreenshotUntil( + kChildBackgroundColor, [](std::map histogram) { + // Expect parent, overlay and child background colors. + // With parent color > child color and overlay color > child color. + const size_t overlay_pixel_count = OverlayPixelCount(histogram); + EXPECT_GT(histogram[kParentBackgroundColor], 0u); + EXPECT_GT(overlay_pixel_count, 0u); + EXPECT_GT(histogram[kChildBackgroundColor], 0u); + EXPECT_GT(histogram[kParentBackgroundColor], + histogram[kChildBackgroundColor]); + EXPECT_GT(overlay_pixel_count, histogram[kChildBackgroundColor]); + })); +} + +TEST_F(FlutterEmbedderTest, HittestEmbeddingWithOverlay) { + LaunchParentViewInRealm({"--showOverlay"}); + + // Take screenshot until we see the child-view's embedded color. + ASSERT_TRUE(TakeScreenshotUntil(kChildBackgroundColor)); + + // The bottom-left corner of the overlay is at the center of the screen, + // which is at (0, 0) in the injection coordinate space. Inject a pointer + // event just outside the overlay's bounds, and ensure that it goes to the + // embedded view. + TryInject(/* x = */ -1, /* y = */ 1); + + // Take screenshot until we see the child-view's tapped color. + ASSERT_TRUE(TakeScreenshotUntil( + kChildTappedColor, [](std::map histogram) { + // Expect parent, overlay and child background colors. + // With parent color > child color and overlay color > child color. + const size_t overlay_pixel_count = OverlayPixelCount(histogram); + EXPECT_GT(histogram[kParentBackgroundColor], 0u); + EXPECT_GT(overlay_pixel_count, 0u); + EXPECT_EQ(histogram[kChildBackgroundColor], 0u); + EXPECT_GT(histogram[kChildTappedColor], 0u); + EXPECT_GT(histogram[kParentBackgroundColor], + histogram[kChildTappedColor]); + EXPECT_GT(overlay_pixel_count, histogram[kChildTappedColor]); + })); +} + +} // namespace flutter_embedder_test diff --git a/shell/platform/fuchsia/flutter/tests/integration/embedder/meta/flutter-embedder-test.cml b/shell/platform/fuchsia/flutter/tests/integration/embedder/meta/flutter-embedder-test.cml new file mode 100644 index 0000000000000..b053b0a4cc24a --- /dev/null +++ b/shell/platform/fuchsia/flutter/tests/integration/embedder/meta/flutter-embedder-test.cml @@ -0,0 +1,27 @@ +{ + include: [ + "gtest_runner.shard.cml", + "sys/component/realm_builder_absolute.shard.cml", + + // This test needs both the vulkan facet and the hermetic-tier-2 facet, + // so we are forced to make it a system test. + "sys/testing/system-test.shard.cml", + ], + program: { + binary: "bin/app", + }, + offer: [ + { + // Offer capabilities needed by components in this test realm. + // Keep it minimal, describe only what's actually needed. + protocol: [ + "fuchsia.logger.LogSink", + "fuchsia.sysmem.Allocator", + "fuchsia.tracing.provider.Registry", + "fuchsia.vulkan.loader.Loader", + ], + from: "parent", + to: "#realm_builder", + }, + ], +} diff --git a/shell/platform/fuchsia/flutter/tests/integration/embedder/meta/gtest_runner.shard.cml b/shell/platform/fuchsia/flutter/tests/integration/embedder/meta/gtest_runner.shard.cml new file mode 100644 index 0000000000000..4593cf2c66ad3 --- /dev/null +++ b/shell/platform/fuchsia/flutter/tests/integration/embedder/meta/gtest_runner.shard.cml @@ -0,0 +1,14 @@ +{ + program: { + runner: "gtest_runner", + }, + capabilities: [ + { protocol: "fuchsia.test.Suite" }, + ], + expose: [ + { + protocol: "fuchsia.test.Suite", + from: "self", + }, + ], +} diff --git a/shell/platform/fuchsia/flutter/tests/integration/embedder/parent-view/BUILD.gn b/shell/platform/fuchsia/flutter/tests/integration/embedder/parent-view/BUILD.gn new file mode 100644 index 0000000000000..ceaf8299f4a1b --- /dev/null +++ b/shell/platform/fuchsia/flutter/tests/integration/embedder/parent-view/BUILD.gn @@ -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. + +import("//build/fuchsia/sdk.gni") +import("//flutter/tools/fuchsia/dart/dart_library.gni") +import("//flutter/tools/fuchsia/flutter/flutter_component.gni") +import("//flutter/tools/fuchsia/gn-sdk/package.gni") + +dart_library("lib") { + package_name = "parent-view" + sources = [ "parent_view.dart" ] + + deps = [ + "//flutter/shell/platform/fuchsia/dart:args", + "//flutter/shell/platform/fuchsia/dart:vector_math", + "//flutter/tools/fuchsia/dart:fuchsia_services", + "//flutter/tools/fuchsia/dart:zircon", + "//flutter/tools/fuchsia/fidl:fuchsia.sys", + "//flutter/tools/fuchsia/fidl:fuchsia.ui.app", + "//flutter/tools/fuchsia/fidl:fuchsia.ui.views", + ] +} + +flutter_component("component") { + main_package = "parent-view" + component_name = "parent-view" + main_dart = "parent_view.dart" + manifest = rebase_path("meta/parent-view.cml") + deps = [ ":lib" ] +} + +fuchsia_package("package") { + package_name = "parent-view" + deps = [ ":component" ] +} diff --git a/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/parent-view2/parent_view2.dart b/shell/platform/fuchsia/flutter/tests/integration/embedder/parent-view/lib/parent_view.dart similarity index 71% rename from shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/parent-view2/parent_view2.dart rename to shell/platform/fuchsia/flutter/tests/integration/embedder/parent-view/lib/parent_view.dart index 6dcddd2d5c666..5fd521661150d 100644 --- a/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/parent-view2/parent_view2.dart +++ b/shell/platform/fuchsia/flutter/tests/integration/embedder/parent-view/lib/parent_view.dart @@ -4,42 +4,49 @@ import 'dart:convert'; import 'dart:typed_data'; +import 'dart:io'; import 'dart:ui'; -import 'package:vector_math/vector_math_64.dart' as vector_math_64; + import 'package:args/args.dart'; -import 'package:fidl_fuchsia_sys/fidl_async.dart'; import 'package:fidl_fuchsia_ui_app/fidl_async.dart'; import 'package:fidl_fuchsia_ui_views/fidl_async.dart'; import 'package:fuchsia_services/services.dart'; +import 'package:vector_math/vector_math_64.dart' as vector_math_64; import 'package:zircon/zircon.dart'; -// TODO(richkadel): To run the test serving the runner and test packages from -// the flutter/engine package server (via -// `//flutter/tools/fuchsia/devshell/serve.sh`), change `fuchsia.com` to -// `engine`. -const _kChildAppUrl = - 'fuchsia-pkg://fuchsia.com/child-view2#meta/child-view2.cmx'; - -TestApp app; +final _argsCsvFilePath = '/config/data/args.csv'; void main(List args) { + print('parent-view: starting'); + + args = args + _GetArgsFromConfigFile(); final parser = ArgParser() ..addFlag('showOverlay', defaultsTo: false) ..addFlag('hitTestable', defaultsTo: true) - ..addFlag('focusable', defaultsTo: true); + ..addFlag('focusable', defaultsTo: true) + ..addFlag('useFlatland', defaultsTo: false); final arguments = parser.parse(args); for (final option in arguments.options) { - print('parent-view2: $option: ${arguments[option]}'); + print('parent-view: $option: ${arguments[option]}'); } - final childViewToken = _launchApp(_kChildAppUrl); - - app = TestApp( - ChildView(childViewToken), - showOverlay: arguments['showOverlay'], - hitTestable: arguments['hitTestable'], - focusable: arguments['focusable'], - ); + TestApp app; + final useFlatland = arguments['useFlatland']; + if (useFlatland) { + app = TestApp( + ChildView(_launchFlatlandChildView()), + showOverlay: arguments['showOverlay'], + hitTestable: arguments['hitTestable'], + focusable: arguments['focusable'], + ); + } else { + app = TestApp( + ChildView.gfx(_launchGfxChildView()), + showOverlay: arguments['showOverlay'], + hitTestable: arguments['hitTestable'], + focusable: arguments['focusable'], + ); + } app.run(); } @@ -64,19 +71,24 @@ class TestApp { void run() { childView.create(hitTestable, focusable, (ByteData reply) { - // The child-view2 should be attached to Scenic now. - // Ready to build the scene. + // Set up window allbacks. window.onPointerDataPacket = (PointerDataPacket packet) { for (final data in packet.data) { - if (data.change == PointerChange.up) { + if (data.change == PointerChange.down) { this._backgroundColor = _black; } } window.scheduleFrame(); }; + window.onMetricsChanged = () { + window.scheduleFrame(); + }; window.onBeginFrame = (Duration duration) { - app.beginFrame(duration); + this.beginFrame(duration); }; + + // The child view should be attached to Scenic now. + // Ready to build the scene. window.scheduleFrame(); }); } @@ -149,45 +161,16 @@ class TestApp { } } -ViewHolderToken _launchApp(String componentUrl) { - final incoming = Incoming(); - final componentController = ComponentControllerProxy(); - - final launcher = LauncherProxy(); - Incoming.fromSvcPath() - ..connectToService(launcher) - ..close(); - launcher.createComponent( - LaunchInfo( - url: componentUrl, - directoryRequest: incoming.request().passChannel(), - ), - componentController.ctrl.request(), - ); - launcher.ctrl.close(); - - ViewProviderProxy viewProvider = ViewProviderProxy(); - incoming - ..connectToService(viewProvider) - ..close(); - - final viewTokens = EventPairPair(); - assert(viewTokens.status == ZX.OK); - final viewHolderToken = ViewHolderToken(value: viewTokens.first); - final viewToken = ViewToken(value: viewTokens.second); - - viewProvider.createView(viewToken.value, null, null); - viewProvider.ctrl.close(); - - return viewHolderToken; -} - class ChildView { - - final ViewHolderToken viewToken; + final ViewHolderToken viewHolderToken; + final ViewportCreationToken viewportCreationToken; final int viewId; - ChildView(this.viewToken) : viewId = viewToken.value.handle.handle { + ChildView(this.viewportCreationToken) : viewHolderToken = null, viewId = viewportCreationToken.value.handle.handle { + assert(viewId != null); + } + + ChildView.gfx(this.viewHolderToken) : viewportCreationToken = null, viewId = viewHolderToken.value.handle.handle { assert(viewId != null); } @@ -227,3 +210,49 @@ class ChildView { callback); } } + +ViewportCreationToken _launchFlatlandChildView() { + ViewProviderProxy viewProvider = ViewProviderProxy(); + Incoming.fromSvcPath() + ..connectToService(viewProvider) + ..close(); + + final viewTokens = ChannelPair(); + assert(viewTokens.status == ZX.OK); + final viewportCreationToken = ViewportCreationToken(value: viewTokens.first); + final viewCreationToken = ViewCreationToken(value: viewTokens.second); + + final createViewArgs = CreateView2Args(viewCreationToken: viewCreationToken); + viewProvider.createView2(createViewArgs); + viewProvider.ctrl.close(); + + return viewportCreationToken; +} + +ViewHolderToken _launchGfxChildView() { + ViewProviderProxy viewProvider = ViewProviderProxy(); + Incoming.fromSvcPath() + ..connectToService(viewProvider) + ..close(); + + final viewTokens = EventPairPair(); + assert(viewTokens.status == ZX.OK); + final viewHolderToken = ViewHolderToken(value: viewTokens.first); + final viewToken = ViewToken(value: viewTokens.second); + + viewProvider.createView(viewToken.value, null, null); + viewProvider.ctrl.close(); + + return viewHolderToken; +} + +List _GetArgsFromConfigFile() { + List args; + final f = File(_argsCsvFilePath); + if (!f.existsSync()) { + return List.empty(); + } + final fileContentCsv = f.readAsStringSync(); + args = fileContentCsv.split('\n'); + return args; +} diff --git a/shell/platform/fuchsia/flutter/tests/integration/embedder/parent-view/meta/parent-view.cml b/shell/platform/fuchsia/flutter/tests/integration/embedder/parent-view/meta/parent-view.cml new file mode 100644 index 0000000000000..aea9b8bc24dab --- /dev/null +++ b/shell/platform/fuchsia/flutter/tests/integration/embedder/parent-view/meta/parent-view.cml @@ -0,0 +1,33 @@ +{ + include: [ "syslog/client.shard.cml" ], + program: { + data: "data/parent-view", + + // Always use the jit runner for now. + // TODO(fxbug.dev/106577): Implement manifest merging build rules for V2 components. + runner: "flutter_jit_runner", + }, + capabilities: [ + { + protocol: [ "fuchsia.ui.app.ViewProvider" ], + }, + ], + use: [ + { + protocol: [ + "fuchsia.ui.app.ViewProvider", + ], + }, + { + directory: "config-data", + rights: [ "r*" ], + path: "/config/data", + }, + ], + expose: [ + { + protocol: [ "fuchsia.ui.app.ViewProvider" ], + from: "self", + }, + ], +} diff --git a/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/parent-view2/pubspec.yaml b/shell/platform/fuchsia/flutter/tests/integration/embedder/parent-view/pubspec.yaml similarity index 100% rename from shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/parent-view2/pubspec.yaml rename to shell/platform/fuchsia/flutter/tests/integration/embedder/parent-view/pubspec.yaml diff --git a/shell/platform/linux/BUILD.gn b/shell/platform/linux/BUILD.gn index 6e70e49162033..4c1cbc4a75dea 100644 --- a/shell/platform/linux/BUILD.gn +++ b/shell/platform/linux/BUILD.gn @@ -141,6 +141,7 @@ source_set("flutter_linux_sources") { "fl_task_runner.cc", "fl_task_runner.h", "fl_text_input_plugin.cc", + "fl_text_input_view_delegate.cc", "fl_texture.cc", "fl_texture_gl.cc", "fl_texture_registrar.cc", @@ -234,6 +235,7 @@ executable("flutter_linux_unittests") { "testing/mock_settings.cc", "testing/mock_signal_handler.cc", "testing/mock_text_input_plugin.cc", + "testing/mock_text_input_view_delegate.cc", "testing/mock_texture_registrar.cc", ] diff --git a/shell/platform/linux/fl_accessible_node.cc b/shell/platform/linux/fl_accessible_node.cc index 0ecbf9b50af14..d0681de80e761 100644 --- a/shell/platform/linux/fl_accessible_node.cc +++ b/shell/platform/linux/fl_accessible_node.cc @@ -89,7 +89,6 @@ enum { kProp0, kPropEngine, kPropId, kPropLast }; static void fl_accessible_node_component_interface_init( AtkComponentIface* iface); static void fl_accessible_node_action_interface_init(AtkActionIface* iface); -static void fl_accessible_node_text_interface_init(AtkTextIface* iface); G_DEFINE_TYPE_WITH_CODE( FlAccessibleNode, @@ -99,9 +98,7 @@ G_DEFINE_TYPE_WITH_CODE( G_IMPLEMENT_INTERFACE(ATK_TYPE_COMPONENT, fl_accessible_node_component_interface_init) G_IMPLEMENT_INTERFACE(ATK_TYPE_ACTION, - fl_accessible_node_action_interface_init) - G_IMPLEMENT_INTERFACE(ATK_TYPE_TEXT, - fl_accessible_node_text_interface_init)) + fl_accessible_node_action_interface_init)) // Returns TRUE if [flag] has changed between [old_flags] and [flags]. static gboolean flag_is_changed(FlutterSemanticsFlag old_flags, @@ -338,13 +335,6 @@ static const gchar* fl_accessible_node_get_name(AtkAction* action, gint i) { return data->name; } -// Implements AtkText::get_text. -static gchar* fl_accessible_node_get_text(AtkText* text, - gint start_offset, - gint end_offset) { - return nullptr; -} - // Implements FlAccessibleNode::set_name. static void fl_accessible_node_set_name_impl(FlAccessibleNode* self, const gchar* name) { @@ -475,10 +465,6 @@ static void fl_accessible_node_action_interface_init(AtkActionIface* iface) { iface->get_name = fl_accessible_node_get_name; } -static void fl_accessible_node_text_interface_init(AtkTextIface* iface) { - iface->get_text = fl_accessible_node_get_text; -} - static void fl_accessible_node_init(FlAccessibleNode* self) { FlAccessibleNodePrivate* priv = FL_ACCESSIBLE_NODE_GET_PRIVATE(self); priv->actions = g_ptr_array_new(); diff --git a/shell/platform/linux/fl_text_input_plugin.cc b/shell/platform/linux/fl_text_input_plugin.cc index b4974c27101f7..bdc0762fe1f46 100644 --- a/shell/platform/linux/fl_text_input_plugin.cc +++ b/shell/platform/linux/fl_text_input_plugin.cc @@ -80,6 +80,8 @@ struct FlTextInputPluginPrivate { // Input method. GtkIMContext* im_context; + FlTextInputViewDelegate* view_delegate; + flutter::TextInputModel* text_model; // A 4x4 matrix that maps from `EditableText` local coordinates to the @@ -487,9 +489,13 @@ static void update_im_cursor_position(FlTextInputPlugin* self) { priv->composing_rect.y * priv->editabletext_transform[1][1] + priv->editabletext_transform[3][1] + priv->composing_rect.height; + // Transform from Flutter view coordinates to GTK window coordinates. + GdkRectangle preedit_rect = {}; + fl_text_input_view_delegate_translate_coordinates( + priv->view_delegate, x, y, &preedit_rect.x, &preedit_rect.y); + // Set the cursor location in window coordinates so that GTK can position any // system input method windows. - GdkRectangle preedit_rect = {x, y, 0, 0}; gtk_im_context_set_cursor_location(priv->im_context, &preedit_rect); } @@ -587,6 +593,12 @@ static void fl_text_input_plugin_dispose(GObject* object) { delete priv->text_model; priv->text_model = nullptr; } + if (priv->view_delegate != nullptr) { + g_object_remove_weak_pointer( + G_OBJECT(priv->view_delegate), + reinterpret_cast(&(priv->view_delegate))); + priv->view_delegate = nullptr; + } G_OBJECT_CLASS(fl_text_input_plugin_parent_class)->dispose(object); } @@ -719,10 +731,13 @@ static void init_im_context(FlTextInputPlugin* self, GtkIMContext* im_context) { G_CONNECT_SWAPPED); } -FlTextInputPlugin* fl_text_input_plugin_new(FlBinaryMessenger* messenger, - GtkIMContext* im_context) { +FlTextInputPlugin* fl_text_input_plugin_new( + FlBinaryMessenger* messenger, + GtkIMContext* im_context, + FlTextInputViewDelegate* view_delegate) { g_return_val_if_fail(FL_IS_BINARY_MESSENGER(messenger), nullptr); g_return_val_if_fail(GTK_IS_IM_CONTEXT(im_context), nullptr); + g_return_val_if_fail(FL_IS_TEXT_INPUT_VIEW_DELEGATE(view_delegate), nullptr); FlTextInputPlugin* self = FL_TEXT_INPUT_PLUGIN( g_object_new(fl_text_input_plugin_get_type(), nullptr)); @@ -737,6 +752,11 @@ FlTextInputPlugin* fl_text_input_plugin_new(FlBinaryMessenger* messenger, init_im_context(self, im_context); + priv->view_delegate = view_delegate; + g_object_add_weak_pointer( + G_OBJECT(view_delegate), + reinterpret_cast(&(priv->view_delegate))); + return self; } diff --git a/shell/platform/linux/fl_text_input_plugin.h b/shell/platform/linux/fl_text_input_plugin.h index d432698ea5f63..d2c25ebb16b50 100644 --- a/shell/platform/linux/fl_text_input_plugin.h +++ b/shell/platform/linux/fl_text_input_plugin.h @@ -8,6 +8,7 @@ #include #include "flutter/shell/platform/linux/fl_key_event.h" +#include "flutter/shell/platform/linux/fl_text_input_view_delegate.h" #include "flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h" G_BEGIN_DECLS @@ -38,14 +39,17 @@ struct _FlTextInputPluginClass { * fl_text_input_plugin_new: * @messenger: an #FlBinaryMessenger. * @im_context: (allow-none): a #GtkIMContext. + * @view_delegate: an #FlTextInputViewDelegate. * * Creates a new plugin that implements SystemChannels.textInput from the * Flutter services library. * * Returns: a new #FlTextInputPlugin. */ -FlTextInputPlugin* fl_text_input_plugin_new(FlBinaryMessenger* messenger, - GtkIMContext* im_context); +FlTextInputPlugin* fl_text_input_plugin_new( + FlBinaryMessenger* messenger, + GtkIMContext* im_context, + FlTextInputViewDelegate* view_delegate); /** * fl_text_input_plugin_filter_keypress diff --git a/shell/platform/linux/fl_text_input_plugin_test.cc b/shell/platform/linux/fl_text_input_plugin_test.cc index 3692e6584c9c8..8f0c55ccb02ac 100644 --- a/shell/platform/linux/fl_text_input_plugin_test.cc +++ b/shell/platform/linux/fl_text_input_plugin_test.cc @@ -11,6 +11,7 @@ #include "flutter/shell/platform/linux/testing/mock_binary_messenger.h" #include "flutter/shell/platform/linux/testing/mock_binary_messenger_response_handle.h" #include "flutter/shell/platform/linux/testing/mock_im_context.h" +#include "flutter/shell/platform/linux/testing/mock_text_input_view_delegate.h" #include "flutter/testing/testing.h" #include "gmock/gmock.h" @@ -189,9 +190,10 @@ static void send_key_event(FlTextInputPlugin* plugin, TEST(FlTextInputPluginTest, MessageHandler) { ::testing::NiceMock messenger; ::testing::NiceMock context; + ::testing::NiceMock delegate; g_autoptr(FlTextInputPlugin) plugin = - fl_text_input_plugin_new(messenger, context); + fl_text_input_plugin_new(messenger, context, delegate); EXPECT_NE(plugin, nullptr); EXPECT_TRUE(messenger.HasMessageHandler("flutter/textinput")); @@ -200,9 +202,10 @@ TEST(FlTextInputPluginTest, MessageHandler) { TEST(FlTextInputPluginTest, SetClient) { ::testing::NiceMock messenger; ::testing::NiceMock context; + ::testing::NiceMock delegate; g_autoptr(FlTextInputPlugin) plugin = - fl_text_input_plugin_new(messenger, context); + fl_text_input_plugin_new(messenger, context, delegate); EXPECT_NE(plugin, nullptr); g_autoptr(FlValue) args = build_input_config({.client_id = 1}); @@ -222,9 +225,10 @@ TEST(FlTextInputPluginTest, SetClient) { TEST(FlTextInputPluginTest, Show) { ::testing::NiceMock messenger; ::testing::NiceMock context; + ::testing::NiceMock delegate; g_autoptr(FlTextInputPlugin) plugin = - fl_text_input_plugin_new(messenger, context); + fl_text_input_plugin_new(messenger, context, delegate); EXPECT_NE(plugin, nullptr); EXPECT_CALL(context, @@ -246,9 +250,10 @@ TEST(FlTextInputPluginTest, Show) { TEST(FlTextInputPluginTest, Hide) { ::testing::NiceMock messenger; ::testing::NiceMock context; + ::testing::NiceMock delegate; g_autoptr(FlTextInputPlugin) plugin = - fl_text_input_plugin_new(messenger, context); + fl_text_input_plugin_new(messenger, context, delegate); EXPECT_NE(plugin, nullptr); EXPECT_CALL(context, @@ -270,9 +275,10 @@ TEST(FlTextInputPluginTest, Hide) { TEST(FlTextInputPluginTest, ClearClient) { ::testing::NiceMock messenger; ::testing::NiceMock context; + ::testing::NiceMock delegate; g_autoptr(FlTextInputPlugin) plugin = - fl_text_input_plugin_new(messenger, context); + fl_text_input_plugin_new(messenger, context, delegate); EXPECT_NE(plugin, nullptr); g_autoptr(FlValue) null = fl_value_new_null(); @@ -291,9 +297,10 @@ TEST(FlTextInputPluginTest, ClearClient) { TEST(FlTextInputPluginTest, PerformAction) { ::testing::NiceMock messenger; ::testing::NiceMock context; + ::testing::NiceMock delegate; g_autoptr(FlTextInputPlugin) plugin = - fl_text_input_plugin_new(messenger, context); + fl_text_input_plugin_new(messenger, context, delegate); EXPECT_NE(plugin, nullptr); // set input config @@ -366,9 +373,10 @@ TEST(FlTextInputPluginTest, PerformAction) { TEST(FlTextInputPluginTest, MoveCursor) { ::testing::NiceMock messenger; ::testing::NiceMock context; + ::testing::NiceMock delegate; g_autoptr(FlTextInputPlugin) plugin = - fl_text_input_plugin_new(messenger, context); + fl_text_input_plugin_new(messenger, context, delegate); EXPECT_NE(plugin, nullptr); // set input config @@ -443,9 +451,10 @@ TEST(FlTextInputPluginTest, MoveCursor) { TEST(FlTextInputPluginTest, Select) { ::testing::NiceMock messenger; ::testing::NiceMock context; + ::testing::NiceMock delegate; g_autoptr(FlTextInputPlugin) plugin = - fl_text_input_plugin_new(messenger, context); + fl_text_input_plugin_new(messenger, context, delegate); EXPECT_NE(plugin, nullptr); // set input config @@ -520,9 +529,10 @@ TEST(FlTextInputPluginTest, Select) { TEST(FlTextInputPluginTest, Composing) { ::testing::NiceMock messenger; ::testing::NiceMock context; + ::testing::NiceMock delegate; g_autoptr(FlTextInputPlugin) plugin = - fl_text_input_plugin_new(messenger, context); + fl_text_input_plugin_new(messenger, context, delegate); EXPECT_NE(plugin, nullptr); g_signal_emit_by_name(context, "preedit-start", nullptr); @@ -589,9 +599,10 @@ TEST(FlTextInputPluginTest, Composing) { TEST(FlTextInputPluginTest, SurroundingText) { ::testing::NiceMock messenger; ::testing::NiceMock context; + ::testing::NiceMock delegate; g_autoptr(FlTextInputPlugin) plugin = - fl_text_input_plugin_new(messenger, context); + fl_text_input_plugin_new(messenger, context, delegate); EXPECT_NE(plugin, nullptr); // set input config @@ -658,9 +669,10 @@ TEST(FlTextInputPluginTest, SurroundingText) { TEST(FlTextInputPluginTest, SetMarkedTextRect) { ::testing::NiceMock messenger; ::testing::NiceMock context; + ::testing::NiceMock delegate; g_autoptr(FlTextInputPlugin) plugin = - fl_text_input_plugin_new(messenger, context); + fl_text_input_plugin_new(messenger, context, delegate); EXPECT_NE(plugin, nullptr); g_signal_emit_by_name(context, "preedit-start", nullptr); @@ -719,11 +731,18 @@ TEST(FlTextInputPluginTest, SetMarkedTextRect) { ::testing::_, SuccessResponse(null), ::testing::_)) .WillOnce(::testing::Return(true)); + EXPECT_CALL(delegate, fl_text_input_view_delegate_translate_coordinates( + ::testing::Eq(delegate), + ::testing::Eq(27), ::testing::Eq(32), ::testing::_, + ::testing::_)) + .WillOnce(::testing::DoAll(::testing::SetArgPointee<3>(123), + ::testing::SetArgPointee<4>(456))); + EXPECT_CALL(context, gtk_im_context_set_cursor_location( ::testing::Eq(context), ::testing::Pointee(::testing::AllOf( - ::testing::Field(&GdkRectangle::x, 27), - ::testing::Field(&GdkRectangle::y, 32), + ::testing::Field(&GdkRectangle::x, 123), + ::testing::Field(&GdkRectangle::y, 456), ::testing::Field(&GdkRectangle::width, 0), ::testing::Field(&GdkRectangle::height, 0))))); @@ -733,9 +752,10 @@ TEST(FlTextInputPluginTest, SetMarkedTextRect) { TEST(FlTextInputPluginTest, TextInputTypeNone) { ::testing::NiceMock messenger; ::testing::NiceMock context; + ::testing::NiceMock delegate; g_autoptr(FlTextInputPlugin) plugin = - fl_text_input_plugin_new(messenger, context); + fl_text_input_plugin_new(messenger, context, delegate); EXPECT_NE(plugin, nullptr); g_autoptr(FlValue) args = build_input_config({ @@ -775,9 +795,10 @@ TEST(FlTextInputPluginTest, TextInputTypeNone) { TEST(FlTextInputPluginTest, TextEditingDelta) { ::testing::NiceMock messenger; ::testing::NiceMock context; + ::testing::NiceMock delegate; g_autoptr(FlTextInputPlugin) plugin = - fl_text_input_plugin_new(messenger, context); + fl_text_input_plugin_new(messenger, context, delegate); EXPECT_NE(plugin, nullptr); // set config @@ -846,9 +867,10 @@ TEST(FlTextInputPluginTest, TextEditingDelta) { TEST(FlTextInputPluginTest, ComposingDelta) { ::testing::NiceMock messenger; ::testing::NiceMock context; + ::testing::NiceMock delegate; g_autoptr(FlTextInputPlugin) plugin = - fl_text_input_plugin_new(messenger, context); + fl_text_input_plugin_new(messenger, context, delegate); EXPECT_NE(plugin, nullptr); // set config diff --git a/shell/platform/linux/fl_text_input_view_delegate.cc b/shell/platform/linux/fl_text_input_view_delegate.cc new file mode 100644 index 0000000000000..52e94d4391a88 --- /dev/null +++ b/shell/platform/linux/fl_text_input_view_delegate.cc @@ -0,0 +1,24 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/shell/platform/linux/fl_text_input_view_delegate.h" + +G_DEFINE_INTERFACE(FlTextInputViewDelegate, + fl_text_input_view_delegate, + G_TYPE_OBJECT) + +static void fl_text_input_view_delegate_default_init( + FlTextInputViewDelegateInterface* iface) {} + +void fl_text_input_view_delegate_translate_coordinates( + FlTextInputViewDelegate* self, + gint view_x, + gint view_y, + gint* window_x, + gint* window_y) { + g_return_if_fail(FL_IS_TEXT_INPUT_VIEW_DELEGATE(self)); + + FL_TEXT_INPUT_VIEW_DELEGATE_GET_IFACE(self)->translate_coordinates( + self, view_x, view_y, window_x, window_y); +} diff --git a/shell/platform/linux/fl_text_input_view_delegate.h b/shell/platform/linux/fl_text_input_view_delegate.h new file mode 100644 index 0000000000000..6c8b491f19f89 --- /dev/null +++ b/shell/platform/linux/fl_text_input_view_delegate.h @@ -0,0 +1,48 @@ +// 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_SHELL_PLATFORM_LINUX_FL_TEXT_INPUT_VIEW_DELEGATE_H_ +#define FLUTTER_SHELL_PLATFORM_LINUX_FL_TEXT_INPUT_VIEW_DELEGATE_H_ + +#include + +#include "flutter/shell/platform/embedder/embedder.h" + +G_BEGIN_DECLS + +G_DECLARE_INTERFACE(FlTextInputViewDelegate, + fl_text_input_view_delegate, + FL, + TEXT_INPUT_VIEW_DELEGATE, + GObject); + +/** + * FlTextInputViewDelegate: + * + * An interface for a class that provides `FlTextInputPlugin` with + * view-related features. + * + * This interface is typically implemented by `FlView`. + */ + +struct _FlTextInputViewDelegateInterface { + GTypeInterface g_iface; + + void (*translate_coordinates)(FlTextInputViewDelegate* delegate, + gint view_x, + gint view_y, + gint* window_x, + gint* window_y); +}; + +void fl_text_input_view_delegate_translate_coordinates( + FlTextInputViewDelegate* delegate, + gint view_x, + gint view_y, + gint* window_x, + gint* window_y); + +G_END_DECLS + +#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_TEXT_INPUT_VIEW_DELEGATE_H_ diff --git a/shell/platform/linux/fl_view.cc b/shell/platform/linux/fl_view.cc index de676c90b0999..445fc77a23826 100644 --- a/shell/platform/linux/fl_view.cc +++ b/shell/platform/linux/fl_view.cc @@ -20,6 +20,7 @@ #include "flutter/shell/platform/linux/fl_scrolling_manager.h" #include "flutter/shell/platform/linux/fl_scrolling_view_delegate.h" #include "flutter/shell/platform/linux/fl_text_input_plugin.h" +#include "flutter/shell/platform/linux/fl_text_input_view_delegate.h" #include "flutter/shell/platform/linux/fl_view_accessible.h" #include "flutter/shell/platform/linux/public/flutter_linux/fl_engine.h" #include "flutter/shell/platform/linux/public/flutter_linux/fl_plugin_registry.h" @@ -82,6 +83,9 @@ static void fl_view_keyboard_delegate_iface_init( static void fl_view_scrolling_delegate_iface_init( FlScrollingViewDelegateInterface* iface); +static void fl_view_text_input_delegate_iface_init( + FlTextInputViewDelegateInterface* iface); + G_DEFINE_TYPE_WITH_CODE( FlView, fl_view, @@ -91,18 +95,22 @@ G_DEFINE_TYPE_WITH_CODE( G_IMPLEMENT_INTERFACE(fl_keyboard_view_delegate_get_type(), fl_view_keyboard_delegate_iface_init) G_IMPLEMENT_INTERFACE(fl_scrolling_view_delegate_get_type(), - fl_view_scrolling_delegate_iface_init)) + fl_view_scrolling_delegate_iface_init) + G_IMPLEMENT_INTERFACE(fl_text_input_view_delegate_get_type(), + fl_view_text_input_delegate_iface_init)) // Initialize keyboard manager. static void init_keyboard(FlView* self) { FlBinaryMessenger* messenger = fl_engine_get_binary_messenger(self->engine); - GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(self)); + GdkWindow* window = + gtk_widget_get_window(gtk_widget_get_toplevel(GTK_WIDGET(self))); g_return_if_fail(GDK_IS_WINDOW(window)); g_autoptr(GtkIMContext) im_context = gtk_im_multicontext_new(); gtk_im_context_set_client_window(im_context, window); - self->text_input_plugin = fl_text_input_plugin_new(messenger, im_context); + self->text_input_plugin = fl_text_input_plugin_new( + messenger, im_context, FL_TEXT_INPUT_VIEW_DELEGATE(self)); self->keyboard_manager = fl_keyboard_manager_new(FL_KEYBOARD_VIEW_DELEGATE(self)); } @@ -333,6 +341,18 @@ static void fl_view_scrolling_delegate_iface_init( }; } +static void fl_view_text_input_delegate_iface_init( + FlTextInputViewDelegateInterface* iface) { + iface->translate_coordinates = [](FlTextInputViewDelegate* delegate, + gint view_x, gint view_y, gint* window_x, + gint* window_y) { + FlView* self = FL_VIEW(delegate); + gtk_widget_translate_coordinates(GTK_WIDGET(self), + gtk_widget_get_toplevel(GTK_WIDGET(self)), + view_x, view_y, window_x, window_y); + }; +} + // Signal handler for GtkWidget::button-press-event static gboolean button_press_event_cb(GtkWidget* widget, GdkEventButton* event, diff --git a/shell/platform/linux/testing/mock_text_input_view_delegate.cc b/shell/platform/linux/testing/mock_text_input_view_delegate.cc new file mode 100644 index 0000000000000..fa8259cb2696a --- /dev/null +++ b/shell/platform/linux/testing/mock_text_input_view_delegate.cc @@ -0,0 +1,74 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/shell/platform/linux/testing/mock_text_input_view_delegate.h" + +using namespace flutter::testing; + +G_DECLARE_FINAL_TYPE(FlMockTextInputViewDelegate, + fl_mock_text_input_view_delegate, + FL, + MOCK_TEXT_INPUT_VIEW_DELEGATE, + GObject) + +struct _FlMockTextInputViewDelegate { + GObject parent_instance; + MockTextInputViewDelegate* mock; +}; + +static FlTextInputViewDelegate* fl_mock_text_input_view_delegate_new( + MockTextInputViewDelegate* mock) { + FlMockTextInputViewDelegate* self = FL_MOCK_TEXT_INPUT_VIEW_DELEGATE( + g_object_new(fl_mock_text_input_view_delegate_get_type(), nullptr)); + self->mock = mock; + return FL_TEXT_INPUT_VIEW_DELEGATE(self); +} + +MockTextInputViewDelegate::MockTextInputViewDelegate() + : instance_(fl_mock_text_input_view_delegate_new(this)) {} + +MockTextInputViewDelegate::~MockTextInputViewDelegate() { + if (FL_IS_TEXT_INPUT_VIEW_DELEGATE(instance_)) { + g_clear_object(&instance_); + } +} + +MockTextInputViewDelegate::operator FlTextInputViewDelegate*() { + return instance_; +} + +static void fl_mock_text_input_view_delegate_iface_init( + FlTextInputViewDelegateInterface* iface); + +G_DEFINE_TYPE_WITH_CODE( + FlMockTextInputViewDelegate, + fl_mock_text_input_view_delegate, + G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE(fl_text_input_view_delegate_get_type(), + fl_mock_text_input_view_delegate_iface_init)) + +static void fl_mock_text_input_view_delegate_class_init( + FlMockTextInputViewDelegateClass* klass) {} + +static void fl_mock_text_input_view_delegate_translate_coordinates( + FlTextInputViewDelegate* view_delegate, + gint view_x, + gint view_y, + gint* window_x, + gint* window_y) { + g_return_if_fail(FL_IS_MOCK_TEXT_INPUT_VIEW_DELEGATE(view_delegate)); + FlMockTextInputViewDelegate* self = + FL_MOCK_TEXT_INPUT_VIEW_DELEGATE(view_delegate); + self->mock->fl_text_input_view_delegate_translate_coordinates( + view_delegate, view_x, view_y, window_x, window_y); +} + +static void fl_mock_text_input_view_delegate_iface_init( + FlTextInputViewDelegateInterface* iface) { + iface->translate_coordinates = + fl_mock_text_input_view_delegate_translate_coordinates; +} + +static void fl_mock_text_input_view_delegate_init( + FlMockTextInputViewDelegate* self) {} diff --git a/shell/platform/linux/testing/mock_text_input_view_delegate.h b/shell/platform/linux/testing/mock_text_input_view_delegate.h new file mode 100644 index 0000000000000..ae43045e15d92 --- /dev/null +++ b/shell/platform/linux/testing/mock_text_input_view_delegate.h @@ -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. + +#ifndef FLUTTER_SHELL_PLATFORM_LINUX_TESTING_MOCK_TEXT_INPUT_VIEW_DELEGATE_H_ +#define FLUTTER_SHELL_PLATFORM_LINUX_TESTING_MOCK_TEXT_INPUT_VIEW_DELEGATE_H_ + +#include + +#include "flutter/shell/platform/linux/fl_text_input_view_delegate.h" + +#include "gmock/gmock.h" + +namespace flutter { +namespace testing { + +// Mock for FlTextInputVuewDelegate. +class MockTextInputViewDelegate { + public: + MockTextInputViewDelegate(); + ~MockTextInputViewDelegate(); + + operator FlTextInputViewDelegate*(); + + MOCK_METHOD5(fl_text_input_view_delegate_translate_coordinates, + void(FlTextInputViewDelegate* delegate, + gint view_x, + gint view_y, + gint* window_x, + gint* window_y)); + + private: + FlTextInputViewDelegate* instance_ = nullptr; +}; + +} // namespace testing +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_LINUX_TESTING_MOCK_TEXT_INPUT_VIEW_DELEGATE_H_ diff --git a/shell/platform/windows/BUILD.gn b/shell/platform/windows/BUILD.gn index 3aa94757e4cff..fd570feb1abae 100644 --- a/shell/platform/windows/BUILD.gn +++ b/shell/platform/windows/BUILD.gn @@ -82,8 +82,6 @@ source_set("flutter_windows_source") { "keyboard_utils.h", "platform_handler.cc", "platform_handler.h", - "platform_handler_win32.cc", - "platform_handler_win32.h", "sequential_id_generator.cc", "sequential_id_generator.h", "settings_plugin.cc", @@ -92,10 +90,8 @@ source_set("flutter_windows_source") { "system_utils.h", "task_runner.cc", "task_runner.h", - "task_runner_win32.cc", - "task_runner_win32.h", - "task_runner_win32_window.cc", - "task_runner_win32_window.h", + "task_runner_window.cc", + "task_runner_window.h", "text_input_manager.cc", "text_input_manager.h", "text_input_plugin.cc", @@ -107,6 +103,8 @@ source_set("flutter_windows_source") { "window_proc_delegate_manager.cc", "window_proc_delegate_manager.h", "window_state.h", + "windows_proc_table.cc", + "windows_proc_table.h", ] libs = [ @@ -162,6 +160,7 @@ shared_library("flutter_windows") { } test_fixtures("flutter_windows_fixtures") { + dart_main = "fixtures/main.dart" fixtures = [] } @@ -177,6 +176,7 @@ executable("flutter_windows_unittests") { "flutter_window_unittests.cc", "flutter_windows_engine_unittests.cc", "flutter_windows_texture_registrar_unittests.cc", + "flutter_windows_unittests.cc", "flutter_windows_view_unittests.cc", "keyboard_key_channel_handler_unittests.cc", "keyboard_key_embedder_handler_unittests.cc", @@ -184,7 +184,6 @@ executable("flutter_windows_unittests") { "keyboard_unittests.cc", "keyboard_utils_unittests.cc", "platform_handler_unittests.cc", - "platform_handler_win32_unittests.cc", "sequential_id_generator_unittests.cc", "settings_plugin_unittests.cc", "system_utils_unittests.cc", @@ -192,6 +191,7 @@ executable("flutter_windows_unittests") { "testing/engine_modifier.h", "testing/flutter_window_test.cc", "testing/flutter_window_test.h", + "testing/mock_direct_manipulation.h", "testing/mock_gl_functions.h", "testing/mock_text_input_manager.cc", "testing/mock_text_input_manager.h", @@ -199,9 +199,16 @@ executable("flutter_windows_unittests") { "testing/mock_window.h", "testing/mock_window_binding_handler.cc", "testing/mock_window_binding_handler.h", + "testing/mock_windows_proc_table.h", "testing/test_keyboard.cc", "testing/test_keyboard.h", "testing/test_keyboard_unittests.cc", + "testing/windows_test.cc", + "testing/windows_test.h", + "testing/windows_test_config_builder.cc", + "testing/windows_test_config_builder.h", + "testing/windows_test_context.cc", + "testing/windows_test_context.h", "testing/wm_builders.cc", "testing/wm_builders.h", "text_input_plugin_unittest.cc", @@ -209,6 +216,9 @@ executable("flutter_windows_unittests") { "window_unittests.cc", ] + configs += + [ "//flutter/shell/platform/common:desktop_library_implementation" ] + public_configs = [ "//flutter:config" ] deps = [ @@ -219,6 +229,8 @@ executable("flutter_windows_unittests") { "//flutter/shell/platform/embedder:embedder_as_internal_library", "//flutter/shell/platform/embedder:embedder_test_utils", "//flutter/testing", + "//flutter/testing:dart", + "//flutter/third_party/tonic", "//third_party/rapidjson", ] } diff --git a/shell/platform/windows/accessibility_bridge_delegate_windows_unittests.cc b/shell/platform/windows/accessibility_bridge_delegate_windows_unittests.cc index 27d2fedce3b85..5db7fc4c7de38 100644 --- a/shell/platform/windows/accessibility_bridge_delegate_windows_unittests.cc +++ b/shell/platform/windows/accessibility_bridge_delegate_windows_unittests.cc @@ -86,7 +86,7 @@ std::unique_ptr GetTestEngine() { MockEmbedderApiForKeyboard(modifier, std::make_shared()); - engine->RunWithEntrypoint(nullptr); + engine->Run(); return engine; } diff --git a/shell/platform/windows/angle_surface_manager.cc b/shell/platform/windows/angle_surface_manager.cc index 8f8daf02ddd54..95eb1d1224873 100644 --- a/shell/platform/windows/angle_surface_manager.cc +++ b/shell/platform/windows/angle_surface_manager.cc @@ -4,15 +4,16 @@ #include "flutter/shell/platform/windows/angle_surface_manager.h" -#include #include +#include "flutter/fml/logging.h" + // Logs an EGL error to stderr. This automatically calls eglGetError() // and logs the error code. static void LogEglError(std::string message) { EGLint error = eglGetError(); - std::cerr << "EGL: " << message << std::endl; - std::cerr << "EGL: eglGetError returned " << error << std::endl; + FML_LOG(ERROR) << "EGL: " << message; + FML_LOG(ERROR) << "EGL: eglGetError returned " << error; } namespace flutter { @@ -245,8 +246,8 @@ void AngleSurfaceManager::ResizeSurface(WindowsRenderTarget* render_target, ClearContext(); DestroySurface(); if (!CreateSurface(render_target, width, height)) { - std::cerr << "AngleSurfaceManager::ResizeSurface failed to create surface" - << std::endl; + FML_LOG(ERROR) + << "AngleSurfaceManager::ResizeSurface failed to create surface"; } } } diff --git a/shell/platform/windows/client_wrapper/flutter_engine.cc b/shell/platform/windows/client_wrapper/flutter_engine.cc index e9130805c824f..00ccbbcff7cab 100644 --- a/shell/platform/windows/client_wrapper/flutter_engine.cc +++ b/shell/platform/windows/client_wrapper/flutter_engine.cc @@ -16,6 +16,7 @@ FlutterEngine::FlutterEngine(const DartProject& project) { c_engine_properties.assets_path = project.assets_path().c_str(); c_engine_properties.icu_data_path = project.icu_data_path().c_str(); c_engine_properties.aot_library_path = project.aot_library_path().c_str(); + c_engine_properties.dart_entrypoint = project.dart_entrypoint().c_str(); const std::vector& entrypoint_args = project.dart_entrypoint_arguments(); @@ -40,6 +41,10 @@ FlutterEngine::~FlutterEngine() { ShutDown(); } +bool FlutterEngine::Run() { + return Run(nullptr); +} + bool FlutterEngine::Run(const char* entry_point) { if (!engine_) { std::cerr << "Cannot run an engine that failed creation." << std::endl; @@ -83,6 +88,18 @@ FlutterDesktopPluginRegistrarRef FlutterEngine::GetRegistrarForPlugin( return FlutterDesktopEngineGetPluginRegistrar(engine_, plugin_name.c_str()); } +void FlutterEngine::SetNextFrameCallback(std::function callback) { + next_frame_callback_ = std::move(callback); + FlutterDesktopEngineSetNextFrameCallback( + engine_, + [](void* user_data) { + FlutterEngine* self = static_cast(user_data); + self->next_frame_callback_(); + self->next_frame_callback_ = nullptr; + }, + this); +} + FlutterDesktopEngineRef FlutterEngine::RelinquishEngine() { owns_engine_ = false; return engine_; diff --git a/shell/platform/windows/client_wrapper/flutter_engine_unittests.cc b/shell/platform/windows/client_wrapper/flutter_engine_unittests.cc index e52fb336a3373..a1274998eadce 100644 --- a/shell/platform/windows/client_wrapper/flutter_engine_unittests.cc +++ b/shell/platform/windows/client_wrapper/flutter_engine_unittests.cc @@ -46,6 +46,13 @@ class TestFlutterWindowsApi : public testing::StubFlutterWindowsApi { // |flutter::testing::StubFlutterWindowsApi| uint64_t EngineProcessMessages() override { return 99; } + // |flutter::testing::StubFlutterWindowsApi| + void EngineSetNextFrameCallback(VoidCallback callback, + void* user_data) override { + next_frame_callback_ = callback; + next_frame_user_data_ = user_data; + } + // |flutter::testing::StubFlutterWindowsApi| void EngineReloadSystemFonts() override { reload_fonts_called_ = true; } @@ -61,12 +68,20 @@ class TestFlutterWindowsApi : public testing::StubFlutterWindowsApi { return dart_entrypoint_arguments_; } + bool has_next_frame_callback() { return next_frame_callback_ != nullptr; } + void run_next_frame_callback() { + next_frame_callback_(next_frame_user_data_); + next_frame_callback_ = nullptr; + } + private: bool create_called_ = false; bool run_called_ = false; bool destroy_called_ = false; bool reload_fonts_called_ = false; std::vector dart_entrypoint_arguments_; + VoidCallback next_frame_callback_ = nullptr; + void* next_frame_user_data_ = nullptr; }; } // namespace @@ -86,6 +101,23 @@ TEST(FlutterEngineTest, CreateDestroy) { EXPECT_EQ(test_api->destroy_called(), true); } +TEST(FlutterEngineTest, CreateDestroyWithCustomEntrypoint) { + testing::ScopedStubFlutterWindowsApi scoped_api_stub( + std::make_unique()); + auto test_api = static_cast(scoped_api_stub.stub()); + { + DartProject project(L"fake/project/path"); + project.set_dart_entrypoint("customEntrypoint"); + FlutterEngine engine(project); + engine.Run(); + EXPECT_EQ(test_api->create_called(), true); + EXPECT_EQ(test_api->run_called(), true); + EXPECT_EQ(test_api->destroy_called(), false); + } + // Destroying should implicitly shut down if it hasn't been done manually. + EXPECT_EQ(test_api->destroy_called(), true); +} + TEST(FlutterEngineTest, ExplicitShutDown) { testing::ScopedStubFlutterWindowsApi scoped_api_stub( std::make_unique()); @@ -151,4 +183,22 @@ TEST(FlutterEngineTest, DartEntrypointArgs) { EXPECT_TRUE(arguments[1] == arguments_ref[1]); } +TEST(FlutterEngineTest, SetNextFrameCallback) { + DartProject project(L"data"); + testing::ScopedStubFlutterWindowsApi scoped_api_stub( + std::make_unique()); + auto test_api = static_cast(scoped_api_stub.stub()); + + FlutterEngine engine(DartProject(L"fake/project/path")); + + bool success = false; + engine.SetNextFrameCallback([&success]() { success = true; }); + + EXPECT_TRUE(test_api->has_next_frame_callback()); + + test_api->run_next_frame_callback(); + + EXPECT_TRUE(success); +} + } // namespace flutter diff --git a/shell/platform/windows/client_wrapper/include/flutter/dart_project.h b/shell/platform/windows/client_wrapper/include/flutter/dart_project.h index cbc07ff502359..903cfc45e9299 100644 --- a/shell/platform/windows/client_wrapper/include/flutter/dart_project.h +++ b/shell/platform/windows/client_wrapper/include/flutter/dart_project.h @@ -45,6 +45,20 @@ class DartProject { ~DartProject() = default; + // Sets the Dart entrypoint to the specified value. + // + // If not set, the default entrypoint (main) is used. Custom Dart entrypoints + // must be decorated with `@pragma('vm:entry-point')`. + void set_dart_entrypoint(const std::string& entrypoint) { + if (entrypoint.empty()) { + return; + } + dart_entrypoint_ = entrypoint; + } + + // Returns the Dart entrypoint. + const std::string& dart_entrypoint() const { return dart_entrypoint_; } + // Sets the command line arguments that should be passed to the Dart // entrypoint. void set_dart_entrypoint_arguments(std::vector arguments) { @@ -77,6 +91,8 @@ class DartProject { // The path to the AOT library. This will always return a path, but non-AOT // builds will not be expected to actually have a library at that path. std::wstring aot_library_path_; + // The Dart entrypoint to launch. + std::string dart_entrypoint_; // The list of arguments to pass through to the Dart entrypoint. std::vector dart_entrypoint_arguments_; }; diff --git a/shell/platform/windows/client_wrapper/include/flutter/flutter_engine.h b/shell/platform/windows/client_wrapper/include/flutter/flutter_engine.h index 464c233fa9bfa..63a820ce2d6bd 100644 --- a/shell/platform/windows/client_wrapper/include/flutter/flutter_engine.h +++ b/shell/platform/windows/client_wrapper/include/flutter/flutter_engine.h @@ -22,7 +22,8 @@ namespace flutter { // // In the future, this will be the API surface used for all interactions with // the engine, rather than having them duplicated on FlutterViewController. -// For now it is only used in the rare where you need a headless Flutter engine. +// For now it is only used in the rare case where you need a headless Flutter +// engine. class FlutterEngine : public PluginRegistry { public: // Creates a new engine for running the given project. @@ -34,13 +35,17 @@ class FlutterEngine : public PluginRegistry { FlutterEngine(FlutterEngine const&) = delete; FlutterEngine& operator=(FlutterEngine const&) = delete; + // Starts running the engine at the entrypoint function specified in the + // DartProject used to configure the engine, or main() by default. + bool Run(); + // Starts running the engine, with an optional entry point. // // If provided, entry_point must be the name of a top-level function from the // same Dart library that contains the app's main() function, and must be // decorated with `@pragma(vm:entry-point)` to ensure the method is not // tree-shaken by the Dart compiler. If not provided, defaults to main(). - bool Run(const char* entry_point = nullptr); + bool Run(const char* entry_point); // Terminates the running engine. void ShutDown(); @@ -73,6 +78,12 @@ class FlutterEngine : public PluginRegistry { // This pointer will remain valid for the lifetime of this instance. BinaryMessenger* messenger() { return messenger_.get(); } + // Schedule a callback to be called after the next frame is drawn. + // + // This must be called from the platform thread. The callback is executed only + // once on the platform thread. + void SetNextFrameCallback(std::function callback); + private: // For access to RelinquishEngine. friend class FlutterViewController; @@ -96,6 +107,9 @@ class FlutterEngine : public PluginRegistry { // or if RelinquishEngine has been called (since the view controller will // run the engine if it hasn't already been run). bool has_been_run_ = false; + + // The callback to execute once the next frame is drawn. + std::function next_frame_callback_ = nullptr; }; } // namespace flutter diff --git a/shell/platform/windows/client_wrapper/testing/stub_flutter_windows_api.cc b/shell/platform/windows/client_wrapper/testing/stub_flutter_windows_api.cc index a829326a618c0..488cc49b8935b 100644 --- a/shell/platform/windows/client_wrapper/testing/stub_flutter_windows_api.cc +++ b/shell/platform/windows/client_wrapper/testing/stub_flutter_windows_api.cc @@ -111,6 +111,14 @@ uint64_t FlutterDesktopEngineProcessMessages(FlutterDesktopEngineRef engine) { return 0; } +void FlutterDesktopEngineSetNextFrameCallback(FlutterDesktopEngineRef engine, + VoidCallback callback, + void* user_data) { + if (s_stub_implementation) { + s_stub_implementation->EngineSetNextFrameCallback(callback, user_data); + } +} + void FlutterDesktopEngineReloadSystemFonts(FlutterDesktopEngineRef engine) { if (s_stub_implementation) { s_stub_implementation->EngineReloadSystemFonts(); diff --git a/shell/platform/windows/client_wrapper/testing/stub_flutter_windows_api.h b/shell/platform/windows/client_wrapper/testing/stub_flutter_windows_api.h index 125fb3614fbec..adc117a838f18 100644 --- a/shell/platform/windows/client_wrapper/testing/stub_flutter_windows_api.h +++ b/shell/platform/windows/client_wrapper/testing/stub_flutter_windows_api.h @@ -62,6 +62,10 @@ class StubFlutterWindowsApi { // Called for FlutterDesktopEngineProcessMessages. virtual uint64_t EngineProcessMessages() { return 0; } + // Called for FlutterDesktopEngineSetNextFrameCallback. + virtual void EngineSetNextFrameCallback(VoidCallback callback, + void* user_data) {} + // Called for FlutterDesktopEngineReloadSystemFonts. virtual void EngineReloadSystemFonts() {} diff --git a/shell/platform/windows/direct_manipulation.h b/shell/platform/windows/direct_manipulation.h index f25470ab4235d..40317333f6916 100644 --- a/shell/platform/windows/direct_manipulation.h +++ b/shell/platform/windows/direct_manipulation.h @@ -22,6 +22,7 @@ class DirectManipulationEventHandler; class DirectManipulationOwner { public: explicit DirectManipulationOwner(Window* window); + virtual ~DirectManipulationOwner() = default; // Initialize a DirectManipulation viewport with specified width and height. // These should match the width and height of the application window. int Init(unsigned int width, unsigned int height); @@ -34,7 +35,7 @@ class DirectManipulationOwner { WindowBindingHandlerDelegate* binding_handler_delegate); // Called when DM_POINTERHITTEST occurs with an acceptable pointer type. Will // start DirectManipulation for that interaction. - void SetContact(UINT contactId); + virtual void SetContact(UINT contactId); // Called to get updates from DirectManipulation. Should be called frequently // to provide smooth updates. void Update(); @@ -57,6 +58,8 @@ class DirectManipulationOwner { Microsoft::WRL::ComPtr viewport_; // Child needed for operation of the DirectManipulation API. fml::RefPtr handler_; + + FML_DISALLOW_COPY_AND_ASSIGN(DirectManipulationOwner); }; // Implements DirectManipulation event handling interfaces, receives calls from diff --git a/shell/platform/windows/external_texture_d3d.cc b/shell/platform/windows/external_texture_d3d.cc index dfd4d1979daf4..cd8785d717c67 100644 --- a/shell/platform/windows/external_texture_d3d.cc +++ b/shell/platform/windows/external_texture_d3d.cc @@ -6,8 +6,8 @@ #include #include -#include +#include "flutter/fml/logging.h" #include "flutter/shell/platform/embedder/embedder_struct_macros.h" namespace flutter { @@ -107,7 +107,7 @@ bool ExternalTextureD3d::CreateOrUpdateTexture( if (egl_surface_ == EGL_NO_SURFACE || eglBindTexImage(surface_manager_->egl_display(), egl_surface_, EGL_BACK_BUFFER) == EGL_FALSE) { - std::cerr << "Binding D3D surface failed." << std::endl; + FML_LOG(ERROR) << "Binding D3D surface failed."; } last_surface_handle_ = handle; } diff --git a/shell/platform/windows/fixtures/main.dart b/shell/platform/windows/fixtures/main.dart new file mode 100644 index 0000000000000..57ba615d984a7 --- /dev/null +++ b/shell/platform/windows/fixtures/main.dart @@ -0,0 +1,75 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:io' as io; +import 'dart:ui' as ui; + +// Signals a waiting latch in the native test. +void signal() native 'Signal'; + +// Signals a waiting latch in the native test, passing a boolean value. +void signalBoolValue(bool value) native 'SignalBoolValue'; + +// Signals a waiting latch in the native test, passing a string value. +void signalStringValue(String value) native 'SignalStringValue'; + +// Signals a waiting latch in the native test, which returns a value to the fixture. +bool signalBoolReturn() native 'SignalBoolReturn'; + +// Notify the native test that the first frame has been scheduled. +void notifyFirstFrameScheduled() native 'NotifyFirstFrameScheduled'; + +void main() { +} + +@pragma('vm:entry-point') +void customEntrypoint() { +} + +@pragma('vm:entry-point') +void verifyNativeFunction() { + signal(); +} + +@pragma('vm:entry-point') +void verifyNativeFunctionWithParameters() { + signalBoolValue(true); +} + +@pragma('vm:entry-point') +void verifyNativeFunctionWithReturn() { + bool value = signalBoolReturn(); + signalBoolValue(value); +} + +@pragma('vm:entry-point') +void readPlatformExecutable() { + signalStringValue(io.Platform.executable); +} + +@pragma('vm:entry-point') +void drawHelloWorld() { + ui.PlatformDispatcher.instance.onBeginFrame = (Duration duration) { + final ui.ParagraphBuilder paragraphBuilder = ui.ParagraphBuilder(ui.ParagraphStyle()) + ..addText('Hello world'); + final ui.Paragraph paragraph = paragraphBuilder.build(); + + paragraph.layout(const ui.ParagraphConstraints(width: 800.0)); + + final ui.PictureRecorder recorder = ui.PictureRecorder(); + final ui.Canvas canvas = ui.Canvas(recorder); + + canvas.drawParagraph(paragraph, ui.Offset.zero); + + final ui.Picture picture = recorder.endRecording(); + final ui.SceneBuilder sceneBuilder = ui.SceneBuilder() + ..addPicture(ui.Offset.zero, picture) + ..pop(); + + ui.window.render(sceneBuilder.build()); + }; + + ui.PlatformDispatcher.instance.scheduleFrame(); + notifyFirstFrameScheduled(); +} diff --git a/shell/platform/windows/flutter_project_bundle.cc b/shell/platform/windows/flutter_project_bundle.cc index d01ef65a17815..df289b526a0f4 100644 --- a/shell/platform/windows/flutter_project_bundle.cc +++ b/shell/platform/windows/flutter_project_bundle.cc @@ -5,8 +5,8 @@ #include "flutter/shell/platform/windows/flutter_project_bundle.h" #include -#include +#include "flutter/fml/logging.h" #include "flutter/shell/platform/common/engine_switches.h" // nogncheck #include "flutter/shell/platform/common/path_utils.h" @@ -20,6 +20,10 @@ FlutterProjectBundle::FlutterProjectBundle( aot_library_path_ = std::filesystem::path(properties.aot_library_path); } + if (properties.dart_entrypoint && properties.dart_entrypoint[0] != '\0') { + dart_entrypoint_ = properties.dart_entrypoint; + } + for (int i = 0; i < properties.dart_entrypoint_argc; i++) { dart_entrypoint_arguments_.push_back( std::string(properties.dart_entrypoint_argv[i])); @@ -30,9 +34,8 @@ FlutterProjectBundle::FlutterProjectBundle( (!aot_library_path_.empty() && aot_library_path_.is_relative())) { std::filesystem::path executable_location = GetExecutableDirectory(); if (executable_location.empty()) { - std::cerr - << "Unable to find executable location to resolve resource paths." - << std::endl; + FML_LOG(ERROR) + << "Unable to find executable location to resolve resource paths."; assets_path_ = std::filesystem::path(); icu_path_ = std::filesystem::path(); } else { @@ -55,14 +58,13 @@ bool FlutterProjectBundle::HasValidPaths() { UniqueAotDataPtr FlutterProjectBundle::LoadAotData( const FlutterEngineProcTable& engine_procs) { if (aot_library_path_.empty()) { - std::cerr - << "Attempted to load AOT data, but no aot_library_path was provided." - << std::endl; + FML_LOG(ERROR) + << "Attempted to load AOT data, but no aot_library_path was provided."; return UniqueAotDataPtr(nullptr, nullptr); } if (!std::filesystem::exists(aot_library_path_)) { - std::cerr << "Can't load AOT data from " << aot_library_path_.u8string() - << "; no such file." << std::endl; + FML_LOG(ERROR) << "Can't load AOT data from " + << aot_library_path_.u8string() << "; no such file."; return UniqueAotDataPtr(nullptr, nullptr); } std::string path_string = aot_library_path_.u8string(); @@ -72,7 +74,7 @@ UniqueAotDataPtr FlutterProjectBundle::LoadAotData( FlutterEngineAOTData data = nullptr; auto result = engine_procs.CreateAOTData(&source, &data); if (result != kSuccess) { - std::cerr << "Failed to load AOT data from: " << path_string << std::endl; + FML_LOG(ERROR) << "Failed to load AOT data from: " << path_string; return UniqueAotDataPtr(nullptr, nullptr); } return UniqueAotDataPtr(data, engine_procs.CollectAOTData); diff --git a/shell/platform/windows/flutter_project_bundle.h b/shell/platform/windows/flutter_project_bundle.h index 3cb5d3ca61622..09770b5c094fc 100644 --- a/shell/platform/windows/flutter_project_bundle.h +++ b/shell/platform/windows/flutter_project_bundle.h @@ -50,6 +50,9 @@ class FlutterProjectBundle { // Logs and returns nullptr on failure. UniqueAotDataPtr LoadAotData(const FlutterEngineProcTable& engine_procs); + // Returns the Dart entrypoint. + const std::string& dart_entrypoint() const { return dart_entrypoint_; } + // Returns the command line arguments to be passed through to the Dart // entrypoint. const std::vector& dart_entrypoint_arguments() const { @@ -63,6 +66,9 @@ class FlutterProjectBundle { // Path to the AOT library file, if any. std::filesystem::path aot_library_path_; + // The Dart entrypoint to launch. + std::string dart_entrypoint_; + // Dart entrypoint arguments. std::vector dart_entrypoint_arguments_; diff --git a/shell/platform/windows/flutter_window.cc b/shell/platform/windows/flutter_window.cc index a4c66be29db8c..67926e0a8ed37 100644 --- a/shell/platform/windows/flutter_window.cc +++ b/shell/platform/windows/flutter_window.cc @@ -8,6 +8,8 @@ #include #include +#include "flutter/fml/logging.h" + namespace flutter { namespace { @@ -121,7 +123,7 @@ static uint64_t ConvertWinButtonToFlutterButton(UINT button) { case XBUTTON2: return kFlutterPointerButtonMouseForward; } - std::cerr << "Mouse button not recognized: " << button << std::endl; + FML_LOG(WARNING) << "Mouse button not recognized: " << button; return 0; } @@ -250,8 +252,7 @@ bool FlutterWindow::OnBitmapSurfaceUpdated(const void* allocation, size_t row_bytes, size_t height) { HDC dc = ::GetDC(GetWindowHandle()); - BITMAPINFO bmi; - memset(&bmi, 0, sizeof(bmi)); + BITMAPINFO bmi = {}; bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bmi.bmiHeader.biWidth = row_bytes / 4; bmi.bmiHeader.biHeight = -height; diff --git a/shell/platform/windows/flutter_windows.cc b/shell/platform/windows/flutter_windows.cc index 9e07ed003b648..c26157e7c71c7 100644 --- a/shell/platform/windows/flutter_windows.cc +++ b/shell/platform/windows/flutter_windows.cc @@ -78,7 +78,7 @@ FlutterDesktopViewControllerRef FlutterDesktopViewControllerCreate( std::unique_ptr(EngineFromHandle(engine))); state->view->CreateRenderSurface(); if (!state->view->GetEngine()->running()) { - if (!state->view->GetEngine()->RunWithEntrypoint(nullptr)) { + if (!state->view->GetEngine()->Run()) { return nullptr; } } @@ -144,7 +144,7 @@ bool FlutterDesktopEngineDestroy(FlutterDesktopEngineRef engine_ref) { bool FlutterDesktopEngineRun(FlutterDesktopEngineRef engine, const char* entry_point) { - return EngineFromHandle(engine)->RunWithEntrypoint(entry_point); + return EngineFromHandle(engine)->Run(entry_point); } uint64_t FlutterDesktopEngineProcessMessages(FlutterDesktopEngineRef engine) { @@ -176,6 +176,13 @@ FlutterDesktopTextureRegistrarRef FlutterDesktopEngineGetTextureRegistrar( EngineFromHandle(engine)->texture_registrar()); } +void FlutterDesktopEngineSetNextFrameCallback(FlutterDesktopEngineRef engine, + VoidCallback callback, + void* user_data) { + EngineFromHandle(engine)->SetNextFrameCallback( + [callback, user_data]() { callback(user_data); }); +} + HWND FlutterDesktopViewGetHWND(FlutterDesktopViewRef view) { return ViewFromHandle(view)->GetPlatformWindow(); } diff --git a/shell/platform/windows/flutter_windows_engine.cc b/shell/platform/windows/flutter_windows_engine.cc index 20d7318aaceab..9396a9e2c15bc 100644 --- a/shell/platform/windows/flutter_windows_engine.cc +++ b/shell/platform/windows/flutter_windows_engine.cc @@ -10,6 +10,8 @@ #include #include +#include "flutter/fml/logging.h" +#include "flutter/fml/paths.h" #include "flutter/fml/platform/win/wstring_conversion.h" #include "flutter/shell/platform/common/client_wrapper/binary_messenger_impl.h" #include "flutter/shell/platform/common/path_utils.h" @@ -156,17 +158,18 @@ FlutterWindowsEngine::FlutterWindowsEngine(const FlutterProjectBundle& project) embedder_api_.struct_size = sizeof(FlutterEngineProcTable); FlutterEngineGetProcAddresses(&embedder_api_); - task_runner_ = TaskRunner::Create( - embedder_api_.GetCurrentTime, [this](const auto* task) { - if (!engine_) { - std::cerr << "Cannot post an engine task when engine is not running." - << std::endl; - return; - } - if (embedder_api_.RunTask(engine_, task) != kSuccess) { - std::cerr << "Failed to post an engine task." << std::endl; - } - }); + task_runner_ = + std::make_unique( + embedder_api_.GetCurrentTime, [this](const auto* task) { + if (!engine_) { + FML_LOG(ERROR) + << "Cannot post an engine task when engine is not running."; + return; + } + if (embedder_api_.RunTask(engine_, task) != kSuccess) { + FML_LOG(ERROR) << "Failed to post an engine task."; + } + }); // Set up the legacy structs backing the API handles. messenger_ = std::make_unique(); @@ -200,9 +203,13 @@ void FlutterWindowsEngine::SetSwitches( project_->SetSwitches(switches); } -bool FlutterWindowsEngine::RunWithEntrypoint(const char* entrypoint) { +bool FlutterWindowsEngine::Run() { + return Run(""); +} + +bool FlutterWindowsEngine::Run(std::string_view entrypoint) { if (!project_->HasValidPaths()) { - std::cerr << "Missing or unresolvable paths to assets." << std::endl; + FML_LOG(ERROR) << "Missing or unresolvable paths to assets."; return false; } std::string assets_path_string = project_->assets_path().u8string(); @@ -210,7 +217,7 @@ bool FlutterWindowsEngine::RunWithEntrypoint(const char* entrypoint) { if (embedder_api_.RunsAOTCompiledDartCode()) { aot_data_ = project_->LoadAotData(embedder_api_); if (!aot_data_) { - std::cerr << "Unable to start engine without AOT data." << std::endl; + FML_LOG(ERROR) << "Unable to start engine without AOT data."; return false; } } @@ -218,8 +225,9 @@ bool FlutterWindowsEngine::RunWithEntrypoint(const char* entrypoint) { // FlutterProjectArgs is expecting a full argv, so when processing it for // flags the first item is treated as the executable and ignored. Add a dummy // value so that all provided arguments are used. + std::string executable_name = GetExecutableName(); + std::vector argv = {executable_name.c_str()}; std::vector switches = project_->GetSwitches(); - std::vector argv = {"placeholder"}; std::transform( switches.begin(), switches.end(), std::back_inserter(argv), [](const std::string& arg) -> const char* { return arg.c_str(); }); @@ -254,10 +262,30 @@ bool FlutterWindowsEngine::RunWithEntrypoint(const char* entrypoint) { FlutterProjectArgs args = {}; args.struct_size = sizeof(FlutterProjectArgs); + args.shutdown_dart_vm_when_done = true; args.assets_path = assets_path_string.c_str(); args.icu_data_path = icu_path_string.c_str(); args.command_line_argc = static_cast(argv.size()); args.command_line_argv = argv.empty() ? nullptr : argv.data(); + + // Fail if conflicting non-default entrypoints are specified in the method + // argument and the project. + // + // TODO(cbracken): https://github.com/flutter/flutter/issues/109285 + // The entrypoint method parameter should eventually be removed from this + // method and only the entrypoint specified in project_ should be used. + if (!project_->dart_entrypoint().empty() && !entrypoint.empty() && + project_->dart_entrypoint() != entrypoint) { + FML_LOG(ERROR) << "Conflicting entrypoints were specified in " + "FlutterDesktopEngineProperties.dart_entrypoint and " + "FlutterDesktopEngineRun(engine, entry_point). "; + return false; + } + if (!entrypoint.empty()) { + args.custom_dart_entrypoint = entrypoint.data(); + } else if (!project_->dart_entrypoint().empty()) { + args.custom_dart_entrypoint = project_->dart_entrypoint().c_str(); + } args.dart_entrypoint_argc = static_cast(entrypoint_argv.size()); args.dart_entrypoint_argv = entrypoint_argv.empty() ? nullptr : entrypoint_argv.data(); @@ -294,15 +322,18 @@ bool FlutterWindowsEngine::RunWithEntrypoint(const char* entrypoint) { host->accessibility_bridge_->AddFlutterSemanticsCustomActionUpdate( action); }; + args.root_isolate_create_callback = [](void* user_data) { + auto host = static_cast(user_data); + if (host->root_isolate_create_callback_) { + host->root_isolate_create_callback_(); + } + }; args.custom_task_runners = &custom_task_runners; if (aot_data_) { args.aot_data = aot_data_.get(); } - if (entrypoint) { - args.custom_dart_entrypoint = entrypoint; - } FlutterRendererConfig renderer_config = surface_manager_ ? GetOpenGLRendererConfig() @@ -311,8 +342,7 @@ bool FlutterWindowsEngine::RunWithEntrypoint(const char* entrypoint) { auto result = embedder_api_.Run(FLUTTER_ENGINE_VERSION, &renderer_config, &args, this, &engine_); if (result != kSuccess || engine_ == nullptr) { - std::cerr << "Failed to start Flutter engine: error " << result - << std::endl; + FML_LOG(ERROR) << "Failed to start Flutter engine: error " << result; return false; } @@ -426,7 +456,7 @@ bool FlutterWindowsEngine::SendPlatformMessage( embedder_api_.PlatformMessageCreateResponseHandle( engine_, reply, user_data, &response_handle); if (result != kSuccess) { - std::cout << "Failed to create response handle\n"; + FML_LOG(ERROR) << "Failed to create response handle"; return false; } } @@ -458,9 +488,9 @@ void FlutterWindowsEngine::SendPlatformMessageResponse( void FlutterWindowsEngine::HandlePlatformMessage( const FlutterPlatformMessage* engine_message) { if (engine_message->struct_size != sizeof(FlutterPlatformMessage)) { - std::cerr << "Invalid message size received. Expected: " - << sizeof(FlutterPlatformMessage) << " but received " - << engine_message->struct_size << std::endl; + FML_LOG(ERROR) << "Invalid message size received. Expected: " + << sizeof(FlutterPlatformMessage) << " but received " + << engine_message->struct_size; return; } @@ -478,6 +508,22 @@ void FlutterWindowsEngine::ScheduleFrame() { embedder_api_.ScheduleFrame(engine_); } +void FlutterWindowsEngine::SetNextFrameCallback(fml::closure callback) { + next_frame_callback_ = std::move(callback); + + embedder_api_.SetNextFrameCallback( + engine_, + [](void* user_data) { + // Embedder callback runs on raster thread. Switch back to platform + // thread. + FlutterWindowsEngine* self = + static_cast(user_data); + + self->task_runner_->PostTask(std::move(self->next_frame_callback_)); + }, + this); +} + void FlutterWindowsEngine::SendSystemLocales() { std::vector languages = GetPreferredLanguageInfo(); std::vector flutter_locales; @@ -488,10 +534,9 @@ void FlutterWindowsEngine::SendSystemLocales() { // Convert the locale list to the locale pointer list that must be provided. std::vector flutter_locale_list; flutter_locale_list.reserve(flutter_locales.size()); - std::transform( - flutter_locales.begin(), flutter_locales.end(), - std::back_inserter(flutter_locale_list), - [](const auto& arg) -> const auto* { return &arg; }); + std::transform(flutter_locales.begin(), flutter_locales.end(), + std::back_inserter(flutter_locale_list), + [](const auto& arg) -> const auto* { return &arg; }); embedder_api_.UpdateLocales(engine_, flutter_locale_list.data(), flutter_locale_list.size()); } @@ -548,4 +593,18 @@ gfx::NativeViewAccessible FlutterWindowsEngine::GetNativeAccessibleFromId( return node_delegate->GetNativeViewAccessible(); } +std::string FlutterWindowsEngine::GetExecutableName() const { + std::pair result = fml::paths::GetExecutablePath(); + if (result.first) { + const std::string& executable_path = result.second; + size_t last_separator = executable_path.find_last_of("/\\"); + if (last_separator == std::string::npos || + last_separator == executable_path.size() - 1) { + return executable_path; + } + return executable_path.substr(last_separator + 1); + } + return "Flutter"; +} + } // namespace flutter diff --git a/shell/platform/windows/flutter_windows_engine.h b/shell/platform/windows/flutter_windows_engine.h index 71233c318524c..700f7889a2ddf 100644 --- a/shell/platform/windows/flutter_windows_engine.h +++ b/shell/platform/windows/flutter_windows_engine.h @@ -9,8 +9,11 @@ #include #include #include +#include +#include #include +#include "flutter/fml/closure.h" #include "flutter/shell/platform/common/accessibility_bridge.h" #include "flutter/shell/platform/common/client_wrapper/binary_messenger_impl.h" #include "flutter/shell/platform/common/client_wrapper/include/flutter/basic_message_channel.h" @@ -72,11 +75,22 @@ class FlutterWindowsEngine { FlutterWindowsEngine(FlutterWindowsEngine const&) = delete; FlutterWindowsEngine& operator=(FlutterWindowsEngine const&) = delete; - // Starts running the engine with the given entrypoint. If null, defaults to - // main(). + // Starts running the entrypoint function specifed in the project bundle. If + // unspecified, defaults to main(). // // Returns false if the engine couldn't be started. - bool RunWithEntrypoint(const char* entrypoint); + bool Run(); + + // Starts running the engine with the given entrypoint. If the empty string + // is specified, defaults to the entrypoint function specified in the project + // bundle, or main() if both are unspecified. + // + // Returns false if the engine couldn't be started or if conflicting, + // non-default values are passed here and in the project bundle.. + // + // DEPRECATED: Prefer setting the entrypoint in the FlutterProjectBundle + // passed to the constructor and calling the no-parameter overload. + bool Run(std::string_view entrypoint); // Returns true if the engine is currently running. bool running() { return engine_ != nullptr; } @@ -163,6 +177,9 @@ class FlutterWindowsEngine { // Informs the engine that a new frame is needed to redraw the content. void ScheduleFrame(); + // Set the callback that is called when the next frame is drawn. + void SetNextFrameCallback(fml::closure callback); + // Attempts to register the texture with the given |texture_id|. bool RegisterExternalTexture(int64_t texture_id); @@ -190,6 +207,22 @@ class FlutterWindowsEngine { // Returns the native accessibility node with the given id. gfx::NativeViewAccessible GetNativeAccessibleFromId(AccessibilityNodeId id); + // Register a root isolate create callback. + // + // The root isolate create callback is invoked at creation of the root Dart + // isolate in the app. This may be used to be notified that execution of the + // main Dart entrypoint is about to begin, and is used by test infrastructure + // to register a native function resolver that can register and resolve + // functions marked as native in the Dart code. + // + // This must be called before calling |Run|. + void SetRootIsolateCreateCallback(const fml::closure& callback) { + root_isolate_create_callback_ = callback; + } + + // Returns the executable name for this process or "Flutter" if unknown. + std::string GetExecutableName() const; + private: // Allows swapping out embedder_api_ calls in tests. friend class EngineModifier; @@ -264,6 +297,12 @@ class FlutterWindowsEngine { // The manager for WindowProc delegate registration and callbacks. std::unique_ptr window_proc_delegate_manager_; + + // The root isolate creation callback. + fml::closure root_isolate_create_callback_; + + // The on frame drawn callback. + fml::closure next_frame_callback_; }; } // namespace flutter diff --git a/shell/platform/windows/flutter_windows_engine_unittests.cc b/shell/platform/windows/flutter_windows_engine_unittests.cc index fad87e75e1846..1417fe48a3f0c 100644 --- a/shell/platform/windows/flutter_windows_engine_unittests.cc +++ b/shell/platform/windows/flutter_windows_engine_unittests.cc @@ -129,7 +129,7 @@ TEST(FlutterWindowsEngine, RunDoesExpectedInitialization) { // Set the AngleSurfaceManager to !nullptr to test ANGLE rendering. modifier.SetSurfaceManager(reinterpret_cast(1)); - engine->RunWithEntrypoint(nullptr); + engine->Run(); EXPECT_TRUE(run_called); EXPECT_TRUE(update_locales_called); @@ -206,7 +206,7 @@ TEST(FlutterWindowsEngine, RunWithoutANGLEUsesSoftware) { // Set the AngleSurfaceManager to nullptr to test software fallback path. modifier.SetSurfaceManager(nullptr); - engine->RunWithEntrypoint(nullptr); + engine->Run(); EXPECT_TRUE(run_called); @@ -351,7 +351,7 @@ TEST(FlutterWindowsEngine, AddPluginRegistrarDestructionCallback) { MockEmbedderApiForKeyboard(modifier, std::make_shared()); - engine->RunWithEntrypoint(nullptr); + engine->Run(); // Verify that destruction handlers don't overwrite each other. int result1 = 0; @@ -389,5 +389,25 @@ TEST(FlutterWindowsEngine, ScheduleFrame) { EXPECT_TRUE(called); } +TEST(FlutterWindowsEngine, SetNextFrameCallback) { + std::unique_ptr engine = GetTestEngine(); + EngineModifier modifier(engine.get()); + + bool called = false; + modifier.embedder_api().SetNextFrameCallback = MOCK_ENGINE_PROC( + SetNextFrameCallback, ([&called](auto engine, auto callback, auto data) { + called = true; + return kSuccess; + })); + + engine->SetNextFrameCallback([]() {}); + EXPECT_TRUE(called); +} + +TEST(FlutterWindowsEngine, GetExecutableName) { + std::unique_ptr engine = GetTestEngine(); + EXPECT_EQ(engine->GetExecutableName(), "flutter_windows_unittests.exe"); +} + } // namespace testing } // namespace flutter diff --git a/shell/platform/windows/flutter_windows_texture_registrar.cc b/shell/platform/windows/flutter_windows_texture_registrar.cc index 9e3b3bd0f77a5..0abc73b75afe3 100644 --- a/shell/platform/windows/flutter_windows_texture_registrar.cc +++ b/shell/platform/windows/flutter_windows_texture_registrar.cc @@ -4,9 +4,9 @@ #include "flutter/shell/platform/windows/flutter_windows_texture_registrar.h" -#include #include +#include "flutter/fml/logging.h" #include "flutter/shell/platform/embedder/embedder_struct_macros.h" #include "flutter/shell/platform/windows/external_texture_d3d.h" #include "flutter/shell/platform/windows/external_texture_pixelbuffer.h" @@ -31,7 +31,7 @@ int64_t FlutterWindowsTextureRegistrar::RegisterTexture( if (texture_info->type == kFlutterDesktopPixelBufferTexture) { if (!texture_info->pixel_buffer_config.callback) { - std::cerr << "Invalid pixel buffer texture callback." << std::endl; + FML_LOG(ERROR) << "Invalid pixel buffer texture callback."; return kInvalidTexture; } @@ -47,7 +47,7 @@ int64_t FlutterWindowsTextureRegistrar::RegisterTexture( surface_type == kFlutterDesktopGpuSurfaceTypeD3d11Texture2D) { auto callback = SAFE_ACCESS(gpu_surface_config, callback, nullptr); if (!callback) { - std::cerr << "Invalid GPU surface descriptor callback." << std::endl; + FML_LOG(ERROR) << "Invalid GPU surface descriptor callback."; return kInvalidTexture; } @@ -58,7 +58,7 @@ int64_t FlutterWindowsTextureRegistrar::RegisterTexture( } } - std::cerr << "Attempted to register texture of unsupport type." << std::endl; + FML_LOG(ERROR) << "Attempted to register texture of unsupport type."; return kInvalidTexture; } diff --git a/shell/platform/windows/flutter_windows_unittests.cc b/shell/platform/windows/flutter_windows_unittests.cc new file mode 100644 index 0000000000000..40cd9e32a3313 --- /dev/null +++ b/shell/platform/windows/flutter_windows_unittests.cc @@ -0,0 +1,231 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/shell/platform/windows/public/flutter_windows.h" + +#include + +#include "flutter/fml/synchronization/count_down_latch.h" +#include "flutter/fml/synchronization/waitable_event.h" +#include "flutter/shell/platform/windows/testing/windows_test.h" +#include "flutter/shell/platform/windows/testing/windows_test_config_builder.h" +#include "flutter/shell/platform/windows/testing/windows_test_context.h" +#include "gtest/gtest.h" +#include "third_party/tonic/converter/dart_converter.h" + +namespace flutter { +namespace testing { + +// Verify that we can fetch a texture registrar. +// Prevent regression: https://github.com/flutter/flutter/issues/86617 +TEST(WindowsNoFixtureTest, GetTextureRegistrar) { + FlutterDesktopEngineProperties properties = {}; + properties.assets_path = L""; + properties.icu_data_path = L"icudtl.dat"; + auto engine = FlutterDesktopEngineCreate(&properties); + ASSERT_NE(engine, nullptr); + auto texture_registrar = FlutterDesktopEngineGetTextureRegistrar(engine); + EXPECT_NE(texture_registrar, nullptr); + FlutterDesktopEngineDestroy(engine); +} + +// Verify we can successfully launch main(). +TEST_F(WindowsTest, LaunchMain) { + auto& context = GetContext(); + WindowsConfigBuilder builder(context); + ViewControllerPtr controller{builder.Run()}; + ASSERT_NE(controller, nullptr); +} + +// Verify we can successfully launch a custom entry point. +TEST_F(WindowsTest, LaunchCustomEntrypoint) { + auto& context = GetContext(); + WindowsConfigBuilder builder(context); + builder.SetDartEntrypoint("customEntrypoint"); + ViewControllerPtr controller{builder.Run()}; + ASSERT_NE(controller, nullptr); +} + +// Verify that engine launches with the custom entrypoint specified in the +// FlutterDesktopEngineRun parameter when no entrypoint is specified in +// FlutterDesktopEngineProperties.dart_entrypoint. +// +// TODO(cbracken): https://github.com/flutter/flutter/issues/109285 +TEST_F(WindowsTest, LaunchCustomEntrypointInEngineRunInvocation) { + auto& context = GetContext(); + WindowsConfigBuilder builder(context); + EnginePtr engine{builder.InitializeEngine()}; + ASSERT_NE(engine, nullptr); + + ASSERT_TRUE(FlutterDesktopEngineRun(engine.get(), "customEntrypoint")); +} + +// Verify that engine fails to launch when a conflicting entrypoint in +// FlutterDesktopEngineProperties.dart_entrypoint and the +// FlutterDesktopEngineRun parameter. +// +// TODO(cbracken): https://github.com/flutter/flutter/issues/109285 +TEST_F(WindowsTest, LaunchConflictingCustomEntrypoints) { + auto& context = GetContext(); + WindowsConfigBuilder builder(context); + builder.SetDartEntrypoint("customEntrypoint"); + EnginePtr engine{builder.InitializeEngine()}; + ASSERT_NE(engine, nullptr); + + ASSERT_FALSE(FlutterDesktopEngineRun(engine.get(), "conflictingEntrypoint")); +} + +// Verify that native functions can be registered and resolved. +TEST_F(WindowsTest, VerifyNativeFunction) { + auto& context = GetContext(); + WindowsConfigBuilder builder(context); + builder.SetDartEntrypoint("verifyNativeFunction"); + + fml::AutoResetWaitableEvent latch; + auto native_entry = + CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { latch.Signal(); }); + context.AddNativeFunction("Signal", native_entry); + + ViewControllerPtr controller{builder.Run()}; + ASSERT_NE(controller, nullptr); + + // Wait until signal has been called. + latch.Wait(); +} + +// Verify that native functions that pass parameters can be registered and +// resolved. +TEST_F(WindowsTest, VerifyNativeFunctionWithParameters) { + auto& context = GetContext(); + WindowsConfigBuilder builder(context); + builder.SetDartEntrypoint("verifyNativeFunctionWithParameters"); + + bool bool_value = false; + fml::AutoResetWaitableEvent latch; + auto native_entry = CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { + auto handle = Dart_GetNativeBooleanArgument(args, 0, &bool_value); + ASSERT_FALSE(Dart_IsError(handle)); + latch.Signal(); + }); + context.AddNativeFunction("SignalBoolValue", native_entry); + + ViewControllerPtr controller{builder.Run()}; + ASSERT_NE(controller, nullptr); + + // Wait until signalBoolValue has been called. + latch.Wait(); + EXPECT_TRUE(bool_value); +} + +// Verify that Platform.executable returns the executable name. +TEST_F(WindowsTest, PlatformExecutable) { + auto& context = GetContext(); + WindowsConfigBuilder builder(context); + builder.SetDartEntrypoint("readPlatformExecutable"); + + std::string executable_name; + fml::AutoResetWaitableEvent latch; + auto native_entry = CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { + auto handle = Dart_GetNativeArgument(args, 0); + ASSERT_FALSE(Dart_IsError(handle)); + executable_name = tonic::DartConverter::FromDart(handle); + latch.Signal(); + }); + context.AddNativeFunction("SignalStringValue", native_entry); + + ViewControllerPtr controller{builder.Run()}; + ASSERT_NE(controller, nullptr); + + // Wait until signalStringValue has been called. + latch.Wait(); + EXPECT_EQ(executable_name, "flutter_windows_unittests.exe"); +} + +// Verify that native functions that return values can be registered and +// resolved. +TEST_F(WindowsTest, VerifyNativeFunctionWithReturn) { + auto& context = GetContext(); + WindowsConfigBuilder builder(context); + builder.SetDartEntrypoint("verifyNativeFunctionWithReturn"); + + bool bool_value_to_return = true; + fml::CountDownLatch latch(2); + auto bool_return_entry = CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { + Dart_SetBooleanReturnValue(args, bool_value_to_return); + latch.CountDown(); + }); + context.AddNativeFunction("SignalBoolReturn", bool_return_entry); + + bool bool_value_passed = false; + auto bool_pass_entry = CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { + auto handle = Dart_GetNativeBooleanArgument(args, 0, &bool_value_passed); + ASSERT_FALSE(Dart_IsError(handle)); + latch.CountDown(); + }); + context.AddNativeFunction("SignalBoolValue", bool_pass_entry); + + ViewControllerPtr controller{builder.Run()}; + ASSERT_NE(controller, nullptr); + + // Wait until signalBoolReturn and signalBoolValue have been called. + latch.Wait(); + EXPECT_TRUE(bool_value_passed); +} + +// Verify the next frame callback is executed. +TEST_F(WindowsTest, NextFrameCallback) { + struct Captures { + fml::AutoResetWaitableEvent frame_scheduled_latch; + fml::AutoResetWaitableEvent frame_drawn_latch; + std::thread::id thread_id; + }; + Captures captures; + + CreateNewThread("test_platform_thread")->PostTask([&]() { + captures.thread_id = std::this_thread::get_id(); + + auto& context = GetContext(); + WindowsConfigBuilder builder(context); + builder.SetDartEntrypoint("drawHelloWorld"); + + auto native_entry = CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) { + ASSERT_FALSE(captures.frame_drawn_latch.IsSignaledForTest()); + captures.frame_scheduled_latch.Signal(); + }); + context.AddNativeFunction("NotifyFirstFrameScheduled", native_entry); + + ViewControllerPtr controller{builder.Run()}; + ASSERT_NE(controller, nullptr); + + auto engine = FlutterDesktopViewControllerGetEngine(controller.get()); + + FlutterDesktopEngineSetNextFrameCallback( + engine, + [](void* user_data) { + auto captures = static_cast(user_data); + + ASSERT_TRUE(captures->frame_scheduled_latch.IsSignaledForTest()); + + // Callback should execute on platform thread. + ASSERT_EQ(std::this_thread::get_id(), captures->thread_id); + + // Signal the test passed and end the Windows message loop. + captures->frame_drawn_latch.Signal(); + ::PostQuitMessage(0); + }, + &captures); + + // Pump messages for the Windows platform task runner. + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + }); + + captures.frame_drawn_latch.Wait(); +} + +} // namespace testing +} // namespace flutter diff --git a/shell/platform/windows/flutter_windows_view.cc b/shell/platform/windows/flutter_windows_view.cc index 23d3ed17e94c8..d9f901219b664 100644 --- a/shell/platform/windows/flutter_windows_view.cc +++ b/shell/platform/windows/flutter_windows_view.cc @@ -63,7 +63,8 @@ void FlutterWindowsView::SetEngine( // Set up the system channel handlers. auto internal_plugin_messenger = internal_plugin_registrar_->messenger(); InitializeKeyboard(); - platform_handler_ = PlatformHandler::Create(internal_plugin_messenger, this); + platform_handler_ = + std::make_unique(internal_plugin_messenger, this); cursor_handler_ = std::make_unique(internal_plugin_messenger, binding_handler_.get()); diff --git a/shell/platform/windows/flutter_windows_view_unittests.cc b/shell/platform/windows/flutter_windows_view_unittests.cc index cf823c6357718..d6830d69081d2 100644 --- a/shell/platform/windows/flutter_windows_view_unittests.cc +++ b/shell/platform/windows/flutter_windows_view_unittests.cc @@ -90,7 +90,7 @@ std::unique_ptr GetTestEngine() { MockEmbedderApiForKeyboard(modifier, key_response_controller); - engine->RunWithEntrypoint(nullptr); + engine->Run(); return engine; } @@ -597,5 +597,95 @@ TEST(FlutterWindowsViewTest, WindowRepaintTests) { EXPECT_TRUE(schedule_frame_called); } +// Ensure that checkboxes have their checked status set apropriately +// Previously, only Radios could have this flag updated +// Resulted in the issue seen at +// https://github.com/flutter/flutter/issues/96218 +// This test ensures that the native state of Checkboxes on Windows, +// specifically, is updated as desired. +TEST(FlutterWindowsViewTest, CheckboxNativeState) { + std::unique_ptr engine = GetTestEngine(); + EngineModifier modifier(engine.get()); + modifier.embedder_api().UpdateSemanticsEnabled = + [](FLUTTER_API_SYMBOL(FlutterEngine) engine, bool enabled) { + return kSuccess; + }; + + auto window_binding_handler = + std::make_unique<::testing::NiceMock>(); + FlutterWindowsView view(std::move(window_binding_handler)); + view.SetEngine(std::move(engine)); + + // Enable semantics to instantiate accessibility bridge. + view.OnUpdateSemanticsEnabled(true); + + auto bridge = view.GetEngine()->accessibility_bridge().lock(); + ASSERT_TRUE(bridge); + + FlutterSemanticsNode root{sizeof(FlutterSemanticsNode), 0}; + root.id = 0; + root.label = "root"; + root.hint = ""; + root.value = ""; + root.increased_value = ""; + root.decreased_value = ""; + root.child_count = 0; + root.custom_accessibility_actions_count = 0; + root.flags = static_cast( + FlutterSemanticsFlag::kFlutterSemanticsFlagHasCheckedState | + FlutterSemanticsFlag::kFlutterSemanticsFlagIsChecked); + bridge->AddFlutterSemanticsNodeUpdate(&root); + + bridge->CommitUpdates(); + + auto root_node = bridge + ->GetFlutterPlatformNodeDelegateFromID( + AccessibilityBridge::kRootNodeId) + .lock(); + EXPECT_EQ(root_node->GetData().role, ax::mojom::Role::kCheckBox); + EXPECT_EQ(root_node->GetData().GetCheckedState(), + ax::mojom::CheckedState::kTrue); + + // Get the IAccessible for the root node. + IAccessible* native_view = root_node->GetNativeViewAccessible(); + ASSERT_TRUE(native_view != nullptr); + + // Look up against the node itself (not one of its children) + VARIANT varchild = {}; + varchild.vt = VT_I4; + + // Verify the checkbox is checked. + varchild.lVal = CHILDID_SELF; + VARIANT native_state = {}; + ASSERT_TRUE(SUCCEEDED(native_view->get_accState(varchild, &native_state))); + EXPECT_TRUE(native_state.lVal & STATE_SYSTEM_CHECKED); + + // Test unchecked too + root.flags = static_cast( + FlutterSemanticsFlag::kFlutterSemanticsFlagHasCheckedState); + bridge->AddFlutterSemanticsNodeUpdate(&root); + bridge->CommitUpdates(); + root_node = bridge + ->GetFlutterPlatformNodeDelegateFromID( + AccessibilityBridge::kRootNodeId) + .lock(); + EXPECT_EQ(root_node->GetData().role, ax::mojom::Role::kCheckBox); + EXPECT_EQ(root_node->GetData().GetCheckedState(), + ax::mojom::CheckedState::kFalse); + + // Get the IAccessible for the root node. + native_view = root_node->GetNativeViewAccessible(); + + // Look up against the node itself (not one of its children) + varchild = {}; + varchild.vt = VT_I4; + + // Verify the checkbox is checked. + varchild.lVal = CHILDID_SELF; + native_state = {}; + ASSERT_TRUE(SUCCEEDED(native_view->get_accState(varchild, &native_state))); + EXPECT_FALSE(native_state.lVal & STATE_SYSTEM_CHECKED); +} + } // namespace testing } // namespace flutter diff --git a/shell/platform/windows/keyboard_key_channel_handler.cc b/shell/platform/windows/keyboard_key_channel_handler.cc index 0359903b9e07f..94041cdb15d1f 100644 --- a/shell/platform/windows/keyboard_key_channel_handler.cc +++ b/shell/platform/windows/keyboard_key_channel_handler.cc @@ -6,8 +6,7 @@ #include -#include - +#include "flutter/fml/logging.h" #include "flutter/shell/platform/common/json_message_codec.h" #include "flutter/shell/platform/windows/keyboard_utils.h" @@ -139,7 +138,7 @@ void KeyboardKeyChannelHandler::KeyboardHook( event.AddMember(kTypeKey, kKeyUp, allocator); break; default: - std::cerr << "Unknown key event action: " << action << std::endl; + FML_LOG(WARNING) << "Unknown key event action: " << action; callback(false); return; } diff --git a/shell/platform/windows/keyboard_key_embedder_handler.cc b/shell/platform/windows/keyboard_key_embedder_handler.cc index 5d596091a4053..0a072e80551f7 100644 --- a/shell/platform/windows/keyboard_key_embedder_handler.cc +++ b/shell/platform/windows/keyboard_key_embedder_handler.cc @@ -186,6 +186,7 @@ void KeyboardKeyEmbedderHandler::KeyboardHookImpl( const bool is_event_down = action == WM_KEYDOWN || action == WM_SYSKEYDOWN; + bool event_key_can_be_repeat = true; UpdateLastSeenCritialKey(key, physical_key, sequence_logical_key); // Synchronize the toggled states of critical keys (such as whether CapsLocks // is enabled). Toggled states can only be changed upon a down event, so if @@ -197,14 +198,15 @@ void KeyboardKeyEmbedderHandler::KeyboardHookImpl( // updated to the true state, while the critical keys whose toggled state have // been changed will be pressed regardless of their true pressed state. // Updating the pressed state will be done by SynchronizeCritialPressedStates. - SynchronizeCritialToggledStates(key, is_event_down); + SynchronizeCritialToggledStates(key, is_event_down, &event_key_can_be_repeat); // Synchronize the pressed states of critical keys (such as whether CapsLocks // is pressed). // // After this function, all critical keys except for the target key will have // their toggled state and pressed state matched with their true states. The // target key's pressed state will be updated immediately after this. - SynchronizeCritialPressedStates(key, physical_key, is_event_down); + SynchronizeCritialPressedStates(key, physical_key, is_event_down, + event_key_can_be_repeat); // The resulting event's `type`. FlutterKeyEventType type; @@ -340,7 +342,8 @@ void KeyboardKeyEmbedderHandler::UpdateLastSeenCritialKey( void KeyboardKeyEmbedderHandler::SynchronizeCritialToggledStates( int event_virtual_key, - bool is_event_down) { + bool is_event_down, + bool* event_key_can_be_repeat) { // NowState ----------------> PreEventState --------------> TrueState // Synchronization Event for (auto& kv : critical_keys_) { @@ -385,6 +388,7 @@ void KeyboardKeyEmbedderHandler::SynchronizeCritialToggledStates( key_info.physical_key, key_info.logical_key, empty_character), nullptr, nullptr); + *event_key_can_be_repeat = false; } key_info.toggled_on = true_toggled; } @@ -394,7 +398,8 @@ void KeyboardKeyEmbedderHandler::SynchronizeCritialToggledStates( void KeyboardKeyEmbedderHandler::SynchronizeCritialPressedStates( int event_virtual_key, int event_physical_key, - bool is_event_down) { + bool is_event_down, + bool event_key_can_be_repeat) { // During an incoming event, there might be a synthesized Flutter event for // each key of each pressing goal, followed by an eventual main Flutter // event. @@ -424,8 +429,18 @@ void KeyboardKeyEmbedderHandler::SynchronizeCritialPressedStates( if (is_event_down) { // For down events, this key is the event key if they have the same // virtual key, because virtual key represents "functionality." + // + // In that case, normally Flutter should synthesize nothing since the + // resulting event can adapt to the current state by dispatching either + // a down or a repeat event. However, in certain cases (when Flutter has + // just synchronized the key's toggling state) the event must not be a + // repeat event. if (virtual_key == event_virtual_key) { - pre_event_pressed = false; + if (event_key_can_be_repeat) { + continue; + } else { + pre_event_pressed = false; + } } } else { // For up events, this key is the event key if they have the same diff --git a/shell/platform/windows/keyboard_key_embedder_handler.h b/shell/platform/windows/keyboard_key_embedder_handler.h index 426a38d42deab..67ff6490e5fc4 100644 --- a/shell/platform/windows/keyboard_key_embedder_handler.h +++ b/shell/platform/windows/keyboard_key_embedder_handler.h @@ -115,12 +115,14 @@ class KeyboardKeyEmbedderHandler // Check each key's state from |get_key_state_| and synthesize events // if their toggling states have been desynchronized. void SynchronizeCritialToggledStates(int event_virtual_key, - bool is_event_down); + bool is_event_down, + bool* event_key_can_be_repeat); // Check each key's state from |get_key_state_| and synthesize events // if their pressing states have been desynchronized. void SynchronizeCritialPressedStates(int event_virtual_key, int event_physical_key, - bool is_event_down); + bool is_event_down, + bool event_key_can_be_repeat); // Wraps perform_send_event_ with state tracking. Use this instead of // |perform_send_event_| to send events to the framework. diff --git a/shell/platform/windows/keyboard_key_embedder_handler_unittests.cc b/shell/platform/windows/keyboard_key_embedder_handler_unittests.cc index aee316a4e16d6..3c526d9ba6558 100644 --- a/shell/platform/windows/keyboard_key_embedder_handler_unittests.cc +++ b/shell/platform/windows/keyboard_key_embedder_handler_unittests.cc @@ -14,6 +14,7 @@ #include "gtest/gtest.h" namespace flutter { +namespace testing { namespace { @@ -71,11 +72,6 @@ UINT DefaultMapVkToScan(UINT virtual_key, bool extended) { extended ? MAPVK_VK_TO_VSC_EX : MAPVK_VK_TO_VSC); } -} // namespace - -namespace testing { - -namespace { constexpr uint64_t kScanCodeKeyA = 0x1e; constexpr uint64_t kScanCodeAltLeft = 0x38; constexpr uint64_t kScanCodeNumpad1 = 0x4f; @@ -86,9 +82,10 @@ constexpr uint64_t kScanCodeShiftRight = 0x36; constexpr uint64_t kVirtualKeyA = 0x41; -using namespace ::flutter::testing::keycodes; } // namespace +using namespace ::flutter::testing::keycodes; + TEST(KeyboardKeyEmbedderHandlerTest, ConvertChar32ToUtf8) { std::string result; diff --git a/shell/platform/windows/keyboard_key_handler.cc b/shell/platform/windows/keyboard_key_handler.cc index d31aee1c28d7f..718c6f2afbcca 100644 --- a/shell/platform/windows/keyboard_key_handler.cc +++ b/shell/platform/windows/keyboard_key_handler.cc @@ -6,8 +6,7 @@ #include -#include - +#include "flutter/fml/logging.h" #include "flutter/shell/platform/windows/keyboard_utils.h" namespace flutter { @@ -48,10 +47,10 @@ void KeyboardKeyHandler::KeyboardHook(int key, incoming->callback = std::move(callback); if (pending_responds_.size() > kMaxPendingEvents) { - std::cerr + FML_LOG(ERROR) << "There are " << pending_responds_.size() << " keyboard events that have not yet received a response from the " - << "framework. Are responses being sent?" << std::endl; + << "framework. Are responses being sent?"; } pending_responds_.push_back(std::move(incoming)); diff --git a/shell/platform/windows/keyboard_manager.cc b/shell/platform/windows/keyboard_manager.cc index 5bd25a636699f..d8891a5a9c996 100644 --- a/shell/platform/windows/keyboard_manager.cc +++ b/shell/platform/windows/keyboard_manager.cc @@ -3,10 +3,10 @@ // found in the LICENSE file. #include -#include #include #include +#include "flutter/fml/logging.h" #include "flutter/shell/platform/windows/keyboard_manager.h" #include "flutter/shell/platform/windows/keyboard_utils.h" @@ -120,15 +120,14 @@ void KeyboardManager::RedispatchEvent(std::unique_ptr event) { UINT result = window_delegate_->Win32DispatchMessage( message.action, message.wparam, message.lparam); if (result != 0) { - std::cerr << "Unable to synthesize event for keyboard event." - << std::endl; + FML_LOG(ERROR) << "Unable to synthesize event for keyboard event."; } } if (pending_redispatches_.size() > kMaxPendingEvents) { - std::cerr + FML_LOG(ERROR) << "There are " << pending_redispatches_.size() << " keyboard events that have not yet received a response from the " - << "framework. Are responses being sent?" << std::endl; + << "framework. Are responses being sent?"; } } diff --git a/shell/platform/windows/keyboard_unittests.cc b/shell/platform/windows/keyboard_unittests.cc index 0bea6b97167d8..4ce5ba4724e06 100644 --- a/shell/platform/windows/keyboard_unittests.cc +++ b/shell/platform/windows/keyboard_unittests.cc @@ -524,7 +524,7 @@ class KeyboardTester { MockEmbedderApiForKeyboard(modifier, key_response_controller); - engine->RunWithEntrypoint(nullptr); + engine->Run(); return engine; } @@ -696,6 +696,18 @@ TEST(KeyboardTest, ShiftLeftUnhandled) { clear_key_calls(); EXPECT_EQ(tester.RedispatchedMessageCountAndClear(), 1); + // Hold ShiftLeft + tester.InjectKeyboardChanges(std::vector{ + WmKeyDownInfo{VK_SHIFT, kScanCodeShiftLeft, kNotExtended, kWasDown}.Build( + kWmResultZero)}); + + EXPECT_EQ(key_calls.size(), 1); + EXPECT_CALL_IS_EVENT(key_calls[0], kFlutterKeyEventTypeRepeat, + kPhysicalShiftLeft, kLogicalShiftLeft, "", + kNotSynthesized); + clear_key_calls(); + EXPECT_EQ(tester.RedispatchedMessageCountAndClear(), 1); + // Release ShiftLeft tester.InjectKeyboardChanges(std::vector{ KeyStateChange{VK_LSHIFT, false, true}, diff --git a/shell/platform/windows/platform_handler.cc b/shell/platform/windows/platform_handler.cc index c3791f152376f..c73677da1b1f8 100644 --- a/shell/platform/windows/platform_handler.cc +++ b/shell/platform/windows/platform_handler.cc @@ -4,7 +4,15 @@ #include "flutter/shell/platform/windows/platform_handler.h" +#include + +#include +#include + +#include "flutter/fml/logging.h" +#include "flutter/fml/platform/win/wstring_conversion.h" #include "flutter/shell/platform/common/json_method_codec.h" +#include "flutter/shell/platform/windows/flutter_windows_view.h" static constexpr char kChannelName[] = "flutter/platform"; @@ -15,26 +23,307 @@ static constexpr char kPlaySoundMethod[] = "SystemSound.play"; static constexpr char kTextPlainFormat[] = "text/plain"; static constexpr char kTextKey[] = "text"; - static constexpr char kUnknownClipboardFormatMessage[] = "Unknown clipboard format"; +static constexpr char kValueKey[] = "value"; +static constexpr int kAccessDeniedErrorCode = 5; +static constexpr int kErrorSuccess = 0; + namespace flutter { -PlatformHandler::PlatformHandler(BinaryMessenger* messenger) +namespace { + +// A scoped wrapper for GlobalAlloc/GlobalFree. +class ScopedGlobalMemory { + public: + // Allocates |bytes| bytes of global memory with the given flags. + ScopedGlobalMemory(unsigned int flags, size_t bytes) { + memory_ = ::GlobalAlloc(flags, bytes); + if (!memory_) { + FML_LOG(ERROR) << "Unable to allocate global memory: " + << ::GetLastError(); + } + } + + ~ScopedGlobalMemory() { + if (memory_) { + if (::GlobalFree(memory_) != nullptr) { + FML_LOG(ERROR) << "Failed to free global allocation: " + << ::GetLastError(); + } + } + } + + // Prevent copying. + ScopedGlobalMemory(ScopedGlobalMemory const&) = delete; + ScopedGlobalMemory& operator=(ScopedGlobalMemory const&) = delete; + + // Returns the memory pointer, which will be nullptr if allocation failed. + void* get() { return memory_; } + + void* release() { + void* memory = memory_; + memory_ = nullptr; + return memory; + } + + private: + HGLOBAL memory_; +}; + +// A scoped wrapper for GlobalLock/GlobalUnlock. +class ScopedGlobalLock { + public: + // Attempts to acquire a global lock on |memory| for the life of this object. + ScopedGlobalLock(HGLOBAL memory) { + source_ = memory; + if (memory) { + locked_memory_ = ::GlobalLock(memory); + if (!locked_memory_) { + FML_LOG(ERROR) << "Unable to acquire global lock: " << ::GetLastError(); + } + } + } + + ~ScopedGlobalLock() { + if (locked_memory_) { + if (!::GlobalUnlock(source_)) { + DWORD error = ::GetLastError(); + if (error != NO_ERROR) { + FML_LOG(ERROR) << "Unable to release global lock: " + << ::GetLastError(); + } + } + } + } + + // Prevent copying. + ScopedGlobalLock(ScopedGlobalLock const&) = delete; + ScopedGlobalLock& operator=(ScopedGlobalLock const&) = delete; + + // Returns the locked memory pointer, which will be nullptr if acquiring the + // lock failed. + void* get() { return locked_memory_; } + + private: + HGLOBAL source_; + void* locked_memory_; +}; + +// A Clipboard wrapper that automatically closes the clipboard when it goes out +// of scope. +class ScopedClipboard : public ScopedClipboardInterface { + public: + ScopedClipboard(); + virtual ~ScopedClipboard(); + + // Prevent copying. + ScopedClipboard(ScopedClipboard const&) = delete; + ScopedClipboard& operator=(ScopedClipboard const&) = delete; + + int Open(HWND window) override; + + bool HasString() override; + + std::variant GetString() override; + + int SetString(const std::wstring string) override; + + private: + bool opened_ = false; +}; + +ScopedClipboard::ScopedClipboard() {} + +ScopedClipboard::~ScopedClipboard() { + if (opened_) { + ::CloseClipboard(); + } +} + +int ScopedClipboard::Open(HWND window) { + opened_ = ::OpenClipboard(window); + + if (!opened_) { + return ::GetLastError(); + } + + return kErrorSuccess; +} + +bool ScopedClipboard::HasString() { + // Allow either plain text format, since getting data will auto-interpolate. + return ::IsClipboardFormatAvailable(CF_UNICODETEXT) || + ::IsClipboardFormatAvailable(CF_TEXT); +} + +std::variant ScopedClipboard::GetString() { + assert(opened_); + + HANDLE data = ::GetClipboardData(CF_UNICODETEXT); + if (data == nullptr) { + return ::GetLastError(); + } + ScopedGlobalLock locked_data(data); + + if (!locked_data.get()) { + return ::GetLastError(); + } + return static_cast(locked_data.get()); +} + +int ScopedClipboard::SetString(const std::wstring string) { + assert(opened_); + if (!::EmptyClipboard()) { + return ::GetLastError(); + } + size_t null_terminated_byte_count = + sizeof(decltype(string)::traits_type::char_type) * (string.size() + 1); + ScopedGlobalMemory destination_memory(GMEM_MOVEABLE, + null_terminated_byte_count); + ScopedGlobalLock locked_memory(destination_memory.get()); + if (!locked_memory.get()) { + return ::GetLastError(); + } + memcpy(locked_memory.get(), string.c_str(), null_terminated_byte_count); + if (!::SetClipboardData(CF_UNICODETEXT, locked_memory.get())) { + return ::GetLastError(); + } + // The clipboard now owns the global memory. + destination_memory.release(); + return kErrorSuccess; +} + +} // namespace + +PlatformHandler::PlatformHandler( + BinaryMessenger* messenger, + FlutterWindowsView* view, + std::optional()>> + scoped_clipboard_provider) : channel_(std::make_unique>( messenger, kChannelName, - &JsonMethodCodec::GetInstance())) { + &JsonMethodCodec::GetInstance())), + view_(view) { channel_->SetMethodCallHandler( [this](const MethodCall& call, std::unique_ptr> result) { HandleMethodCall(call, std::move(result)); }); + if (scoped_clipboard_provider.has_value()) { + scoped_clipboard_provider_ = scoped_clipboard_provider.value(); + } else { + scoped_clipboard_provider_ = []() { + return std::make_unique(); + }; + } } PlatformHandler::~PlatformHandler() = default; +void PlatformHandler::GetPlainText( + std::unique_ptr> result, + std::string_view key) { + std::unique_ptr clipboard = + scoped_clipboard_provider_(); + + int open_result = clipboard->Open(std::get(*view_->GetRenderTarget())); + if (open_result != kErrorSuccess) { + rapidjson::Document error_code; + error_code.SetInt(open_result); + result->Error(kClipboardError, "Unable to open clipboard", error_code); + return; + } + if (!clipboard->HasString()) { + result->Success(rapidjson::Document()); + return; + } + std::variant get_string_result = clipboard->GetString(); + if (std::holds_alternative(get_string_result)) { + rapidjson::Document error_code; + error_code.SetInt(std::get(get_string_result)); + result->Error(kClipboardError, "Unable to get clipboard data", error_code); + return; + } + + rapidjson::Document document; + document.SetObject(); + rapidjson::Document::AllocatorType& allocator = document.GetAllocator(); + document.AddMember( + rapidjson::Value(key.data(), allocator), + rapidjson::Value( + fml::WideStringToUtf8(std::get(get_string_result)), + allocator), + allocator); + result->Success(document); +} + +void PlatformHandler::GetHasStrings( + std::unique_ptr> result) { + std::unique_ptr clipboard = + scoped_clipboard_provider_(); + + bool hasStrings; + int open_result = clipboard->Open(std::get(*view_->GetRenderTarget())); + if (open_result != kErrorSuccess) { + // Swallow errors of type ERROR_ACCESS_DENIED. These happen when the app is + // not in the foreground and GetHasStrings is irrelevant. + // See https://github.com/flutter/flutter/issues/95817. + if (open_result != kAccessDeniedErrorCode) { + rapidjson::Document error_code; + error_code.SetInt(open_result); + result->Error(kClipboardError, "Unable to open clipboard", error_code); + return; + } + hasStrings = false; + } else { + hasStrings = clipboard->HasString(); + } + + rapidjson::Document document; + document.SetObject(); + rapidjson::Document::AllocatorType& allocator = document.GetAllocator(); + document.AddMember(rapidjson::Value(kValueKey, allocator), + rapidjson::Value(hasStrings), allocator); + result->Success(document); +} + +void PlatformHandler::SetPlainText( + const std::string& text, + std::unique_ptr> result) { + std::unique_ptr clipboard = + scoped_clipboard_provider_(); + + int open_result = clipboard->Open(std::get(*view_->GetRenderTarget())); + if (open_result != kErrorSuccess) { + rapidjson::Document error_code; + error_code.SetInt(open_result); + result->Error(kClipboardError, "Unable to open clipboard", error_code); + return; + } + int set_result = clipboard->SetString(fml::Utf8ToWideString(text)); + if (set_result != kErrorSuccess) { + rapidjson::Document error_code; + error_code.SetInt(set_result); + result->Error(kClipboardError, "Unable to set clipboard data", error_code); + return; + } + result->Success(); +} + +void PlatformHandler::SystemSoundPlay( + const std::string& sound_type, + std::unique_ptr> result) { + if (sound_type.compare(kSoundTypeAlert) == 0) { + MessageBeep(MB_OK); + result->Success(); + } else { + result->NotImplemented(); + } +} + void PlatformHandler::HandleMethodCall( const MethodCall& method_call, std::unique_ptr> result) { diff --git a/shell/platform/windows/platform_handler.h b/shell/platform/windows/platform_handler.h index 3483273e961a4..4da0ab2180527 100644 --- a/shell/platform/windows/platform_handler.h +++ b/shell/platform/windows/platform_handler.h @@ -5,6 +5,13 @@ #ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_PLATFORM_HANDLER_H_ #define FLUTTER_SHELL_PLATFORM_WINDOWS_PLATFORM_HANDLER_H_ +#include + +#include +#include +#include +#include + #include "flutter/shell/platform/common/client_wrapper/include/flutter/binary_messenger.h" #include "flutter/shell/platform/common/client_wrapper/include/flutter/method_channel.h" #include "rapidjson/document.h" @@ -12,39 +19,40 @@ namespace flutter { class FlutterWindowsView; +class ScopedClipboardInterface; // Handler for internal system channels. class PlatformHandler { public: - explicit PlatformHandler(BinaryMessenger* messenger); + explicit PlatformHandler( + BinaryMessenger* messenger, + FlutterWindowsView* view, + std::optional()>> + scoped_clipboard_provider = std::nullopt); virtual ~PlatformHandler(); - // Creates a new platform handler using the given messenger and view. - static std::unique_ptr Create(BinaryMessenger* messenger, - FlutterWindowsView* view); - protected: // Gets plain text from the clipboard and provides it to |result| as the // value in a dictionary with the given |key|. virtual void GetPlainText( std::unique_ptr> result, - std::string_view key) = 0; + std::string_view key); // Provides a boolean to |result| as the value in a dictionary at key // "value" representing whether or not the clipboard has a non-empty string. virtual void GetHasStrings( - std::unique_ptr> result) = 0; + std::unique_ptr> result); // Sets the clipboard's plain text to |text|, and reports the result (either // an error, or null for success) to |result|. virtual void SetPlainText( const std::string& text, - std::unique_ptr> result) = 0; + std::unique_ptr> result); virtual void SystemSoundPlay( const std::string& sound_type, - std::unique_ptr> result) = 0; + std::unique_ptr> result); // A error type to use for error responses. static constexpr char kClipboardError[] = "Clipboard error"; @@ -59,6 +67,42 @@ class PlatformHandler { // The MethodChannel used for communication with the Flutter engine. std::unique_ptr> channel_; + + // A reference to the Flutter view. + FlutterWindowsView* view_; + + // A scoped clipboard provider that can be passed in for mocking in tests. + // Use this to acquire clipboard in each operation to avoid blocking clipboard + // unnecessarily. See flutter/flutter#103205. + std::function()> + scoped_clipboard_provider_; +}; + +// A public interface for ScopedClipboard, so that it can be injected into +// PlatformHandler. +class ScopedClipboardInterface { + public: + virtual ~ScopedClipboardInterface(){}; + + // Attempts to open the clipboard for the given window, returning the error + // code in the case of failure and 0 otherwise. + virtual int Open(HWND window) = 0; + + // Returns true if there is string data available to get. + virtual bool HasString() = 0; + + // Returns string data from the clipboard. + // + // If getting a string fails, returns the error code. + // + // Open(...) must have succeeded to call this method. + virtual std::variant GetString() = 0; + + // Sets the string content of the clipboard, returning the error code on + // failure and 0 otherwise. + // + // Open(...) must have succeeded to call this method. + virtual int SetString(const std::wstring string) = 0; }; } // namespace flutter diff --git a/shell/platform/windows/platform_handler_unittests.cc b/shell/platform/windows/platform_handler_unittests.cc index bcbeb070e80e8..02a98281f4295 100644 --- a/shell/platform/windows/platform_handler_unittests.cc +++ b/shell/platform/windows/platform_handler_unittests.cc @@ -7,6 +7,8 @@ #include #include "flutter/shell/platform/common/json_method_codec.h" +#include "flutter/shell/platform/windows/flutter_windows_view.h" +#include "flutter/shell/platform/windows/testing/mock_window_binding_handler.h" #include "flutter/shell/platform/windows/testing/test_binary_messenger.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -30,16 +32,24 @@ static constexpr char kFakeContentType[] = "text/madeupcontenttype"; static constexpr char kSoundTypeAlert[] = "SystemSoundType.alert"; +static constexpr char kValueKey[] = "value"; +static constexpr int kAccessDeniedErrorCode = 5; +static constexpr int kErrorSuccess = 0; +static constexpr int kArbitraryErrorCode = 1; + // Test implementation of PlatformHandler to allow testing the PlatformHandler // logic. class TestPlatformHandler : public PlatformHandler { public: - explicit TestPlatformHandler(BinaryMessenger* messenger) - : PlatformHandler(messenger) {} + explicit TestPlatformHandler( + BinaryMessenger* messenger, + FlutterWindowsView* view, + std::optional()>> + scoped_clipboard_provider = std::nullopt) + : PlatformHandler(messenger, view, scoped_clipboard_provider) {} - virtual ~TestPlatformHandler() {} + virtual ~TestPlatformHandler() = default; - // |PlatformHandler| MOCK_METHOD2(GetPlainText, void(std::unique_ptr>, std::string_view key)); @@ -64,11 +74,86 @@ class MockMethodResult : public MethodResult { MOCK_METHOD0(NotImplementedInternal, void()); }; +// A test version of system clipboard. +class MockSystemClipboard { + public: + void OpenClipboard() { opened = true; } + void CloseClipboard() { opened = false; } + bool opened = false; +}; + +// A test version of the private ScopedClipboard. +class TestScopedClipboard : public ScopedClipboardInterface { + public: + TestScopedClipboard(int open_error, + bool has_strings, + std::shared_ptr clipboard); + ~TestScopedClipboard(); + + // Prevent copying. + TestScopedClipboard(TestScopedClipboard const&) = delete; + TestScopedClipboard& operator=(TestScopedClipboard const&) = delete; + + int Open(HWND window) override; + + bool HasString() override; + + std::variant GetString() override; + + int SetString(const std::wstring string) override; + + private: + bool opened_ = false; + bool has_strings_; + int open_error_; + std::shared_ptr clipboard_; +}; + +TestScopedClipboard::TestScopedClipboard( + int open_error, + bool has_strings, + std::shared_ptr clipboard = nullptr) { + open_error_ = open_error; + has_strings_ = has_strings; + clipboard_ = clipboard; +} + +TestScopedClipboard::~TestScopedClipboard() { + if ((!open_error_) && clipboard_ != nullptr) { + clipboard_->CloseClipboard(); + } +} + +int TestScopedClipboard::Open(HWND window) { + if ((!open_error_) && clipboard_ != nullptr) { + clipboard_->OpenClipboard(); + } + return open_error_; +} + +bool TestScopedClipboard::HasString() { + return has_strings_; +} + +std::variant TestScopedClipboard::GetString() { + return -1; +} + +int TestScopedClipboard::SetString(const std::wstring string) { + return -1; +} + } // namespace TEST(PlatformHandler, GettingTextCallsThrough) { TestBinaryMessenger messenger; - TestPlatformHandler platform_handler(&messenger); + FlutterWindowsView view( + std::make_unique<::testing::NiceMock>()); + auto system_clipboard = std::make_shared(); + TestPlatformHandler platform_handler(&messenger, &view, [system_clipboard]() { + return std::make_unique(kErrorSuccess, false, + system_clipboard); + }); auto args = std::make_unique(rapidjson::kStringType); auto& allocator = args->GetAllocator(); @@ -92,7 +177,13 @@ TEST(PlatformHandler, GettingTextCallsThrough) { TEST(PlatformHandler, RejectsGettingUnknownTypes) { TestBinaryMessenger messenger; - TestPlatformHandler platform_handler(&messenger); + FlutterWindowsView view( + std::make_unique<::testing::NiceMock>()); + auto system_clipboard = std::make_shared(); + TestPlatformHandler platform_handler(&messenger, &view, [system_clipboard]() { + return std::make_unique(kErrorSuccess, false, + system_clipboard); + }); auto args = std::make_unique(rapidjson::kStringType); auto& allocator = args->GetAllocator(); @@ -114,7 +205,13 @@ TEST(PlatformHandler, RejectsGettingUnknownTypes) { TEST(PlatformHandler, GetHasStringsCallsThrough) { TestBinaryMessenger messenger; - TestPlatformHandler platform_handler(&messenger); + FlutterWindowsView view( + std::make_unique<::testing::NiceMock>()); + auto system_clipboard = std::make_shared(); + TestPlatformHandler platform_handler(&messenger, &view, [system_clipboard]() { + return std::make_unique(kErrorSuccess, false, + system_clipboard); + }); auto args = std::make_unique(rapidjson::kStringType); auto& allocator = args->GetAllocator(); @@ -139,7 +236,13 @@ TEST(PlatformHandler, GetHasStringsCallsThrough) { TEST(PlatformHandler, RejectsGetHasStringsOnUnknownTypes) { TestBinaryMessenger messenger; - TestPlatformHandler platform_handler(&messenger); + FlutterWindowsView view( + std::make_unique<::testing::NiceMock>()); + auto system_clipboard = std::make_shared(); + TestPlatformHandler platform_handler(&messenger, &view, [system_clipboard]() { + return std::make_unique(kErrorSuccess, false, + system_clipboard); + }); auto args = std::make_unique(rapidjson::kStringType); auto& allocator = args->GetAllocator(); @@ -161,7 +264,13 @@ TEST(PlatformHandler, RejectsGetHasStringsOnUnknownTypes) { TEST(PlatformHandler, SettingTextCallsThrough) { TestBinaryMessenger messenger; - TestPlatformHandler platform_handler(&messenger); + FlutterWindowsView view( + std::make_unique<::testing::NiceMock>()); + auto system_clipboard = std::make_shared(); + TestPlatformHandler platform_handler(&messenger, &view, [system_clipboard]() { + return std::make_unique(kErrorSuccess, false, + system_clipboard); + }); auto args = std::make_unique(rapidjson::kObjectType); auto& allocator = args->GetAllocator(); @@ -187,7 +296,13 @@ TEST(PlatformHandler, SettingTextCallsThrough) { TEST(PlatformHandler, RejectsSettingUnknownTypes) { TestBinaryMessenger messenger; - TestPlatformHandler platform_handler(&messenger); + FlutterWindowsView view( + std::make_unique<::testing::NiceMock>()); + auto system_clipboard = std::make_shared(); + TestPlatformHandler platform_handler(&messenger, &view, [system_clipboard]() { + return std::make_unique(kErrorSuccess, false, + system_clipboard); + }); auto args = std::make_unique(rapidjson::kObjectType); auto& allocator = args->GetAllocator(); @@ -209,7 +324,13 @@ TEST(PlatformHandler, RejectsSettingUnknownTypes) { TEST(PlatformHandler, PlayingSystemSoundCallsThrough) { TestBinaryMessenger messenger; - TestPlatformHandler platform_handler(&messenger); + FlutterWindowsView view( + std::make_unique<::testing::NiceMock>()); + auto system_clipboard = std::make_shared(); + TestPlatformHandler platform_handler(&messenger, &view, [system_clipboard]() { + return std::make_unique(kErrorSuccess, false, + system_clipboard); + }); auto args = std::make_unique(rapidjson::kStringType); auto& allocator = args->GetAllocator(); @@ -233,5 +354,171 @@ TEST(PlatformHandler, PlayingSystemSoundCallsThrough) { [](const uint8_t* reply, size_t reply_size) {})); } +// Regression test for https://github.com/flutter/flutter/issues/95817. +TEST(PlatformHandler, HasStringsAccessDeniedReturnsFalseWithoutError) { + TestBinaryMessenger messenger; + FlutterWindowsView view( + std::make_unique<::testing::NiceMock>()); + // HasStrings will receive access denied on the clipboard, but will return + // false without error. + PlatformHandler platform_handler(&messenger, &view, []() { + return std::make_unique(kAccessDeniedErrorCode, true); + }); + + auto args = std::make_unique(rapidjson::kStringType); + auto& allocator = args->GetAllocator(); + args->SetString(kTextPlainFormat); + auto encoded = JsonMethodCodec::GetInstance().EncodeMethodCall( + MethodCall(kHasStringsClipboardMethod, + std::move(args))); + + MockMethodResult result; + rapidjson::Document document; + document.SetObject(); + rapidjson::Document::AllocatorType& document_allocator = + document.GetAllocator(); + document.AddMember(rapidjson::Value(kValueKey, document_allocator), + rapidjson::Value(false), document_allocator); + + EXPECT_CALL(result, SuccessInternal(_)) + .WillOnce([](const rapidjson::Document* document) { + ASSERT_FALSE((*document)[kValueKey].GetBool()); + }); + EXPECT_TRUE(messenger.SimulateEngineMessage( + kChannelName, encoded->data(), encoded->size(), + [&](const uint8_t* reply, size_t reply_size) { + JsonMethodCodec::GetInstance().DecodeAndProcessResponseEnvelope( + reply, reply_size, &result); + })); +} + +TEST(PlatformHandler, HasStringsSuccessWithStrings) { + TestBinaryMessenger messenger; + FlutterWindowsView view( + std::make_unique<::testing::NiceMock>()); + // HasStrings will succeed and return true. + PlatformHandler platform_handler(&messenger, &view, []() { + return std::make_unique(kErrorSuccess, true); + }); + + auto args = std::make_unique(rapidjson::kStringType); + auto& allocator = args->GetAllocator(); + args->SetString(kTextPlainFormat); + auto encoded = JsonMethodCodec::GetInstance().EncodeMethodCall( + MethodCall(kHasStringsClipboardMethod, + std::move(args))); + + MockMethodResult result; + rapidjson::Document document; + document.SetObject(); + rapidjson::Document::AllocatorType& document_allocator = + document.GetAllocator(); + document.AddMember(rapidjson::Value(kValueKey, document_allocator), + rapidjson::Value(false), document_allocator); + + EXPECT_CALL(result, SuccessInternal(_)) + .WillOnce([](const rapidjson::Document* document) { + ASSERT_TRUE((*document)[kValueKey].GetBool()); + }); + EXPECT_TRUE(messenger.SimulateEngineMessage( + kChannelName, encoded->data(), encoded->size(), + [&](const uint8_t* reply, size_t reply_size) { + JsonMethodCodec::GetInstance().DecodeAndProcessResponseEnvelope( + reply, reply_size, &result); + })); +} + +TEST(PlatformHandler, HasStringsSuccessWithoutStrings) { + TestBinaryMessenger messenger; + FlutterWindowsView view( + std::make_unique<::testing::NiceMock>()); + // HasStrings will succeed and return false. + PlatformHandler platform_handler(&messenger, &view, []() { + return std::make_unique(kErrorSuccess, false); + }); + + auto args = std::make_unique(rapidjson::kStringType); + auto& allocator = args->GetAllocator(); + args->SetString(kTextPlainFormat); + auto encoded = JsonMethodCodec::GetInstance().EncodeMethodCall( + MethodCall(kHasStringsClipboardMethod, + std::move(args))); + + MockMethodResult result; + rapidjson::Document document; + document.SetObject(); + rapidjson::Document::AllocatorType& document_allocator = + document.GetAllocator(); + document.AddMember(rapidjson::Value(kValueKey, document_allocator), + rapidjson::Value(false), document_allocator); + + EXPECT_CALL(result, SuccessInternal(_)) + .WillOnce([](const rapidjson::Document* document) { + ASSERT_FALSE((*document)[kValueKey].GetBool()); + }); + EXPECT_TRUE(messenger.SimulateEngineMessage( + kChannelName, encoded->data(), encoded->size(), + [&](const uint8_t* reply, size_t reply_size) { + JsonMethodCodec::GetInstance().DecodeAndProcessResponseEnvelope( + reply, reply_size, &result); + })); +} + +TEST(PlatformHandler, HasStringsError) { + TestBinaryMessenger messenger; + FlutterWindowsView view( + std::make_unique<::testing::NiceMock>()); + // HasStrings will fail. + PlatformHandler platform_handler(&messenger, &view, []() { + return std::make_unique(kArbitraryErrorCode, true); + }); + + auto args = std::make_unique(rapidjson::kStringType); + auto& allocator = args->GetAllocator(); + args->SetString(kTextPlainFormat); + auto encoded = JsonMethodCodec::GetInstance().EncodeMethodCall( + MethodCall(kHasStringsClipboardMethod, + std::move(args))); + + MockMethodResult result; + rapidjson::Document document; + document.SetObject(); + rapidjson::Document::AllocatorType& document_allocator = + document.GetAllocator(); + document.AddMember(rapidjson::Value(kValueKey, document_allocator), + rapidjson::Value(false), document_allocator); + + EXPECT_CALL(result, SuccessInternal(_)).Times(0); + EXPECT_CALL(result, ErrorInternal(_, _, _)).Times(1); + EXPECT_TRUE(messenger.SimulateEngineMessage( + kChannelName, encoded->data(), encoded->size(), + [&](const uint8_t* reply, size_t reply_size) { + JsonMethodCodec::GetInstance().DecodeAndProcessResponseEnvelope( + reply, reply_size, &result); + })); +} + +// Regression test for https://github.com/flutter/flutter/issues/103205. +TEST(PlatformHandler, ReleaseClipboard) { + auto system_clipboard = std::make_shared(); + + TestBinaryMessenger messenger; + FlutterWindowsView view( + std::make_unique<::testing::NiceMock>()); + TestPlatformHandler platform_handler(&messenger, &view, [system_clipboard]() { + return std::make_unique(kErrorSuccess, false, + system_clipboard); + }); + + platform_handler.GetPlainText(std::make_unique(), "text"); + ASSERT_FALSE(system_clipboard->opened); + + platform_handler.GetHasStrings(std::make_unique()); + ASSERT_FALSE(system_clipboard->opened); + + platform_handler.SetPlainText("", std::make_unique()); + ASSERT_FALSE(system_clipboard->opened); +} + } // namespace testing } // namespace flutter diff --git a/shell/platform/windows/platform_handler_win32.cc b/shell/platform/windows/platform_handler_win32.cc deleted file mode 100644 index 6932e8e0dd7fd..0000000000000 --- a/shell/platform/windows/platform_handler_win32.cc +++ /dev/null @@ -1,309 +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. - -#include "flutter/shell/platform/windows/platform_handler_win32.h" - -#include - -#include -#include -#include - -#include "flutter/fml/platform/win/wstring_conversion.h" -#include "flutter/shell/platform/windows/flutter_windows_view.h" - -static constexpr char kValueKey[] = "value"; -static constexpr int kAccessDeniedErrorCode = 5; -static constexpr int kErrorSuccess = 0; - -namespace flutter { - -namespace { - -// A scoped wrapper for GlobalAlloc/GlobalFree. -class ScopedGlobalMemory { - public: - // Allocates |bytes| bytes of global memory with the given flags. - ScopedGlobalMemory(unsigned int flags, size_t bytes) { - memory_ = ::GlobalAlloc(flags, bytes); - if (!memory_) { - std::cerr << "Unable to allocate global memory: " << ::GetLastError(); - } - } - - ~ScopedGlobalMemory() { - if (memory_) { - if (::GlobalFree(memory_) != nullptr) { - std::cerr << "Failed to free global allocation: " << ::GetLastError(); - } - } - } - - // Prevent copying. - ScopedGlobalMemory(ScopedGlobalMemory const&) = delete; - ScopedGlobalMemory& operator=(ScopedGlobalMemory const&) = delete; - - // Returns the memory pointer, which will be nullptr if allocation failed. - void* get() { return memory_; } - - void* release() { - void* memory = memory_; - memory_ = nullptr; - return memory; - } - - private: - HGLOBAL memory_; -}; - -// A scoped wrapper for GlobalLock/GlobalUnlock. -class ScopedGlobalLock { - public: - // Attempts to acquire a global lock on |memory| for the life of this object. - ScopedGlobalLock(HGLOBAL memory) { - source_ = memory; - if (memory) { - locked_memory_ = ::GlobalLock(memory); - if (!locked_memory_) { - std::cerr << "Unable to acquire global lock: " << ::GetLastError(); - } - } - } - - ~ScopedGlobalLock() { - if (locked_memory_) { - if (!::GlobalUnlock(source_)) { - DWORD error = ::GetLastError(); - if (error != NO_ERROR) { - std::cerr << "Unable to release global lock: " << ::GetLastError(); - } - } - } - } - - // Prevent copying. - ScopedGlobalLock(ScopedGlobalLock const&) = delete; - ScopedGlobalLock& operator=(ScopedGlobalLock const&) = delete; - - // Returns the locked memory pointer, which will be nullptr if acquiring the - // lock failed. - void* get() { return locked_memory_; } - - private: - HGLOBAL source_; - void* locked_memory_; -}; - -// A Clipboard wrapper that automatically closes the clipboard when it goes out -// of scope. -class ScopedClipboard : public ScopedClipboardInterface { - public: - ScopedClipboard(); - virtual ~ScopedClipboard(); - - // Prevent copying. - ScopedClipboard(ScopedClipboard const&) = delete; - ScopedClipboard& operator=(ScopedClipboard const&) = delete; - - int Open(HWND window) override; - - bool HasString() override; - - std::variant GetString() override; - - int SetString(const std::wstring string) override; - - private: - bool opened_ = false; -}; - -ScopedClipboard::ScopedClipboard() {} - -ScopedClipboard::~ScopedClipboard() { - if (opened_) { - ::CloseClipboard(); - } -} - -int ScopedClipboard::Open(HWND window) { - opened_ = ::OpenClipboard(window); - - if (!opened_) { - return ::GetLastError(); - } - - return kErrorSuccess; -} - -bool ScopedClipboard::HasString() { - // Allow either plain text format, since getting data will auto-interpolate. - return ::IsClipboardFormatAvailable(CF_UNICODETEXT) || - ::IsClipboardFormatAvailable(CF_TEXT); -} - -std::variant ScopedClipboard::GetString() { - assert(opened_); - - HANDLE data = ::GetClipboardData(CF_UNICODETEXT); - if (data == nullptr) { - return ::GetLastError(); - } - ScopedGlobalLock locked_data(data); - - if (!locked_data.get()) { - return ::GetLastError(); - } - return static_cast(locked_data.get()); -} - -int ScopedClipboard::SetString(const std::wstring string) { - assert(opened_); - if (!::EmptyClipboard()) { - return ::GetLastError(); - } - size_t null_terminated_byte_count = - sizeof(decltype(string)::traits_type::char_type) * (string.size() + 1); - ScopedGlobalMemory destination_memory(GMEM_MOVEABLE, - null_terminated_byte_count); - ScopedGlobalLock locked_memory(destination_memory.get()); - if (!locked_memory.get()) { - return ::GetLastError(); - } - memcpy(locked_memory.get(), string.c_str(), null_terminated_byte_count); - if (!::SetClipboardData(CF_UNICODETEXT, locked_memory.get())) { - return ::GetLastError(); - } - // The clipboard now owns the global memory. - destination_memory.release(); - return kErrorSuccess; -} - -} // namespace - -// static -std::unique_ptr PlatformHandler::Create( - BinaryMessenger* messenger, - FlutterWindowsView* view) { - return std::make_unique(messenger, view); -} - -PlatformHandlerWin32::PlatformHandlerWin32( - BinaryMessenger* messenger, - FlutterWindowsView* view, - std::optional()>> - scoped_clipboard_provider) - : PlatformHandler(messenger), view_(view) { - if (scoped_clipboard_provider.has_value()) { - scoped_clipboard_provider_ = scoped_clipboard_provider.value(); - } else { - scoped_clipboard_provider_ = []() { - return std::make_unique(); - }; - } -} - -PlatformHandlerWin32::~PlatformHandlerWin32() = default; - -void PlatformHandlerWin32::GetPlainText( - std::unique_ptr> result, - std::string_view key) { - std::unique_ptr clipboard = - scoped_clipboard_provider_(); - - int open_result = clipboard->Open(std::get(*view_->GetRenderTarget())); - if (open_result != kErrorSuccess) { - rapidjson::Document error_code; - error_code.SetInt(open_result); - result->Error(kClipboardError, "Unable to open clipboard", error_code); - return; - } - if (!clipboard->HasString()) { - result->Success(rapidjson::Document()); - return; - } - std::variant get_string_result = clipboard->GetString(); - if (std::holds_alternative(get_string_result)) { - rapidjson::Document error_code; - error_code.SetInt(std::get(get_string_result)); - result->Error(kClipboardError, "Unable to get clipboard data", error_code); - return; - } - - rapidjson::Document document; - document.SetObject(); - rapidjson::Document::AllocatorType& allocator = document.GetAllocator(); - document.AddMember( - rapidjson::Value(key.data(), allocator), - rapidjson::Value( - fml::WideStringToUtf8(std::get(get_string_result)), - allocator), - allocator); - result->Success(document); -} - -void PlatformHandlerWin32::GetHasStrings( - std::unique_ptr> result) { - std::unique_ptr clipboard = - scoped_clipboard_provider_(); - - bool hasStrings; - int open_result = clipboard->Open(std::get(*view_->GetRenderTarget())); - if (open_result != kErrorSuccess) { - // Swallow errors of type ERROR_ACCESS_DENIED. These happen when the app is - // not in the foreground and GetHasStrings is irrelevant. - // See https://github.com/flutter/flutter/issues/95817. - if (open_result != kAccessDeniedErrorCode) { - rapidjson::Document error_code; - error_code.SetInt(open_result); - result->Error(kClipboardError, "Unable to open clipboard", error_code); - return; - } - hasStrings = false; - } else { - hasStrings = clipboard->HasString(); - } - - rapidjson::Document document; - document.SetObject(); - rapidjson::Document::AllocatorType& allocator = document.GetAllocator(); - document.AddMember(rapidjson::Value(kValueKey, allocator), - rapidjson::Value(hasStrings), allocator); - result->Success(document); -} - -void PlatformHandlerWin32::SetPlainText( - const std::string& text, - std::unique_ptr> result) { - std::unique_ptr clipboard = - scoped_clipboard_provider_(); - - int open_result = clipboard->Open(std::get(*view_->GetRenderTarget())); - if (open_result != kErrorSuccess) { - rapidjson::Document error_code; - error_code.SetInt(open_result); - result->Error(kClipboardError, "Unable to open clipboard", error_code); - return; - } - int set_result = clipboard->SetString(fml::Utf8ToWideString(text)); - if (set_result != kErrorSuccess) { - rapidjson::Document error_code; - error_code.SetInt(set_result); - result->Error(kClipboardError, "Unable to set clipboard data", error_code); - return; - } - result->Success(); -} - -void PlatformHandlerWin32::SystemSoundPlay( - const std::string& sound_type, - std::unique_ptr> result) { - if (sound_type.compare(kSoundTypeAlert) == 0) { - MessageBeep(MB_OK); - result->Success(); - } else { - result->NotImplemented(); - } -} - -} // namespace flutter diff --git a/shell/platform/windows/platform_handler_win32.h b/shell/platform/windows/platform_handler_win32.h deleted file mode 100644 index 86cf2a3adfe1f..0000000000000 --- a/shell/platform/windows/platform_handler_win32.h +++ /dev/null @@ -1,87 +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. - -#ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_PLATFORM_HANDLER_WIN32_H_ -#define FLUTTER_SHELL_PLATFORM_WINDOWS_PLATFORM_HANDLER_WIN32_H_ - -#include "flutter/shell/platform/common/client_wrapper/include/flutter/binary_messenger.h" -#include "flutter/shell/platform/common/client_wrapper/include/flutter/method_channel.h" -#include "flutter/shell/platform/windows/flutter_windows_view.h" -#include "flutter/shell/platform/windows/platform_handler.h" -#include "rapidjson/document.h" - -namespace flutter { - -class FlutterWindowsView; - -// A public interface for ScopedClipboard, so that it can be injected into -// PlatformHandlerWin32. -class ScopedClipboardInterface { - public: - virtual ~ScopedClipboardInterface(){}; - - // Attempts to open the clipboard for the given window, returning the error - // code in the case of failure and 0 otherwise. - virtual int Open(HWND window) = 0; - - // Returns true if there is string data available to get. - virtual bool HasString() = 0; - - // Returns string data from the clipboard. - // - // If getting a string fails, returns the error code. - // - // Open(...) must have succeeded to call this method. - virtual std::variant GetString() = 0; - - // Sets the string content of the clipboard, returning the error code on - // failure and 0 otherwise. - // - // Open(...) must have succeeded to call this method. - virtual int SetString(const std::wstring string) = 0; -}; - -// Win32 implementation of PlatformHandler. -class PlatformHandlerWin32 : public PlatformHandler { - public: - explicit PlatformHandlerWin32( - BinaryMessenger* messenger, - FlutterWindowsView* view, - std::optional()>> - scoped_clipboard_provider = std::nullopt); - - virtual ~PlatformHandlerWin32(); - - protected: - // |PlatformHandler| - void GetPlainText(std::unique_ptr> result, - std::string_view key) override; - - // |PlatformHandler| - void GetHasStrings( - std::unique_ptr> result) override; - - // |PlatformHandler| - void SetPlainText( - const std::string& text, - std::unique_ptr> result) override; - - // |PlatformHandler| - void SystemSoundPlay( - const std::string& sound_type, - std::unique_ptr> result) override; - - private: - // A reference to the Flutter view. - FlutterWindowsView* view_; - // A scoped clipboard provider that can be passed in for mocking in tests. - // Use this to acquire clipboard in each operation to avoid blocking clipboard - // unnecessarily. See flutter/flutter#103205. - std::function()> - scoped_clipboard_provider_; -}; - -} // namespace flutter - -#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_PLATFORM_HANDLER_WIN32_H_ diff --git a/shell/platform/windows/platform_handler_win32_unittests.cc b/shell/platform/windows/platform_handler_win32_unittests.cc deleted file mode 100644 index 6b1afabae8a75..0000000000000 --- a/shell/platform/windows/platform_handler_win32_unittests.cc +++ /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. - -#include "flutter/shell/platform/windows/platform_handler_win32.h" - -#include - -#include "flutter/shell/platform/common/json_method_codec.h" -#include "flutter/shell/platform/windows/testing/mock_window_binding_handler.h" -#include "flutter/shell/platform/windows/testing/test_binary_messenger.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "rapidjson/document.h" - -namespace flutter { -namespace testing { - -namespace { -using ::testing::_; - -static constexpr char kChannelName[] = "flutter/platform"; - -static constexpr char kHasStringsClipboardMethod[] = "Clipboard.hasStrings"; - -static constexpr char kTextPlainFormat[] = "text/plain"; - -static constexpr char kValueKey[] = "value"; -static constexpr int kAccessDeniedErrorCode = 5; -static constexpr int kErrorSuccess = 0; -static constexpr int kArbitraryErrorCode = 1; - -} // namespace - -// A test version of system clipboard. -class MockSystemClipboard { - public: - void OpenClipboard() { opened = true; } - void CloseClipboard() { opened = false; } - bool opened = false; -}; - -class TestPlatformHandlerWin32 : public PlatformHandlerWin32 { - public: - explicit TestPlatformHandlerWin32( - BinaryMessenger* messenger, - FlutterWindowsView* view, - std::optional()>> - scoped_clipboard_provider = std::nullopt) - : PlatformHandlerWin32(messenger, view, scoped_clipboard_provider) {} - - virtual ~TestPlatformHandlerWin32() = default; - - FRIEND_TEST(PlatformHandlerWin32, ReleaseClipboard); -}; - -// A test version of the private ScopedClipboard. -class TestScopedClipboard : public ScopedClipboardInterface { - public: - TestScopedClipboard(int open_error, - bool has_strings, - std::shared_ptr clipboard); - ~TestScopedClipboard(); - - // Prevent copying. - TestScopedClipboard(TestScopedClipboard const&) = delete; - TestScopedClipboard& operator=(TestScopedClipboard const&) = delete; - - int Open(HWND window) override; - - bool HasString() override; - - std::variant GetString() override; - - int SetString(const std::wstring string) override; - - private: - bool opened_ = false; - bool has_strings_; - int open_error_; - std::shared_ptr clipboard_; -}; - -TestScopedClipboard::TestScopedClipboard( - int open_error, - bool has_strings, - std::shared_ptr clipboard = nullptr) { - open_error_ = open_error; - has_strings_ = has_strings; - clipboard_ = clipboard; -} - -TestScopedClipboard::~TestScopedClipboard() { - if ((!open_error_) && clipboard_ != nullptr) { - clipboard_->CloseClipboard(); - } -} - -int TestScopedClipboard::Open(HWND window) { - if ((!open_error_) && clipboard_ != nullptr) { - clipboard_->OpenClipboard(); - } - return open_error_; -} - -bool TestScopedClipboard::HasString() { - return has_strings_; -} - -std::variant TestScopedClipboard::GetString() { - return -1; -} - -int TestScopedClipboard::SetString(const std::wstring string) { - return -1; -} - -class MockMethodResult : public MethodResult { - public: - MOCK_METHOD1(SuccessInternal, void(const rapidjson::Document*)); - MOCK_METHOD3(ErrorInternal, - void(const std::string&, - const std::string&, - const rapidjson::Document*)); - MOCK_METHOD0(NotImplementedInternal, void()); -}; - -// Regression test for https://github.com/flutter/flutter/issues/95817. -TEST(PlatformHandlerWin32, HasStringsAccessDeniedReturnsFalseWithoutError) { - TestBinaryMessenger messenger; - FlutterWindowsView view( - std::make_unique<::testing::NiceMock>()); - // HasStrings will receive access denied on the clipboard, but will return - // false without error. - PlatformHandlerWin32 platform_handler(&messenger, &view, []() { - return std::make_unique(kAccessDeniedErrorCode, true); - }); - - auto args = std::make_unique(rapidjson::kStringType); - auto& allocator = args->GetAllocator(); - args->SetString(kTextPlainFormat); - auto encoded = JsonMethodCodec::GetInstance().EncodeMethodCall( - MethodCall(kHasStringsClipboardMethod, - std::move(args))); - - MockMethodResult result; - rapidjson::Document document; - document.SetObject(); - rapidjson::Document::AllocatorType& document_allocator = - document.GetAllocator(); - document.AddMember(rapidjson::Value(kValueKey, document_allocator), - rapidjson::Value(false), document_allocator); - - EXPECT_CALL(result, SuccessInternal(_)) - .WillOnce([](const rapidjson::Document* document) { - ASSERT_FALSE((*document)[kValueKey].GetBool()); - }); - EXPECT_TRUE(messenger.SimulateEngineMessage( - kChannelName, encoded->data(), encoded->size(), - [&](const uint8_t* reply, size_t reply_size) { - JsonMethodCodec::GetInstance().DecodeAndProcessResponseEnvelope( - reply, reply_size, &result); - })); -} - -TEST(PlatformHandlerWin32, HasStringsSuccessWithStrings) { - TestBinaryMessenger messenger; - FlutterWindowsView view( - std::make_unique<::testing::NiceMock>()); - // HasStrings will succeed and return true. - PlatformHandlerWin32 platform_handler(&messenger, &view, []() { - return std::make_unique(kErrorSuccess, true); - }); - - auto args = std::make_unique(rapidjson::kStringType); - auto& allocator = args->GetAllocator(); - args->SetString(kTextPlainFormat); - auto encoded = JsonMethodCodec::GetInstance().EncodeMethodCall( - MethodCall(kHasStringsClipboardMethod, - std::move(args))); - - MockMethodResult result; - rapidjson::Document document; - document.SetObject(); - rapidjson::Document::AllocatorType& document_allocator = - document.GetAllocator(); - document.AddMember(rapidjson::Value(kValueKey, document_allocator), - rapidjson::Value(false), document_allocator); - - EXPECT_CALL(result, SuccessInternal(_)) - .WillOnce([](const rapidjson::Document* document) { - ASSERT_TRUE((*document)[kValueKey].GetBool()); - }); - EXPECT_TRUE(messenger.SimulateEngineMessage( - kChannelName, encoded->data(), encoded->size(), - [&](const uint8_t* reply, size_t reply_size) { - JsonMethodCodec::GetInstance().DecodeAndProcessResponseEnvelope( - reply, reply_size, &result); - })); -} - -TEST(PlatformHandlerWin32, HasStringsSuccessWithoutStrings) { - TestBinaryMessenger messenger; - FlutterWindowsView view( - std::make_unique<::testing::NiceMock>()); - // HasStrings will succeed and return false. - PlatformHandlerWin32 platform_handler(&messenger, &view, []() { - return std::make_unique(kErrorSuccess, false); - }); - - auto args = std::make_unique(rapidjson::kStringType); - auto& allocator = args->GetAllocator(); - args->SetString(kTextPlainFormat); - auto encoded = JsonMethodCodec::GetInstance().EncodeMethodCall( - MethodCall(kHasStringsClipboardMethod, - std::move(args))); - - MockMethodResult result; - rapidjson::Document document; - document.SetObject(); - rapidjson::Document::AllocatorType& document_allocator = - document.GetAllocator(); - document.AddMember(rapidjson::Value(kValueKey, document_allocator), - rapidjson::Value(false), document_allocator); - - EXPECT_CALL(result, SuccessInternal(_)) - .WillOnce([](const rapidjson::Document* document) { - ASSERT_FALSE((*document)[kValueKey].GetBool()); - }); - EXPECT_TRUE(messenger.SimulateEngineMessage( - kChannelName, encoded->data(), encoded->size(), - [&](const uint8_t* reply, size_t reply_size) { - JsonMethodCodec::GetInstance().DecodeAndProcessResponseEnvelope( - reply, reply_size, &result); - })); -} - -TEST(PlatformHandlerWin32, HasStringsError) { - TestBinaryMessenger messenger; - FlutterWindowsView view( - std::make_unique<::testing::NiceMock>()); - // HasStrings will fail. - PlatformHandlerWin32 platform_handler(&messenger, &view, []() { - return std::make_unique(kArbitraryErrorCode, true); - }); - - auto args = std::make_unique(rapidjson::kStringType); - auto& allocator = args->GetAllocator(); - args->SetString(kTextPlainFormat); - auto encoded = JsonMethodCodec::GetInstance().EncodeMethodCall( - MethodCall(kHasStringsClipboardMethod, - std::move(args))); - - MockMethodResult result; - rapidjson::Document document; - document.SetObject(); - rapidjson::Document::AllocatorType& document_allocator = - document.GetAllocator(); - document.AddMember(rapidjson::Value(kValueKey, document_allocator), - rapidjson::Value(false), document_allocator); - - EXPECT_CALL(result, SuccessInternal(_)).Times(0); - EXPECT_CALL(result, ErrorInternal(_, _, _)).Times(1); - EXPECT_TRUE(messenger.SimulateEngineMessage( - kChannelName, encoded->data(), encoded->size(), - [&](const uint8_t* reply, size_t reply_size) { - JsonMethodCodec::GetInstance().DecodeAndProcessResponseEnvelope( - reply, reply_size, &result); - })); -} - -// Regression test for https://github.com/flutter/flutter/issues/103205. -TEST(PlatformHandlerWin32, ReleaseClipboard) { - auto system_clipboard = std::make_shared(); - - TestBinaryMessenger messenger; - FlutterWindowsView view( - std::make_unique<::testing::NiceMock>()); - TestPlatformHandlerWin32 platform_handler( - &messenger, &view, [system_clipboard]() { - return std::make_unique(kErrorSuccess, false, - system_clipboard); - }); - - platform_handler.GetPlainText(std::make_unique(), "text"); - ASSERT_FALSE(system_clipboard->opened); - - platform_handler.GetHasStrings(std::make_unique()); - ASSERT_FALSE(system_clipboard->opened); - - platform_handler.SetPlainText("", std::make_unique()); - ASSERT_FALSE(system_clipboard->opened); -} - -} // namespace testing -} // namespace flutter diff --git a/shell/platform/windows/public/flutter_windows.h b/shell/platform/windows/public/flutter_windows.h index ad03dd3a123f4..c5306beba0730 100644 --- a/shell/platform/windows/public/flutter_windows.h +++ b/shell/platform/windows/public/flutter_windows.h @@ -17,6 +17,8 @@ extern "C" { #endif +typedef void (*VoidCallback)(void* /* user data */); + // Opaque reference to a Flutter window controller. typedef struct FlutterDesktopViewControllerState* FlutterDesktopViewControllerRef; @@ -47,6 +49,14 @@ typedef struct { // it will be ignored in that case. const wchar_t* aot_library_path; + // The name of the top-level Dart entrypoint function. If null or the empty + // string, 'main' is assumed. If a custom entrypoint is used, this parameter + // must specifiy the name of a top-level function in the same Dart library as + // the app's main() function. Custom entrypoint functions must be decorated + // with `@pragma('vm:entry-point')` to ensure the method is not tree-shaken + // by the Dart compiler. + const char* dart_entrypoint; + // Number of elements in the array passed in as dart_entrypoint_argv. int dart_entrypoint_argc; @@ -129,13 +139,19 @@ FLUTTER_EXPORT FlutterDesktopEngineRef FlutterDesktopEngineCreate( // |engine| is no longer valid after this call. FLUTTER_EXPORT bool FlutterDesktopEngineDestroy(FlutterDesktopEngineRef engine); -// Starts running the given engine instance and optional entry point in the Dart -// project. If the entry point is null, defaults to main(). +// Starts running the given engine instance. +// +// The entry_point parameter is deprecated but preserved for +// backward-compatibility. If desired, a custom Dart entrypoint function can be +// set in the dart_entrypoint field of the FlutterDesktopEngineProperties +// struct passed to FlutterDesktopEngineCreate. // -// If provided, entry_point must be the name of a top-level function from the +// If sprecified, entry_point must be the name of a top-level function from the // same Dart library that contains the app's main() function, and must be // decorated with `@pragma(vm:entry-point)` to ensure the method is not -// tree-shaken by the Dart compiler. +// tree-shaken by the Dart compiler. If conflicting non-null values are passed +// to this function and via the FlutterDesktopEngineProperties struct, the run +// will fail. // // Returns false if running the engine failed. FLUTTER_EXPORT bool FlutterDesktopEngineRun(FlutterDesktopEngineRef engine, @@ -169,8 +185,16 @@ FlutterDesktopEngineGetMessenger(FlutterDesktopEngineRef engine); // Returns the texture registrar associated with the engine. FLUTTER_EXPORT FlutterDesktopTextureRegistrarRef -FlutterDesktopEngineGetTextureRegistrar( - FlutterDesktopTextureRegistrarRef texture_registrar); +FlutterDesktopEngineGetTextureRegistrar(FlutterDesktopEngineRef engine); + +// Schedule a callback to be called after the next frame is drawn. +// +// This must be called from the platform thread. The callback is executed only +// once on the platform thread. +FLUTTER_EXPORT void FlutterDesktopEngineSetNextFrameCallback( + FlutterDesktopEngineRef engine, + VoidCallback callback, + void* user_data); // ========== View ========== diff --git a/shell/platform/windows/task_runner.cc b/shell/platform/windows/task_runner.cc index 464afbc43827e..10a699403d919 100644 --- a/shell/platform/windows/task_runner.cc +++ b/shell/platform/windows/task_runner.cc @@ -12,7 +12,15 @@ namespace flutter { TaskRunner::TaskRunner(CurrentTimeProc get_current_time, const TaskExpiredCallback& on_task_expired) : get_current_time_(get_current_time), - on_task_expired_(std::move(on_task_expired)) {} + on_task_expired_(std::move(on_task_expired)) { + main_thread_id_ = GetCurrentThreadId(); + task_runner_window_ = TaskRunnerWindow::GetSharedInstance(); + task_runner_window_->AddDelegate(this); +} + +TaskRunner::~TaskRunner() { + task_runner_window_->RemoveDelegate(this); +} std::chrono::nanoseconds TaskRunner::ProcessTasks() { const TaskTimePoint now = GetCurrentTimeForTask(); @@ -101,4 +109,12 @@ void TaskRunner::EnqueueTask(Task task) { WakeUp(); } +bool TaskRunner::RunsTasksOnCurrentThread() const { + return GetCurrentThreadId() == main_thread_id_; +} + +void TaskRunner::WakeUp() { + task_runner_window_->WakeUp(); +} + } // namespace flutter diff --git a/shell/platform/windows/task_runner.h b/shell/platform/windows/task_runner.h index 685ed77e93f08..b211dbedcd427 100644 --- a/shell/platform/windows/task_runner.h +++ b/shell/platform/windows/task_runner.h @@ -14,21 +14,30 @@ #include #include "flutter/shell/platform/embedder/embedder.h" +#include "flutter/shell/platform/windows/task_runner_window.h" namespace flutter { typedef uint64_t (*CurrentTimeProc)(); -class TaskRunner { +// A custom task runner that integrates with user32 GetMessage semantics so +// that host app can own its own message loop and flutter still gets to process +// tasks on a timely basis. +class TaskRunner : public TaskRunnerWindow::Delegate { public: using TaskTimePoint = std::chrono::steady_clock::time_point; using TaskExpiredCallback = std::function; using TaskClosure = std::function; - virtual ~TaskRunner() = default; + // Creates a new task runner with the current thread, current time + // provider, and callback for tasks that are ready to be run. + TaskRunner(CurrentTimeProc get_current_time, + const TaskExpiredCallback& on_task_expired); + + virtual ~TaskRunner(); // Returns `true` if the current thread is this runner's thread. - virtual bool RunsTasksOnCurrentThread() const = 0; + virtual bool RunsTasksOnCurrentThread() const; // Post a Flutter engine task to the event loop for delayed execution. void PostFlutterTask(FlutterTask flutter_task, @@ -47,33 +56,9 @@ class TaskRunner { } } - // Creates a new task runner with the current thread, current time - // provider, and callback for tasks that are ready to be run. - static std::unique_ptr Create( - CurrentTimeProc get_current_time, - const TaskExpiredCallback& on_task_expired); - - protected: - TaskRunner(CurrentTimeProc get_current_time, - const TaskExpiredCallback& on_task_expired); - - // Schedules timers to call `ProcessTasks()` at the runner's thread. - virtual void WakeUp() = 0; - - // Executes expired task, and returns the duration until the next task - // deadline if exists, otherwise returns `std::chrono::nanoseconds::max()`. - // - // Each platform implementations must call this to schedule the tasks. + // |TaskRunnerWindow::Delegate| std::chrono::nanoseconds ProcessTasks(); - // Returns the current TaskTimePoint that can be used to determine whether a - // task is expired. - // - // Tests can override this to mock up the time. - virtual TaskTimePoint GetCurrentTimeForTask() const { - return TaskTimePoint::clock::now(); - } - private: typedef std::variant TaskVariant; @@ -95,6 +80,17 @@ class TaskRunner { // Enqueues the given task. void EnqueueTask(Task task); + // Schedules timers to call `ProcessTasks()` at the runner's thread. + virtual void WakeUp(); + + // Returns the current TaskTimePoint that can be used to determine whether a + // task is expired. + // + // Tests can override this to mock up the time. + virtual TaskTimePoint GetCurrentTimeForTask() const { + return TaskTimePoint::clock::now(); + } + // Returns a TaskTimePoint computed from the given target time from Flutter. TaskTimePoint TimePointFromFlutterTime( uint64_t flutter_target_time_nanos) const; @@ -103,6 +99,8 @@ class TaskRunner { TaskExpiredCallback on_task_expired_; std::mutex task_queue_mutex_; std::priority_queue, Task::Comparer> task_queue_; + DWORD main_thread_id_; + std::shared_ptr task_runner_window_; TaskRunner(const TaskRunner&) = delete; TaskRunner& operator=(const TaskRunner&) = delete; diff --git a/shell/platform/windows/task_runner_win32.cc b/shell/platform/windows/task_runner_win32.cc deleted file mode 100644 index 01c7179c792f5..0000000000000 --- a/shell/platform/windows/task_runner_win32.cc +++ /dev/null @@ -1,40 +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. - -#include "flutter/shell/platform/windows/task_runner_win32.h" - -namespace flutter { - -// static -std::unique_ptr TaskRunner::Create( - CurrentTimeProc get_current_time, - const TaskExpiredCallback& on_task_expired) { - return std::make_unique(get_current_time, on_task_expired); -} - -TaskRunnerWin32::TaskRunnerWin32(CurrentTimeProc get_current_time, - const TaskExpiredCallback& on_task_expired) - : TaskRunner(get_current_time, on_task_expired) { - main_thread_id_ = GetCurrentThreadId(); - task_runner_window_ = TaskRunnerWin32Window::GetSharedInstance(); - task_runner_window_->AddDelegate(this); -} - -TaskRunnerWin32::~TaskRunnerWin32() { - task_runner_window_->RemoveDelegate(this); -} - -bool TaskRunnerWin32::RunsTasksOnCurrentThread() const { - return GetCurrentThreadId() == main_thread_id_; -} - -std::chrono::nanoseconds TaskRunnerWin32::ProcessTasks() { - return TaskRunner::ProcessTasks(); -} - -void TaskRunnerWin32::WakeUp() { - task_runner_window_->WakeUp(); -} - -} // namespace flutter diff --git a/shell/platform/windows/task_runner_win32.h b/shell/platform/windows/task_runner_win32.h deleted file mode 100644 index 6e3ceb8476f63..0000000000000 --- a/shell/platform/windows/task_runner_win32.h +++ /dev/null @@ -1,46 +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. - -#ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_TASK_RUNNER_WIN32_H_ -#define FLUTTER_SHELL_PLATFORM_WINDOWS_TASK_RUNNER_WIN32_H_ - -#include - -#include "flutter/shell/platform/embedder/embedder.h" -#include "flutter/shell/platform/windows/task_runner.h" -#include "flutter/shell/platform/windows/task_runner_win32_window.h" - -namespace flutter { - -// A custom task runner that integrates with user32 GetMessage semantics so that -// host app can own its own message loop and flutter still gets to process -// tasks on a timely basis. -class TaskRunnerWin32 : public TaskRunner, - public TaskRunnerWin32Window::Delegate { - public: - TaskRunnerWin32(CurrentTimeProc get_current_time, - const TaskExpiredCallback& on_task_expired); - virtual ~TaskRunnerWin32(); - - // |TaskRunner| - bool RunsTasksOnCurrentThread() const override; - - // |TaskRunnerWin32Window::Delegate| - std::chrono::nanoseconds ProcessTasks() override; - - protected: - // |TaskRunner| - void WakeUp() override; - - private: - DWORD main_thread_id_; - std::shared_ptr task_runner_window_; - - TaskRunnerWin32(const TaskRunnerWin32&) = delete; - TaskRunnerWin32& operator=(const TaskRunnerWin32&) = delete; -}; - -} // namespace flutter - -#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_TASK_RUNNER_WIN32_H_ diff --git a/shell/platform/windows/task_runner_win32_window.cc b/shell/platform/windows/task_runner_window.cc similarity index 70% rename from shell/platform/windows/task_runner_win32_window.cc rename to shell/platform/windows/task_runner_window.cc index a08f88cd33621..5aa21da9dc67b 100644 --- a/shell/platform/windows/task_runner_win32_window.cc +++ b/shell/platform/windows/task_runner_window.cc @@ -2,14 +2,15 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "flutter/shell/platform/windows/task_runner_win32_window.h" +#include "flutter/shell/platform/windows/task_runner_window.h" #include -#include + +#include "flutter/fml/logging.h" namespace flutter { -TaskRunnerWin32Window::TaskRunnerWin32Window() { +TaskRunnerWindow::TaskRunnerWindow() { WNDCLASS window_class = RegisterWindowClass(); window_handle_ = CreateWindowEx(0, window_class.lpszClassName, L"", 0, 0, 0, 0, 0, @@ -31,7 +32,7 @@ TaskRunnerWin32Window::TaskRunnerWin32Window() { } } -TaskRunnerWin32Window::~TaskRunnerWin32Window() { +TaskRunnerWindow::~TaskRunnerWindow() { if (window_handle_) { DestroyWindow(window_handle_); window_handle_ = nullptr; @@ -39,37 +40,36 @@ TaskRunnerWin32Window::~TaskRunnerWin32Window() { UnregisterClass(window_class_name_.c_str(), nullptr); } -std::shared_ptr -TaskRunnerWin32Window::GetSharedInstance() { - static std::weak_ptr instance; +std::shared_ptr TaskRunnerWindow::GetSharedInstance() { + static std::weak_ptr instance; auto res = instance.lock(); if (!res) { // can't use make_shared with private contructor - res.reset(new TaskRunnerWin32Window()); + res.reset(new TaskRunnerWindow()); instance = res; } return res; } -void TaskRunnerWin32Window::WakeUp() { +void TaskRunnerWindow::WakeUp() { if (!PostMessage(window_handle_, WM_NULL, 0, 0)) { - std::cerr << "Failed to post message to main thread." << std::endl; + FML_LOG(ERROR) << "Failed to post message to main thread."; } } -void TaskRunnerWin32Window::AddDelegate(Delegate* delegate) { +void TaskRunnerWindow::AddDelegate(Delegate* delegate) { delegates_.push_back(delegate); SetTimer(std::chrono::nanoseconds::zero()); } -void TaskRunnerWin32Window::RemoveDelegate(Delegate* delegate) { +void TaskRunnerWindow::RemoveDelegate(Delegate* delegate) { auto i = std::find(delegates_.begin(), delegates_.end(), delegate); if (i != delegates_.end()) { delegates_.erase(i); } } -void TaskRunnerWin32Window::ProcessTasks() { +void TaskRunnerWindow::ProcessTasks() { auto next = std::chrono::nanoseconds::max(); auto delegates_copy(delegates_); for (auto delegate : delegates_copy) { @@ -82,7 +82,7 @@ void TaskRunnerWin32Window::ProcessTasks() { SetTimer(next); } -void TaskRunnerWin32Window::SetTimer(std::chrono::nanoseconds when) { +void TaskRunnerWindow::SetTimer(std::chrono::nanoseconds when) { if (when == std::chrono::nanoseconds::max()) { KillTimer(window_handle_, 0); } else { @@ -91,7 +91,7 @@ void TaskRunnerWin32Window::SetTimer(std::chrono::nanoseconds when) { } } -WNDCLASS TaskRunnerWin32Window::RegisterWindowClass() { +WNDCLASS TaskRunnerWindow::RegisterWindowClass() { window_class_name_ = L"FlutterTaskRunnerWindow"; WNDCLASS window_class{}; @@ -110,9 +110,9 @@ WNDCLASS TaskRunnerWin32Window::RegisterWindowClass() { } LRESULT -TaskRunnerWin32Window::HandleMessage(UINT const message, - WPARAM const wparam, - LPARAM const lparam) noexcept { +TaskRunnerWindow::HandleMessage(UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { switch (message) { case WM_TIMER: case WM_NULL: @@ -122,11 +122,11 @@ TaskRunnerWin32Window::HandleMessage(UINT const message, return DefWindowProcW(window_handle_, message, wparam, lparam); } -LRESULT TaskRunnerWin32Window::WndProc(HWND const window, - UINT const message, - WPARAM const wparam, - LPARAM const lparam) noexcept { - if (auto* that = reinterpret_cast( +LRESULT TaskRunnerWindow::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (auto* that = reinterpret_cast( GetWindowLongPtr(window, GWLP_USERDATA))) { return that->HandleMessage(message, wparam, lparam); } else { diff --git a/shell/platform/windows/task_runner_win32_window.h b/shell/platform/windows/task_runner_window.h similarity index 78% rename from shell/platform/windows/task_runner_win32_window.h rename to shell/platform/windows/task_runner_window.h index a11d8ef0dd628..ae874f82af71f 100644 --- a/shell/platform/windows/task_runner_win32_window.h +++ b/shell/platform/windows/task_runner_window.h @@ -15,14 +15,18 @@ namespace flutter { // Hidden HWND responsible for processing flutter tasks on main thread -class TaskRunnerWin32Window { +class TaskRunnerWindow { public: class Delegate { public: + // Executes expired task, and returns the duration until the next task + // deadline if exists, otherwise returns `std::chrono::nanoseconds::max()`. + // + // Each platform implementation must call this to schedule the tasks. virtual std::chrono::nanoseconds ProcessTasks() = 0; }; - static std::shared_ptr GetSharedInstance(); + static std::shared_ptr GetSharedInstance(); // Triggers processing delegate tasks on main thread void WakeUp(); @@ -30,10 +34,10 @@ class TaskRunnerWin32Window { void AddDelegate(Delegate* delegate); void RemoveDelegate(Delegate* delegate); - ~TaskRunnerWin32Window(); + ~TaskRunnerWindow(); private: - TaskRunnerWin32Window(); + TaskRunnerWindow(); void ProcessTasks(); diff --git a/shell/platform/windows/testing/mock_direct_manipulation.h b/shell/platform/windows/testing/mock_direct_manipulation.h new file mode 100644 index 0000000000000..be1fe35027f07 --- /dev/null +++ b/shell/platform/windows/testing/mock_direct_manipulation.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_SHELL_PLATFORM_WINDOWS_TESTING_MOCK_DIRECT_MANIPULATION_H_ +#define FLUTTER_SHELL_PLATFORM_WINDOWS_TESTING_MOCK_DIRECT_MANIPULATION_H_ + +#include "flutter/shell/platform/windows/direct_manipulation.h" +#include "gmock/gmock.h" + +namespace flutter { +namespace testing { + +/// Mock for the |DirectManipulationOwner| base class. +class MockDirectManipulationOwner : public DirectManipulationOwner { + public: + explicit MockDirectManipulationOwner(Window* window) + : DirectManipulationOwner(window){}; + virtual ~MockDirectManipulationOwner() = default; + + MOCK_METHOD1(SetContact, void(UINT contact_id)); + + private: + FML_DISALLOW_COPY_AND_ASSIGN(MockDirectManipulationOwner); +}; + +} // namespace testing +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_TESTING_MOCK_DIRECT_MANIPULATION_H_ diff --git a/shell/platform/windows/testing/mock_window.cc b/shell/platform/windows/testing/mock_window.cc index a6d3dcda64b14..9021aecb3352a 100644 --- a/shell/platform/windows/testing/mock_window.cc +++ b/shell/platform/windows/testing/mock_window.cc @@ -7,8 +7,9 @@ namespace flutter { namespace testing { MockWindow::MockWindow() : Window(){}; -MockWindow::MockWindow(std::unique_ptr text_input_manager) - : Window(std::move(text_input_manager)){}; +MockWindow::MockWindow(std::unique_ptr window_proc_table, + std::unique_ptr text_input_manager) + : Window(std::move(window_proc_table), std::move(text_input_manager)){}; MockWindow::~MockWindow() = default; @@ -23,6 +24,11 @@ LRESULT MockWindow::Win32DefWindowProc(HWND hWnd, return kWmResultDefault; } +void MockWindow::SetDirectManipulationOwner( + std::unique_ptr owner) { + direct_manipulation_owner_ = std::move(owner); +} + LRESULT MockWindow::InjectWindowMessage(UINT const message, WPARAM const wparam, LPARAM const lparam) { diff --git a/shell/platform/windows/testing/mock_window.h b/shell/platform/windows/testing/mock_window.h index 107e016c390f8..8885096fa1d28 100644 --- a/shell/platform/windows/testing/mock_window.h +++ b/shell/platform/windows/testing/mock_window.h @@ -18,7 +18,8 @@ namespace testing { class MockWindow : public Window { public: MockWindow(); - MockWindow(std::unique_ptr text_input_manager); + MockWindow(std::unique_ptr windows_proc_table, + std::unique_ptr text_input_manager); virtual ~MockWindow(); // Prevent copying. @@ -28,6 +29,10 @@ class MockWindow : public Window { // Wrapper for GetCurrentDPI() which is a protected method. UINT GetDpi(); + // Set the Direct Manipulation owner for testing purposes. + void SetDirectManipulationOwner( + std::unique_ptr owner); + // Simulates a WindowProc message from the OS. LRESULT InjectWindowMessage(UINT const message, WPARAM const wparam, diff --git a/shell/platform/windows/testing/mock_windows_proc_table.h b/shell/platform/windows/testing/mock_windows_proc_table.h new file mode 100644 index 0000000000000..7c136def68654 --- /dev/null +++ b/shell/platform/windows/testing/mock_windows_proc_table.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_SHELL_PLATFORM_WINDOWS_TESTING_MOCK_WINDOWS_PROC_TABLE_H_ +#define FLUTTER_SHELL_PLATFORM_WINDOWS_TESTING_MOCK_WINDOWS_PROC_TABLE_H_ + +#include "flutter/shell/platform/windows/windows_proc_table.h" +#include "gmock/gmock.h" + +namespace flutter { +namespace testing { + +/// Mock for the |WindowsProcTable| base class. +class MockWindowsProcTable : public WindowsProcTable { + public: + MockWindowsProcTable() = default; + virtual ~MockWindowsProcTable() = default; + + MOCK_METHOD2(GetPointerType, + BOOL(UINT32 pointer_id, POINTER_INPUT_TYPE* pointer_type)); + + private: + FML_DISALLOW_COPY_AND_ASSIGN(MockWindowsProcTable); +}; + +} // namespace testing +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_TESTING_MOCK_WINDOWS_PROC_TABLE_H_ diff --git a/shell/platform/windows/testing/windows_test.cc b/shell/platform/windows/testing/windows_test.cc new file mode 100644 index 0000000000000..bd5bbb1c0012a --- /dev/null +++ b/shell/platform/windows/testing/windows_test.cc @@ -0,0 +1,26 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/shell/platform/windows/testing/windows_test.h" + +#include + +#include "flutter/shell/platform/windows/testing/windows_test_context.h" +#include "flutter/testing/testing.h" + +namespace flutter { +namespace testing { + +WindowsTest::WindowsTest() : context_(GetFixturesDirectory()) {} + +std::string WindowsTest::GetFixturesDirectory() const { + return GetFixturesPath(); +} + +WindowsTestContext& WindowsTest::GetContext() { + return context_; +} + +} // namespace testing +} // namespace flutter diff --git a/shell/platform/windows/testing/windows_test.h b/shell/platform/windows/testing/windows_test.h new file mode 100644 index 0000000000000..21744d0ea8634 --- /dev/null +++ b/shell/platform/windows/testing/windows_test.h @@ -0,0 +1,47 @@ +// 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_SHELL_PLATFORM_WINDOWS_TESTING_WINDOWS_TEST_H_ +#define FLUTTER_SHELL_PLATFORM_WINDOWS_TESTING_WINDOWS_TEST_H_ + +#include + +#include "flutter/fml/macros.h" +#include "flutter/shell/platform/windows/testing/windows_test_context.h" +#include "flutter/testing/thread_test.h" + +namespace flutter { +namespace testing { + +/// A GoogleTest test fixture for Windows tests. +/// +/// Supports looking up the test fixture data defined in the GN `test_fixtures` +/// associated with the unit test executable target. This typically includes +/// the kernel bytecode `kernel_blob.bin` compiled from the Dart file specified +/// in the test fixture's `dart_main` property, as well as any other data files +/// used in tests, such as image files used in a screenshot golden test. +/// +/// This test class can be used in GoogleTest tests using the standard +/// `TEST_F(WindowsTest, TestName)` macro. +class WindowsTest : public ThreadTest { + public: + WindowsTest(); + + // Returns the path to test fixture data such as kernel bytecode or images + // used by the C++ side of the test. + std::string GetFixturesDirectory() const; + + // Returns the test context associated with this fixture. + WindowsTestContext& GetContext(); + + private: + WindowsTestContext context_; + + FML_DISALLOW_COPY_AND_ASSIGN(WindowsTest); +}; + +} // namespace testing +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_TESTING_WINDOWS_TEST_H_ diff --git a/shell/platform/windows/testing/windows_test_config_builder.cc b/shell/platform/windows/testing/windows_test_config_builder.cc new file mode 100644 index 0000000000000..35376e241f062 --- /dev/null +++ b/shell/platform/windows/testing/windows_test_config_builder.cc @@ -0,0 +1,104 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/shell/platform/windows/testing/windows_test_config_builder.h" + +#include + +#include +#include +#include + +#include "flutter/fml/logging.h" +#include "flutter/shell/platform/windows/flutter_windows_engine.h" +#include "flutter/shell/platform/windows/public/flutter_windows.h" +#include "flutter/shell/platform/windows/testing/windows_test_context.h" + +namespace flutter { +namespace testing { + +WindowsConfigBuilder::WindowsConfigBuilder(WindowsTestContext& context) + : context_(context) {} + +WindowsConfigBuilder::~WindowsConfigBuilder() = default; + +void WindowsConfigBuilder::SetDartEntrypoint(std::string_view entrypoint) { + if (entrypoint.empty()) { + return; + } + dart_entrypoint_ = entrypoint; +} + +void WindowsConfigBuilder::AddDartEntrypointArgument(std::string_view arg) { + if (arg.empty()) { + return; + } + + dart_entrypoint_arguments_.emplace_back(std::move(arg)); +} + +FlutterDesktopEngineProperties WindowsConfigBuilder::GetEngineProperties() + const { + FlutterDesktopEngineProperties engine_properties = {}; + engine_properties.assets_path = context_.GetAssetsPath().c_str(); + engine_properties.icu_data_path = context_.GetIcuDataPath().c_str(); + + // Set Dart entrypoint. + engine_properties.dart_entrypoint = dart_entrypoint_.c_str(); + + // Set Dart entrypoint argc, argv. + std::vector dart_args; + dart_args.reserve(dart_entrypoint_arguments_.size()); + for (const auto& arg : dart_entrypoint_arguments_) { + dart_args.push_back(arg.c_str()); + } + if (!dart_args.empty()) { + engine_properties.dart_entrypoint_argv = dart_args.data(); + engine_properties.dart_entrypoint_argc = dart_args.size(); + } else { + // Clear this out in case this is not the first engine launch from the + // embedder config builder. + engine_properties.dart_entrypoint_argv = nullptr; + engine_properties.dart_entrypoint_argc = 0; + } + + return engine_properties; +} + +EnginePtr WindowsConfigBuilder::InitializeEngine() const { + FlutterDesktopEngineProperties engine_properties = GetEngineProperties(); + return EnginePtr(FlutterDesktopEngineCreate(&engine_properties)); +} + +ViewControllerPtr WindowsConfigBuilder::Run() const { + InitializeCOM(); + + EnginePtr engine = InitializeEngine(); + if (!engine) { + return {}; + } + + // Register native functions. + FlutterWindowsEngine* windows_engine = + reinterpret_cast(engine.get()); + windows_engine->SetRootIsolateCreateCallback( + context_.GetRootIsolateCallback()); + + int width = 600; + int height = 400; + ViewControllerPtr controller( + FlutterDesktopViewControllerCreate(width, height, engine.release())); + if (!controller) { + return {}; + } + + return controller; +} + +void WindowsConfigBuilder::InitializeCOM() const { + FML_CHECK(SUCCEEDED(::CoInitializeEx(nullptr, COINIT_MULTITHREADED))); +} + +} // namespace testing +} // namespace flutter diff --git a/shell/platform/windows/testing/windows_test_config_builder.h b/shell/platform/windows/testing/windows_test_config_builder.h new file mode 100644 index 0000000000000..33b2660b2f147 --- /dev/null +++ b/shell/platform/windows/testing/windows_test_config_builder.h @@ -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. + +#ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_TESTING_WINDOWS_TEST_CONFIG_BUILDER_H_ +#define FLUTTER_SHELL_PLATFORM_WINDOWS_TESTING_WINDOWS_TEST_CONFIG_BUILDER_H_ + +#include +#include +#include + +#include "flutter/fml/macros.h" +#include "flutter/fml/unique_object.h" +#include "flutter/shell/platform/windows/public/flutter_windows.h" +#include "flutter/shell/platform/windows/testing/windows_test_context.h" + +namespace flutter { +namespace testing { + +// Deleter for FlutterDesktopEngineRef objects. +struct EngineDeleter { + typedef FlutterDesktopEngineRef pointer; + void operator()(FlutterDesktopEngineRef engine) { + FML_CHECK(FlutterDesktopEngineDestroy(engine)); + } +}; + +// Unique pointer wrapper for FlutterDesktopEngineRef. +using EnginePtr = std::unique_ptr; + +// Deleter for FlutterViewControllerRef objects. +struct ViewControllerDeleter { + typedef FlutterDesktopViewControllerRef pointer; + void operator()(FlutterDesktopViewControllerRef engine) { + FlutterDesktopViewControllerDestroy(engine); + } +}; + +// Unique pointer wrapper for FlutterDesktopViewControllerRef. +using ViewControllerPtr = + std::unique_ptr; + +// Test configuration builder for WindowsTests. +// +// Utility class for configuring engine and view controller launch arguments, +// and launching the engine to run a test fixture. +class WindowsConfigBuilder { + public: + explicit WindowsConfigBuilder(WindowsTestContext& context); + ~WindowsConfigBuilder(); + + // Returns the desktop engine properties configured for this test. + FlutterDesktopEngineProperties GetEngineProperties() const; + + // Sets the Dart entrypoint to the specified value. + // + // If not set, the default entrypoint (main) is used. Custom Dart entrypoints + // must be decorated with `@pragma('vm:entry-point')`. + void SetDartEntrypoint(std::string_view entrypoint); + + // Adds an argument to the Dart entrypoint arguments List. + void AddDartEntrypointArgument(std::string_view arg); + + // Returns a configured and initialized engine. + EnginePtr InitializeEngine() const; + + // Returns a configured and initialized view controller running the default + // Dart entrypoint. + ViewControllerPtr Run() const; + + private: + // Initialize COM, so that it is available for use in the library and/or + // plugins. + void InitializeCOM() const; + + WindowsTestContext& context_; + std::string dart_entrypoint_; + std::vector dart_entrypoint_arguments_; + + FML_DISALLOW_COPY_AND_ASSIGN(WindowsConfigBuilder); +}; + +} // namespace testing +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_TESTING_WINDOWS_TEST_CONFIG_BUILDER_H_ diff --git a/shell/platform/windows/testing/windows_test_context.cc b/shell/platform/windows/testing/windows_test_context.cc new file mode 100644 index 0000000000000..87ba928fbbe74 --- /dev/null +++ b/shell/platform/windows/testing/windows_test_context.cc @@ -0,0 +1,48 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/shell/platform/windows/testing/windows_test_context.h" + +#include "flutter/fml/platform/win/wstring_conversion.h" + +namespace flutter { +namespace testing { + +WindowsTestContext::WindowsTestContext(std::string_view assets_path) + : assets_path_(fml::Utf8ToWideString(assets_path)), + native_resolver_(std::make_shared()) { + isolate_create_callbacks_.push_back( + [weak_resolver = + std::weak_ptr{native_resolver_}]() { + if (auto resolver = weak_resolver.lock()) { + resolver->SetNativeResolverForIsolate(); + } + }); +} + +WindowsTestContext::~WindowsTestContext() = default; + +const std::wstring& WindowsTestContext::GetAssetsPath() const { + return assets_path_; +} + +const std::wstring& WindowsTestContext::GetIcuDataPath() const { + return icu_data_path_; +} + +void WindowsTestContext::AddNativeFunction(std::string_view name, + Dart_NativeFunction function) { + native_resolver_->AddNativeCallback(std::string{name}, function); +} + +fml::closure WindowsTestContext::GetRootIsolateCallback() { + return [this]() { + for (auto closure : this->isolate_create_callbacks_) { + closure(); + } + }; +} + +} // namespace testing +} // namespace flutter diff --git a/shell/platform/windows/testing/windows_test_context.h b/shell/platform/windows/testing/windows_test_context.h new file mode 100644 index 0000000000000..813aa44126623 --- /dev/null +++ b/shell/platform/windows/testing/windows_test_context.h @@ -0,0 +1,59 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_TESTING_WINDOWS_TEST_CONTEXT_H_ +#define FLUTTER_SHELL_PLATFORM_WINDOWS_TESTING_WINDOWS_TEST_CONTEXT_H_ + +#include +#include +#include + +#include "flutter/fml/closure.h" +#include "flutter/fml/macros.h" +#include "flutter/testing/test_dart_native_resolver.h" + +namespace flutter { +namespace testing { + +// Context associated with the current Windows test fixture. +// +// Context data includes global Flutter and Dart runtime context such as the +// path of Flutter's asset directory, ICU path, and resolvers for any +// registered native functions. +class WindowsTestContext { + public: + explicit WindowsTestContext(std::string_view assets_path = ""); + virtual ~WindowsTestContext(); + + // Returns the path to assets required by the Flutter runtime. + const std::wstring& GetAssetsPath() const; + + // Returns the path to the ICU library data file. + const std::wstring& GetIcuDataPath() const; + + // Registers a native function callable from Dart code in test fixtures. In + // the Dart test fixture, the associated function can be declared as: + // + // ReturnType functionName() native 'IdentifyingName'; + // + // where `IdentifyingName` matches the |name| parameter to this method. + void AddNativeFunction(std::string_view name, Dart_NativeFunction function); + + // Returns the root isolate create callback to register with the Flutter + // runtime. + fml::closure GetRootIsolateCallback(); + + private: + std::wstring assets_path_; + std::wstring icu_data_path_ = L"icudtl.dat"; + std::vector isolate_create_callbacks_; + std::shared_ptr native_resolver_; + + FML_DISALLOW_COPY_AND_ASSIGN(WindowsTestContext); +}; + +} // namespace testing +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_TESTING_WINDOWS_TEST_CONTEXT_H_ diff --git a/shell/platform/windows/window.cc b/shell/platform/windows/window.cc index a61ac35a4c909..08f5368a1472a 100644 --- a/shell/platform/windows/window.cc +++ b/shell/platform/windows/window.cc @@ -52,10 +52,12 @@ static const int kLinesPerScrollWindowsDefault = 3; } // namespace -Window::Window() : Window(nullptr) {} +Window::Window() : Window(nullptr, nullptr) {} -Window::Window(std::unique_ptr text_input_manager) +Window::Window(std::unique_ptr windows_proc_table, + std::unique_ptr text_input_manager) : touch_id_generator_(kMinTouchDeviceId, kMaxTouchDeviceId), + windows_proc_table_(std::move(windows_proc_table)), text_input_manager_(std::move(text_input_manager)) { // Get the DPI of the primary monitor as the initial DPI. If Per-Monitor V2 is // supported, |current_dpi_| should be updated in the @@ -67,6 +69,9 @@ Window::Window(std::unique_ptr text_input_manager) // https://github.com/flutter/flutter/issues/107248 UpdateScrollOffsetMultiplier(); + if (windows_proc_table_ == nullptr) { + windows_proc_table_ = std::make_unique(); + } if (text_input_manager_ == nullptr) { text_input_manager_ = std::make_unique(); } @@ -482,11 +487,11 @@ Window::HandleMessage(UINT const message, break; case DM_POINTERHITTEST: { if (direct_manipulation_owner_) { - UINT contactId = GET_POINTERID_WPARAM(wparam); - POINTER_INPUT_TYPE pointerType; - if (GetPointerType(contactId, &pointerType) && - pointerType == PT_TOUCHPAD) { - direct_manipulation_owner_->SetContact(contactId); + UINT contact_id = GET_POINTERID_WPARAM(wparam); + POINTER_INPUT_TYPE pointer_type; + if (windows_proc_table_->GetPointerType(contact_id, &pointer_type) && + pointer_type == PT_TOUCHPAD) { + direct_manipulation_owner_->SetContact(contact_id); } } break; diff --git a/shell/platform/windows/window.h b/shell/platform/windows/window.h index fe62fa5d2e965..7782803b10a52 100644 --- a/shell/platform/windows/window.h +++ b/shell/platform/windows/window.h @@ -18,6 +18,7 @@ #include "flutter/shell/platform/windows/keyboard_manager.h" #include "flutter/shell/platform/windows/sequential_id_generator.h" #include "flutter/shell/platform/windows/text_input_manager.h" +#include "flutter/shell/platform/windows/windows_proc_table.h" #include "flutter/third_party/accessibility/gfx/native_widget_types.h" namespace flutter { @@ -28,7 +29,8 @@ namespace flutter { class Window : public KeyboardManager::WindowDelegate { public: Window(); - Window(std::unique_ptr text_input_manager); + Window(std::unique_ptr windows_proc_table, + std::unique_ptr text_input_manager); virtual ~Window(); // Initializes as a child window with size using |width| and |height| and @@ -266,6 +268,10 @@ class Window : public KeyboardManager::WindowDelegate { double mouse_x_ = 0; double mouse_y_ = 0; + // Abstracts Windows APIs that may not be available on all supported versions + // of Windows. + std::unique_ptr windows_proc_table_; + // Manages IME state. std::unique_ptr text_input_manager_; diff --git a/shell/platform/windows/window_unittests.cc b/shell/platform/windows/window_unittests.cc index 182dc0918d804..1557d03b7a6a3 100644 --- a/shell/platform/windows/window_unittests.cc +++ b/shell/platform/windows/window_unittests.cc @@ -2,11 +2,17 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include + +#include "flutter/shell/platform/windows/testing/mock_direct_manipulation.h" #include "flutter/shell/platform/windows/testing/mock_text_input_manager.h" #include "flutter/shell/platform/windows/testing/mock_window.h" +#include "flutter/shell/platform/windows/testing/mock_windows_proc_table.h" +#include "gmock/gmock.h" #include "gtest/gtest.h" using testing::_; +using testing::Eq; using testing::InSequence; using testing::Invoke; using testing::Return; @@ -37,9 +43,11 @@ TEST(MockWindow, VerticalScroll) { } TEST(MockWindow, OnImeCompositionCompose) { - MockTextInputManager* text_input_manager = new MockTextInputManager(); + auto windows_proc_table = std::make_unique(); + auto* text_input_manager = new MockTextInputManager(); std::unique_ptr text_input_manager_ptr(text_input_manager); - MockWindow window(std::move(text_input_manager_ptr)); + MockWindow window(std::move(windows_proc_table), + std::move(text_input_manager_ptr)); EXPECT_CALL(*text_input_manager, GetComposingString()) .WillRepeatedly( Return(std::optional(std::u16string(u"nihao")))); @@ -61,9 +69,11 @@ TEST(MockWindow, OnImeCompositionCompose) { } TEST(MockWindow, OnImeCompositionResult) { - MockTextInputManager* text_input_manager = new MockTextInputManager(); + auto windows_proc_table = std::make_unique(); + auto* text_input_manager = new MockTextInputManager(); std::unique_ptr text_input_manager_ptr(text_input_manager); - MockWindow window(std::move(text_input_manager_ptr)); + MockWindow window(std::move(windows_proc_table), + std::move(text_input_manager_ptr)); EXPECT_CALL(*text_input_manager, GetComposingString()) .WillRepeatedly( Return(std::optional(std::u16string(u"nihao")))); @@ -85,9 +95,11 @@ TEST(MockWindow, OnImeCompositionResult) { } TEST(MockWindow, OnImeCompositionResultAndCompose) { - MockTextInputManager* text_input_manager = new MockTextInputManager(); + auto windows_proc_table = std::make_unique(); + auto* text_input_manager = new MockTextInputManager(); std::unique_ptr text_input_manager_ptr(text_input_manager); - MockWindow window(std::move(text_input_manager_ptr)); + MockWindow window(std::move(windows_proc_table), + std::move(text_input_manager_ptr)); // This situation is that Google Japanese Input finished composing "今日" in // "今日は" but is still composing "は". @@ -121,9 +133,11 @@ TEST(MockWindow, OnImeCompositionResultAndCompose) { } TEST(MockWindow, OnImeCompositionClearChange) { - MockTextInputManager* text_input_manager = new MockTextInputManager(); + auto windows_proc_table = std::make_unique(); + auto* text_input_manager = new MockTextInputManager(); std::unique_ptr text_input_manager_ptr(text_input_manager); - MockWindow window(std::move(text_input_manager_ptr)); + MockWindow window(std::move(windows_proc_table), + std::move(text_input_manager_ptr)); EXPECT_CALL(window, OnComposeChange(std::u16string(u""), 0)).Times(1); EXPECT_CALL(window, OnComposeCommit()).Times(1); ON_CALL(window, OnImeComposition) @@ -207,19 +221,19 @@ TEST(MockWindow, KeyDownPrintable) { .Times(1) .WillOnce(respond_false); EXPECT_CALL(window, OnText(_)).Times(1); - Win32Message messages[] = {{WM_KEYDOWN, 65, lparam, kWmResultDontCheck}, - {WM_CHAR, 65, lparam, kWmResultDontCheck}}; - window.InjectMessageList(2, messages); + std::array messages = { + Win32Message{WM_KEYDOWN, 65, lparam, kWmResultDontCheck}, + Win32Message{WM_CHAR, 65, lparam, kWmResultDontCheck}}; + window.InjectMessageList(2, messages.data()); } TEST(MockWindow, KeyDownWithCtrl) { MockWindow window; // Simulate CONTROL pressed - BYTE keyboard_state[256]; - memset(keyboard_state, 0, 256); + std::array keyboard_state; keyboard_state[VK_CONTROL] = -1; - SetKeyboardState(keyboard_state); + SetKeyboardState(keyboard_state.data()); LPARAM lparam = CreateKeyEventLparam(30, false, false); @@ -230,8 +244,8 @@ TEST(MockWindow, KeyDownWithCtrl) { window.InjectWindowMessage(WM_KEYDOWN, 65, lparam); - memset(keyboard_state, 0, 256); - SetKeyboardState(keyboard_state); + keyboard_state.fill(0); + SetKeyboardState(keyboard_state.data()); } TEST(MockWindow, KeyDownWithCtrlToggled) { @@ -244,10 +258,9 @@ TEST(MockWindow, KeyDownWithCtrlToggled) { }; // Simulate CONTROL toggled - BYTE keyboard_state[256]; - memset(keyboard_state, 0, 256); + std::array keyboard_state; keyboard_state[VK_CONTROL] = 1; - SetKeyboardState(keyboard_state); + SetKeyboardState(keyboard_state.data()); LPARAM lparam = CreateKeyEventLparam(30, false, false); @@ -261,8 +274,8 @@ TEST(MockWindow, KeyDownWithCtrlToggled) { {WM_CHAR, 65, lparam, kWmResultDontCheck}}; window.InjectMessageList(2, messages); - memset(keyboard_state, 0, 256); - SetKeyboardState(keyboard_state); + keyboard_state.fill(0); + SetKeyboardState(keyboard_state.data()); } TEST(MockWindow, Paint) { @@ -271,5 +284,81 @@ TEST(MockWindow, Paint) { window.InjectWindowMessage(WM_PAINT, 0, 0); } +// Verify direct manipulation isn't notified of pointer hit tests. +TEST(MockWindow, PointerHitTest) { + UINT32 pointer_id = 123; + auto windows_proc_table = std::make_unique(); + auto text_input_manager = std::make_unique(); + + EXPECT_CALL(*windows_proc_table, GetPointerType(Eq(pointer_id), _)) + .Times(1) + .WillOnce([](UINT32 pointer_id, POINTER_INPUT_TYPE* type) { + *type = PT_POINTER; + return TRUE; + }); + + MockWindow window(std::move(windows_proc_table), + std::move(text_input_manager)); + + auto direct_manipulation = + std::make_unique(&window); + + EXPECT_CALL(*direct_manipulation, SetContact).Times(0); + + window.SetDirectManipulationOwner(std::move(direct_manipulation)); + window.InjectWindowMessage(DM_POINTERHITTEST, MAKEWPARAM(pointer_id, 0), 0); +} + +// Verify direct manipulation is notified of touchpad hit tests. +TEST(MockWindow, TouchPadHitTest) { + UINT32 pointer_id = 123; + auto windows_proc_table = std::make_unique(); + auto text_input_manager = std::make_unique(); + + EXPECT_CALL(*windows_proc_table, GetPointerType(Eq(pointer_id), _)) + .Times(1) + .WillOnce([](UINT32 pointer_id, POINTER_INPUT_TYPE* type) { + *type = PT_TOUCHPAD; + return TRUE; + }); + + MockWindow window(std::move(windows_proc_table), + std::move(text_input_manager)); + + auto direct_manipulation = + std::make_unique(&window); + + EXPECT_CALL(*direct_manipulation, SetContact(Eq(pointer_id))).Times(1); + + window.SetDirectManipulationOwner(std::move(direct_manipulation)); + window.InjectWindowMessage(DM_POINTERHITTEST, MAKEWPARAM(pointer_id, 0), 0); +} + +// Verify direct manipulation isn't notified of unknown hit tests. +// This can happen if determining the pointer type fails, for example, +// if GetPointerType is unsupported by the current Windows version. +// See: https://github.com/flutter/flutter/issues/109412 +TEST(MockWindow, UnknownPointerTypeSkipsDirectManipulation) { + UINT32 pointer_id = 123; + auto windows_proc_table = std::make_unique(); + auto text_input_manager = std::make_unique(); + + EXPECT_CALL(*windows_proc_table, GetPointerType(Eq(pointer_id), _)) + .Times(1) + .WillOnce( + [](UINT32 pointer_id, POINTER_INPUT_TYPE* type) { return FALSE; }); + + MockWindow window(std::move(windows_proc_table), + std::move(text_input_manager)); + + auto direct_manipulation = + std::make_unique(&window); + + EXPECT_CALL(*direct_manipulation, SetContact).Times(0); + + window.SetDirectManipulationOwner(std::move(direct_manipulation)); + window.InjectWindowMessage(DM_POINTERHITTEST, MAKEWPARAM(pointer_id, 0), 0); +} + } // namespace testing } // namespace flutter diff --git a/shell/platform/windows/windows_proc_table.cc b/shell/platform/windows/windows_proc_table.cc new file mode 100644 index 0000000000000..d2b5bd99f4264 --- /dev/null +++ b/shell/platform/windows/windows_proc_table.cc @@ -0,0 +1,28 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/shell/platform/windows/windows_proc_table.h" + +namespace flutter { + +WindowsProcTable::WindowsProcTable() { + user32_ = fml::NativeLibrary::Create("user32.dll"); + get_pointer_type_ = + user32_->ResolveFunction("GetPointerType"); +} + +WindowsProcTable::~WindowsProcTable() { + user32_ = nullptr; +} + +BOOL WindowsProcTable::GetPointerType(UINT32 pointer_id, + POINTER_INPUT_TYPE* pointer_type) { + if (!get_pointer_type_.has_value()) { + return FALSE; + } + + return get_pointer_type_.value()(pointer_id, pointer_type); +} + +} // namespace flutter diff --git a/shell/platform/windows/windows_proc_table.h b/shell/platform/windows/windows_proc_table.h new file mode 100644 index 0000000000000..db6397d2e4781 --- /dev/null +++ b/shell/platform/windows/windows_proc_table.h @@ -0,0 +1,42 @@ +// 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_SHELL_PLATFORM_WINDOWS_WINDOWS_PROC_TABLE_H_ +#define FLUTTER_SHELL_PLATFORM_WINDOWS_WINDOWS_PROC_TABLE_H_ + +#include "flutter/fml/native_library.h" + +#include + +namespace flutter { + +// Lookup table for Windows APIs that aren't available on all versions of +// Windows. +class WindowsProcTable { + public: + WindowsProcTable(); + virtual ~WindowsProcTable(); + + // Retrieves the pointer type for a specified pointer. + // + // Used to react differently to touch or pen inputs. Returns false on failure. + // Available in Windows 8 and newer, otherwise returns false. + virtual BOOL GetPointerType(UINT32 pointer_id, + POINTER_INPUT_TYPE* pointer_type); + + private: + using GetPointerType_ = BOOL __stdcall(UINT32 pointerId, + POINTER_INPUT_TYPE* pointerType); + + // The User32.dll library, used to resolve functions at runtime. + fml::RefPtr user32_; + + std::optional get_pointer_type_; + + FML_DISALLOW_COPY_AND_ASSIGN(WindowsProcTable); +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_WINDOWS_PROC_TABLE_H_ diff --git a/shell/testing/tester_main.cc b/shell/testing/tester_main.cc index 155e2f071d6d6..c1eab867f6a1c 100644 --- a/shell/testing/tester_main.cc +++ b/shell/testing/tester_main.cc @@ -60,7 +60,12 @@ class TesterExternalViewEmbedder : public ExternalViewEmbedder { std::vector GetCurrentCanvases() override { return {&canvas_}; } // |ExternalViewEmbedder| - SkCanvas* CompositeEmbeddedView(int view_id) override { return &canvas_; } + std::vector GetCurrentBuilders() override { return {}; } + + // |ExternalViewEmbedder| + EmbedderPaintContext CompositeEmbeddedView(int view_id) override { + return {&canvas_, nullptr}; + } private: SkCanvas canvas_; diff --git a/sky/packages/sky_engine/BUILD.gn b/sky/packages/sky_engine/BUILD.gn index 89ff158dbcd47..88e059a8dae08 100644 --- a/sky/packages/sky_engine/BUILD.gn +++ b/sky/packages/sky_engine/BUILD.gn @@ -22,6 +22,7 @@ import("//third_party/dart/sdk/lib/js/js_sources.gni") import("//third_party/dart/sdk/lib/js_util/js_util_sources.gni") import("//third_party/dart/sdk/lib/math/math_sources.gni") import("//third_party/dart/sdk/lib/typed_data/typed_data_sources.gni") +import("//third_party/dart/sdk/lib/wasm/wasm_sources.gni") if (!is_fuchsia) { copy("copy_sky_engine_authors") { @@ -145,6 +146,13 @@ copy("typed_data") { ] } +copy("wasm") { + lib_path = rebase_path("wasm", "", dart_sdk_lib_path) + sources = rebase_path(wasm_sdk_sources, "", lib_path) + outputs = + [ "$root_gen_dir/dart-pkg/sky_engine/lib/wasm/{{source_file_part}}" ] +} + copy("copy_dart_ui") { sources = dart_ui_files @@ -178,6 +186,7 @@ group("copy_dart_sdk") { ":js_util", ":math", ":typed_data", + ":wasm", ] } @@ -202,6 +211,7 @@ generated_file("_embedder_yaml") { " \"dart:math\": \"math/math.dart\"", " \"dart:typed_data\": \"typed_data/typed_data.dart\"", " \"dart:ui\": \"ui/ui.dart\"", + " \"dart:wasm\": \"wasm/wasm_types.dart\"", "", " \"dart:_http\": \"_http/http.dart\"", " \"dart:_interceptors\": \"_interceptors/interceptors.dart\"", diff --git a/sky/packages/sky_engine/LICENSE b/sky/packages/sky_engine/LICENSE index a9e91b2f11ea2..0056073125a97 100644 --- a/sky/packages/sky_engine/LICENSE +++ b/sky/packages/sky_engine/LICENSE @@ -7469,34 +7469,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- fuchsia_sdk -Copyright 2016 The Fuchsia Authors. All rights reserved. -Copyright (c) 2009 Corey Tabaka - -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. - -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. --------------------------------------------------------------------------------- -fuchsia_sdk - Copyright 2017 The Fuchsia Authors. All rights reserved. Redistribution and use in source and binary forms, with or without @@ -15497,6 +15469,38 @@ skia Copyright 2022 Google LLC. +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. +-------------------------------------------------------------------------------- +skia + +Copyright 2022 Google, LLC + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/sky/packages/sky_engine/lib/_embedder.yaml b/sky/packages/sky_engine/lib/_embedder.yaml index c59d3623cd678..d14230709a8a7 100644 --- a/sky/packages/sky_engine/lib/_embedder.yaml +++ b/sky/packages/sky_engine/lib/_embedder.yaml @@ -16,6 +16,7 @@ embedded_libs: "dart:math": "../../../../../third_party/dart/sdk/lib/math/math.dart" "dart:typed_data": "../../../../../third_party/dart/sdk/lib/typed_data/typed_data.dart" "dart:ui": "../../../../lib/ui/ui.dart" + "dart:wasm": "../../../../../third_party/dart/sdk/lib/wasm/wasm_types.dart" "dart:_http": "../../../../../third_party/dart/sdk/lib/_http/http.dart" "dart:_interceptors": "../../../../../third_party/dart/sdk/lib/_interceptors/interceptors.dart" diff --git a/sky/tools/create_full_ios_framework.py b/sky/tools/create_full_ios_framework.py new file mode 100644 index 0000000000000..fb3dd707b595c --- /dev/null +++ b/sky/tools/create_full_ios_framework.py @@ -0,0 +1,244 @@ +#!/usr/bin/env python3 +# +# 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. + +# Generates and zip the ios flutter framework including the architecture +# dependent snapshot. + +import argparse +import os +import shutil +import subprocess +import sys + +from create_xcframework import create_xcframework + +DSYMUTIL = os.path.join( + os.path.dirname(__file__), '..', '..', '..', 'buildtools', 'mac-x64', + 'clang', 'bin', 'dsymutil' +) + +buildroot_dir = os.path.abspath( + os.path.join(os.path.realpath(__file__), '..', '..', '..', '..') +) + + +def main(): + parser = argparse.ArgumentParser( + description=( + 'Creates Flutter.framework, Flutter.xcframework and ' + 'copies architecture-dependent gen_snapshot binaries to output dir' + ) + ) + + parser.add_argument('--dst', type=str, required=True) + parser.add_argument('--clang-dir', type=str, default='clang_x64') + parser.add_argument('--x64-out-dir', type=str) + parser.add_argument('--arm64-out-dir', type=str, required=True) + parser.add_argument('--simulator-x64-out-dir', type=str, required=True) + parser.add_argument('--simulator-arm64-out-dir', type=str, required=False) + parser.add_argument('--strip', action="store_true", default=False) + parser.add_argument('--dsym', action="store_true", default=False) + parser.add_argument( + '--strip-bitcode', + dest='strip_bitcode', + action="store_true", + default=False + ) + + args = parser.parse_args() + + dst = ( + args.dst + if os.path.isabs(args.dst) else os.path.join(buildroot_dir, args.dst) + ) + + arm64_out_dir = ( + args.arm64_out_dir if os.path.isabs(args.arm64_out_dir) else + os.path.join(buildroot_dir, args.arm64_out_dir) + ) + + x64_out_dir = None + if args.x64_out_dir: + x64_out_dir = ( + args.x64_out_dir if os.path.isabs(args.x64_out_dir) else + os.path.join(buildroot_dir, args.x64_out_dir) + ) + + simulator_x64_out_dir = None + if args.simulator_x64_out_dir: + simulator_x64_out_dir = ( + args.simulator_x64_out_dir if os.path.isabs(args.simulator_x64_out_dir) + else os.path.join(buildroot_dir, args.simulator_x64_out_dir) + ) + + framework = os.path.join(dst, 'Flutter.framework') + simulator_framework = os.path.join(dst, 'sim', 'Flutter.framework') + arm64_framework = os.path.join(arm64_out_dir, 'Flutter.framework') + simulator_x64_framework = os.path.join( + simulator_x64_out_dir, 'Flutter.framework' + ) + + simulator_arm64_out_dir = None + if args.simulator_arm64_out_dir: + simulator_arm64_out_dir = ( + args.simulator_arm64_out_dir if os.path.isabs( + args.simulator_arm64_out_dir + ) else os.path.join(buildroot_dir, args.simulator_arm64_out_dir) + ) + + if args.simulator_arm64_out_dir is not None: + simulator_arm64_framework = os.path.join( + simulator_arm64_out_dir, 'Flutter.framework' + ) + + if not os.path.isdir(arm64_framework): + print('Cannot find iOS arm64 Framework at %s' % arm64_framework) + return 1 + + if not os.path.isdir(simulator_x64_framework): + print('Cannot find iOS x64 simulator Framework at %s' % simulator_framework) + return 1 + + if not os.path.isfile(DSYMUTIL): + print('Cannot find dsymutil at %s' % DSYMUTIL) + return 1 + + create_framework( + args, dst, framework, arm64_framework, simulator_framework, + simulator_x64_framework, simulator_arm64_framework + ) + framework_binary = os.path.join(framework, 'Flutter') + process_framework(args, dst, framework, framework_binary) + generate_gen_snapshot(args, dst, x64_out_dir, arm64_out_dir) + zip_archive(dst) + + +def create_framework( + args, dst, framework, arm64_framework, simulator_framework, + simulator_x64_framework, simulator_arm64_framework +): + arm64_dylib = os.path.join(arm64_framework, 'Flutter') + simulator_x64_dylib = os.path.join(simulator_x64_framework, 'Flutter') + simulator_arm64_dylib = os.path.join(simulator_arm64_framework, 'Flutter') + if not os.path.isfile(arm64_dylib): + print('Cannot find iOS arm64 dylib at %s' % arm64_dylib) + return 1 + + if not os.path.isfile(simulator_x64_dylib): + print('Cannot find iOS simulator dylib at %s' % simulator_dylib) + return 1 + + shutil.rmtree(framework, True) + shutil.copytree(arm64_framework, framework) + framework_binary = os.path.join(framework, 'Flutter') + + if args.simulator_arm64_out_dir is not None: + shutil.rmtree(simulator_framework, True) + shutil.copytree(simulator_arm64_framework, simulator_framework) + + simulator_framework_binary = os.path.join(simulator_framework, 'Flutter') + + # Create the arm64/x64 simulator fat framework. + subprocess.check_call([ + 'lipo', simulator_x64_dylib, simulator_arm64_dylib, '-create', + '-output', simulator_framework_binary + ]) + process_framework( + args, dst, simulator_framework, simulator_framework_binary + ) + simulator_framework = simulator_framework + else: + simulator_framework = simulator_x64_framework + + # Create XCFramework from the arm-only fat framework and the arm64/x64 simulator frameworks, or just the + # x64 simulator framework if only that one exists. + xcframeworks = [simulator_framework, framework] + create_xcframework(location=dst, name='Flutter', frameworks=xcframeworks) + + # Add the x64 simulator into the fat framework + subprocess.check_call([ + 'lipo', arm64_dylib, simulator_x64_dylib, '-create', '-output', + framework_binary + ]) + + +def embed_codesign_configuration(config_path, contents): + with open(config_path, 'w') as f: + f.write('\n'.join(contents) + '\n') + + +def zip_archive(dst): + ios_file_with_entitlements = ['gen_snapshot_arm64'] + ios_file_without_entitlements = [ + 'Flutter.xcframework/ios-arm64/Flutter.framework/Flutter', + 'Flutter.xcframework/ios-arm64_x86_64-simulator/Flutter.framework/Flutter' + ] + embed_codesign_configuration( + os.path.join(dst, 'entitlements.txt'), ios_file_with_entitlements + ) + + embed_codesign_configuration( + os.path.join(dst, 'without_entitlements.txt'), + ios_file_without_entitlements + ) + + subprocess.check_call([ + 'zip', + '-r', + 'artifacts.zip', + 'gen_snapshot_arm64', + 'Flutter.xcframework', + 'entitlements.txt', + 'without_entitlements.txt', + ], + cwd=dst) + if (os.path.exists(os.path.join(dst, 'Flutter.dSYM'))): + subprocess.check_call(['zip', '-r', 'Flutter.dSYM.zip', 'Flutter.dSYM'], + cwd=dst) + + +def process_framework(args, dst, framework, framework_binary): + if args.strip_bitcode: + subprocess.check_call([ + 'xcrun', 'bitcode_strip', '-r', framework_binary, '-o', framework_binary + ]) + + if args.dsym: + dsym_out = os.path.splitext(framework)[0] + '.dSYM' + subprocess.check_call([DSYMUTIL, '-o', dsym_out, framework_binary]) + + if args.strip: + # copy unstripped + unstripped_out = os.path.join(dst, 'Flutter.unstripped') + shutil.copyfile(framework_binary, unstripped_out) + + subprocess.check_call(["strip", "-x", "-S", framework_binary]) + + +def generate_gen_snapshot(args, dst, x64_out_dir, arm64_out_dir): + if x64_out_dir: + _generate_gen_snapshot(x64_out_dir, os.path.join(dst, 'gen_snapshot_x64')) + + if arm64_out_dir: + _generate_gen_snapshot( + os.path.join(arm64_out_dir, args.clang_dir), + os.path.join(dst, 'gen_snapshot_arm64') + ) + + +def _generate_gen_snapshot(directory, destination): + gen_snapshot_dir = os.path.join(directory, 'gen_snapshot') + if not os.path.isfile(gen_snapshot_dir): + print('Cannot find gen_snapshot at %s' % gen_snapshot_dir) + sys.exit(1) + + subprocess.check_call([ + 'xcrun', 'bitcode_strip', '-r', gen_snapshot_dir, '-o', destination + ]) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/sky/tools/create_macos_framework.py b/sky/tools/create_macos_framework.py index 2f61b8094dabb..c34e20216ca1d 100755 --- a/sky/tools/create_macos_framework.py +++ b/sky/tools/create_macos_framework.py @@ -12,11 +12,17 @@ from create_xcframework import create_xcframework +buildroot_dir = os.path.abspath( + os.path.join(os.path.realpath(__file__), '..', '..', '..', '..') +) + DSYMUTIL = os.path.join( os.path.dirname(__file__), '..', '..', '..', 'buildtools', 'mac-x64', 'clang', 'bin', 'dsymutil' ) +out_dir = os.path.join(buildroot_dir, 'out') + def main(): parser = argparse.ArgumentParser( @@ -31,9 +37,22 @@ def main(): args = parser.parse_args() - fat_framework = os.path.join(args.dst, 'FlutterMacOS.framework') - arm64_framework = os.path.join(args.arm64_out_dir, 'FlutterMacOS.framework') - x64_framework = os.path.join(args.x64_out_dir, 'FlutterMacOS.framework') + dst = ( + args.dst + if os.path.isabs(args.dst) else os.path.join(buildroot_dir, args.dst) + ) + arm64_out_dir = ( + args.arm64_out_dir if os.path.isabs(args.arm64_out_dir) else + os.path.join(buildroot_dir, args.arm64_out_dir) + ) + x64_out_dir = ( + args.x64_out_dir if os.path.isabs(args.x64_out_dir) else + os.path.join(buildroot_dir, args.x64_out_dir) + ) + + fat_framework = os.path.join(dst, 'FlutterMacOS.framework') + arm64_framework = os.path.join(arm64_out_dir, 'FlutterMacOS.framework') + x64_framework = os.path.join(x64_out_dir, 'FlutterMacOS.framework') arm64_dylib = os.path.join(arm64_framework, 'FlutterMacOS') x64_dylib = os.path.join(x64_framework, 'FlutterMacOS') @@ -66,20 +85,31 @@ def main(): ) # Create the arm64/x64 fat framework. - subprocess.check_call([ + result = subprocess.run([ 'lipo', arm64_dylib, x64_dylib, '-create', '-output', fat_framework_binary ]) - process_framework(args, fat_framework, fat_framework_binary) + if result.returncode != 0: + print( + 'Error processing command with stdout[%s] and stderr[%s]' % + (result.stdout, result.stderr) + ) + return 1 + process_framework(dst, args, fat_framework, fat_framework_binary) -def process_framework(args, fat_framework, fat_framework_binary): +def process_framework(dst, args, fat_framework, fat_framework_binary): if args.dsym: dsym_out = os.path.splitext(fat_framework)[0] + '.dSYM' subprocess.check_call([DSYMUTIL, '-o', dsym_out, fat_framework_binary]) + subprocess.check_call([ + 'zip', '-r', + '%s/FlutterMacOS.dSYM.zip' % dst, + '%s/FlutterMacOS.dSYM/Contents' % dst + ]) if args.strip: # copy unstripped - unstripped_out = os.path.join(args.dst, 'FlutterMacOS.unstripped') + unstripped_out = os.path.join(dst, 'FlutterMacOS.unstripped') shutil.copyfile(fat_framework_binary, unstripped_out) subprocess.check_call(["strip", "-x", "-S", fat_framework_binary]) diff --git a/sky/tools/create_macos_gen_snapshots.py b/sky/tools/create_macos_gen_snapshots.py index 1ecb61d433db3..e76c43282ca3c 100755 --- a/sky/tools/create_macos_gen_snapshots.py +++ b/sky/tools/create_macos_gen_snapshots.py @@ -9,6 +9,10 @@ import sys import os +buildroot_dir = os.path.abspath( + os.path.join(os.path.realpath(__file__), '..', '..', '..', '..') +) + def main(): parser = argparse.ArgumentParser( @@ -23,21 +27,36 @@ def main(): args = parser.parse_args() + dst = ( + args.dst + if os.path.isabs(args.dst) else os.path.join(buildroot_dir, args.dst) + ) + if args.x64_out_dir: - generate_gen_snapshot( - args.x64_out_dir, os.path.join(args.dst, 'gen_snapshot_x64') + x64_out_dir = ( + args.x64_out_dir if os.path.isabs(args.x64_out_dir) else + os.path.join(buildroot_dir, args.x64_out_dir) ) + generate_gen_snapshot(x64_out_dir, os.path.join(dst, 'gen_snapshot_x64')) if args.arm64_out_dir: + arm64_out_dir = ( + args.arm64_out_dir if os.path.isabs(args.arm64_out_dir) else + os.path.join(buildroot_dir, args.arm64_out_dir) + ) generate_gen_snapshot( - os.path.join(args.arm64_out_dir, args.clang_dir), - os.path.join(args.dst, 'gen_snapshot_arm64') + os.path.join(arm64_out_dir, args.clang_dir), + os.path.join(dst, 'gen_snapshot_arm64') ) if args.armv7_out_dir: + armv7_out_dir = ( + args.armv7_out_dir if os.path.isabs(args.armv7_out_dir) else + os.path.join(buildroot_dir, args.armv7_out_dir) + ) generate_gen_snapshot( - os.path.join(args.armv7_out_dir, args.clang_dir), - os.path.join(args.dst, 'gen_snapshot_armv7') + os.path.join(armv7_out_dir, args.clang_dir), + os.path.join(dst, 'gen_snapshot_armv7') ) diff --git a/testing/android_background_image/android/app/build.gradle b/testing/android_background_image/android/app/build.gradle index cfa8d98bedf23..c7346c23e8422 100644 --- a/testing/android_background_image/android/app/build.gradle +++ b/testing/android_background_image/android/app/build.gradle @@ -16,8 +16,8 @@ android { // The others are irrelevant for a test application. disable 'UnpackedNativeCode','MissingApplicationIcon','GoogleAppIndexingApiWarning','GoogleAppIndexingWarning','GradleDependency','NewerVersionAvailable' } - buildToolsVersion = '33.0.0-rc4' - compileSdkVersion 32 + buildToolsVersion = '33.0.0' + compileSdkVersion 33 compileOptions { sourceCompatibility JavaVersion.VERSION_11 targetCompatibility JavaVersion.VERSION_11 @@ -25,7 +25,7 @@ android { defaultConfig { applicationId 'dev.flutter.android_background_image' minSdkVersion 16 - targetSdkVersion 32 + targetSdkVersion 33 versionCode 1 versionName '1.0' } diff --git a/testing/dart/assets_test.dart b/testing/dart/assets_test.dart index 7a2522c399fc5..64943c2179a91 100644 --- a/testing/dart/assets_test.dart +++ b/testing/dart/assets_test.dart @@ -27,6 +27,13 @@ void main() { expect(buffer.length == 354679, true); }); + test('Can load an asset with a space in the key', () async { + // This assets actual path is "fixtures/DashInNooglerHat%20WithSpace.jpg" + final ImmutableBuffer buffer = await ImmutableBuffer.fromAsset('DashInNooglerHat WithSpace.jpg'); + + expect(buffer.length == 354679, true); + }); + test('can dispose immutable buffer', () async { final ImmutableBuffer buffer = await ImmutableBuffer.fromAsset('DashInNooglerHat.jpg'); diff --git a/testing/dart/canvas_test.dart b/testing/dart/canvas_test.dart index d220aff07535f..e9dc8b1337d51 100644 --- a/testing/dart/canvas_test.dart +++ b/testing/dart/canvas_test.dart @@ -505,6 +505,39 @@ void main() { expect(data.buffer.asUint8List()[3], 0xFF); }); + test('toImage and toImageSync have identical contents', () async { + // Note: on linux this stil seems to be different. + // TODO(jonahwilliams): https://github.com/flutter/flutter/issues/108835 + if (Platform.isLinux) { + return; + } + + final PictureRecorder recorder = PictureRecorder(); + final Canvas canvas = Canvas(recorder); + canvas.drawRect( + const Rect.fromLTWH(20, 20, 100, 100), + Paint()..color = const Color(0xA0FF6D00), + ); + final Picture picture = recorder.endRecording(); + final Image toImageImage = await picture.toImage(200, 200); + final Image toImageSyncImage = picture.toImageSync(200, 200); + + // To trigger observable difference in alpha, draw image + // on a second canvas. + Future drawOnCanvas(Image image) async { + final PictureRecorder recorder = PictureRecorder(); + final Canvas canvas = Canvas(recorder); + canvas.drawPaint(Paint()..color = const Color(0x4FFFFFFF)); + canvas.drawImage(image, Offset.zero, Paint()); + final Image resultImage = await recorder.endRecording().toImage(200, 200); + return (await resultImage.toByteData())!; + } + + final ByteData dataSync = await drawOnCanvas(toImageImage); + final ByteData data = await drawOnCanvas(toImageSyncImage); + expect(data, listEquals(dataSync)); + }); + test('Canvas.drawParagraph throws when Paragraph.layout was not called', () async { // Regression test for https://github.com/flutter/flutter/issues/97172 bool assertsEnabled = false; @@ -896,3 +929,12 @@ void main() { expect(canvas.getDestinationClipBounds(), initialDestinationBounds); }); } + +Matcher listEquals(ByteData expected) => (dynamic v) { + Expect.type(v); + final ByteData value = v as ByteData; + expect(value.lengthInBytes, expected.lengthInBytes); + for (int i = 0; i < value.lengthInBytes; i++) { + expect(value.getUint8(i), expected.getUint8(i)); + } +}; diff --git a/testing/dart/channel_buffers_test.dart b/testing/dart/channel_buffers_test.dart index 8781d0d29cede..4683a62254d77 100644 --- a/testing/dart/channel_buffers_test.dart +++ b/testing/dart/channel_buffers_test.dart @@ -52,7 +52,6 @@ void main() { // Ignoring the returned future because the completion of the drain is // communicated using the `completer`. - // ignore: unawaited_futures buffers.drain(channel, (ByteData? drainedData, ui.PlatformMessageResponseCallback drainedCallback) async { log.add('callback'); completer.complete(); diff --git a/testing/dart/compositing_test.dart b/testing/dart/compositing_test.dart index 10c70978d22b5..cb65eb0ed44fe 100644 --- a/testing/dart/compositing_test.dart +++ b/testing/dart/compositing_test.dart @@ -36,6 +36,28 @@ void main() { expect(data.buffer.asUint8List()[3], 0xFF); }); + test('Scene.toImageSync succeeds with texture layer', () async { + final SceneBuilder builder = SceneBuilder(); + builder.pushOffset(10, 10); + builder.addTexture(0, width: 10, height: 10); + + final Scene scene = builder.build(); + final Image image = scene.toImageSync(10, 10); + scene.dispose(); + + expect(image.width, 10); + expect(image.height, 10); + + final ByteData? data = await image.toByteData(); + + expect(data, isNotNull); + expect(data!.lengthInBytes, 10 * 10 * 4); + expect(data.buffer.asUint8List()[0], 0); + expect(data.buffer.asUint8List()[1], 0); + expect(data.buffer.asUint8List()[2], 0); + expect(data.buffer.asUint8List()[3], 0); + }); + test('addPicture with disposed picture does not crash', () { bool assertsEnabled = false; assert(() { @@ -382,6 +404,7 @@ void main() { ); }); testNoSharing((SceneBuilder builder, EngineLayer? oldLayer) { + // ignore: deprecated_member_use return builder.pushPhysicalShape(path: Path(), color: const Color.fromARGB(0, 0, 0, 0), oldLayer: oldLayer as PhysicalShapeEngineLayer?, elevation: 0.0); }); testNoSharing((SceneBuilder builder, EngineLayer? oldLayer) { diff --git a/testing/dart/fragment_shader_test.dart b/testing/dart/fragment_shader_test.dart index d77e7b6835913..5fcc63f3ad328 100644 --- a/testing/dart/fragment_shader_test.dart +++ b/testing/dart/fragment_shader_test.dart @@ -36,8 +36,25 @@ void main() async { samplerUniforms: [imageShader], ); await _expectShaderRendersGreen(shader); + blueGreenImage.dispose(); }); + test('blue-green image renders green - GPU image', () async { + final FragmentProgram program = await FragmentProgram.fromAsset( + 'blue_green_sampler.frag.iplr', + ); + final Image blueGreenImage = _createBlueGreenImageSync(); + final ImageShader imageShader = ImageShader( + blueGreenImage, TileMode.clamp, TileMode.clamp, _identityMatrix); + final Shader shader = program.shader( + floatUniforms: Float32List.fromList([]), + samplerUniforms: [imageShader], + ); + await _expectShaderRendersGreen(shader); + blueGreenImage.dispose(); + }); + + test('shader with uniforms renders correctly', () async { final FragmentProgram program = await FragmentProgram.fromAsset( 'uniforms.frag.iplr', @@ -64,6 +81,22 @@ void main() async { expect(toFloat(renderedBytes.getUint8(3)), closeTo(1.0, epsilon)); }); + test('shader with array uniforms renders correctly', () async { + final FragmentProgram program = await FragmentProgram.fromAsset( + 'uniform_arrays.frag.iplr', + ); + + final List floatArray = List.generate( + 24, (int i) => i.toDouble(), + ); + final Shader shader = program.shader( + floatUniforms: Float32List.fromList([ + ...floatArray, + ])); + + await _expectShaderRendersGreen(shader); + }); + test('The ink_sparkle shader is accepted', () async { final FragmentProgram program = await FragmentProgram.fromAsset( 'ink_sparkle.frag.iplr', @@ -118,6 +151,16 @@ void main() async { expect(throws, equals(true)); }); + test('user defined functions do not redefine builtins', () async { + final FragmentProgram program = await FragmentProgram.fromAsset( + 'no_builtin_redefinition.frag.iplr', + ); + final Shader shader = program.shader( + floatUniforms: Float32List.fromList([1.0]), + ); + await _expectShaderRendersGreen(shader); + }); + test('fromAsset accepts a shader with no uniforms', () async { final FragmentProgram program = await FragmentProgram.fromAsset( 'no_uniforms.frag.iplr', @@ -292,6 +335,21 @@ Future _createBlueGreenImage() async { return frame.image; } +// A 10x10 image where the left half is blue and the right half is green. +Image _createBlueGreenImageSync() { + final PictureRecorder recorder = PictureRecorder(); + final Canvas canvas = Canvas(recorder); + canvas.drawRect(const Rect.fromLTWH(0, 0, 5, 10), Paint()..color = const Color(0xFF0000FF)); + canvas.drawRect(const Rect.fromLTWH(5, 0, 5, 10), Paint()..color = const Color(0xFF00FF00)); + final Picture picture = recorder.endRecording(); + try { + return picture.toImageSync(10, 10); + } finally { + picture.dispose(); + } +} + + final Float64List _identityMatrix = Float64List.fromList([ 1, 0, 0, 0, 0, 1, 0, 0, diff --git a/testing/dart/image_shader_test.dart b/testing/dart/image_shader_test.dart index 56c502b132596..6124e5746d5ab 100644 --- a/testing/dart/image_shader_test.dart +++ b/testing/dart/image_shader_test.dart @@ -9,12 +9,55 @@ import 'package:litetest/litetest.dart'; import 'canvas_test.dart' show createImage, testCanvas; void main() { + bool assertsEnabled = false; + assert(() { + assertsEnabled = true; + return true; + }()); + test('Construct an ImageShader', () async { final Image image = await createImage(50, 50); final ImageShader shader = ImageShader(image, TileMode.clamp, TileMode.clamp, Float64List(16)); - final Paint paint = Paint()..shader=shader; + final Paint paint = Paint()..shader = shader; + const Rect rect = Rect.fromLTRB(0, 0, 100, 100); + testCanvas((Canvas canvas) => canvas.drawRect(rect, paint)); + + if (assertsEnabled) { + expect(shader.debugDisposed, false); + } + shader.dispose(); + if (assertsEnabled) { + expect(shader.debugDisposed, true); + } + + image.dispose(); + }); + + test('ImageShader with disposed image', () async { + final Image image = await createImage(50, 50); + image.dispose(); + + if (assertsEnabled) { + expectAssertion(() => ImageShader(image, TileMode.clamp, TileMode.clamp, Float64List(16))); + } else { + throwsException(() => ImageShader(image, TileMode.clamp, TileMode.clamp, Float64List(16))); + } + }); + + test('Disposed image shader in a paint', () async { + final Image image = await createImage(50, 50); + final ImageShader shader = ImageShader(image, TileMode.clamp, TileMode.clamp, Float64List(16)); + shader.dispose(); + + if (assertsEnabled) { + expectAssertion(() => Paint()..shader = shader); + return; + } + final Paint paint = Paint()..shader = shader; const Rect rect = Rect.fromLTRB(0, 0, 100, 100); testCanvas((Canvas canvas) => canvas.drawRect(rect, paint)); + image.dispose(); + }); test('Construct an ImageShader - GPU image', () async { @@ -25,11 +68,19 @@ void main() { final Image image = picture.toImageSync(50, 50); picture.dispose(); - // TODO(dnfield): this should not throw once - // https://github.com/flutter/flutter/issues/105085 is fixed. - expect( - () => ImageShader(image, TileMode.clamp, TileMode.clamp, Float64List(16)), - throwsException, - ); + final ImageShader shader = ImageShader(image, TileMode.clamp, TileMode.clamp, Float64List(16)); + final Paint paint = Paint()..shader=shader; + const Rect rect = Rect.fromLTRB(0, 0, 100, 100); + testCanvas((Canvas canvas) => canvas.drawRect(rect, paint)); + + if (assertsEnabled) { + expect(shader.debugDisposed, false); + } + shader.dispose(); + if (assertsEnabled) { + expect(shader.debugDisposed, true); + } + + image.dispose(); }); } diff --git a/testing/dart/observatory/BUILD.gn b/testing/dart/observatory/BUILD.gn index 3aca101451b36..a0e6afbe6be17 100644 --- a/testing/dart/observatory/BUILD.gn +++ b/testing/dart/observatory/BUILD.gn @@ -8,6 +8,7 @@ tests = [ "skp_test.dart", "tracing_test.dart", "shader_reload_test.dart", + "vmservice_methods_test.dart", ] foreach(test, tests) { diff --git a/testing/dart/observatory/vmservice_methods_test.dart b/testing/dart/observatory/vmservice_methods_test.dart new file mode 100644 index 0000000000000..ed62cb80cc3d8 --- /dev/null +++ b/testing/dart/observatory/vmservice_methods_test.dart @@ -0,0 +1,107 @@ +// 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:developer' as developer; +import 'dart:typed_data'; +import 'dart:ui' as ui; + +import 'package:litetest/litetest.dart'; +import 'package:vm_service/vm_service.dart' as vms; +import 'package:vm_service/vm_service_io.dart'; + +void main() { + test('Setting invalid directory returns an error', () async { + vms.VmService? vmService; + try { + final developer.ServiceProtocolInfo info = await developer.Service.getInfo(); + if (info.serverUri == null) { + fail('This test must not be run with --disable-observatory.'); + } + + vmService = await vmServiceConnectUri( + 'ws://localhost:${info.serverUri!.port}${info.serverUri!.path}ws', + ); + final String viewId = await getViewId(vmService); + + dynamic error; + try { + await vmService.callMethod( + '_flutter.setAssetBundlePath', + args: {'viewId': viewId, 'assetDirectory': ''}, + ); + } catch (err) { + error = err; + } + expect(error != null, true); + } finally { + await vmService?.dispose(); + } + }); + + test('Reload fonts request sends font change notification', () async { + vms.VmService? vmService; + try { + final developer.ServiceProtocolInfo info = + await developer.Service.getInfo(); + if (info.serverUri == null) { + fail('This test must not be run with --disable-observatory.'); + } + + final Completer completer = Completer(); + ui.window.onPlatformMessage = (String name, ByteData? data, ui.PlatformMessageResponseCallback? callback) { + final ByteBuffer buffer = data!.buffer; + final Uint8List list = buffer.asUint8List(data.offsetInBytes, data.lengthInBytes); + completer.complete(PlatformResponse(name: name, contents: utf8.decode(list))); + }; + + vmService = await vmServiceConnectUri( + 'ws://localhost:${info.serverUri!.port}${info.serverUri!.path}ws', + ); + final String viewId = await getViewId(vmService); + + final vms.Response fontChangeResponse = await vmService.callMethod( + '_flutter.reloadAssetFonts', + args: {'viewId': viewId}, + ); + + expect(fontChangeResponse.type, 'Success'); + expect( + await completer.future, + const PlatformResponse( + name: 'flutter/system', + contents: '{"type":"fontsChange"}', + ), + ); + } finally { + await vmService?.dispose(); + } + }); +} + +Future getViewId(vms.VmService vmService) async { + final vms.Response response = await vmService.callMethod('_flutter.listViews'); + final List? rawViews = response.json!['views'] as List?; + return (rawViews![0]! as Map?)!['id']! as String; +} + +class PlatformResponse { + const PlatformResponse({ + required this.name, + required this.contents, + }); + + final String name; + final String contents; + + @override + bool operator ==(Object other) => + other is PlatformResponse && + other.name == name && + other.contents == contents; + + @override + int get hashCode => Object.hash(name, contents); +} diff --git a/testing/dart/text_test.dart b/testing/dart/text_test.dart index b845b654b41f7..13bb1645bb433 100644 --- a/testing/dart/text_test.dart +++ b/testing/dart/text_test.dart @@ -19,7 +19,7 @@ Future readFile(String fileName) async { return file.readAsBytes(); } -void testFontWeightLerp() { +void testFontWeight() { test('FontWeight.lerp works with non-null values', () { expect(FontWeight.lerp(FontWeight.w400, FontWeight.w600, .5), equals(FontWeight.w500)); }); @@ -35,6 +35,18 @@ void testFontWeightLerp() { test('FontWeight.lerp returns FontWeight.w400 if b is null', () { expect(FontWeight.lerp(FontWeight.w400, null, 1), equals(FontWeight.w400)); }); + + test('FontWeights have the correct value', () { + expect(FontWeight.w100.value, 100); + expect(FontWeight.w200.value, 200); + expect(FontWeight.w300.value, 300); + expect(FontWeight.w400.value, 400); + expect(FontWeight.w500.value, 500); + expect(FontWeight.w600.value, 600); + expect(FontWeight.w700.value, 700); + expect(FontWeight.w800.value, 800); + expect(FontWeight.w900.value, 900); + }); } void testParagraphStyle() { @@ -277,7 +289,7 @@ void testFontVariation() { } void main() { - testFontWeightLerp(); + testFontWeight(); testParagraphStyle(); testTextStyle(); testTextHeightBehavior(); diff --git a/testing/fuchsia/test_suites.yaml b/testing/fuchsia/test_suites.yaml index 8dcbc8a9e2e1c..86630bdb73450 100644 --- a/testing/fuchsia/test_suites.yaml +++ b/testing/fuchsia/test_suites.yaml @@ -1,15 +1,13 @@ # This configuration file specifies several test suites with their package and # test command for femu_test.py. -# Legacy Component Framework v1 components. -- test_command: run-test-component fuchsia-pkg://fuchsia.com/flutter-embedder-test2#meta/flutter-embedder-test2.cmx - packages: - - flutter-embedder-test2-0.far - - gen/flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/child-view2/child-view2/child-view2.far - - gen/flutter/shell/platform/fuchsia/flutter/integration_flutter_tests/embedder/parent-view2/parent-view2/parent-view2.far - - flutter_jit_runner-0.far - # v2 components. +- test_command: run-test-suite fuchsia-pkg://fuchsia.com/flutter-embedder-test#meta/flutter-embedder-test.cm + packages: + - flutter-embedder-test-0.far + - oot_flutter_jit_runner-0.far + - gen/flutter/shell/platform/fuchsia/flutter/tests/integration/embedder/child-view/child-view/child-view.far + - gen/flutter/shell/platform/fuchsia/flutter/tests/integration/embedder/parent-view/parent-view/parent-view.far - test_command: run-test-suite fuchsia-pkg://fuchsia.com/dart_runner_tests#meta/dart_runner_tests.cm package: dart_runner_tests-0.far - test_command: run-test-suite fuchsia-pkg://fuchsia.com/flutter_runner_tests#meta/flutter_runner_tests.cm diff --git a/testing/ios/IosUnitTests/IosUnitTests.xcodeproj/project.pbxproj b/testing/ios/IosUnitTests/IosUnitTests.xcodeproj/project.pbxproj index 3632c55063a94..b14ff6444597d 100644 --- a/testing/ios/IosUnitTests/IosUnitTests.xcodeproj/project.pbxproj +++ b/testing/ios/IosUnitTests/IosUnitTests.xcodeproj/project.pbxproj @@ -72,6 +72,7 @@ 689EC1E2281B30D3008FEB58 /* FlutterSpellCheckPluginTest.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FlutterSpellCheckPluginTest.mm; sourceTree = ""; }; 68B6091227F62F990036AC78 /* VsyncWaiterIosTest.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = VsyncWaiterIosTest.mm; sourceTree = ""; }; 3DD7D38C27D2B81000DA365C /* FlutterUndoManagerPluginTest.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FlutterUndoManagerPluginTest.mm; sourceTree = ""; }; + 73F12C22288F92FF00AFC3A6 /* FlutterViewControllerTest_mrc.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FlutterViewControllerTest_mrc.mm; sourceTree = ""; }; F7521D7226BB671E005F15C5 /* libios_test_flutter.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libios_test_flutter.dylib; path = "../../../../out/$(FLUTTER_ENGINE)/libios_test_flutter.dylib"; sourceTree = ""; }; F7521D7526BB673E005F15C5 /* libocmock_shared.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libocmock_shared.dylib; path = "../../../../out/$(FLUTTER_ENGINE)/libocmock_shared.dylib"; sourceTree = ""; }; F77E081726FA9CE6003E6E4C /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = "../../../../out/$(FLUTTER_ENGINE)/Flutter.framework"; sourceTree = ""; }; @@ -112,6 +113,7 @@ 0AC2331224BA71D300A85907 /* FlutterEnginePlatformViewTest.mm */, 0AC2331924BA71D300A85907 /* FlutterPluginAppLifeCycleDelegateTest.mm */, 0AC2332124BA71D300A85907 /* FlutterViewControllerTest.mm */, + 73F12C22288F92FF00AFC3A6 /* FlutterViewControllerTest_mrc.mm */, ); name = Source; path = ../../../shell/platform/darwin/ios/framework/Source; diff --git a/testing/litetest/lib/litetest.dart b/testing/litetest/lib/litetest.dart index efc627d13c63f..a4d9873d58bb7 100644 --- a/testing/litetest/lib/litetest.dart +++ b/testing/litetest/lib/litetest.dart @@ -2,10 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:async_helper/async_minitest.dart'; // ignore: unused_import -import 'package:expect/expect.dart'; // ignore: unused_import - -import 'src/matchers.dart'; // ignore: unused_import import 'src/test_suite.dart'; export 'package:async_helper/async_minitest.dart' hide test; diff --git a/testing/scenario_app/android/app/build.gradle b/testing/scenario_app/android/app/build.gradle index d36ff250c7383..5f2a94523b996 100644 --- a/testing/scenario_app/android/app/build.gradle +++ b/testing/scenario_app/android/app/build.gradle @@ -18,8 +18,8 @@ android { // The others are irrelevant for a test application. disable 'UnpackedNativeCode','MissingApplicationIcon','GoogleAppIndexingApiWarning','GoogleAppIndexingWarning','GradleDependency','NewerVersionAvailable','Registered' } - buildToolsVersion = '33.0.0-rc4' - compileSdkVersion 32 + buildToolsVersion = '33.0.0' + compileSdkVersion 33 compileOptions { sourceCompatibility JavaVersion.VERSION_11 targetCompatibility JavaVersion.VERSION_11 @@ -27,7 +27,7 @@ android { defaultConfig { applicationId 'dev.flutter.scenarios' minSdkVersion 18 - targetSdkVersion 32 + targetSdkVersion 33 versionCode 1 versionName '1.0' testInstrumentationRunner 'dev.flutter.TestRunner' diff --git a/testing/scenario_app/bin/android_integration_tests.dart b/testing/scenario_app/bin/android_integration_tests.dart index f9e99976f1a55..02fbdfa265290 100644 --- a/testing/scenario_app/bin/android_integration_tests.dart +++ b/testing/scenario_app/bin/android_integration_tests.dart @@ -71,7 +71,7 @@ void main(List args) async { try { goldenFile = File(join(screenshotPath, fileName))..writeAsBytesSync(fileContent, flush: true); } on FileSystemException catch (err) { - panic(['failed to create screenshot $fileName: ${err.toString()}']); + panic(['failed to create screenshot $fileName: $err']); } log('wrote ${goldenFile.absolute.path}'); if (isSkiaGoldClientAvailable) { @@ -79,13 +79,13 @@ void main(List args) async { .addImg(fileName, goldenFile, screenshotSize: screenshot.pixelCount) .catchError((dynamic err) { - panic(['skia gold comparison failed: ${err.toString()}']); + panic(['skia gold comparison failed: $err']); }); pendingComparisons.add(comparison); } }, onError: (dynamic err) { - panic(['error while receiving bytes: ${err.toString()}']); + panic(['error while receiving bytes: $err']); }, cancelOnError: true); }); diff --git a/testing/scenario_app/lib/src/touches_scenario.dart b/testing/scenario_app/lib/src/touches_scenario.dart index a20355108764e..4b289d2af41c2 100644 --- a/testing/scenario_app/lib/src/touches_scenario.dart +++ b/testing/scenario_app/lib/src/touches_scenario.dart @@ -9,12 +9,12 @@ import 'scenario.dart'; /// A scenario that sends back messages when touches are received. class TouchesScenario extends Scenario { - final Map _knownDevices = {}; - int _sequenceNo = 0; - /// Constructor for `TouchesScenario`. TouchesScenario(PlatformDispatcher dispatcher) : super(dispatcher); + final Map _knownDevices = {}; + int _sequenceNo = 0; + @override void onBeginFrame(Duration duration) { // It is necessary to render frames for touch events to work properly on iOS diff --git a/third_party/accessibility/BUILD.gn b/third_party/accessibility/BUILD.gn index 469849c8196da..eb217af52fe73 100644 --- a/third_party/accessibility/BUILD.gn +++ b/third_party/accessibility/BUILD.gn @@ -75,6 +75,19 @@ if (enable_unittests) { "ax/platform/test_ax_node_wrapper.h", ] + if (is_mac) { + sources += [ "ax/platform/ax_platform_node_mac_unittest.mm" ] + frameworks = [ + "AppKit.framework", + "CoreFoundation.framework", + "CoreGraphics.framework", + "CoreText.framework", + "IOSurface.framework", + ] + + cflags_objcc = flutter_cflags_objcc + ldflags = [ "-ObjC" ] + } if (is_win) { sources += [ "ax/platform/ax_fragment_root_win_unittest.cc", diff --git a/third_party/accessibility/ax/platform/ax_platform_node_mac.mm b/third_party/accessibility/ax/platform/ax_platform_node_mac.mm index 78c318bba2e36..572b2ca741266 100644 --- a/third_party/accessibility/ax/platform/ax_platform_node_mac.mm +++ b/third_party/accessibility/ax/platform/ax_platform_node_mac.mm @@ -1016,7 +1016,9 @@ - (NSRange)accessibilityRangeForLine:(NSInteger)line { } - (NSRange)accessibilityRangeForPosition:(NSPoint)point { - BASE_UNREACHABLE(); + // TODO(a-wallen): Framework needs to send Text Metrics + // to the AXTree in order for a NSPoint (x, y) to be + // translated to the appropriate range of UTF-16 chars. return NSMakeRange(0, 0); } diff --git a/third_party/accessibility/ax/platform/ax_platform_node_mac_unittest.h b/third_party/accessibility/ax/platform/ax_platform_node_mac_unittest.h new file mode 100644 index 0000000000000..96c34ebb0d15f --- /dev/null +++ b/third_party/accessibility/ax/platform/ax_platform_node_mac_unittest.h @@ -0,0 +1,29 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_ACCESSIBILITY_PLATFORM_AX_PLATFORM_NODE_MAC_UNITTEST_H_ +#define UI_ACCESSIBILITY_PLATFORM_AX_PLATFORM_NODE_MAC_UNITTEST_H_ + +#include "ax_platform_node_mac.h" +#include "ax_platform_node_unittest.h" + +namespace ui { + +// A test fixture that supports accessing the macOS-specific node +// implementations for the AXTree under test. +class AXPlatformNodeMacTest : public AXPlatformNodeTest { + public: + AXPlatformNodeMacTest(); + ~AXPlatformNodeMacTest() override; + + void SetUp() override; + void TearDown() override; + + protected: + AXPlatformNode* AXPlatformNodeFromNode(AXNode* node); +}; + +} // namespace ui + +#endif // UI_ACCESSIBILITY_PLATFORM_AX_PLATFORM_NODE_MAC_UNITTEST_H_ diff --git a/third_party/accessibility/ax/platform/ax_platform_node_mac_unittest.mm b/third_party/accessibility/ax/platform/ax_platform_node_mac_unittest.mm new file mode 100644 index 0000000000000..2b7629a8c0655 --- /dev/null +++ b/third_party/accessibility/ax/platform/ax_platform_node_mac_unittest.mm @@ -0,0 +1,70 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ax_platform_node_mac_unittest.h" + +#include "ax_platform_node_mac.h" +#include "gtest/gtest.h" +#include "test_ax_node_wrapper.h" +#include "third_party/accessibility/ax/ax_node_data.h" + +namespace ui { + +AXPlatformNodeMacTest::AXPlatformNodeMacTest() = default; + +AXPlatformNodeMacTest::~AXPlatformNodeMacTest() = default; + +void AXPlatformNodeMacTest::SetUp() {} + +void AXPlatformNodeMacTest::TearDown() { + // Destroy the tree and make sure we're not leaking any objects. + DestroyTree(); + TestAXNodeWrapper::SetGlobalIsWebContent(false); + ASSERT_EQ(0U, AXPlatformNodeBase::GetInstanceCountForTesting()); +} + +AXPlatformNode* AXPlatformNodeMacTest::AXPlatformNodeFromNode(AXNode* node) { + const TestAXNodeWrapper* wrapper = TestAXNodeWrapper::GetOrCreate(GetTree(), node); + return wrapper ? wrapper->ax_platform_node() : nullptr; +} + +// Verify that we can get an AXPlatformNodeMac and AXPlatformNodeCocoa from the tree. +TEST_F(AXPlatformNodeMacTest, CanGetCocoaPlatformNodeFromTree) { + AXNodeData root; + root.id = 1; + root.relative_bounds.bounds = gfx::RectF(0, 0, 40, 40); + + Init(root); + AXNode* root_node = GetRootAsAXNode(); + ASSERT_TRUE(root_node != nullptr); + + AXPlatformNode* platform_node = AXPlatformNodeFromNode(root_node); + ASSERT_TRUE(platform_node != nullptr); + + AXPlatformNodeCocoa* native_root = platform_node->GetNativeViewAccessible(); + EXPECT_TRUE(native_root != nullptr); +} + +// Test that [AXPlatformNodeCocoa accessbilityRangeForPosition:] doesn't crash. +// https://github.com/flutter/flutter/issues/102416 +TEST_F(AXPlatformNodeMacTest, AccessibilityRangeForPositionDoesntCrash) { + AXNodeData root; + root.id = 1; + root.relative_bounds.bounds = gfx::RectF(0, 0, 40, 40); + + Init(root); + AXNode* root_node = GetRootAsAXNode(); + ASSERT_TRUE(root_node != nullptr); + + AXPlatformNode* platform_node = AXPlatformNodeFromNode(root_node); + ASSERT_TRUE(platform_node != nullptr); + + NSPoint point = NSMakePoint(0, 0); + AXPlatformNodeCocoa* native_root = platform_node->GetNativeViewAccessible(); + ASSERT_TRUE(native_root != nullptr); + + [native_root accessibilityRangeForPosition:(NSPoint)point]; +} + +} // namespace ui diff --git a/third_party/tonic/typed_data/dart_byte_data.cc b/third_party/tonic/typed_data/dart_byte_data.cc index cab038d14f85d..7416f12f62221 100644 --- a/third_party/tonic/typed_data/dart_byte_data.cc +++ b/third_party/tonic/typed_data/dart_byte_data.cc @@ -12,16 +12,16 @@ namespace tonic { namespace { -// For large objects it is more efficient to use an external typed data object -// with a buffer allocated outside the Dart heap. -const int kExternalSizeThreshold = 1000; - void FreeFinalizer(void* isolate_callback_data, void* peer) { free(peer); } } // anonymous namespace +// For large objects it is more efficient to use an external typed data object +// with a buffer allocated outside the Dart heap. +const size_t DartByteData::kExternalSizeThreshold = 1000; + Dart_Handle DartByteData::Create(const void* data, size_t length) { if (length < kExternalSizeThreshold) { auto handle = DartByteData{data, length}.dart_handle(); diff --git a/third_party/tonic/typed_data/dart_byte_data.h b/third_party/tonic/typed_data/dart_byte_data.h index 9e14bc969fb8e..911d0d5da0d7e 100644 --- a/third_party/tonic/typed_data/dart_byte_data.h +++ b/third_party/tonic/typed_data/dart_byte_data.h @@ -14,6 +14,7 @@ namespace tonic { class DartByteData { public: + static const size_t kExternalSizeThreshold; static Dart_Handle Create(const void* data, size_t length); explicit DartByteData(Dart_Handle list); diff --git a/tools/analysis_options.yaml b/tools/analysis_options.yaml new file mode 100644 index 0000000000000..7f47e0448b47f --- /dev/null +++ b/tools/analysis_options.yaml @@ -0,0 +1,5 @@ +include: ../analysis_options.yaml + +linter: + rules: + avoid_print: false diff --git a/tools/android_lint/bin/main.dart b/tools/android_lint/bin/main.dart index e304caa158da5..88cfd040e231b 100644 --- a/tools/android_lint/bin/main.dart +++ b/tools/android_lint/bin/main.dart @@ -73,7 +73,7 @@ Future runLint(ArgParser argParser, ArgResults argResults) async { - + '''); for (final FileSystemEntity entity in androidDir.listSync(recursive: true)) { @@ -95,7 +95,7 @@ Future runLint(ArgParser argParser, ArgResults argResults) async { final List lintArgs = [ path.join(androidSdkDir.path, 'cmdline-tools', 'latest', 'bin', 'lint'), '--project', projectXmlPath, - '--compile-sdk-version', '31', + '--compile-sdk-version', '33', '--showall', '--exitcode', // Set non-zero exit code on errors '-Wall', diff --git a/tools/android_lint/project.xml b/tools/android_lint/project.xml index 1b1c2d514f6ef..af48aa0b7c75c 100644 --- a/tools/android_lint/project.xml +++ b/tools/android_lint/project.xml @@ -2,7 +2,7 @@ - + diff --git a/tools/android_sdk/create_cipd_packages.sh b/tools/android_sdk/create_cipd_packages.sh index b3bbb3184e8b5..c618ddeb025b8 100755 --- a/tools/android_sdk/create_cipd_packages.sh +++ b/tools/android_sdk/create_cipd_packages.sh @@ -3,11 +3,14 @@ # This script requires depot_tools to be on path. print_usage () { - echo "Usage: create_cipd_united_package.sh [PATH_TO_SDK_DIR]" - echo " where:" - echo " - VERSION_TAG is the tag of the cipd packages, e.g. 28r6 or 31v1" - echo " - PATH_TO_SDK_DIR is the path to the sdk folder. If omitted, this defaults to" + echo "Usage:" + echo " ./create_cipd_united_package.sh [PATH_TO_SDK_DIR]" + echo " Downloads, packages, and uploads Android SDK packages where:" + echo " - VERSION_TAG is the tag of the cipd packages, e.g. 28r6 or 31v1" + echo " - PATH_TO_SDK_DIR is the path to the sdk folder. If omitted, this defaults to" echo " your ANDROID_SDK_ROOT environment variable." + echo " ./create_cipd_united_package.sh list" + echo " Lists the available packages for use in 'packages.txt'" echo "" echo "This script downloads the packages specified in packages.txt and uploads" echo "them to CIPD for linux, mac, and windows." @@ -69,10 +72,16 @@ while [ ! -f "$sdkmanager_path" ]; do ((i++)) done +# list available packages +if [ $version_tag == "list" ]; then + $sdkmanager_path --list --include_obsolete + exit 0 +fi + # We create a new temporary SDK directory because the default working directory # tends to not update/re-download packages if they are being used. This guarantees # a clean install of Android SDK. -temp_dir=`mktemp -d -t android_sdk` +temp_dir=`mktemp -d -t android_sdkXXXX` for platform in "${platforms[@]}"; do sdk_root="$temp_dir/sdk_$platform" @@ -99,8 +108,11 @@ for platform in "${platforms[@]}"; do done # Special treatment for NDK to move to expected directory. - mv $upload_dir/sdk/ndk-bundle $upload_dir - mv $upload_dir/ndk-bundle $upload_dir/ndk + mv $upload_dir/sdk/ndk $upload_dir/ndk-bundle + ndk_sub_paths=`find $upload_dir/ndk-bundle -maxdepth 1 -type d` + ndk_sub_paths_arr=($ndk_sub_paths) + mv ${ndk_sub_paths_arr[1]} $upload_dir/ndk + rm -rf $upload_dir/ndk-bundle # Accept all licenses to ensure they are generated and uploaded. yes "y" | $sdkmanager_path --licenses --sdk_root=$sdk_root diff --git a/tools/android_sdk/packages.txt b/tools/android_sdk/packages.txt index c0961a9c7fe65..b257101a496af 100644 --- a/tools/android_sdk/packages.txt +++ b/tools/android_sdk/packages.txt @@ -1,6 +1,6 @@ -platforms;android-32:platforms +platforms;android-33:platforms cmdline-tools;latest:cmdline-tools -build-tools;33.0.0-rc4:build-tools +build-tools;33.0.0:build-tools platform-tools:platform-tools tools:tools -ndk-bundle:ndk-bundle \ No newline at end of file +ndk;22.1.7171670:ndk \ No newline at end of file diff --git a/tools/api_check/lib/apicheck.dart b/tools/api_check/lib/apicheck.dart index 1ba28a086c001..8413cc6f37e95 100644 --- a/tools/api_check/lib/apicheck.dart +++ b/tools/api_check/lib/apicheck.dart @@ -36,11 +36,11 @@ List getDartClassFields({ final RegExp fieldExp = RegExp(r'_k(\w*)Index'); final List fields = []; for (final CompilationUnitMember unitMember in result.unit.declarations) { - if (unitMember is ClassDeclaration && unitMember.name.name == className) { + if (unitMember is ClassDeclaration && unitMember.name.name == className) { // ignore: deprecated_member_use for (final ClassMember classMember in unitMember.members) { if (classMember is FieldDeclaration) { for (final VariableDeclaration field in classMember.fields.variables) { - final String fieldName = field.name.name; + final String fieldName = field.name.name; // ignore: deprecated_member_use final RegExpMatch? match = fieldExp.firstMatch(fieldName); if (match != null) { fields.add(match.group(1)!); diff --git a/tools/api_check/pubspec.yaml b/tools/api_check/pubspec.yaml index 8ac0e26b0dafc..60a1493a0247a 100644 --- a/tools/api_check/pubspec.yaml +++ b/tools/api_check/pubspec.yaml @@ -28,13 +28,13 @@ environment: dependencies: analyzer: any _fe_analyzer_shared: any + pub_semver: any dev_dependencies: async_helper: any expect: any litetest: any path: any - pub_semver: any smith: any dependency_overrides: diff --git a/tools/cipd/android_embedding_bundle/build.gradle b/tools/cipd/android_embedding_bundle/build.gradle index 66d2dfbc9aeb8..93c76e84fd384 100644 --- a/tools/cipd/android_embedding_bundle/build.gradle +++ b/tools/cipd/android_embedding_bundle/build.gradle @@ -29,7 +29,7 @@ allprojects { apply plugin: "com.android.application" android { - compileSdkVersion 32 + compileSdkVersion 33 } configurations { diff --git a/tools/const_finder/test/const_finder_test.dart b/tools/const_finder/test/const_finder_test.dart index 4289c584fb312..9eac1900b7e97 100644 --- a/tools/const_finder/test/const_finder_test.dart +++ b/tools/const_finder/test/const_finder_test.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// ignore_for_file: avoid_dynamic_calls + import 'dart:convert' show jsonEncode; import 'dart:io'; @@ -175,7 +177,7 @@ Future main(List args) async { final String frontendServer = args[0]; final String sdkRoot = args[1]; try { - void _checkProcessResult(ProcessResult result) { + void checkProcessResult(ProcessResult result) { if (result.exitCode != 0) { stdout.writeln(result.stdout); stderr.writeln(result.stderr); @@ -186,7 +188,7 @@ Future main(List args) async { stdout.writeln('Generating kernel fixtures...'); stdout.writeln(consts); - _checkProcessResult(Process.runSync(dart, [ + checkProcessResult(Process.runSync(dart, [ frontendServer, '--sdk-root=$sdkRoot', '--target=flutter', @@ -197,7 +199,7 @@ Future main(List args) async { box, ])); - _checkProcessResult(Process.runSync(dart, [ + checkProcessResult(Process.runSync(dart, [ frontendServer, '--sdk-root=$sdkRoot', '--target=flutter', @@ -208,7 +210,7 @@ Future main(List args) async { consts, ])); - _checkProcessResult(Process.runSync(dart, [ + checkProcessResult(Process.runSync(dart, [ frontendServer, '--sdk-root=$sdkRoot', '--target=flutter', diff --git a/tools/const_finder/test/fixtures/lib/box.dart b/tools/const_finder/test/fixtures/lib/box.dart index f84961d10883b..ea31a1ed3be4e 100644 --- a/tools/const_finder/test/fixtures/lib/box.dart +++ b/tools/const_finder/test/fixtures/lib/box.dart @@ -9,9 +9,9 @@ // https://github.com/dart-lang/sdk/blob/ca3ad264a64937d5d336cd04dbf2746d1b7d8fc4/tests/language_2/canonicalize/hashing_memoize_instance_test.dart class Box { + const Box(this.content1, this.content2); final Object content1; final Object content2; - const Box(this.content1, this.content2); } const Box box1_0 = Box(null, null); @@ -217,7 +217,7 @@ const Box box2_98 = Box(box2_97, box2_97); const Box box2_99 = Box(box2_98, box2_98); Object confuse(Box x) { - try { throw x; } catch (e) { return e; } + try { throw x; } catch (e) { return e; } // ignore: only_throw_errors } void main() { diff --git a/tools/const_finder/test/fixtures/lib/consts.dart b/tools/const_finder/test/fixtures/lib/consts.dart index ba66e7a946fb0..a13a5772a4521 100644 --- a/tools/const_finder/test/fixtures/lib/consts.dart +++ b/tools/const_finder/test/fixtures/lib/consts.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// ignore_for_file: prefer_const_constructors, unused_local_variable +// ignore_for_file: prefer_const_constructors, unused_local_variable, depend_on_referenced_packages import 'dart:core'; import 'package:const_finder_fixtures_package/package.dart'; diff --git a/tools/const_finder/test/fixtures/lib/consts_and_non.dart b/tools/const_finder/test/fixtures/lib/consts_and_non.dart index 6b85ac2687cd3..fa91c318bcb7b 100644 --- a/tools/const_finder/test/fixtures/lib/consts_and_non.dart +++ b/tools/const_finder/test/fixtures/lib/consts_and_non.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// ignore_for_file: prefer_const_constructors, unused_local_variable +// ignore_for_file: prefer_const_constructors, unused_local_variable, depend_on_referenced_packages import 'dart:core'; import 'package:const_finder_fixtures_package/package.dart'; diff --git a/tools/const_finder/test/fixtures/pkg/package.dart b/tools/const_finder/test/fixtures/pkg/package.dart index 87a92bb57221d..b5256d53e154b 100644 --- a/tools/const_finder/test/fixtures/pkg/package.dart +++ b/tools/const_finder/test/fixtures/pkg/package.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// ignore_for_file: prefer_const_constructors +// ignore_for_file: prefer_const_constructors, depend_on_referenced_packages import 'package:const_finder_fixtures/target.dart'; void createTargetInPackage() { diff --git a/tools/font-subset/BUILD.gn b/tools/font-subset/BUILD.gn index e12e3025bb54b..53df3e102e74e 100644 --- a/tools/font-subset/BUILD.gn +++ b/tools/font-subset/BUILD.gn @@ -22,6 +22,10 @@ executable("_font-subset") { "CoreText.framework", ] } + + metadata = { + font_subset_without_entitlement = [ "font-subset" ] + } } generated_file("_font-subset-license") { @@ -40,6 +44,14 @@ generated_file("_font-subset-license") { ] } +generated_file("font_entitlement_config") { + outputs = [ "$target_gen_dir/font_subset_without_entitlements.txt" ] + + data_keys = [ "font_subset_without_entitlement" ] + + deps = [ ":_font-subset" ] +} + zip_bundle("font-subset") { if (is_mac) { # Mac artifacts sometimes use mac and sometimes darwin. Standardizing the @@ -74,4 +86,13 @@ zip_bundle("font-subset") { ":_font-subset-license", "//flutter/tools/const_finder", ] + if (is_mac) { + deps += [ ":font_entitlement_config" ] + files += [ + { + source = "$target_gen_dir/font_subset_without_entitlements.txt" + destination = "without_entitlements.txt" + }, + ] + } } diff --git a/tools/fuchsia/build_fuchsia_artifacts.py b/tools/fuchsia/build_fuchsia_artifacts.py index c9171372510cc..302f9bf5e04d1 100755 --- a/tools/fuchsia/build_fuchsia_artifacts.py +++ b/tools/fuchsia/build_fuchsia_artifacts.py @@ -83,6 +83,7 @@ def RunGN(variant_dir, flags): def BuildNinjaTargets(variant_dir, targets): assert os.path.exists(os.path.join(_out_dir, variant_dir)) + print('Running autoninja for targets: %s', targets) RunExecutable(['autoninja', '-C', os.path.join(_out_dir, variant_dir)] + targets) @@ -310,15 +311,8 @@ def ProcessCIPDPackage(upload, engine_version): def BuildTarget( - runtime_mode, - arch, - optimized, - enable_lto, - enable_legacy, - asan, - dart_version_git_info, - prebuilt_dart_sdk, - additional_targets=[] + runtime_mode, arch, optimized, enable_lto, enable_legacy, asan, + dart_version_git_info, prebuilt_dart_sdk, build_targets ): unopt = "_unopt" if not optimized else "" out_dir = 'fuchsia_%s%s_%s' % (runtime_mode, unopt, arch) @@ -344,7 +338,7 @@ def BuildTarget( flags.append('--no-prebuilt-dart-sdk') RunGN(out_dir, flags) - BuildNinjaTargets(out_dir, ['flutter'] + additional_targets) + BuildNinjaTargets(out_dir, build_targets) return @@ -473,7 +467,7 @@ def main(): runtime_mode, arch, optimized, enable_lto, enable_legacy, args.asan, not args.no_dart_version_git_info, not args.no_prebuilt_dart_sdk, - args.targets.split(",") if args.targets else [] + args.targets.split(",") if args.targets else ['flutter'] ) CopyBuildToBucket(runtime_mode, arch, optimized, product) diff --git a/tools/fuchsia/fuchsia_archive.gni b/tools/fuchsia/fuchsia_archive.gni index c0cc730973d64..a30d392c526e9 100644 --- a/tools/fuchsia/fuchsia_archive.gni +++ b/tools/fuchsia/fuchsia_archive.gni @@ -36,6 +36,8 @@ template("_compile_cml") { "--output", rebase_path(invoker.output, root_build_dir), "--includepath", + rebase_path("$fuchsia_sdk/pkg/", root_build_dir), + "--includepath", get_path_info(invoker.manifest, "dir"), "--includepath", rebase_path("//"), @@ -258,42 +260,66 @@ template("fuchsia_archive") { # cmx_file (optional): # A path to the .cmx file for the test archive. # If not defined, a generated .cml file for the test archive will be used instead. +# cml_file (optional): +# The path to the V2 component manifest (.cml file) for the test archive's component. +# Should include the file extension. +# If not defined, a generated .cml file for the test archive will be used instead. # libraries (optional): # Paths to .so libraries that should be dynamically linked to the binary. # resources (optional): # Files that should be placed into the `data/` directory of the archive. template("fuchsia_test_archive") { assert(defined(invoker.deps), "package must define deps") - - # Interpolate test_suite.cml template with the test suite's name. - test_suite = target_name - interpolate_cml_target = "${test_suite}_interpolate_cml" - generated_cml_file = "$root_out_dir/$test_suite.cml" - action(interpolate_cml_target) { - testonly = true - script = "//flutter/tools/fuchsia/interpolate_test_suite.py" - sources = [ "//flutter/testing/fuchsia/meta/test_suite.cml" ] - args = [ - "--input", - rebase_path("//flutter/testing/fuchsia/meta/test_suite.cml"), - "--test-suite", - test_suite, - "--output", - rebase_path(generated_cml_file), - ] - outputs = [ generated_cml_file ] + _deps = [] + if (defined(invoker.deps)) { + _deps += invoker.deps } - far_base_dir = "$root_out_dir/${target_name}_far" + if (defined(invoker.cml_file)) { + _far_base_dir = "$root_out_dir/${target_name}_far" + _cml_file_name = get_path_info(invoker.cml_file, "name") + _compile_cml_target = "${target_name}_${_cml_file_name}_compile_cml" - # Compile the resulting interpolated test suite's cml. - compile_test_suite_cml_target = "${test_suite}_test_suite_compile_cml" - _compile_cml(compile_test_suite_cml_target) { - testonly = true - deps = [ ":$interpolate_cml_target" ] + _compile_cml(_compile_cml_target) { + forward_variables_from(invoker, [ "testonly" ]) + + manifest = invoker.cml_file + output = "$_far_base_dir/meta/${_cml_file_name}.cm" + } + _deps += [ ":$_compile_cml_target" ] + } else { + # Interpolate test_suite.cml template with the test suite's name. + test_suite = target_name + interpolate_cml_target = "${test_suite}_interpolate_cml" + generated_cml_file = "$root_out_dir/$test_suite.cml" + action(interpolate_cml_target) { + testonly = true + script = "//flutter/tools/fuchsia/interpolate_test_suite.py" + sources = [ "//flutter/testing/fuchsia/meta/test_suite.cml" ] + args = [ + "--input", + rebase_path("//flutter/testing/fuchsia/meta/test_suite.cml"), + "--test-suite", + test_suite, + "--output", + rebase_path(generated_cml_file), + ] + outputs = [ generated_cml_file ] + } + + far_base_dir = "$root_out_dir/${target_name}_far" + + # Compile the resulting interpolated test suite's cml. + compile_test_suite_cml_target = "${test_suite}_test_suite_compile_cml" + _compile_cml(compile_test_suite_cml_target) { + testonly = true + deps = [ ":$interpolate_cml_target" ] + + manifest = generated_cml_file + output = "$far_base_dir/meta/${test_suite}.cm" + } - manifest = generated_cml_file - output = "$far_base_dir/meta/${test_suite}.cm" + _deps += [ ":$compile_test_suite_cml_target" ] } _fuchsia_archive(target_name) { @@ -309,15 +335,6 @@ template("fuchsia_test_archive") { libraries += invoker.libraries } - # TODO(fxbug.dev/79873): Only cfv2 components should be allowed after - # FakeScenic is available. - if (defined(invoker.cmx_file)) { - cmx_file = invoker.cmx_file - - # Don't include cml files. - deps = invoker.deps - } else { - deps = invoker.deps + [ ":$compile_test_suite_cml_target" ] - } + deps = _deps } } diff --git a/tools/fuchsia/gn-sdk/pm_tool.gni b/tools/fuchsia/gn-sdk/pm_tool.gni index 5323a3d4f1c3f..73dabf0d6be02 100644 --- a/tools/fuchsia/gn-sdk/pm_tool.gni +++ b/tools/fuchsia/gn-sdk/pm_tool.gni @@ -84,7 +84,10 @@ template("fuchsia_pm_tool") { package_name, ] if (command == "build") { + assert(fuchsia_target_api_level != -1, + "Must set a target api level when creating an archive") args += [ + "--api-level=${fuchsia_target_api_level}", command, "-output-package-manifest", rebase_path(_pkg_output_manifest, root_build_dir), diff --git a/tools/gen_locale.dart b/tools/gen_locale.dart index 22c4c894a6636..a154a8f2d9a73 100644 --- a/tools/gen_locale.dart +++ b/tools/gen_locale.dart @@ -11,6 +11,8 @@ // this script (the first set for _canonicalizeLanguageCode and the second set // for _canonicalizeRegionCode). +// ignore_for_file: avoid_print + import 'dart:async'; import 'dart:convert'; import 'dart:io'; @@ -19,7 +21,7 @@ const String registry = 'https://www.iana.org/assignments/language-subtag-regist Map> parseSection(String section) { final Map> result = >{}; - List lastHeading; + late List lastHeading; for (final String line in section.split('\n')) { if (line == '') { continue; @@ -30,12 +32,11 @@ Map> parseSection(String section) { } final int colon = line.indexOf(':'); if (colon <= 0) { - throw 'not sure how to deal with "$line"'; + throw StateError('not sure how to deal with "$line"'); } final String name = line.substring(0, colon); final String value = line.substring(colon + 2); - lastHeading = result.putIfAbsent(name, () => []); - result[name].add(value); + lastHeading = result.putIfAbsent(name, () => [])..add(value); } return result; } @@ -45,29 +46,29 @@ Future main() async { final String body = (await (await (await client.getUrl(Uri.parse(registry))).close()).transform(utf8.decoder).toList()).join(); final List>> sections = body.split('%%').map>>(parseSection).toList(); final Map> outputs = >{'language': [], 'region': []}; - String fileDate; + String? fileDate; for (final Map> section in sections) { if (fileDate == null) { // first block should contain a File-Date metadata line. - fileDate = section['File-Date'].single; + fileDate = section['File-Date']!.single; continue; } assert(section.containsKey('Type'), section.toString()); - final String type = section['Type'].single; + final String type = section['Type']!.single; if ((type == 'language' || type == 'region') && (section.containsKey('Preferred-Value'))) { assert(section.containsKey('Subtag'), section.toString()); - final String subtag = section['Subtag'].single; - final List descriptions = section['Description']; + final String subtag = section['Subtag']!.single; + final List descriptions = section['Description']!; assert(descriptions.isNotEmpty); assert(section.containsKey('Deprecated')); - final String comment = section.containsKey('Comment') ? section['Comment'].single : 'deprecated ${section['Deprecated'].single}'; - final String preferredValue = section['Preferred-Value'].single; - outputs[type].add("'$subtag': '$preferredValue', // ${descriptions.join(", ")}; $comment"); + final String comment = section.containsKey('Comment') ? section['Comment']!.single : 'deprecated ${section['Deprecated']!.single}'; + final String preferredValue = section['Preferred-Value']!.single; + outputs[type]!.add("'$subtag': '$preferredValue', // ${descriptions.join(", ")}; $comment"); } } print('// Mappings generated for language subtag registry as of $fileDate.'); print('// For languageCode:'); - print(outputs['language'].join('\n')); + print(outputs['language']!.join('\n')); print('// For regionCode:'); - print(outputs['region'].join('\n')); + print(outputs['region']!.join('\n')); } diff --git a/tools/gen_objcdoc.sh b/tools/gen_objcdoc.sh index dd0583b939231..7f7d65d4f4bd6 100755 --- a/tools/gen_objcdoc.sh +++ b/tools/gen_objcdoc.sh @@ -7,27 +7,29 @@ set -e +if [[ $# -eq 0 ]]; then + echo "Error: Argument specifying output directory required." + exit 1 +fi + +# Move to the flutter checkout +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +pushd "$SCRIPT_DIR/../../flutter" + FLUTTER_UMBRELLA_HEADER=$(find ../out -maxdepth 4 -type f -name Flutter.h | grep 'ios_' | head -n 1) if [[ ! -f "$FLUTTER_UMBRELLA_HEADER" ]] then echo "Error: This script must be run at the root of the Flutter source tree with at least one built Flutter.framework in ../out/ios*/Flutter.framework." + echo "Running from: $(pwd)" exit 1 fi - -# If the script is running from within LUCI we use the LUCI_WORKDIR, if not we force the caller of the script -# to pass an output directory as the first parameter. -OUTPUT_DIR="" - -if [[ -z "$LUCI_CI" ]]; then - if [[ $# -eq 0 ]]; then - echo "Error: Argument specifying output directory required." - exit 1 - else - OUTPUT_DIR="$1" - fi -else - OUTPUT_DIR="$LUCI_WORKDIR/objectc_docs" +OUTPUT_DIR="$1/objectc_docs" +ZIP_DESTINATION="$1" +if [ "${OUTPUT_DIR:0:1}" != "/" ] +then + ZIP_DESTINATION="$SCRIPT_DIR/../../$1" + OUTPUT_DIR="$ZIP_DESTINATION/objectc_docs" fi # If GEM_HOME is set, prefer using its copy of jazzy. @@ -83,3 +85,8 @@ if [[ $EXPECTED_CLASSES != $ACTUAL_CLASSES ]]; then diff <(echo "$EXPECTED_CLASSES") <(echo "$ACTUAL_CLASSES") exit -1 fi + +# Create the final zip file. +pushd $OUTPUT_DIR +zip -r "$ZIP_DESTINATION/ios-objcdoc.zip" . +popd diff --git a/tools/githooks/lib/githooks.dart b/tools/githooks/lib/githooks.dart index aedcac4db1479..14c3d4349fff7 100644 --- a/tools/githooks/lib/githooks.dart +++ b/tools/githooks/lib/githooks.dart @@ -19,6 +19,10 @@ Future run(List args) async { // Add top-level arguments. runner.argParser + ..addFlag( + 'enable-clang-tidy', + help: 'Enable running clang-tidy on changed files.', + ) ..addOption( 'flutter', abbr: 'f', diff --git a/tools/githooks/lib/src/pre_push_command.dart b/tools/githooks/lib/src/pre_push_command.dart index 80b653cbabe71..6b456cbe53ebe 100644 --- a/tools/githooks/lib/src/pre_push_command.dart +++ b/tools/githooks/lib/src/pre_push_command.dart @@ -20,10 +20,18 @@ class PrePushCommand extends Command { Future run() async { final Stopwatch sw = Stopwatch()..start(); final bool verbose = globalResults!['verbose']! as bool; + final bool enableClangTidy = globalResults!['enable-clang-tidy']! as bool; final String flutterRoot = globalResults!['flutter']! as String; + + if (!enableClangTidy) { + print('The clang-tidy check is disabled. To enable set the environment ' + 'variable PRE_PUSH_CLANG_TIDY to any value.'); + } + final List checkResults = [ await _runFormatter(flutterRoot, verbose), - await _runClangTidy(flutterRoot, verbose), + if (enableClangTidy) + await _runClangTidy(flutterRoot, verbose), ]; sw.stop(); io.stdout.writeln('pre-push checks finished in ${sw.elapsed}'); @@ -51,7 +59,8 @@ class PrePushCommand extends Command { 'compile_commands.json', )); if (!compileCommands.existsSync()) { - io.stderr.writeln('clang-tidy requires a fully built host_debug or host_debug_unopt build directory'); + io.stderr.writeln('clang-tidy requires a fully built host_debug or ' + 'host_debug_unopt build directory'); return false; } } diff --git a/tools/githooks/pre-push b/tools/githooks/pre-push index 2d0651e40ddaf..4e10ce870add5 100755 --- a/tools/githooks/pre-push +++ b/tools/githooks/pre-push @@ -15,15 +15,24 @@ import sys SRC_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) FLUTTER_DIR = os.path.join(SRC_ROOT, 'flutter') DART_BIN = os.path.join(SRC_ROOT, 'third_party', 'dart', 'tools', 'sdks', 'dart-sdk', 'bin') - +ENABLE_CLANG_TIDY = os.environ.get('PRE_PUSH_CLANG_TIDY') def Main(argv): + githook_args = [ + '--flutter', + FLUTTER_DIR, + ] + + if ENABLE_CLANG_TIDY is not None: + githook_args += [ + '--enable-clang-tidy', + ] + result = subprocess.run([ os.path.join(DART_BIN, 'dart'), '--disable-dart-dev', os.path.join(FLUTTER_DIR, 'tools', 'githooks', 'bin', 'main.dart'), - '--flutter', - FLUTTER_DIR, + ] + githook_args + [ 'pre-push', ], cwd=SRC_ROOT) return result.returncode diff --git a/tools/gn b/tools/gn index 2cfc42920b044..7b218ff116802 100755 --- a/tools/gn +++ b/tools/gn @@ -94,11 +94,17 @@ def cpu_for_target_arch(arch): def is_host_build(args): # If target_os == None, then this is a host build. - # However, for linux arm64 builds, we cross compile from x64 hosts, so the + if args.target_os is None: + return True + # For linux arm64 builds, we cross compile from x64 hosts, so the # target_os='linux' and linux-cpu='arm64' - return args.target_os is None or ( - args.target_os == 'linux' and args.linux_cpu == 'arm64' - ) + if args.target_os == 'linux' and args.linux_cpu == 'arm64': + return True + # The Mac and host targets are redundant. Again, necessary to disambiguate + # during cross-compilation. + if args.target_os == 'mac': + return True + return False # Determines whether a prebuilt Dart SDK can be used instead of building one. @@ -226,6 +232,8 @@ def to_gn_args(args): gn_args['skia_use_wuffs'] = True gn_args['skia_use_expat'] = args.target_os == 'android' gn_args['skia_use_fontconfig'] = args.enable_fontconfig + gn_args['skia_use_legacy_layer_bounds' + ] = True # Temporary: See skbug.com/12083, skbug.com/12303. if args.enable_skshaper: gn_args['skia_use_icu'] = True diff --git a/tools/javadoc/gen_javadoc.py b/tools/javadoc/gen_javadoc.py index e10a046cb051c..92b49ad77118d 100755 --- a/tools/javadoc/gen_javadoc.py +++ b/tools/javadoc/gen_javadoc.py @@ -58,7 +58,7 @@ def main(): classpath = [ args.android_source_root, os.path.join( - args.third_party, 'android_tools/sdk/platforms/android-32/android.jar' + args.third_party, 'android_tools/sdk/platforms/android-33/android.jar' ), os.path.join( args.third_party, 'android_embedding_dependencies', 'lib', '*' diff --git a/web_sdk/libraries.json b/web_sdk/libraries.json index c0ddf48ca527f..01a9fece85a0e 100644 --- a/web_sdk/libraries.json +++ b/web_sdk/libraries.json @@ -32,5 +32,21 @@ "uri": "lib/_engine/engine.dart" } } + }, + "wasm": { + "include": [ + { + "path": "../dart-sdk/lib/libraries.json", + "target": "wasm" + } + ], + "libraries": { + "ui": { + "uri": "lib/ui/ui.dart" + }, + "_engine": { + "uri": "lib/_engine/engine.dart" + } + } } } \ No newline at end of file diff --git a/web_sdk/libraries.yaml b/web_sdk/libraries.yaml index 73cfbe3bf8258..46bb23f079dec 100644 --- a/web_sdk/libraries.yaml +++ b/web_sdk/libraries.yaml @@ -34,3 +34,14 @@ dart2js: _engine: uri: "lib/_engine/engine.dart" + +wasm: + include: + - {path: "../dart-sdk/lib/libraries.json", target: wasm} + + libraries: + ui: + uri: "lib/ui/ui.dart" + + _engine: + uri: "lib/_engine/engine.dart" diff --git a/web_sdk/pubspec.yaml b/web_sdk/pubspec.yaml index cfc4aeca2f5d5..bd2d22bba6c72 100644 --- a/web_sdk/pubspec.yaml +++ b/web_sdk/pubspec.yaml @@ -1,10 +1,13 @@ name: web_sdk_tests -author: Flutter Authors # Keep the SDK version range in sync with lib/web_ui/pubspec.yaml environment: sdk: ">=2.12.0-0 <3.0.0" +dependencies: + args: 2.3.1 + path: 1.8.2 + dev_dependencies: analyzer: 4.3.1 test: 1.21.4 diff --git a/web_sdk/sdk_rewriter.dart b/web_sdk/sdk_rewriter.dart index 39da4b6dfe7a6..bacc7683443e0 100644 --- a/web_sdk/sdk_rewriter.dart +++ b/web_sdk/sdk_rewriter.dart @@ -10,8 +10,8 @@ import 'package:path/path.dart' as path; final ArgParser argParser = ArgParser() ..addOption('output-dir') ..addOption('input-dir') - ..addFlag('ui', defaultsTo: false) - ..addFlag('engine', defaultsTo: false) + ..addFlag('ui') + ..addFlag('engine') ..addMultiOption('input') ..addOption('stamp'); @@ -45,7 +45,6 @@ import 'dart:developer' as developer; import 'dart:js_util' as js_util; import 'dart:_js_annotations'; import 'dart:math' as math; -import 'dart:svg' as svg; import 'dart:typed_data'; import 'dart:ui' as ui; '''), @@ -171,7 +170,7 @@ String _preprocessEnginePartFile(String source) { // Do nothing. } else { // Insert the part directive at the beginning of the file. - source = 'part of engine;\n' + source; + source = 'part of engine;\n$source'; } return source; } diff --git a/web_sdk/test/api_conform_test.dart b/web_sdk/test/api_conform_test.dart index 1a659ca3790ec..755b6f9a3345c 100644 --- a/web_sdk/test/api_conform_test.dart +++ b/web_sdk/test/api_conform_test.dart @@ -2,13 +2,14 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// ignore_for_file: avoid_print + import 'dart:io'; import 'package:analyzer/dart/analysis/features.dart'; import 'package:analyzer/dart/analysis/results.dart'; import 'package:analyzer/dart/analysis/utilities.dart'; import 'package:analyzer/dart/ast/ast.dart'; -import 'package:pub_semver/pub_semver.dart'; // Ignore members defined on Object. const Set _kObjectMembers = { @@ -187,8 +188,8 @@ void main() { // check nullability if (uiParam is SimpleFormalParameter && webParam is SimpleFormalParameter) { - bool isUiNullable = uiParam.type?.question != null; - bool isWebNullable = webParam.type?.question != null; + final bool isUiNullable = uiParam.type?.question != null; + final bool isWebNullable = webParam.type?.question != null; if (isUiNullable != isWebNullable) { failed = true; print('Warning: lib/ui/ui.dart $className.$methodName parameter $i ' @@ -263,8 +264,8 @@ void main() { '${uiParam.identifier!.name} is named, but not in lib/web_ui/ui.dart.'); } - bool isUiNullable = uiParam.type?.question != null; - bool isWebNullable = webParam.type?.question != null; + final bool isUiNullable = uiParam.type?.question != null; + final bool isWebNullable = webParam.type?.question != null; if (isUiNullable != isWebNullable) { failed = true; print('Warning: lib/ui/ui.dart $typeDefName parameter $i ' diff --git a/web_sdk/test/js_access_test.dart b/web_sdk/test/js_access_test.dart index ee41a1e83d880..985ab28e3180d 100644 --- a/web_sdk/test/js_access_test.dart +++ b/web_sdk/test/js_access_test.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// ignore_for_file: avoid_print + /// Checks that JavaScript API is accessed properly. /// /// JavaScript access needs to be audited to make sure it follows security best diff --git a/web_sdk/test/sdk_rewriter_test.dart b/web_sdk/test/sdk_rewriter_test.dart index 4d3cccc8a9b07..4e17279f3353e 100644 --- a/web_sdk/test/sdk_rewriter_test.dart +++ b/web_sdk/test/sdk_rewriter_test.dart @@ -33,7 +33,6 @@ import 'dart:developer' as developer; import 'dart:js_util' as js_util; import 'dart:_js_annotations'; import 'dart:math' as math; -import 'dart:svg' as svg; import 'dart:typed_data'; import 'dart:ui' as ui; @@ -79,7 +78,7 @@ export 'engine/file3.dart'; '$caught', 'Exception: on line 3: unexpected code in /path/to/lib/web_ui/lib/src/engine.dart. ' 'This file may only contain comments and exports. Found:\n' - 'import \'dart:something\';', + "import 'dart:something';", ); }); diff --git a/web_sdk/web_engine_tester/lib/golden_tester.dart b/web_sdk/web_engine_tester/lib/golden_tester.dart index e5c2f04a9667a..09ce5212e2b9a 100644 --- a/web_sdk/web_engine_tester/lib/golden_tester.dart +++ b/web_sdk/web_engine_tester/lib/golden_tester.dart @@ -7,7 +7,7 @@ import 'dart:convert'; import 'package:test/test.dart'; // ignore: implementation_imports -import 'package:ui/src/engine.dart' show operatingSystem, OperatingSystem, useCanvasKit; +import 'package:ui/src/engine.dart' show OperatingSystem, operatingSystem, renderer; // ignore: implementation_imports import 'package:ui/src/engine/dom.dart'; import 'package:ui/ui.dart'; @@ -64,7 +64,11 @@ Future matchGoldenFile(String filename, 'height': region.height }, 'pixelComparison': pixelComparison.toString(), - 'isCanvaskitTest': useCanvasKit, + // We use the renderer tag here rather than `renderer is CanvasKitRenderer` + // because these unit tests operate on the post-transformed (sdk_rewriter) + // sdk where the internal classes like `CanvasKitRenderer` are no longer + // visible. + 'isCanvaskitTest': renderer.rendererTag == 'canvaskit', }; // Chrome on macOS renders slightly differently from Linux, so allow it an diff --git a/web_sdk/web_engine_tester/lib/static/host.dart b/web_sdk/web_engine_tester/lib/static/host.dart index e98cc23128f35..320dba2134da1 100644 --- a/web_sdk/web_engine_tester/lib/static/host.dart +++ b/web_sdk/web_engine_tester/lib/static/host.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// ignore_for_file: avoid_dynamic_calls + @JS() library test.host; @@ -22,7 +24,7 @@ class _TestRunner { /// Returns the current content shell runner, or `null` if none exists. @JS() -external _TestRunner? get testRunner; +external _TestRunner? get testRunner; // ignore: library_private_types_in_public_api /// A class that exposes the test API to JS. /// @@ -31,6 +33,8 @@ external _TestRunner? get testRunner; @JS() @anonymous class _JSApi { + external factory _JSApi({void Function() resume, void Function() restartCurrent}); + /// Causes the test runner to resume running, as though the user had clicked /// the "play" button. external Function get resume; @@ -38,8 +42,6 @@ class _JSApi { /// Causes the test runner to restart the current test once it finishes /// running. external Function get restartCurrent; - - external factory _JSApi({void Function() resume, void Function() restartCurrent}); } /// Sets the top-level `dartTest` object so that it's visible to JS. @@ -160,7 +162,7 @@ void main() { })); }, (dynamic error, StackTrace stackTrace) { - print('$error\n${Trace.from(stackTrace).terse}'); + print('$error\n${Trace.from(stackTrace).terse}'); // ignore: avoid_print }, ); } diff --git a/web_sdk/web_engine_tester/pubspec.yaml b/web_sdk/web_engine_tester/pubspec.yaml index 489401ff2913e..25219b1071327 100644 --- a/web_sdk/web_engine_tester/pubspec.yaml +++ b/web_sdk/web_engine_tester/pubspec.yaml @@ -9,5 +9,6 @@ dependencies: stream_channel: 2.1.0 test: 1.17.7 webkit_inspection_protocol: 1.0.0 + stack_trace: 1.10.0 ui: path: ../../lib/web_ui diff --git a/web_sdk/web_test_utils/lib/environment.dart b/web_sdk/web_test_utils/lib/environment.dart index 81466b4bba828..30ea72edb9e1f 100644 --- a/web_sdk/web_test_utils/lib/environment.dart +++ b/web_sdk/web_test_utils/lib/environment.dart @@ -36,6 +36,16 @@ class Environment { return _prepareEnvironmentFromEngineDir(script, directory); } + Environment._({ + required this.self, + required this.webUiRootDir, + required this.engineSrcDir, + required this.engineToolsDir, + required this.outDir, + required this.hostDebugUnoptDir, + required this.dartSdkDir, + }); + static Environment _prepareEnvironmentFromEngineDir( io.File self, io.Directory engineSrcDir) { final io.Directory engineToolsDir = @@ -72,16 +82,6 @@ class Environment { ); } - Environment._({ - required this.self, - required this.webUiRootDir, - required this.engineSrcDir, - required this.engineToolsDir, - required this.outDir, - required this.hostDebugUnoptDir, - required this.dartSdkDir, - }); - /// The Dart script that's currently running. final io.File self; diff --git a/web_sdk/web_test_utils/lib/goldens.dart b/web_sdk/web_test_utils/lib/goldens.dart index 2f3f199614b52..e39e1f7fa4863 100644 --- a/web_sdk/web_test_utils/lib/goldens.dart +++ b/web_sdk/web_test_utils/lib/goldens.dart @@ -26,12 +26,22 @@ void main(List args) { final Image imageB = decodeNamedImage(fileB.readAsBytesSync(), 'b.png')!; final ImageDiff diff = ImageDiff( golden: imageA, other: imageB, pixelComparison: PixelComparison.fuzzy); - print('Diff: ${(diff.rate * 100).toStringAsFixed(4)}%'); + print('Diff: ${(diff.rate * 100).toStringAsFixed(4)}%'); // ignore: avoid_print } /// This class encapsulates visually diffing an Image with any other. /// Both images need to be the exact same size. class ImageDiff { + /// Image diff constructor which requires two [Image]s to compare and + /// [PixelComparison] algorithm. + ImageDiff({ + required this.golden, + required this.other, + required this.pixelComparison, + }) { + _computeDiff(); + } + /// The image to match final Image golden; @@ -53,16 +63,6 @@ class ImageDiff { /// This gets set to 1 (100% difference) when golden and other aren't the same size. double get rate => _wrongPixels / _pixelCount; - /// Image diff constructor which requires two [Image]s to compare and - /// [PixelComparison] algorithm. - ImageDiff({ - required this.golden, - required this.other, - required this.pixelComparison, - }) { - _computeDiff(); - } - int _pixelCount = 0; int _wrongPixels = 0; @@ -136,8 +136,6 @@ class ImageDiff { getGreen(pixel), getBlue(pixel), ]; - default: - throw 'Unrecognized pixel comparison value: $pixelComparison'; } } diff --git a/web_sdk/web_test_utils/lib/image_compare.dart b/web_sdk/web_test_utils/lib/image_compare.dart index 29de42db0757e..aeadee1863841 100644 --- a/web_sdk/web_test_utils/lib/image_compare.dart +++ b/web_sdk/web_test_utils/lib/image_compare.dart @@ -72,7 +72,7 @@ Future compareImage( // At the moment, we don't support local screenshot testing because we use // Skia Gold to handle our screenshots and diffing. In the future, we might // implement local screenshot testing if there's a need. - print('Screenshot generated: file://$screenshotPath'); + print('Screenshot generated: file://$screenshotPath'); // ignore: avoid_print return 'OK'; } @@ -137,8 +137,8 @@ Golden file $filename did not match the image generated by the test. if (diff.rate < maxDiffRateFailure) { // Issue a warning but do not fail the test. - print('WARNING:'); - print(message); + print('WARNING:'); // ignore: avoid_print + print(message); // ignore: avoid_print return 'OK'; } else { // Fail test @@ -150,7 +150,7 @@ Golden file $filename did not match the image generated by the test. Future _getGolden(String filename) { // TODO(mdebbar): Fetch the golden from Skia Gold. - return Future.value(null); + return Future.value(); } String _getFullScreenshotPath(String filename) {