diff --git a/.ci.yaml b/.ci.yaml index fd2435e6c525d..0adf7f1749fb6 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: diff --git a/DEPS b/DEPS index 0ac30c02abb35..4f54f8ef97ea5 100644 --- a/DEPS +++ b/DEPS @@ -73,7 +73,7 @@ vars = { 'download_dart_sdk': True, # Checkout Android dependencies only on platforms where we build for Android targets. - 'download_android_deps': 'host_cpu == "x64" and (host_os == "mac" or host_os == "linux")', + 'download_android_deps': '(host_os == "mac" or host_os == "linux")', # Checkout Windows dependencies only if we are building on Windows. 'download_windows_deps' : 'host_os == "win"', @@ -341,7 +341,7 @@ deps = { Var('dart_git') + '/yaml_edit.git' + '@' + Var('dart_yaml_edit_rev'), 'src/third_party/dart/tools/sdks': - {'packages': [{'version': 'version:2.17.0', 'package': 'dart/dart-sdk/${{platform}}'}], 'dep_type': 'cipd'}, + {'packages': [{'version': 'version:2.19.0-374.0.dev', '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. diff --git a/ci/deploy.py b/ci/deploy.py new file mode 100755 index 0000000000000..cdbe5f6b5885a --- /dev/null +++ b/ci/deploy.py @@ -0,0 +1,644 @@ +#build_android_aot, engine/engine + +from ast import arg +import os +import os.path as path +import zipfile +import shutil +import glob +import subprocess + + +def make_path(*components): + return path.join(os.getcwd(), *components) + + +def get_engine_hash(): + os.chdir("./flutter") + p = subprocess.Popen(["git", "rev-parse", "HEAD"], stdout=subprocess.PIPE) + hash = p.stdout.readline().decode("utf-8") + os.chdir("..") + return hash[:-1] + + +ENGINE_HASH = get_engine_hash() +DEPLOY_PATH = make_path("deploy", ENGINE_HASH) + +ICU_DATA_PATH = make_path("third_party", "icu", "flutter", "icudtl.dat") + +CWD = os.getcwd() + +FLUTTER_DIR = make_path("flutter") + +HOST_DEBUG = make_path("out", "host_debug") +HOST_PROFILE = make_path("out", "host_profile") +HOST_RELEASE = make_path("out", "host_release") + +TMP_DIR = path.join(DEPLOY_PATH, "tmp") + + +def zip(out_archive, files=[], directories=[]): + if len(files) == 0 and len(directories) == 0: + raise SystemExit( + f"No files and no directories have been provided to be zipped for {out_archive}" + ) + if path.exists(TMP_DIR) and path.isdir(TMP_DIR): + shutil.rmtree(TMP_DIR) + os.mkdir(TMP_DIR) + for file in files: + shutil.copy(file, TMP_DIR) + for dir in directories: + basename = path.basename(dir) + shutil.copytree( + dir, path.join(TMP_DIR, basename), symlinks=True, dirs_exist_ok=True + ) + os.chdir(TMP_DIR) + execute_command(f'zip -9 -y -r {out_archive} .') + os.chdir(CWD) + shutil.rmtree(TMP_DIR) + + +def zip_directory_content(out_archive, directory_path): + os.chdir(directory_path) + execute_command(f"zip -9 -y -r {out_archive} ./") + os.chdir(CWD) + + +def execute_command(command): + print(f"Executing command: '{command}'") + exit_code = os.system(command) + print(f"Command '{command}' executed with code {exit_code}") + if exit_code != 0: + raise SystemExit(f"Command {command} exited with code {exit_code}.") + + +def build(target): + execute_command(f"ninja -C out/{target}") + + +def gn(params): + execute_command("./flutter/tools/gn " + " ".join(params)) + + +def check_cwd(): + cwd = os.getcwd() + if not cwd.endswith("engine/src"): + raise SystemExit("The script must run in the engine/src directory.") + print("Script is running in engine/src") + + +def clean_deploy_directory(): + if path.exists(DEPLOY_PATH) and path.isdir(DEPLOY_PATH): + shutil.rmtree(DEPLOY_PATH) + execute_command(f"mkdir -p {DEPLOY_PATH}") + + +def set_use_prebuild_dart_sdk(v): + os.environ["FLUTTER_PREBUILT_DART_SDK"] = str(v) + + +def build_host(): + # host_debug + print("Generating host_debug") + gn([ + "--runtime-mode", "debug", "--no-lto", "--full-dart-sdk", + "--no-prebuilt-dart-sdk", "--build-embedder-examples" + ]) + # host_profile + print("Generating host_profile") + gn([ + "--runtime-mode", "profile", "--no-lto", "--no-prebuilt-dart-sdk", + "--build-embedder-examples" + ]) + # host_release + print("Generating host_release") + gn([ + "--runtime-mode", "release", "--no-lto", "--no-prebuilt-dart-sdk", + "--build-embedder-examples" + ]) + # build host_debug + build("host_debug") + # build host_profile + build("host_profile") + # build host_release + build("host_release") + + flutter_embedder_framework = path.join( + HOST_DEBUG, "FlutterEmbedder.framework" + ) + zip( + path.join(DEPLOY_PATH, "FlutterEmbedder.framework.zip"), + directories=[flutter_embedder_framework] + ) + + host_debug_dills = path.join(HOST_DEBUG, "flutter_patched_sdk", "*.dill.S") + host_release_dills = path.join( + DEPLOY_PATH, "flutter_patched_sdk_product", "*.dill.S" + ) + + for file_path_to_remove in glob.glob(host_debug_dills): + os.remove(file_path_to_remove) + + zip( + make_path(DEPLOY_PATH, "flutter_patched_sdk.zip"), + directories=[path.join(HOST_DEBUG, "flutter_patched_sdk")] + ) + + flutter_patched_sdk_product = path.join( + DEPLOY_PATH, "flutter_patched_sdk_product" + ) + shutil.copytree( + path.join(HOST_RELEASE, "flutter_patched_sdk"), + flutter_patched_sdk_product, + symlinks=True + ) + + for file_path_to_remove in glob.glob(host_release_dills): + os.remove(file_path_to_remove) + + zip( + path.join(DEPLOY_PATH, "flutter_patched_sdk_product.zip"), + directories=[flutter_patched_sdk_product] + ) + + shutil.rmtree(path.join(DEPLOY_PATH, "flutter_patched_sdk_product")) + + +def build_mac(): + # mac_debug_arm64 + print("Generating mac_debug_arm64") + gn([ + "--mac", "--mac-cpu", "arm64", "--runtime-mode", "debug", "--no-lto", + "--full-dart-sdk", "--no-prebuilt-dart-sdk" + ]) + # mac_profile_arm64 + print("Generating mac_profile_arm64") + gn([ + "--mac", "--mac-cpu", "arm64", "--runtime-mode", "profile", "--no-lto", + "--no-prebuilt-dart-sdk" + ]) + # mac_release_arm64 + print("Generating mac_release_arm64") + gn([ + "--mac", "--mac-cpu", "arm64", "--runtime-mode", "release", "--no-lto", + "--no-prebuilt-dart-sdk" + ]) + # build host_debug + build("mac_debug_arm64") + # build host_profile + build("mac_profile_arm64") + # build host_release + build("mac_release_arm64") + # debug macos + package_macos_variant("debug", "mac_debug_arm64", "host_debug", "darwin-x64") + # profile macos + package_macos_variant( + "profile", "mac_profile_arm64", "host_profile", "darwin-x64-profile" + ) + # release macos + package_macos_variant( + "release", "mac_release_arm64", "host_release", "darwin-x64-release" + ) + + print("Creating darwin-x64 artifacts.zip") + zip( + make_path(DEPLOY_PATH, "darwin-x64", "artifacts.zip"), + files=[ + ICU_DATA_PATH, + path.join(HOST_DEBUG, "flutter_tester"), + path.join(HOST_DEBUG, "impellerc"), + path.join( + HOST_DEBUG, "gen", "flutter", "lib", "snapshot", + "isolate_snapshot.bin" + ), + path.join( + HOST_DEBUG, "gen", "flutter", "lib", "snapshot", + "vm_isolate_snapshot.bin" + ), + path.join(HOST_DEBUG, "gen", "frontend_server.dart.snapshot"), + path.join(HOST_DEBUG, "gen_snapshot") + ] + ) + print("Done creating darwin-x64 artifacts.zip") + + print("Creating darwin-x64-profile artifacts.zip") + zip( + make_path(DEPLOY_PATH, "darwin-x64-profile", "artifacts.zip"), + files=[path.join(HOST_PROFILE, "gen_snapshot")] + ) + print("Done creating darwin-x64-profile artifacts.zip") + + print("Creating darwin-x64-release artifacts.zip") + zip( + make_path(DEPLOY_PATH, "darwin-x64-release", "artifacts.zip"), + files=[path.join(HOST_RELEASE, "gen_snapshot")] + ) + print("Done creating darwin-x64-release artifacts.zip") + + print("Creating darwin-x64 FlutterEmbedder.framework.zip") + zip( + make_path(DEPLOY_PATH, "darwin-x64", "FlutterEmbedder.framework.zip"), + files=[path.join(DEPLOY_PATH, "FlutterEmbedder.framework.zip")] + ) + print("Done creating darwin-x64 FlutterEmbedder.framework.zip") + + print("Creating darwin-x64 font-subset.zip") + shutil.copy( + make_path(HOST_RELEASE, "zip_archives", "darwin-x64", "font-subset.zip"), + make_path(DEPLOY_PATH, "darwin-x64", "font-subset.zip") + ) + print("Done creating darwin-x64 font-subset.zip") + + print("Creating dart-sdk-darwin-x64.zip") + zip( + make_path(DEPLOY_PATH, "dart-sdk-darwin-x64.zip"), + directories=[make_path(HOST_DEBUG, "dart-sdk")] + ) + print("Done creating dart-sdk-darwin-x64.zip") + + print("Creating dart-sdk-darwin-arm64.zip") + zip( + make_path(DEPLOY_PATH, "dart-sdk-darwin-arm64.zip"), + directories=[make_path("out", "mac_debug_arm64", "dart-sdk")] + ) + print("Done creating dart-sdk-darwin-arm64.zip") + + print("Creating flutter-web-sdk-darwin-x64.zip") + zip( + make_path(DEPLOY_PATH, "flutter-web-sdk-darwin-x64.zip"), + directories=[make_path(HOST_DEBUG, "flutter_web_sdk")] + ) + print("Done creating flutter-web-sdk-darwin-x64.zip") + + +def package_macos_variant(label, arm64_out, x64_out, bucket_name): + out_directory = make_path("out") + label_directory = path.join(out_directory, label) + arm64_directory = path.join(out_directory, arm64_out) + x64_directory = path.join(out_directory, x64_out) + bucket_directory = path.join(DEPLOY_PATH, bucket_name) + + os.mkdir(bucket_directory) + + create_macos_framework_command = " ".join([ + "python3", "./flutter/sky/tools/create_macos_framework.py", + f"--dst {label_directory}", f"--arm64-out-dir {arm64_directory}", + f"--x64-out-dir {x64_directory}" + ]) + + if label == "release": + create_macos_framework_command += " --dsym --strip" + + print(f"Create macOS {label} FlutterMacOS.framework") + execute_command(create_macos_framework_command) + + create_macos_gen_snapshot_command = " ".join([ + "python3", "./flutter/sky/tools/create_macos_gen_snapshots.py", + f"--dst {label_directory}", f"--arm64-out-dir {arm64_directory}", + f"--x64-out-dir {x64_directory}" + ]) + + print(f"Create macOS {label} gen_snapshot") + execute_command(create_macos_gen_snapshot_command) + + macos_framework = make_path(bucket_directory, "FlutterMacOS.framework.zip") + macos_framework_temp = make_path( + bucket_directory, "FlutterMacOS.framework_tmp.zip" + ) + zip_directory_content( + macos_framework, path.join(label_directory, "FlutterMacOS.framework") + ) + zip(macos_framework_temp, files=[macos_framework]) + os.remove(macos_framework) + os.rename(macos_framework_temp, macos_framework) + + gen_snapshot_zip = path.join(bucket_directory, "gen_snapshot.zip") + gen_snapshot_arm64 = path.join(label_directory, "gen_snapshot_arm64") + gen_snapshot_x64 = path.join(label_directory, "gen_snapshot_x64") + zip(gen_snapshot_zip, files=[gen_snapshot_arm64, gen_snapshot_x64]) + + +def get_maven_remote_name(artifact_filename): + artifact_id, artifact_extension = artifact_filename.split(".", 2) + if artifact_id.endswith("-sources"): + filename_pattern = '%s-1.0.0-%s-sources.%s' + else: + filename_pattern = '%s-1.0.0-%s.%s' + + artifact_id = artifact_id.replace('-sources', '') + filename = filename_pattern % (artifact_id, ENGINE_HASH, artifact_extension) + + return 'io/flutter/%s/1.0.0-%s/%s' % (artifact_id, ENGINE_HASH, filename) + + +def create_maven_artifacts(maven_artifacts): + for artifact in maven_artifacts: + file_name = path.basename(artifact) + remote_name = get_maven_remote_name(file_name) + maven_directory = make_path(DEPLOY_PATH, path.dirname(remote_name)) + os.makedirs(maven_directory, exist_ok=True) + shutil.copy(artifact, make_path(DEPLOY_PATH, remote_name)) + + +def build_android_jit(): + print("Generating android_jit_release_x86") + gn(["--android", "--android-cpu=x86", "--runtime-mode=jit_release"]) + build("android_jit_release_x86") + print("Creating android-x86-jit-release artifacts.zip") + with zipfile.ZipFile(make_path("out", "android-x86-jit-release", "artifacts"), + "w") as zip_file: + zip_file.write( + make_path("out", "android_jit_release_x86", "flutter.jar"), + arcname="flutter.jar" + ) + print("Done creating android-x86-jit-release artifacts.zip") + + +def build_android_debug(): + variants = [ + ('arm', 'android_debug', 'android-arm', 'armeabi_v7a'), + ('arm64', 'android_debug_arm64', 'android-arm64', 'arm64_v8a'), + # ('x86', 'android_debug_x86', 'android-x86', 'x86'), NOT SUPPORTED + ('x64', 'android_debug_x64', 'android-x64', 'x86_64'), + ] + for android_cpu, out_directory, artifacts_directory, abi in variants: + print(f"Generating {out_directory}") + gn(["--android", f"--android-cpu={android_cpu}", "--no-lto"]) + build(out_directory) + + os.mkdir(make_path(DEPLOY_PATH, artifacts_directory)) + + print(f"Creating {artifacts_directory} artifacts.zip") + zip( + make_path(DEPLOY_PATH, artifacts_directory, "artifacts.zip"), + files=[make_path("out", out_directory, "flutter.jar")] + ) + print(f"Done creating {artifacts_directory} artifacts.zip") + + print(f"Creating {artifacts_directory} symbols.zip") + zip( + make_path(DEPLOY_PATH, artifacts_directory, "symbols.zip"), + files=[make_path("out", out_directory, "libflutter.so")] + ) + print(f"Done creating {artifacts_directory} symbols.zip") + + print(f"Creating MAVEN artifacts for {out_directory}") + create_maven_artifacts([ + make_path(f"out", out_directory, f"{abi}_debug.jar"), + make_path(f"out", out_directory, f"{abi}_debug.pom") + ]) + print(f"Done creating MAVEN artifacts for {out_directory}") + + print(f"Creating MAVEN artifacts for embedding") + create_maven_artifacts([ + make_path(f"out", "android_debug", "flutter_embedding_debug.jar"), + make_path(f"out", "android_debug", "flutter_embedding_debug.pom"), + make_path(f"out", "android_debug", "flutter_embedding_debug-sources.jar"), + ]) + print(f"Done creating MAVEN artifacts for embedding") + + print("Creating sky_engine.zip") + execute_command(f"ninja -C out/android_debug :dist") + zip( + make_path(DEPLOY_PATH, "sky_engine.zip"), + directories=[ + make_path("out", "android_debug", "dist", "packages", "sky_engine") + ] + ) + print("Done creating sky_engine.zip") + + shutil.copy( + make_path("out", "android_debug", "zip_archives", "android-javadoc.zip"), + make_path(DEPLOY_PATH, "android-javadoc.zip") + ) + + +def build_android_aot(): + variants = [ + # android_cpu, out_directory, artifacts_directory, clang_directory, android_triple, abi + ( + "arm64", "android_profile_arm64", "android-arm64-profile", + "clang_x64", "aarch64-linux-android", "arm64_v8a", "profile" + ), + ( + "arm64", "android_release_arm64", "android-arm64-release", + "clang_x64", "aarch64-linux-android", "arm64_v8a", "release" + ), + ( + "arm", "android_profile", "android-arm-profile", "clang_x64", + "arm-linux-androidabi", "armeabi_v7a", "profile" + ), + ( + "arm", "android_release", "android-arm-release", "clang_x64", + "arm-linux-androidabi", "armeabi_v7a", "release" + ), + ( + "x64", "android_profile_x64", "android-x64-profile", "clang_x64", + "x86_64-linux-android", "x86_64", "profile" + ), + ( + "x64", "android_release_x64", "android-x64-release", "clang_x64", + "x86_64-linux-android", "x86_64", "release" + ), + ] + + for android_cpu, out_directory, artifacts_directory, clang_directory, android_triple, abi, runtime_mode in variants: + gn([ + "--android", "--runtime-mode", runtime_mode, "--android-cpu", + android_cpu + ]) + build(out_directory) + + os.makedirs(make_path(DEPLOY_PATH, artifacts_directory), exist_ok=True) + + print(f"Creating {artifacts_directory} artifacts.zip") + zip( + make_path(DEPLOY_PATH, artifacts_directory, "artifacts.zip"), + files=[make_path("out", out_directory, "flutter.jar")] + ) + print(f"Done creating {artifacts_directory} artifacts.zip") + + print(f"Creating {artifacts_directory} symbols.zip") + zip( + make_path(DEPLOY_PATH, artifacts_directory, "symbols.zip"), + files=[make_path("out", out_directory, "libflutter.so")] + ) + print(f"Done creating {artifacts_directory} symbols.zip") + + print(f"Creating MAVEN artifacts for {out_directory}") + create_maven_artifacts([ + make_path(f"out", out_directory, f"{abi}_{runtime_mode}.jar"), + make_path(f"out", out_directory, f"{abi}_{runtime_mode}.pom") + ]) + print(f"Done creating MAVEN artifacts for {out_directory}") + + zip( + make_path(DEPLOY_PATH, artifacts_directory, "linux-x64.zip"), + files=[ + make_path("out", out_directory, clang_directory, "gen_snapshot") + ] + ) + + if out_directory == "android_profile_arm64" or out_directory == "android_release_arm64": + print(f"Creating MAVEN artifacts for embedding") + create_maven_artifacts([ + make_path( + "out", out_directory, f"flutter_embedding_{runtime_mode}.jar" + ), + make_path( + "out", out_directory, f"flutter_embedding_{runtime_mode}.pom" + ), + make_path( + "out", out_directory, + f"flutter_embedding_{runtime_mode}-sources.jar" + ), + ]) + execute_command(f"ninja -C out/{out_directory} flutter/lib/snapshot") + zip( + make_path(DEPLOY_PATH, artifacts_directory, "darwin-x64.zip"), + files=[ + make_path("out", out_directory, clang_directory, "gen_snapshot") + ] + ) + + +def build_android(): + build_android_debug() + build_android_aot() + + +def build_objc_doc(): + objc_doc_directory = make_path("out", "objcdoc") + os.makedirs(objc_doc_directory, exist_ok=True) + os.chdir(FLUTTER_DIR) + command = f"./tools/gen_objcdoc.sh {objc_doc_directory}" + execute_command(command) + os.chdir(CWD) + zip_directory_content( + make_path(DEPLOY_PATH, 'ios-objcdoc.zip'), objc_doc_directory + ) + + +def package_ios_variant( + label, + arm64_out, + armv7_out, #Deprecated + sim_x64_out, + sim_arm64_out, + bucket_name, + strip_bitcode=False +): + out_directory = make_path("out") + label_directory = make_path("out", label) + create_ios_framework_command = " ".join([ + "./flutter/sky/tools/create_ios_framework.py", + "--dst", + label_directory, + "--arm64-out-dir", + path.join(out_directory, arm64_out), + #"--armv7-out-dir", + #path.join(out_directory, armv7_out), + "--simulator-x64-out-dir", + path.join(out_directory, sim_x64_out), + "--simulator-arm64-out-dir", + path.join(out_directory, sim_arm64_out), + ]) + + if strip_bitcode: + create_ios_framework_command += " --strip-bitcode" + + if label == 'release': + create_ios_framework_command += " --dsym --strip" + + execute_command(create_ios_framework_command) + + # Package the multi-arch gen_snapshot for macOS. + create_macos_gen_snapshot_command = " ".join([ + "./flutter/sky/tools/create_macos_gen_snapshots.py", + '--dst', + label_directory, + '--arm64-out-dir', + path.join(out_directory, arm64_out), + #'--armv7-out-dir', + #path.join(out_directory, armv7_out), + ]) + + execute_command(create_macos_gen_snapshot_command) + + os.makedirs(make_path(DEPLOY_PATH, bucket_name), exist_ok=True) + + zip( + make_path(DEPLOY_PATH, bucket_name, "artifacts.zip"), + files=[ + #make_path(label_directory, "gen_snapshot_armv7"), + make_path(label_directory, "gen_snapshot_arm64") + ], + directories=[make_path(label_directory, "Flutter.xcframework")] + ) + + if label == 'release': + zip( + make_path(DEPLOY_PATH, bucket_name, "Flutter.dSYM.zip"), + directories=[path.join(label_directory, 'Flutter.dSYM')] + ) + + +def build_ios(): + # ios_debug_sim + gn(["--ios", "--runtime-mode", "debug", "--simulator", "--no-lto"]) + # ios_debug_sim_arm64 + gn([ + "--ios", "--runtime-mode", "debug", "--simulator", + "--simulator-cpu=arm64", "--no-lto", "--no-goma" + ]) + # ios_debug + gn(["--ios", "--runtime-mode", "debug", "--bitcode"]) + # ios_debug_arm + #gn(["--ios", "--runtime-mode", "debug", "--bitcode", "--ios-cpu=arm"]) + build("ios_debug_sim") + build("ios_debug_sim_arm64") + build("ios_debug") + #build("ios_debug_arm") + #build_objc_doc() + package_ios_variant( + "debug", "ios_debug", "ios_debug_arm", "ios_debug_sim", + "ios_debug_sim_arm64", "ios" + ) + gn(['--ios', '--runtime-mode', 'profile', '--bitcode']) + #gn(['--ios', '--runtime-mode', 'profile', '--bitcode', '--ios-cpu=arm']) + build('ios_profile') + #build('ios_profile_arm') + package_ios_variant( + 'profile', 'ios_profile', 'ios_profile_arm', 'ios_debug_sim', + 'ios_debug_sim_arm64', 'ios-profile' + ) + gn(['--ios', '--runtime-mode', 'release', '--bitcode', '--no-goma']) + #gn([ + # '--ios', '--runtime-mode', 'release', '--bitcode', '--no-goma', + # '--ios-cpu=arm' + #]) + build('ios_release') + #build('ios_release_arm') + package_ios_variant( + 'release', 'ios_release', 'ios_release_arm', 'ios_debug_sim', + 'ios_debug_sim_arm64', 'ios-release' + ) + package_ios_variant( + 'release', 'ios_release', 'ios_release_arm', 'ios_debug_sim', + 'ios_debug_sim_arm64', 'ios-release-nobitcode', True + ) + + +def main(): + check_cwd() + clean_deploy_directory() + set_use_prebuild_dart_sdk(True) + build_host() + build_mac() + build_android() + build_ios() + set_use_prebuild_dart_sdk(False) + + +if __name__ == "__main__": + main() diff --git a/flow/BUILD.gn b/flow/BUILD.gn index c42fa8e7ab18c..61c3dbd51dd0d 100644 --- a/flow/BUILD.gn +++ b/flow/BUILD.gn @@ -22,6 +22,8 @@ source_set("flow") { "layer_snapshot_store.h", "layers/backdrop_filter_layer.cc", "layers/backdrop_filter_layer.h", + "layers/blend_layer.cc", + "layers/blend_layer.h", "layers/cacheable_layer.cc", "layers/cacheable_layer.h", "layers/clip_path_layer.cc", diff --git a/flow/layers/blend_layer.cc b/flow/layers/blend_layer.cc new file mode 100644 index 0000000000000..11688f240e39a --- /dev/null +++ b/flow/layers/blend_layer.cc @@ -0,0 +1,144 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/flow/layers/blend_layer.h" + +#include "flutter/flow/layers/cacheable_layer.h" +#include "flutter/flow/raster_cache_util.h" +#include "third_party/skia/include/core/SkPaint.h" + +namespace flutter { + +// the blend_layer couldn't cache itself, so the cache_threshold is the +// max_int + +BlendLayer::BlendLayer(SkAlpha alpha, const SkPoint& offset) + : BlendLayer(alpha, offset, SkBlendMode::kSrcOver) {} + +BlendLayer::BlendLayer(SkAlpha alpha, + const SkPoint& offset, + SkBlendMode blend_mode) + : CacheableContainerLayer(std::numeric_limits::max(), true), + alpha_(alpha), + offset_(offset), + blend_mode_(blend_mode), + children_can_accept_opacity_(false) {} + +void BlendLayer::Diff(DiffContext* context, const Layer* old_layer) { + DiffContext::AutoSubtreeRestore subtree(context); + auto* prev = static_cast(old_layer); + if (!context->IsSubtreeDirty()) { + FML_DCHECK(prev); + if (alpha_ != prev->alpha_ || offset_ != prev->offset_ || + blend_mode_ != prev->blend_mode_) { + context->MarkSubtreeDirty(context->GetOldLayerPaintRegion(old_layer)); + } + } + context->PushTransform(SkMatrix::Translate(offset_.fX, offset_.fY)); + if (context->has_raster_cache()) { + context->SetTransform( + RasterCacheUtil::GetIntegralTransCTM(context->GetTransform())); + } + DiffChildren(context, prev); + context->SetLayerPaintRegion(this, context->CurrentSubtreeRegion()); +} + +void BlendLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { + TRACE_EVENT0("flutter", "BlendLayer::Preroll"); + + SkMatrix child_matrix = matrix; + child_matrix.preTranslate(offset_.fX, offset_.fY); + + // Similar to what's done in TransformLayer::Preroll, we have to apply the + // reverse transformation to the cull rect to properly cull child layers. + context->cull_rect = context->cull_rect.makeOffset(-offset_.fX, -offset_.fY); + + context->mutators_stack.PushTransform( + SkMatrix::Translate(offset_.fX, offset_.fY)); + context->mutators_stack.PushOpacity(alpha_); + + AutoCache auto_cache = + AutoCache(layer_raster_cache_item_.get(), context, child_matrix); + Layer::AutoPrerollSaveLayerState save = + Layer::AutoPrerollSaveLayerState::Create(context); + + // Collect inheritance information on our children in Preroll so that + // we can decide whether or not to use a saveLayer in Paint. + context->subtree_can_inherit_opacity = true; + // ContainerLayer will turn the flag off if any children are + // incompatible or if they overlap + ContainerLayer::Preroll(context, child_matrix); + // We store the inheritance ability of our children for |Paint| + set_children_can_accept_opacity(context->subtree_can_inherit_opacity); + + // Now we let our parent layers know that we, too, can inherit opacity + // regardless of what our children are capable of + context->subtree_can_inherit_opacity = true; + context->mutators_stack.Pop(); + context->mutators_stack.Pop(); + + set_paint_bounds(paint_bounds().makeOffset(offset_.fX, offset_.fY)); + + if (children_can_accept_opacity()) { + // For opacity layer, we can use raster_cache children only when the + // children can't accept opacity so if the children_can_accept_opacity we + // should tell the AutoCache object don't do raster_cache. + auto_cache.ShouldNotBeCached(); + } + + // Restore cull_rect + context->cull_rect = context->cull_rect.makeOffset(offset_.fX, offset_.fY); +} + +void BlendLayer::Paint(PaintContext& context) const { + TRACE_EVENT0("flutter", "BlendLayer::Paint"); + FML_DCHECK(needs_painting(context)); + + SkAutoCanvasRestore save(context.internal_nodes_canvas, true); + context.internal_nodes_canvas->translate(offset_.fX, offset_.fY); + if (context.raster_cache) { + context.internal_nodes_canvas->setMatrix( + RasterCacheUtil::GetIntegralTransCTM( + context.leaf_nodes_canvas->getTotalMatrix())); + } + + SkScalar inherited_opacity = context.inherited_opacity; + SkScalar subtree_opacity = opacity() * inherited_opacity; + + if (children_can_accept_opacity()) { + context.inherited_opacity = subtree_opacity; + PaintChildren(context); + context.inherited_opacity = inherited_opacity; + return; + } + + SkPaint paint; + paint.setAlphaf(subtree_opacity); + paint.setBlendMode(blend_mode_); + + if (layer_raster_cache_item_->Draw(context, &paint)) { + return; + } + + // Skia may clip the content with saveLayerBounds (although it's not a + // guaranteed clip). So we have to provide a big enough saveLayerBounds. To do + // so, we first remove the offset from paint bounds since it's already in the + // matrix. Then we round out the bounds. + // + // Note that the following lines are only accessible when the raster cache is + // not available (e.g., when we're using the software backend in golden + // tests). + SkRect saveLayerBounds; + paint_bounds() + .makeOffset(-offset_.fX, -offset_.fY) + .roundOut(&saveLayerBounds); + + Layer::AutoSaveLayer save_layer = + Layer::AutoSaveLayer::Create(context, saveLayerBounds, &paint); + context.inherited_opacity = SK_Scalar1; + PaintChildren(context); + context.inherited_opacity = inherited_opacity; +} + +} // namespace flutter diff --git a/flow/layers/blend_layer.h b/flow/layers/blend_layer.h new file mode 100644 index 0000000000000..0af0f10938c75 --- /dev/null +++ b/flow/layers/blend_layer.h @@ -0,0 +1,63 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_FLOW_LAYERS_BLEND_LAYER_H_ +#define FLUTTER_FLOW_LAYERS_BLEND_LAYER_H_ + +#include "flutter/flow/layers/cacheable_layer.h" +#include "flutter/flow/layers/layer.h" +#include "include/core/SkMatrix.h" + +namespace flutter { + +// Don't add an BlendLayer with no children to the layer tree. Painting an +// BlendLayer is very costly due to the saveLayer call. If there's no child, +// having the BlendLayer or not has the same effect. In debug_unopt build, +// |Preroll| will assert if there are no children. +class BlendLayer : public CacheableContainerLayer { + public: + BlendLayer(SkAlpha alpha, const SkPoint& offset, SkBlendMode blend_mode); + // An offset is provided here because BlendLayer.addToScene method in the + // Flutter framework can take an optional offset argument. + // + // By default, that offset is always zero, and all the offsets are handled by + // some parent TransformLayers. But we allow the offset to be non-zero for + // backward compatibility. If it's non-zero, the old behavior is to propage + // that offset to all the leaf layers (e.g., DisplayListLayer). That will make + // the retained rendering inefficient as a small offset change could propagate + // to many leaf layers. Therefore we try to capture that offset here to stop + // the propagation as repainting the BlendLayer is expensive. + BlendLayer(SkAlpha alpha, const SkPoint& offset); + + void Diff(DiffContext* context, const Layer* old_layer) override; + + void Preroll(PrerollContext* context, const SkMatrix& matrix) override; + + void Paint(PaintContext& context) const override; + + // Returns whether the children are capable of inheriting an opacity value + // and modifying their rendering accordingly. This value is only guaranteed + // to be valid after the local |Preroll| method is called. + bool children_can_accept_opacity() const { + return children_can_accept_opacity_ && blend_mode_ == SkBlendMode::kSrcOver; + ; + } + void set_children_can_accept_opacity(bool value) { + children_can_accept_opacity_ = value; + } + + SkScalar opacity() const { return alpha_ * 1.0 / SK_AlphaOPAQUE; } + + private: + SkAlpha alpha_; + SkPoint offset_; + SkBlendMode blend_mode_; + bool children_can_accept_opacity_; + + FML_DISALLOW_COPY_AND_ASSIGN(BlendLayer); +}; + +} // namespace flutter + +#endif // FLUTTER_FLOW_LAYERS_BLEND_LAYER_H_ diff --git a/flow/layers/layer_tree.cc b/flow/layers/layer_tree.cc index 79cf6cc13ecd3..8663e47e3d4e5 100644 --- a/flow/layers/layer_tree.cc +++ b/flow/layers/layer_tree.cc @@ -222,5 +222,4 @@ sk_sp LayerTree::Flatten(const SkRect& bounds) { return builder.Build(); } - } // namespace flutter diff --git a/flow/layers/layer_tree.h b/flow/layers/layer_tree.h index c7daa5f809e9f..df22fbf30b8e1 100644 --- a/flow/layers/layer_tree.h +++ b/flow/layers/layer_tree.h @@ -18,6 +18,14 @@ namespace flutter { +class RenderSurfaceProvider { + public: + virtual SkCanvas* get_canvas() = 0; + virtual RasterCache* get_raster_cache() = 0; + virtual GrDirectContext* get_context() = 0; + virtual SkColorSpace* get_color_space() = 0; +}; + class LayerTree { public: LayerTree(const SkISize& frame_size, float device_pixel_ratio); diff --git a/flow/layers/offscreen_surface.cc b/flow/layers/offscreen_surface.cc index dc50bd8bc1cd9..7c8831aa3b17c 100644 --- a/flow/layers/offscreen_surface.cc +++ b/flow/layers/offscreen_surface.cc @@ -4,6 +4,7 @@ #include "flutter/flow/layers/offscreen_surface.h" +#include "flutter/fml/build_config.h" #include "third_party/skia/include/core/SkImageEncoder.h" #include "third_party/skia/include/core/SkPictureRecorder.h" #include "third_party/skia/include/core/SkSerialProcs.h" @@ -70,6 +71,37 @@ OffscreenSurface::OffscreenSurface(GrDirectContext* surface_context, offscreen_surface_ = CreateSnapshotSurface(surface_context, size); } +OffscreenSurface::OffscreenSurface(int64_t texture_id, + GrDirectContext* surface_context, + const SkISize& size) { +#if defined(FML_OS_ANDROID) + GrGLTextureInfo tInfo; + tInfo.fTarget = 0x0DE1; // GR_GL_TEXTURE2D_2D; + tInfo.fID = texture_id; + tInfo.fFormat = 0x8058; // GR_GL_RGBA8; + const GrBackendTexture tex(size.width(), size.height(), GrMipmapped::kNo, + tInfo); + const auto colorSpace(SkColorSpace::MakeSRGB()); + offscreen_surface_ = SkSurface::MakeFromBackendTexture( + reinterpret_cast(surface_context), tex, + kBottomLeft_GrSurfaceOrigin, 1, kRGBA_8888_SkColorType, colorSpace, + nullptr, nullptr, nullptr); +#elif defined(FML_OS_IOS) || defined(FML_OS_MACOSX) + GrMtlTextureInfo tInfo; + tInfo.fTexture = + sk_cfp(reinterpret_cast(texture_id)); + const GrBackendTexture tex(size.width(), size.height(), GrMipmapped::kNo, + tInfo); + const auto colorSpace(SkColorSpace::MakeSRGB()); + offscreen_surface_ = SkSurface::MakeFromBackendTexture( + reinterpret_cast(surface_context), tex, + kBottomLeft_GrSurfaceOrigin, 1, kBGRA_8888_SkColorType, colorSpace, + nullptr, nullptr, nullptr); +#else + offscreen_surface_ = nullptr; +#endif +} + sk_sp OffscreenSurface::GetRasterData(bool compressed) const { return flutter::GetRasterData(offscreen_surface_, compressed); } diff --git a/flow/layers/offscreen_surface.h b/flow/layers/offscreen_surface.h index bfa192fb85e93..ff4b22e2c9345 100644 --- a/flow/layers/offscreen_surface.h +++ b/flow/layers/offscreen_surface.h @@ -17,6 +17,10 @@ class OffscreenSurface { explicit OffscreenSurface(GrDirectContext* surface_context, const SkISize& size); + OffscreenSurface(int64_t texture_id, + GrDirectContext* surface_context, + const SkISize& size); + ~OffscreenSurface() = default; sk_sp GetRasterData(bool compressed) const; diff --git a/fml/memory/weak_ptr.h b/fml/memory/weak_ptr.h index a78d8cc039389..44038d9dbf0c6 100644 --- a/fml/memory/weak_ptr.h +++ b/fml/memory/weak_ptr.h @@ -103,6 +103,8 @@ class WeakPtr { return get(); } + T* unsafeGet() const { return (flag_ && flag_->is_valid()) ? ptr_ : nullptr; } + protected: explicit WeakPtr(T* ptr, fml::RefPtr&& flag) : ptr_(ptr), flag_(std::move(flag)) {} diff --git a/lib/snapshot/BUILD.gn b/lib/snapshot/BUILD.gn index f78ff9f98d0ba..003cfff5d72e1 100644 --- a/lib/snapshot/BUILD.gn +++ b/lib/snapshot/BUILD.gn @@ -205,13 +205,20 @@ bin_to_linkable("platform_strong_dill_linkable") { if (host_os == "mac" && (target_cpu == "arm" || target_cpu == "arm64")) { action("create_arm_gen_snapshot") { - output_dir = "$root_out_dir/clang_x64" + if (host_cpu == "arm64") { + clang_dir = "clang_arm64" + } else { + clang_dir = "clang_x64" + } + output_dir = "$root_out_dir/$clang_dir" script = "//flutter/sky/tools/create_macos_gen_snapshots.py" visibility = [ ":*" ] deps = [ "//third_party/dart/runtime/bin:gen_snapshot($host_toolchain)" ] args = [ "--dst", rebase_path(output_dir), + "--clang-dir", + clang_dir, ] if (target_cpu == "arm") { args += [ diff --git a/lib/ui/BUILD.gn b/lib/ui/BUILD.gn index 6d6fef7278cbd..ef975b75ff2d8 100644 --- a/lib/ui/BUILD.gn +++ b/lib/ui/BUILD.gn @@ -77,12 +77,16 @@ source_set("ui") { "painting/picture.h", "painting/picture_recorder.cc", "painting/picture_recorder.h", + "painting/render_surface.cc", + "painting/render_surface.h", "painting/rrect.cc", "painting/rrect.h", "painting/shader.cc", "painting/shader.h", "painting/single_frame_codec.cc", "painting/single_frame_codec.h", + "painting/texture_descriptor.cc", + "painting/texture_descriptor.h", "painting/vertices.cc", "painting/vertices.h", "plugins/callback_cache.cc", diff --git a/lib/ui/compositing.dart b/lib/ui/compositing.dart index b3b51035edcb8..1bc570949712d 100644 --- a/lib/ui/compositing.dart +++ b/lib/ui/compositing.dart @@ -6,6 +6,11 @@ // @dart = 2.12 part of dart.ui; +@pragma('vm:entry-point') +class RendererBackgroundException { + +} + /// An opaque object representing a composited scene. /// /// To create a Scene object, use a [SceneBuilder]. @@ -43,6 +48,21 @@ class Scene extends NativeFieldWrapperClass1 { String? _toImage(int width, int height, _Callback<_Image?> callback) native 'Scene_toImage'; + Future renderToSurface(int width, int height, RenderSurface renderSurface, {bool flipVertical = false}) { + final Completer completer = Completer.sync(); + _renderToSurface(width, height, renderSurface, flipVertical, (bool success) { + if (!success) { + completer.completeError(RendererBackgroundException()); + } else { + completer.complete(); + } + }); + return completer.future; + } + + + void _renderToSurface(int width, int height, RenderSurface renderSurface, bool flipVertical, void Function(bool success) callback) native 'Scene_renderToSurface'; + /// Releases the resources used by this scene. /// /// After calling this function, the scene is cannot be used further. @@ -153,6 +173,16 @@ class OpacityEngineLayer extends _EngineLayerWrapper { OpacityEngineLayer._(EngineLayer nativeLayer) : super._(nativeLayer); } + +/// An opaque handle to a blend engine layer. +/// +/// Instances of this class are created by [SceneBuilder.pushBlend]. +/// +/// {@macro dart.ui.sceneBuilder.oldLayerCompatibility} +class BlendEngineLayer extends _EngineLayerWrapper { + BlendEngineLayer._(EngineLayer nativeLayer) : super._(nativeLayer); +} + /// An opaque handle to a color filter engine layer. /// /// Instances of this class are created by [SceneBuilder.pushColorFilter]. @@ -448,6 +478,24 @@ class SceneBuilder extends NativeFieldWrapperClass1 { void _pushOpacity(EngineLayer layer, int alpha, double dx, double dy, EngineLayer? oldLayer) native 'SceneBuilder_pushOpacity'; + /// Pushes a color filter operation onto the operation stack. + BlendEngineLayer pushBlend( + int alpha, + BlendMode blendMode, { + Offset? offset = Offset.zero, + BlendEngineLayer? oldLayer, + }) { + assert(_debugCheckCanBeUsedAsOldLayer(oldLayer, 'pushBlend')); + final EngineLayer engineLayer = EngineLayer._(); + _pushBlend(engineLayer, alpha, offset!.dx, offset.dy, blendMode.index, oldLayer?._nativeLayer); + final BlendEngineLayer layer = BlendEngineLayer._(engineLayer); + assert(_debugPushLayer(layer)); + return layer; + } + + void _pushBlend(EngineLayer layer, int alpha, double dx, double dy, int blendMode, EngineLayer? oldLayer) + native 'SceneBuilder_pushBlend'; + /// Pushes a color filter operation onto the operation stack. /// /// The given color is applied to the objects' rasterization using the given diff --git a/lib/ui/compositing/scene.cc b/lib/ui/compositing/scene.cc index a6448970f662d..6ba39b7f00f76 100644 --- a/lib/ui/compositing/scene.cc +++ b/lib/ui/compositing/scene.cc @@ -4,9 +4,11 @@ #include "flutter/lib/ui/compositing/scene.h" +#include "flutter/fml/make_copyable.h" #include "flutter/fml/trace_event.h" #include "flutter/lib/ui/painting/image.h" #include "flutter/lib/ui/painting/picture.h" +#include "flutter/lib/ui/painting/render_surface.h" #include "flutter/lib/ui/ui_dart_state.h" #include "flutter/lib/ui/window/platform_configuration.h" #include "flutter/lib/ui/window/window.h" @@ -23,6 +25,7 @@ IMPLEMENT_WRAPPERTYPEINFO(ui, Scene); #define FOR_EACH_BINDING(V) \ V(Scene, toImage) \ + V(Scene, renderToSurface) \ V(Scene, dispose) DART_BIND_ALL(Scene, FOR_EACH_BINDING) @@ -83,6 +86,46 @@ Dart_Handle Scene::toImage(uint32_t width, return Picture::RasterizeToImage(picture, width, height, raw_image_callback); } +void Scene::renderToSurface(int32_t width, + int32_t height, + fml::RefPtr render_surface, + bool flipVertical, + Dart_Handle callback) { + auto* dart_state = UIDartState::Current(); + const auto ui_task_runner = dart_state->GetTaskRunners().GetUITaskRunner(); + const auto raster_task_runner = + dart_state->GetTaskRunners().GetRasterTaskRunner(); + auto persistent_callback = + std::make_unique(dart_state, callback); + const auto snapshot_delegate = dart_state->GetSnapshotDelegate(); + + const auto ui_task = + fml::MakeCopyable([persistent_callback = std::move(persistent_callback)]( + bool success) mutable { + auto dart_state = persistent_callback->dart_state().lock(); + if (!dart_state) { + return; + } + tonic::DartState::Scope scope(dart_state); + tonic::DartInvoke(persistent_callback->Get(), {tonic::ToDart(success)}); + persistent_callback.reset(); + }); + + const auto raster_task = fml::MakeCopyable( + [ui_task = std::move(ui_task), ui_task_runner = std::move(ui_task_runner), + this, render_surface, snapshot_delegate, flipVertical]() { + const auto success = snapshot_delegate->DrawLayerToSurface( + layer_tree_.get(), render_surface->get_offscreen_surface(), + flipVertical); + fml::TaskRunner::RunNowOrPostTask( + std::move(ui_task_runner), + [ui_task = std::move(ui_task), success] { ui_task(success); }); + }); + + fml::TaskRunner::RunNowOrPostTask(std::move(raster_task_runner), + std::move(raster_task)); +} + std::unique_ptr Scene::takeLayerTree() { return std::move(layer_tree_); } diff --git a/lib/ui/compositing/scene.h b/lib/ui/compositing/scene.h index f7a8d93711814..2215eaca2c9b1 100644 --- a/lib/ui/compositing/scene.h +++ b/lib/ui/compositing/scene.h @@ -10,6 +10,7 @@ #include "flutter/flow/layers/layer_tree.h" #include "flutter/lib/ui/dart_wrapper.h" +#include "flutter/lib/ui/painting/render_surface.h" #include "third_party/skia/include/core/SkPicture.h" namespace tonic { @@ -36,6 +37,12 @@ class Scene : public RefCountedDartWrappable { uint32_t height, Dart_Handle image_callback); + void renderToSurface(int32_t width, + int32_t height, + fml::RefPtr render_surface, + bool flipVertical, + Dart_Handle callback); + void dispose(); static void RegisterNatives(tonic::DartLibraryNatives* natives); diff --git a/lib/ui/compositing/scene_builder.cc b/lib/ui/compositing/scene_builder.cc index 297e3b7c1092a..734d92c520fe3 100644 --- a/lib/ui/compositing/scene_builder.cc +++ b/lib/ui/compositing/scene_builder.cc @@ -5,6 +5,7 @@ #include "flutter/lib/ui/compositing/scene_builder.h" #include "flutter/flow/layers/backdrop_filter_layer.h" +#include "flutter/flow/layers/blend_layer.h" #include "flutter/flow/layers/clip_path_layer.h" #include "flutter/flow/layers/clip_rect_layer.h" #include "flutter/flow/layers/clip_rrect_layer.h" @@ -46,6 +47,7 @@ IMPLEMENT_WRAPPERTYPEINFO(ui, SceneBuilder); V(SceneBuilder, pushClipRRect) \ V(SceneBuilder, pushClipPath) \ V(SceneBuilder, pushOpacity) \ + V(SceneBuilder, pushBlend) \ V(SceneBuilder, pushColorFilter) \ V(SceneBuilder, pushImageFilter) \ V(SceneBuilder, pushBackdropFilter) \ @@ -172,6 +174,21 @@ void SceneBuilder::pushOpacity(Dart_Handle layer_handle, } } +void SceneBuilder::pushBlend(Dart_Handle layer_handle, + int alpha, + double dx, + double dy, + int blendMode, + fml::RefPtr oldLayer) { + auto layer = std::make_shared( + alpha, SkPoint::Make(dx, dy), static_cast(blendMode)); + PushLayer(layer); + EngineLayer::MakeRetained(layer_handle, layer); + + if (oldLayer && oldLayer->Layer()) { + layer->AssignOldLayer(oldLayer->Layer().get()); + } +} void SceneBuilder::pushColorFilter(Dart_Handle layer_handle, const ColorFilter* color_filter, fml::RefPtr oldLayer) { diff --git a/lib/ui/compositing/scene_builder.h b/lib/ui/compositing/scene_builder.h index fe9d3c76cbe05..f470f90109b38 100644 --- a/lib/ui/compositing/scene_builder.h +++ b/lib/ui/compositing/scene_builder.h @@ -60,6 +60,12 @@ class SceneBuilder : public RefCountedDartWrappable { double dx, double dy, fml::RefPtr oldLayer); + void pushBlend(Dart_Handle layer_handle, + int alpha, + double dx, + double dy, + int blendMode, + fml::RefPtr oldLayer); void pushColorFilter(Dart_Handle layer_handle, const ColorFilter* color_filter, fml::RefPtr oldLayer); diff --git a/lib/ui/dart_ui.cc b/lib/ui/dart_ui.cc index bf3e190deabc9..5a52bd8898e35 100644 --- a/lib/ui/dart_ui.cc +++ b/lib/ui/dart_ui.cc @@ -25,6 +25,7 @@ #include "flutter/lib/ui/painting/path_measure.h" #include "flutter/lib/ui/painting/picture.h" #include "flutter/lib/ui/painting/picture_recorder.h" +#include "flutter/lib/ui/painting/render_surface.h" #include "flutter/lib/ui/painting/vertices.h" #include "flutter/lib/ui/semantics/semantics_update.h" #include "flutter/lib/ui/semantics/semantics_update_builder.h" @@ -79,6 +80,7 @@ void DartUI::InitForGlobal() { ParagraphBuilder::RegisterNatives(g_natives); Picture::RegisterNatives(g_natives); PictureRecorder::RegisterNatives(g_natives); + RenderSurface::RegisterNatives(g_natives); Scene::RegisterNatives(g_natives); SceneBuilder::RegisterNatives(g_natives); SemanticsUpdate::RegisterNatives(g_natives); diff --git a/lib/ui/painting.dart b/lib/ui/painting.dart index 79b027ac996c2..277dba2563599 100644 --- a/lib/ui/painting.dart +++ b/lib/ui/painting.dart @@ -1660,12 +1660,28 @@ class Image { StackTrace? _debugStack; + void Function()? disposeCallback; + /// The number of image pixels along the image's horizontal axis. final int width; /// The number of image pixels along the image's vertical axis. final int height; + static List fromTextures(List textureDescriptors) { + final List images = _Image.fromTextures(textureDescriptors.map((TextureDescriptor e) => e._rawValues).toList(growable: false)).map((_Image?_image) => _image != null ? Image._(_image, _image.width, _image.height) : null).toList(growable: false); + final int inLength = textureDescriptors.length; + final int outLength = images.length; + if (inLength != outLength) { + for (final Image? image in images) { + image?.dispose(); + } + throw RendererBackgroundException(); + } + + return images; + } + bool _disposed = false; /// Release this handle's claim on the underlying Image. This handle is no /// longer usable after this method is called. @@ -1684,6 +1700,10 @@ class Image { final bool removed = _image._handles.remove(this); assert(removed); if (_image._handles.isEmpty) { + final _disposeCallback = disposeCallback; + if (_disposeCallback != null) { + _disposeCallback(); + } _image.dispose(); } } @@ -1814,7 +1834,9 @@ class Image { ); } assert(!_image._disposed); - return Image._(_image, width, height); + final Image image = Image._(_image, width, height); + image.disposeCallback = disposeCallback; + return image; } /// Returns true if `other` is a [clone] of this and thus shares the same @@ -1846,6 +1868,8 @@ class _Image extends NativeFieldWrapperClass1 { int get height native 'Image_height'; + static List<_Image?> fromTextures(List textureDescriptors) native 'Image_FromTextures'; + Future toByteData({ImageByteFormat format = ImageByteFormat.rawRgba}) { return _futurize((_Callback callback) { return _toByteData(format.index, (Uint8List? encoded) { @@ -1882,6 +1906,23 @@ class _Image extends NativeFieldWrapperClass1 { /// Callback signature for [decodeImageFromList]. typedef ImageDecoderCallback = void Function(Image result); +@pragma('vm:entry-point') +class TextureDescriptor { + + @pragma('vm:entry-point') + TextureDescriptor._(this._rawValues); + + static TextureDescriptor fromTextureId(int textureId, int width, int height, { PixelFormat pixelFormat = PixelFormat.rgba8888 }) { + return TextureDescriptor._(Int64List.fromList([0, textureId, width, height, pixelFormat.index])); + } + + static TextureDescriptor fromTexturePointer(int texturePointer, int width, int height, { PixelFormat pixelFormat = PixelFormat.bgra8888 }) { + return TextureDescriptor._(Int64List.fromList([1, texturePointer, width, height, pixelFormat.index])); + } + + final Int64List _rawValues; +} + /// Information for a single frame of an animation. /// /// To obtain an instance of the [FrameInfo] interface, see @@ -3914,6 +3955,10 @@ class FragmentProgram extends NativeFieldWrapperClass1 { return Future.microtask(() => FragmentProgram._(spirv: spirv, debugPrint: debugPrint)); } + static FragmentProgram setShader({required String sksl}) { + throw UnimplementedError('setShader is not supported on native, use compile instead.'); + } + @pragma('vm:entry-point') FragmentProgram._({ required ByteBuffer spirv, @@ -5831,6 +5876,44 @@ class ImageDescriptor extends NativeFieldWrapperClass1 { void _instantiateCodec(Codec outCodec, int targetWidth, int targetHeight) native 'ImageDescriptor_instantiateCodec'; } + +class RenderSurface extends NativeFieldWrapperClass1 { + RenderSurface._(int texture) { + constructor(texture); + } + + static Future fromTextureId(int textureId, int width, int height) { + final RenderSurface renderSurface = RenderSurface._(textureId); + final Completer completer = Completer.sync(); + renderSurface.setup(width, height, () { + completer.complete(renderSurface); + }); + return completer.future; + } + + void constructor(int texture) native 'RenderSurface_constructor'; + void setup(int width, int height, void Function() callback) native 'RenderSurface_setup'; + + void toBytes(ByteBuffer buffer) { + throw UnimplementedError('toBytes is not implemented for native'); + } + + Image? makeImageSnapshotFromSource(Object src) { + throw UnimplementedError('makeImageSnapshotFromSource is not implemented for native'); + } + + Future dispose() async { + final Completer completer = Completer.sync(); + _dispose(() { + completer.complete(); + }); + return completer.future; + } + void _dispose(void Function() callback) native 'RenderSurface_dispose'; + + int rawTexture() native 'RenderSurface_raw_texture'; +} + /// Generic callback signature, used by [_futurize]. typedef _Callback = void Function(T result); diff --git a/lib/ui/painting/image.cc b/lib/ui/painting/image.cc index 0ef33bf6f1f30..38ade63657f2c 100644 --- a/lib/ui/painting/image.cc +++ b/lib/ui/painting/image.cc @@ -8,11 +8,54 @@ #include #include "flutter/lib/ui/painting/image_encoding.h" +#include "third_party/dart/runtime/include/dart_api.h" #include "third_party/tonic/converter/dart_converter.h" #include "third_party/tonic/dart_args.h" #include "third_party/tonic/dart_binding_macros.h" #include "third_party/tonic/dart_library_natives.h" +namespace tonic { + +inline Dart_Handle LookupNullableType(const std::string& library_name, + const std::string& type_name) { + auto library = + Dart_LookupLibrary(DartConverter::ToDart(library_name)); + if (CheckAndHandleError(library)) { + return library; + } + auto type_string = DartConverter::ToDart(type_name); + if (CheckAndHandleError(type_string)) { + return type_string; + } + auto type = Dart_GetNullableType(library, type_string, 0, nullptr); + if (CheckAndHandleError(type)) { + return type; + } + return type; +} + +template <> +struct DartConverter>> { + using ValueType = fml::RefPtr; + static Dart_Handle ToDart(const std::vector& images) { + Dart_Handle nullable_type = LookupNullableType("dart:ui", "_Image"); + if (Dart_IsError(nullable_type)) + return nullable_type; + Dart_Handle list = Dart_NewListOfType(nullable_type, images.size()); + if (Dart_IsError(list)) + return list; + for (size_t i = 0; i < images.size(); i++) { + Dart_Handle dart_image = DartConverter::ToDart(images[i]); + if (Dart_IsError(dart_image)) + return dart_image; + Dart_Handle result = Dart_ListSetAt(list, i, dart_image); + if (Dart_IsError(result)) + return result; + } + return list; + } +}; +} // namespace tonic namespace flutter { typedef CanvasImage Image; @@ -35,7 +78,8 @@ const tonic::DartWrapperInfo& Image::dart_wrapper_info_ = FOR_EACH_BINDING(DART_NATIVE_CALLBACK) void CanvasImage::RegisterNatives(tonic::DartLibraryNatives* natives) { - natives->Register({FOR_EACH_BINDING(DART_REGISTER_NATIVE)}); + natives->Register({{"Image_FromTextures", Image::FromTextures, 1, true}, + FOR_EACH_BINDING(DART_REGISTER_NATIVE)}); } CanvasImage::CanvasImage() = default; @@ -63,4 +107,34 @@ size_t CanvasImage::GetAllocationSize() const { size, static_cast(0), static_cast(std::numeric_limits::max() / 10)); } + +void CanvasImage::FromTextures(Dart_NativeArguments args) { + Dart_Handle texture_descriptor_list = Dart_GetNativeArgument(args, 0); + + auto* dart_state = UIDartState::Current(); + auto unref_queue = dart_state->GetSkiaUnrefQueue(); + auto snapshot_delegate = dart_state->GetSnapshotDelegate().unsafeGet(); + + intptr_t count; + Dart_ListLength(texture_descriptor_list, &count); + + std::vector> images; + + for (intptr_t i = 0; i < count; i++) { + auto dart_texture_descriptor = Dart_ListGetAt(texture_descriptor_list, i); + auto raw_texture_descriptor = tonic::Int64List(dart_texture_descriptor); + auto texture_descriptor = TextureDescriptor::Init(raw_texture_descriptor); + auto _image = snapshot_delegate->UploadTexture(texture_descriptor); + + if (_image != nullptr) { + auto dart_image = CanvasImage::Create(); + auto gpu_image = SkiaGPUObject{std::move(_image), unref_queue}; + dart_image->set_image(DlImageGPU::Make(std::move(gpu_image))); + images.push_back(dart_image); + } else { + break; + } + } + Dart_SetReturnValue(args, tonic::ToDart(images)); +} } // namespace flutter diff --git a/lib/ui/painting/image.h b/lib/ui/painting/image.h index d9466720768ab..54b34ad27701d 100644 --- a/lib/ui/painting/image.h +++ b/lib/ui/painting/image.h @@ -11,6 +11,7 @@ #include "flutter/lib/ui/painting/display_list_image_gpu.h" #include "flutter/lib/ui/ui_dart_state.h" #include "third_party/skia/include/core/SkImage.h" +#include "third_party/tonic/typed_data/typed_list.h" namespace tonic { class DartLibraryNatives; @@ -45,6 +46,8 @@ class CanvasImage final : public RefCountedDartWrappable { static void RegisterNatives(tonic::DartLibraryNatives* natives); + static void FromTextures(Dart_NativeArguments args); + private: CanvasImage(); diff --git a/lib/ui/painting/render_surface.cc b/lib/ui/painting/render_surface.cc new file mode 100644 index 0000000000000..5a04235a3688a --- /dev/null +++ b/lib/ui/painting/render_surface.cc @@ -0,0 +1,119 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/lib/ui/painting/render_surface.h" +#include "flutter/flow/layers/layer_tree.h" +#include "flutter/fml/make_copyable.h" +#include "flutter/lib/ui/ui_dart_state.h" +#include "third_party/tonic/dart_args.h" +#include "third_party/tonic/dart_binding_macros.h" +#include "third_party/tonic/dart_library_natives.h" +#include "third_party/tonic/dart_persistent_value.h" + +namespace flutter { + +static void RenderSurface_constructor(Dart_NativeArguments args) { + UIDartState::ThrowIfUIOperationsProhibited(); + DartCallConstructor(&RenderSurface::Create, args); +} + +IMPLEMENT_WRAPPERTYPEINFO(ui, RenderSurface); + +#define FOR_EACH_BINDING(V) \ + V(RenderSurface, setup) \ + V(RenderSurface, raw_texture) \ + V(RenderSurface, dispose) + +FOR_EACH_BINDING(DART_NATIVE_CALLBACK) + +void RenderSurface::RegisterNatives(tonic::DartLibraryNatives* natives) { + natives->Register( + {{"RenderSurface_constructor", RenderSurface_constructor, 2, true}, + FOR_EACH_BINDING(DART_REGISTER_NATIVE)}); +} + +fml::RefPtr RenderSurface::Create(int64_t raw_texture) { + return fml::MakeRefCounted(raw_texture); +} + +void RenderSurface::setup(int32_t width, int32_t height, Dart_Handle callback) { + auto* dart_state = UIDartState::Current(); + const auto ui_task_runner = dart_state->GetTaskRunners().GetUITaskRunner(); + const auto raster_task_runner = + dart_state->GetTaskRunners().GetRasterTaskRunner(); + auto persistent_callback = + std::make_unique(dart_state, callback); + const auto snapshot_delegate = dart_state->GetSnapshotDelegate(); + + const auto ui_task = fml::MakeCopyable( + [persistent_callback = std::move(persistent_callback)]() mutable { + auto dart_state = persistent_callback->dart_state().lock(); + if (!dart_state) { + return; + } + tonic::DartState::Scope scope(dart_state); + tonic::DartInvoke(persistent_callback->Get(), {}); + persistent_callback.reset(); + }); + + const auto raster_task = fml::MakeCopyable( + [ui_task = std::move(ui_task), ui_task_runner = std::move(ui_task_runner), + snapshot_delegate = std::move(snapshot_delegate), width, height, + this]() { + _surface = std::make_unique( + _raw_texture, snapshot_delegate->GetContext(), + SkISize::Make(width, height)); + fml::TaskRunner::RunNowOrPostTask(std::move(ui_task_runner), + std::move(ui_task)); + }); + + fml::TaskRunner::RunNowOrPostTask(std::move(raster_task_runner), + std::move(raster_task)); +} + +int64_t RenderSurface::raw_texture() { + return _raw_texture; +} + +OffscreenSurface* RenderSurface::get_offscreen_surface() { + return _surface.get(); +} + +void RenderSurface::dispose(Dart_Handle callback) { + auto* dart_state = UIDartState::Current(); + const auto ui_task_runner = dart_state->GetTaskRunners().GetUITaskRunner(); + const auto raster_task_runner = + dart_state->GetTaskRunners().GetRasterTaskRunner(); + auto persistent_callback = + std::make_unique(dart_state, callback); + + const auto ui_task = fml::MakeCopyable( + [persistent_callback = std::move(persistent_callback)]() mutable { + auto dart_state = persistent_callback->dart_state().lock(); + if (!dart_state) { + return; + } + tonic::DartState::Scope scope(dart_state); + tonic::DartInvoke(persistent_callback->Get(), {}); + persistent_callback.reset(); + }); + + const auto raster_task = + fml::MakeCopyable([ui_task = std::move(ui_task), + ui_task_runner = std::move(ui_task_runner), this]() { + _surface.reset(); + fml::TaskRunner::RunNowOrPostTask(std::move(ui_task_runner), + std::move(ui_task)); + }); + + fml::TaskRunner::RunNowOrPostTask(std::move(raster_task_runner), + std::move(raster_task)); +} + +RenderSurface::RenderSurface(int64_t raw_texture) + : _surface{nullptr}, _raw_texture{raw_texture} {} + +RenderSurface::~RenderSurface() {} + +} // namespace flutter diff --git a/lib/ui/painting/render_surface.h b/lib/ui/painting/render_surface.h new file mode 100644 index 0000000000000..716a40fbf864a --- /dev/null +++ b/lib/ui/painting/render_surface.h @@ -0,0 +1,41 @@ +#ifndef FLUTTER_LIB_UI_PAINTING_SURFACE_H_ +#define FLUTTER_LIB_UI_PAINTING_SURFACE_H_ + +#include "flutter/flow/layers/offscreen_surface.h" +#include "flutter/lib/ui/dart_wrapper.h" +#include "third_party/skia/include/core/SkSurface.h" + +namespace tonic { +class DartLibraryNatives; +} + +namespace flutter { + +class RenderSurface : public RefCountedDartWrappable { + DEFINE_WRAPPERTYPEINFO(); + FML_FRIEND_MAKE_REF_COUNTED(RenderSurface); + + public: + static fml::RefPtr Create(int64_t raw_texture); + static void RegisterNatives(tonic::DartLibraryNatives* natives); + + ~RenderSurface() override; + + void setup(int32_t width, int32_t height, Dart_Handle callback); + + int64_t raw_texture(); + + void dispose(Dart_Handle callback); + + OffscreenSurface* get_offscreen_surface(); + + private: + RenderSurface(int64_t raw_texture); + + std::unique_ptr _surface; + int64_t _raw_texture; +}; + +} // namespace flutter + +#endif // FLUTTER_LIB_UI_PAINTING_SURFACE_H_ diff --git a/lib/ui/painting/texture_descriptor.cc b/lib/ui/painting/texture_descriptor.cc new file mode 100644 index 0000000000000..1def1045761fb --- /dev/null +++ b/lib/ui/painting/texture_descriptor.cc @@ -0,0 +1,80 @@ +#include "flutter/lib/ui/painting/texture_descriptor.h" +#include "third_party/skia/include/gpu/GrBackendSurface.h" + +#define TEXTURE_MODE_ID 0 +#define TEXTURE_MODE_POINTER 1 + +namespace flutter { +std::shared_ptr TextureDescriptor::Init( + const tonic::Int64List& raw_values) { + const auto texture_mode = raw_values[0]; + switch (texture_mode) { + case TEXTURE_MODE_ID: + return std::shared_ptr( + new AndroidTextureDescriptor(raw_values)); + break; + + case TEXTURE_MODE_POINTER: + return std::shared_ptr( + new IOSTextureDescriptor(raw_values)); + break; + + default: + return std::shared_ptr(nullptr); + } +} + +TextureDescriptor::TextureDescriptor(const tonic::Int64List& raw_values) { + raw_texture_ = raw_values[1]; + width_ = static_cast(raw_values[2]); + height_ = static_cast(raw_values[3]); + switch (raw_values[4]) { + case ImageDescriptor::PixelFormat::kRGBA8888: + color_type_ = SkColorType::kRGBA_8888_SkColorType; + break; + case ImageDescriptor::PixelFormat::kBGRA8888: + color_type_ = SkColorType::kBGRA_8888_SkColorType; + break; + default: + color_type_ = SkColorType::kUnknown_SkColorType; + break; + } +} + +AndroidTextureDescriptor::AndroidTextureDescriptor( + const tonic::Int64List& raw_values) + : TextureDescriptor(raw_values) {} + +GrBackendTexture AndroidTextureDescriptor::backendTexure() const { +#ifdef SK_GL + uint32_t format = 0x8058; // GL_RGBA8 0x8058_(OES) +#if defined(FML_OS_ANDROID) + uint32_t target = 0x8D65; // GL_TEXTURE_EXTERNAL_OES +#else + uint32_t target = 0x0DE1; // GL_TEXTURE_2D +#endif + const GrGLTextureInfo texture_info{ + target, static_cast(raw_texture_), format}; +#else + const GrMockTextureInfo texture_info; +#endif + return GrBackendTexture{static_cast(width_), static_cast(height_), + GrMipMapped::kNo, texture_info}; +} + +IOSTextureDescriptor::IOSTextureDescriptor(const tonic::Int64List& raw_values) + : TextureDescriptor(raw_values) {} + +GrBackendTexture IOSTextureDescriptor::backendTexure() const { +#ifdef SK_METAL + GrMtlTextureInfo texture_info; + texture_info.fTexture = + sk_cfp(reinterpret_cast(raw_texture_)); +#else + const GrMockTextureInfo texture_info; +#endif + return GrBackendTexture{static_cast(width_), static_cast(height_), + GrMipMapped::kNo, texture_info}; +} + +} // namespace flutter diff --git a/lib/ui/painting/texture_descriptor.h b/lib/ui/painting/texture_descriptor.h new file mode 100644 index 0000000000000..9cf2b05066bd6 --- /dev/null +++ b/lib/ui/painting/texture_descriptor.h @@ -0,0 +1,52 @@ +#ifndef FLUTTER_LIB_UI_PAINTING_TEXTURE_DESCRIPTOR_H_ +#define FLUTTER_LIB_UI_PAINTING_TEXTURE_DESCRIPTOR_H_ + +#include + +#include "flutter/lib/ui/painting/image_descriptor.h" +#include "third_party/tonic/typed_data/typed_list.h" + +namespace flutter { +class TextureDescriptor { + public: + virtual ~TextureDescriptor() = default; + + virtual GrBackendTexture backendTexure() const = 0; + + SkColorType colorType() const { return color_type_; } + + static std::shared_ptr Init( + const tonic::Int64List& raw_values); + + protected: + int64_t raw_texture_; + int32_t width_; + int32_t height_; + SkColorType color_type_; + + TextureDescriptor(const tonic::Int64List& raw_values); + + private: + TextureDescriptor() = delete; +}; + +class AndroidTextureDescriptor final : public TextureDescriptor { + public: + ~AndroidTextureDescriptor() override = default; + + GrBackendTexture backendTexure() const override; + + AndroidTextureDescriptor(const tonic::Int64List& raw_values); +}; + +class IOSTextureDescriptor final : public TextureDescriptor { + public: + ~IOSTextureDescriptor() override = default; + + GrBackendTexture backendTexure() const override; + + IOSTextureDescriptor(const tonic::Int64List& raw_values); +}; +} // namespace flutter + +#endif diff --git a/lib/ui/snapshot_delegate.h b/lib/ui/snapshot_delegate.h index 7c87a01c865d9..65ef27cbe23b7 100644 --- a/lib/ui/snapshot_delegate.h +++ b/lib/ui/snapshot_delegate.h @@ -6,6 +6,9 @@ #define FLUTTER_LIB_UI_SNAPSHOT_DELEGATE_H_ #include +#include "flutter/flow/layers/layer_tree.h" +#include "flutter/flow/layers/offscreen_surface.h" +#include "flutter/lib/ui/painting/texture_descriptor.h" #include "flutter/display_list/display_list.h" #include "third_party/skia/include/core/SkImage.h" @@ -28,6 +31,21 @@ class SnapshotDelegate { SkISize picture_size) = 0; virtual sk_sp ConvertToRasterImage(sk_sp image) = 0; + + virtual sk_sp UploadTexture( + std::shared_ptr& descriptor) = 0; + + virtual sk_sp MakeSurface(int32_t width, + int32_t height, + int64_t raw_texture) = 0; + + virtual bool DrawLayerToSurface(LayerTree* tree, + OffscreenSurface* snapshot_surface, + bool flip_vertical) = 0; + + virtual RasterCache* GetRasterCache() = 0; + + virtual GrDirectContext* GetContext() = 0; }; } // namespace flutter diff --git a/lib/ui/text.dart b/lib/ui/text.dart index 85311f4f772d4..3777838a73c4f 100644 --- a/lib/ui/text.dart +++ b/lib/ui/text.dart @@ -3143,4 +3143,7 @@ FutureOr _sendFontChangeMessage() async { } } +Future unloadFontFamily(String fontFamily) { + throw UnimplementedError('Unloading font is only supported on CanvasKit rendered web.'); +} void _loadFontFromList(Uint8List list, _Callback callback, String fontFamily) native 'loadFontFromList'; diff --git a/lib/web_ui/lib/canvas.dart b/lib/web_ui/lib/canvas.dart index 995bc66299617..a29435ba3c103 100644 --- a/lib/web_ui/lib/canvas.dart +++ b/lib/web_ui/lib/canvas.dart @@ -154,6 +154,7 @@ abstract class Canvas { } abstract class Picture { + Future renderToSurface(int width, int height, RenderSurface renderSurface, {bool flipVertical = false}); Future toImage(int width, int height); void dispose(); bool get debugDisposed; diff --git a/lib/web_ui/lib/compositing.dart b/lib/web_ui/lib/compositing.dart index de1e6cdbee255..178704c25d9cd 100644 --- a/lib/web_ui/lib/compositing.dart +++ b/lib/web_ui/lib/compositing.dart @@ -5,6 +5,7 @@ part of ui; abstract class Scene { + Future renderToSurface(int width, int height, RenderSurface renderSurface, {bool flipVertical = false}); Future toImage(int width, int height); void dispose(); } @@ -21,6 +22,8 @@ abstract class ClipPathEngineLayer implements EngineLayer {} abstract class OpacityEngineLayer implements EngineLayer {} +abstract class BlendEngineLayer implements EngineLayer {} + abstract class ColorFilterEngineLayer implements EngineLayer {} abstract class ImageFilterEngineLayer implements EngineLayer {} @@ -68,6 +71,12 @@ abstract class SceneBuilder { Offset offset = Offset.zero, OpacityEngineLayer? oldLayer, }); + BlendEngineLayer pushBlend( + int alpha, + BlendMode blendMode, { + Offset offset = Offset.zero, + BlendEngineLayer? oldLayer, + }); ColorFilterEngineLayer pushColorFilter( ColorFilter filter, { ColorFilterEngineLayer? oldLayer, diff --git a/lib/web_ui/lib/painting.dart b/lib/web_ui/lib/painting.dart index 7883b77b7a95d..4368f435f1997 100644 --- a/lib/web_ui/lib/painting.dart +++ b/lib/web_ui/lib/painting.dart @@ -347,6 +347,103 @@ abstract class Image { String toString() => '[$width\u00D7$height]'; } +abstract class TextureDescriptor { + TextureDescriptor._(); + static TextureDescriptor fromTextureId(int textureId, int width, int height, { PixelFormat pixelFormat = PixelFormat.rgba8888 }) { + throw UnimplementedError(); + } + + static TextureDescriptor fromTexturePointer(int textureId, int width, int height, { PixelFormat pixelFormat = PixelFormat.bgra8888 }) { + throw UnimplementedError(); + } +} + +class RenderSurface extends engine.ManagedSkiaObject { + final Object texture; + final int width; + final int height; + engine.SkGrContext? _grContext; + + RenderSurface._(this.texture, this.width, this.height); + + static Future fromTextureId(Object textureId, int width, int height) async { + // Setup is run via createDefault in the parent constructor + return RenderSurface._(textureId, width, height); + } + + @override + bool get isResurrectionExpensive => true; + + void toBytes(ByteBuffer buffer) { + if (rawSkiaObject == null) { + throw Exception('Failed to create GPU-backed SkSurface for RenderSurface'); + } + final engine.SkGrContext? grContext = engine.SurfaceFactory.instance.baseSurface.grContext; + if (grContext == null) { + throw Exception('No grContext from baseSurface when setting up RenderSurface.'); + } + rawSkiaObject!.readPixelsGL(buffer.asUint8List(), grContext); + } + + engine.SkSurface setup(int width, int height) { + final engine.SkGrContext? grContext = engine.SurfaceFactory.instance.baseSurface.grContext; + if (grContext == null) { + throw Exception('No grContext from baseSurface when setting up RenderSurface.'); + } + + _grContext = grContext; + final engine.SkSurface? surface = engine.canvasKit.MakeRenderTarget(grContext, width, height); + + if (surface == null) { + throw Exception('Failed to create GPU-backed SkSurface for RenderSurface'); + } + + return surface; + } + + Image? makeImageSnapshotFromSource(Object src) { + if (rawSkiaObject == null) { + print("RenderSurface's SkiaSurface is not ready when making image from source."); + return null; + } + + // TODO: patchy workaround, fix firing onchange from surface update if possible + final engine.SkGrContext? currContext = engine.SurfaceFactory.instance.baseSurface.grContext; + if (_grContext != currContext) { + delete(); + rawSkiaObject = setup(width, height); + _grContext = currContext; + } + + rawSkiaObject!.updateFromSource(src, width, height, false); + rawSkiaObject!.flush(); + return engine.CkImage(rawSkiaObject!.makeImageSnapshot()); + } + + Future dispose() async { + // SkSurface.dispose doesn't do anything for GPU-backed surfaces + } + + int rawTexture() { + throw UnimplementedError(); + } + + @override + engine.SkSurface createDefault() { + return setup(width, height); + } + + @override + void delete() { + rawSkiaObject?.delete(); + } + + @override + engine.SkSurface resurrect() { + return createDefault(); + } +} + abstract class ColorFilter { const factory ColorFilter.mode(Color color, BlendMode blendMode) = engine.EngineColorFilter.mode; const factory ColorFilter.matrix(List matrix) = engine.EngineColorFilter.matrix; @@ -835,6 +932,7 @@ class ImageDescriptor { } class FragmentProgram { + final engine.CkRuntimeEffect runtimeEffect; static Future compile({ required ByteBuffer spirv, bool debugPrint = false, @@ -842,10 +940,30 @@ class FragmentProgram { throw UnsupportedError('FragmentProgram is not supported for the CanvasKit or HTML renderers.'); } - FragmentProgram._(); + FragmentProgram._(String sksl): runtimeEffect = engine.CkRuntimeEffect(sksl); + + static FragmentProgram setShader({ + required String sksl + }) { + if (engine.useCanvasKit) { + return FragmentProgram._(sksl); + } + + throw UnimplementedError('Fragment program is only supported for CanvasKit renderer'); + } Shader shader({ Float32List? floatUniforms, List? samplerUniforms, - }) => throw UnsupportedError('FragmentProgram is not supported for the CanvasKit or HTML renderers.'); + }) { + if (floatUniforms == null || samplerUniforms == null) { + throw Exception('Passing null float uniforms or samplerUniforms is not supported on web.'); + } + + if (!engine.useCanvasKit) { + throw UnimplementedError('Fragment program is only supported for CanvasKit renderer'); + } + + return runtimeEffect.makeShader(floatUniforms, samplerUniforms); + } } diff --git a/lib/web_ui/lib/src/engine.dart b/lib/web_ui/lib/src/engine.dart index 166f78ffac659..893510d6eeba6 100644 --- a/lib/web_ui/lib/src/engine.dart +++ b/lib/web_ui/lib/src/engine.dart @@ -32,6 +32,7 @@ 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/image_webhtml_codecs.dart'; export 'engine/canvaskit/initialization.dart'; export 'engine/canvaskit/interval_tree.dart'; export 'engine/canvaskit/layer.dart'; 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 9ee1a22bc6465..3d2eb6df0744d 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart @@ -68,6 +68,7 @@ extension H5vccExtension on H5vcc { class CanvasKit {} extension CanvasKitExtension on CanvasKit { + external SkRuntimeEffect get RuntimeEffect; external SkBlendModeEnum get BlendMode; external SkPaintStyleEnum get PaintStyle; external SkStrokeCapEnum get StrokeCap; @@ -117,6 +118,8 @@ extension CanvasKitExtension on CanvasKit { SkData skData, ); + external SkSurface? MakeRenderTarget(SkGrContext grContext, int width, int height); + // Text decoration enum is embedded in the CanvasKit object itself. external int get NoDecoration; external int get UnderlineDecoration; @@ -222,6 +225,21 @@ extension SkSurfaceExtension on SkSurface { external int height(); external void dispose(); external SkImage makeImageSnapshot(); + external void updateFromSource(Object src, int width, int height, bool srcIsPremul); + external SkImage? makeImageFromTextureSource(Object src, SkImageInfo? info, bool srcIsPremul); + external void readPixelsGL(Uint8List buffer, SkGrContext grContext); + external void delete(); +} + +@JS('window.flutterCanvasKit.RuntimeEffect') +@staticInterop +class SkRuntimeEffect {} + +extension SkRuntimeEffectExtension on SkRuntimeEffect { + external SkShader makeShader(Float32List floats, Float32List? matrix); + external SkShader makeShaderWithChildren(Float32List floats, List children, Float32List? matrix); + external SkRuntimeEffect Make(String sksl); + external void delete(); } @JS() @@ -2081,6 +2099,10 @@ extension SkFontFeatureExtension on SkFontFeature { @staticInterop class SkTypeface {} +extension SkTypefaceExtension on SkTypeface { + external void delete(); +} + @JS('window.flutterCanvasKit.Font') @staticInterop class SkFont { @@ -2112,6 +2134,7 @@ class TypefaceFontProvider extends SkFontMgr { extension TypefaceFontProviderExtension on SkFontMgr { external void registerFont(Uint8List font, String family); + external void registerFontFromTypeface(SkTypeface typeface, String family); } @JS() @@ -2231,6 +2254,7 @@ class SkTypefaceFactory {} extension SkTypefaceFactoryExtension on SkTypefaceFactory { external SkTypeface? MakeFreeTypeFaceFromData(ByteBuffer fontData); + external void unloadTypeFace(SkTypeface? typeface); } /// Collects Skia objects that are no longer necessary. 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 428096a548fa3..ff455250ed06f 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/font_fallbacks.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/font_fallbacks.dart @@ -231,7 +231,7 @@ class FontFallbackData { 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(countedFamily, typeface)); // Insert emoji font before all other fallback fonts so we use the emoji // whenever it's available. if (family == 'Noto Color Emoji Compat') { diff --git a/lib/web_ui/lib/src/engine/canvaskit/fonts.dart b/lib/web_ui/lib/src/engine/canvaskit/fonts.dart index 48f3b3348c040..508b1d8f4f34b 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/fonts.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/fonts.dart @@ -61,7 +61,7 @@ class SkiaFontCollection { familyToFontMap.clear(); for (final RegisteredFont font in _downloadedFonts) { - fontProvider!.registerFont(font.bytes, font.family); + fontProvider!.registerFontFromTypeface(font.typeface, font.family); familyToFontMap .putIfAbsent(font.family, () => []) .add(SkFont(font.typeface)); @@ -69,7 +69,7 @@ class SkiaFontCollection { for (final RegisteredFont font in FontFallbackData.instance.registeredFallbackFonts) { - fontProvider!.registerFont(font.bytes, font.family); + fontProvider!.registerFontFromTypeface(font.typeface, font.family); familyToFontMap .putIfAbsent(font.family, () => []) .add(SkFont(font.typeface)); @@ -103,7 +103,7 @@ class SkiaFontCollection { final SkTypeface? typeface = canvasKit.Typeface.MakeFreeTypeFaceFromData(list.buffer); if (typeface != null) { - _downloadedFonts.add(RegisteredFont(list, fontFamily, typeface)); + _downloadedFonts.add(RegisteredFont(fontFamily, typeface)); await ensureFontsLoaded(); } else { printWarning('Failed to parse font family "$fontFamily"'); @@ -111,6 +111,14 @@ class SkiaFontCollection { } } + Future unloadFontFamily(String fontFamily) async { + _downloadedFonts.where((RegisteredFont font) => font.family == fontFamily).forEach((RegisteredFont font) { + font.typeface.delete(); + }); + _downloadedFonts.removeWhere((RegisteredFont font) => font.family == fontFamily); + await ensureFontsLoaded(); + } + /// Loads fonts from `FontManifest.json`. Future registerFonts(AssetManager assetManager) async { ByteData byteData; @@ -189,7 +197,7 @@ class SkiaFontCollection { final SkTypeface? typeface = canvasKit.Typeface.MakeFreeTypeFaceFromData(bytes.buffer); if (typeface != null) { - return RegisteredFont(bytes, family, typeface); + return RegisteredFont(family, typeface); } else { printWarning('Failed to load font $family at $url'); printWarning('Verify that $url contains a valid font.'); @@ -225,15 +233,12 @@ class RegisteredFont { /// The font family name for this font. final String family; - /// The byte data for this font. - final Uint8List bytes; - /// The [SkTypeface] created from this font's [bytes]. /// /// This is used to determine which code points are supported by this font. final SkTypeface typeface; - RegisteredFont(this.bytes, this.family, this.typeface) { + RegisteredFont(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 9a51bb74fcaea..1b4378001b7ed 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/image.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/image.dart @@ -14,6 +14,7 @@ import '../util.dart'; import 'canvaskit_api.dart'; import 'image_wasm_codecs.dart'; import 'image_web_codecs.dart'; +import 'image_webhtml_codecs.dart'; import 'skia_object_cache.dart'; /// Instantiates a [ui.Codec] backed by an `SkAnimatedImage` from Skia. @@ -29,7 +30,7 @@ FutureOr skiaInstantiateImageCodec(Uint8List list, targetHeight: targetHeight, ); } else { - return CkAnimatedImage.decodeFromBytes(list, 'encoded image bytes'); + return CkHtmlImage.decodeFromBytes(list, 'encoded image bytes'); } } @@ -93,11 +94,11 @@ void debugRestoreHttpRequestFactory() { /// requesting from URI. Future skiaInstantiateWebImageCodec( String url, WebOnlyImageCodecChunkCallback? chunkCallback) async { - final Uint8List list = await fetchImage(url, chunkCallback); if (browserSupportsImageDecoder) { + final Uint8List list = await fetchImage(url, chunkCallback); return CkBrowserImageDecoder.create(data: list, debugSource: url.toString()); } else { - return CkAnimatedImage.decodeFromBytes(list, url); + return CkHtmlImage.decodeFromUrl(url); } } diff --git a/lib/web_ui/lib/src/engine/canvaskit/image_webhtml_codecs.dart b/lib/web_ui/lib/src/engine/canvaskit/image_webhtml_codecs.dart new file mode 100644 index 0000000000000..a510481ed1267 --- /dev/null +++ b/lib/web_ui/lib/src/engine/canvaskit/image_webhtml_codecs.dart @@ -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. + +library image_webhtml_codecs; + +import 'dart:async'; +import 'dart:typed_data'; + +import 'package:ui/ui.dart' as ui; +import '../dom.dart'; + +import 'canvaskit_api.dart'; +import 'image.dart'; + +class CkHtmlImage implements ui.Codec { + /// Decodes an image from a list of encoded bytes. + CkHtmlImage.decodeFromBytes(this._bytes, this.src) { + _imageElement = createDomHTMLImageElement(); + final DomBlob blob = createDomBlob([this._bytes]); + final String url = domWindow.URL.createObjectURL(blob); + _imageElement!.src = url; + } + + CkHtmlImage.decodeFromUrl(this.src) { + _imageElement = createDomHTMLImageElement(); + _imageElement!.src = src; + _imageElement!.crossOrigin = 'anonymous'; + } + + DomHTMLImageElement? _imageElement; + + final String src; + Uint8List? _bytes; + + bool _disposed = false; + bool get debugDisposed => _disposed; + + bool _debugCheckIsNotDisposed() { + assert(!_disposed, 'This image has been disposed.'); + return true; + } + + @override + void dispose() { + assert( + !_disposed, + 'Cannot dispose a codec that has already been disposed.', + ); + _disposed = true; + } + + @override + int get frameCount { + assert(_debugCheckIsNotDisposed()); + return 1; + } + + @override + int get repetitionCount { + assert(_debugCheckIsNotDisposed()); + return 1; + } + + @override + Future getNextFrame() async { + assert(_debugCheckIsNotDisposed()); + + final DomHTMLImageElement img = _imageElement!; + + await img.decode(); + + final SkImage? skImage = canvasKit.MakeLazyImageFromTextureSource( + _imageElement!, + SkPartialImageInfo( + alphaType: canvasKit.AlphaType.Premul, + colorType: canvasKit.ColorType.RGBA_8888, + colorSpace: SkColorSpaceSRGB, + width: img.naturalWidth, + height: img.naturalHeight, + ), + ); + + final ui.FrameInfo currentFrame = AnimatedImageFrameInfo( + const Duration(microseconds: 0), + CkImage(skImage!), + ); + + return Future.value(currentFrame); + } +} diff --git a/lib/web_ui/lib/src/engine/canvaskit/layer.dart b/lib/web_ui/lib/src/engine/canvaskit/layer.dart index 74b1b18a00216..9a62c23e8655a 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/layer.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/layer.dart @@ -351,6 +351,49 @@ class OpacityEngineLayer extends ContainerLayer } } +/// A layer that paints its children with the given opacity and blend. +class BlendEngineLayer extends ContainerLayer + implements ui.BlendEngineLayer { + final int _alpha; + final ui.BlendMode _blendMode; + final ui.Offset _offset; + + BlendEngineLayer(this._alpha, this._blendMode, this._offset); + + @override + void preroll(PrerollContext prerollContext, Matrix4 matrix) { + final Matrix4 childMatrix = Matrix4.copy(matrix); + childMatrix.translate(_offset.dx, _offset.dy); + prerollContext.mutatorsStack + .pushTransform(Matrix4.translationValues(_offset.dx, _offset.dy, 0.0)); + prerollContext.mutatorsStack.pushOpacity(_alpha); + super.preroll(prerollContext, childMatrix); + prerollContext.mutatorsStack.pop(); + prerollContext.mutatorsStack.pop(); + paintBounds = paintBounds.translate(_offset.dx, _offset.dy); + } + + @override + void paint(PaintContext paintContext) { + assert(needsPainting); + + final CkPaint paint = CkPaint(); + paint.color = ui.Color.fromARGB(_alpha, 0, 0, 0); + paint.blendMode = _blendMode; + + paintContext.internalNodesCanvas.save(); + paintContext.internalNodesCanvas.translate(_offset.dx, _offset.dy); + + final ui.Rect saveLayerBounds = paintBounds.shift(-_offset); + + paintContext.internalNodesCanvas.saveLayer(saveLayerBounds, paint); + paintChildren(paintContext); + // Restore twice: once for the translate and once for the saveLayer. + paintContext.internalNodesCanvas.restore(); + paintContext.internalNodesCanvas.restore(); + } +} + /// A layer that transforms its child layers by the given transform matrix. class TransformEngineLayer extends ContainerLayer implements ui.TransformEngineLayer { 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 f0cbd4a12a545..a61644a9e4da1 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 @@ -25,6 +25,12 @@ class LayerScene implements ui.Scene { final ui.Picture picture = layerTree.flatten(); return picture.toImage(width, height); } + + @override + Future renderToSurface(int width, int height, ui.RenderSurface renderSurface, {bool flipVertical = false}) async { + final ui.Picture picture = layerTree.flatten(); + await picture.renderToSurface(width, height, renderSurface, flipVertical: flipVertical); + } } class LayerSceneBuilder implements ui.SceneBuilder { @@ -172,6 +178,17 @@ class LayerSceneBuilder implements ui.SceneBuilder { return pushLayer(OpacityEngineLayer(alpha, offset)); } + @override + BlendEngineLayer pushBlend( + int alpha, + ui.BlendMode blendMode, { + ui.Offset offset = ui.Offset.zero, + ui.EngineLayer? oldLayer, + }) { + // throw UnimplementedError('not implemented'); + return pushLayer(BlendEngineLayer(alpha, blendMode, offset)); + } + @override PhysicalShapeEngineLayer pushPhysicalShape({ required ui.Path path, diff --git a/lib/web_ui/lib/src/engine/canvaskit/picture.dart b/lib/web_ui/lib/src/engine/canvaskit/picture.dart index 0227246be5157..6e030314feb2f 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/picture.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/picture.dart @@ -10,6 +10,7 @@ import 'canvas.dart'; import 'canvaskit_api.dart'; import 'image.dart'; import 'skia_object_cache.dart'; +import 'surface_factory.dart'; /// Implements [ui.Picture] on top of [SkPicture]. /// @@ -90,14 +91,31 @@ class CkPicture extends ManagedSkiaObject implements ui.Picture { rawSkiaObject = null; } - @override + @override Future toImage(int width, int height) async { assert(debugCheckNotDisposed('Cannot convert picture to image.')); - final SkSurface skSurface = canvasKit.MakeSurface(width, height); + + final SkGrContext? grContext = SurfaceFactory.instance.baseSurface.grContext; + SkSurface? skSurface; + bool isGpuBacked = false; + if (grContext != null) { + skSurface = canvasKit.MakeRenderTarget(grContext, width, height); + isGpuBacked = skSurface != null; + } + + if (skSurface == null) { + skSurface = canvasKit.MakeSurface(width, height); + print('Failed to create GPU backed surface, using software surface as fallback'); + } + final SkCanvas skCanvas = skSurface.getCanvas(); skCanvas.drawPicture(skiaObject); final SkImage skImage = skSurface.makeImageSnapshot(); - skSurface.dispose(); + if (isGpuBacked) { + skSurface.delete(); + } else { + skSurface.dispose(); + } return CkImage(skImage); } @@ -127,4 +145,23 @@ class CkPicture extends ManagedSkiaObject implements ui.Picture { rawSkiaObject?.delete(); } } + + @override + Future renderToSurface(int width, int height, ui.RenderSurface renderSurface, {bool flipVertical = false}) async { + if (renderSurface.rawSkiaObject== null) { + throw Exception('Render surface not initialized'); + } + + final SkCanvas canvas = renderSurface.rawSkiaObject!.getCanvas(); + canvas.save(); + + if (flipVertical) { + canvas.translate(0, height.toDouble()); + canvas.scale(1, -1); + } + + canvas.drawPicture(rawSkiaObject!); + canvas.restore(); + renderSurface.rawSkiaObject!.flush(); + } } diff --git a/lib/web_ui/lib/src/engine/canvaskit/shader.dart b/lib/web_ui/lib/src/engine/canvaskit/shader.dart index f8c25d4827cca..93ab788ae9c6d 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/shader.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/shader.dart @@ -24,6 +24,61 @@ abstract class CkShader extends ManagedSkiaObject } } +class CkRuntimeShader extends CkShader implements ui.Shader { + final SkShader Function() createShader; + CkRuntimeShader(this.createShader); + + @override + SkShader createDefault() { + return createShader(); + } + + @override + SkShader resurrect() { + return createDefault(); + } +} + +class CkRuntimeEffect extends ManagedSkiaObject { + final String sksl; + CkRuntimeEffect(this.sksl): assert(useCanvasKit); + + CkShader makeShader(Float32List floatUniforms, List samplerUniforms) { + final List samplerObjects = samplerUniforms.map( + (ui.ImageShader e) => (e as CkImageShader).withQuality(ui.FilterQuality.none) + ).toList(growable: false); + + final Float32List floatUniformsWithSamplerSizes = Float32List(floatUniforms.length + samplerUniforms.length * 2); + floatUniforms.asMap().forEach((int index, double value) { + floatUniformsWithSamplerSizes[index] = value; + }); + + // Append the sizes of each sampler to the uniforms + samplerUniforms.asMap().forEach((int index, ui.ImageShader sampler) { + final CkImageShader ckShader = sampler as CkImageShader; + floatUniformsWithSamplerSizes[floatUniforms.length + index * 2] = ckShader._image.width.toDouble(); + floatUniformsWithSamplerSizes[floatUniforms.length + index * 2 + 1] = ckShader._image.height.toDouble(); + }); + + return CkRuntimeShader(() => rawSkiaObject!.makeShaderWithChildren(floatUniformsWithSamplerSizes, samplerObjects, null)); + } + + @override + SkRuntimeEffect createDefault() { + return canvasKit.RuntimeEffect.Make(sksl); + } + + @override + void delete() { + rawSkiaObject?.delete(); + } + + @override + SkRuntimeEffect resurrect() { + return createDefault(); + } +} + class CkGradientSweep extends CkShader implements ui.Gradient { CkGradientSweep(this.center, this.colors, this.colorStops, this.tileMode, this.startAngle, this.endAngle, this.matrix4) diff --git a/lib/web_ui/lib/src/engine/canvaskit/surface.dart b/lib/web_ui/lib/src/engine/canvaskit/surface.dart index 81df0d4cac742..37922f2b55774 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/surface.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/surface.dart @@ -80,6 +80,7 @@ class Surface { /// later. void Function(DomEvent)? _cachedContextRestoredListener; + SkGrContext? get grContext => _grContext; SkGrContext? _grContext; int? _glContext; int? _skiaCacheBytes; diff --git a/lib/web_ui/lib/src/engine/dom.dart b/lib/web_ui/lib/src/engine/dom.dart index 78dffb92c520d..b44defc807582 100644 --- a/lib/web_ui/lib/src/engine/dom.dart +++ b/lib/web_ui/lib/src/engine/dom.dart @@ -493,6 +493,7 @@ extension DomHTMLImageElementExtension on DomHTMLImageElement { external int get naturalHeight; external set width(int? value); external set height(int? value); + external set crossOrigin(String? value); Future decode() => js_util.promiseToFuture(js_util.callMethod(this, 'decode', [])); } diff --git a/lib/web_ui/lib/src/engine/html/scene.dart b/lib/web_ui/lib/src/engine/html/scene.dart index 7ae9a36d69096..5985eaeb1fcaf 100644 --- a/lib/web_ui/lib/src/engine/html/scene.dart +++ b/lib/web_ui/lib/src/engine/html/scene.dart @@ -29,6 +29,11 @@ class SurfaceScene implements ui.Scene { /// After calling this function, the scene is cannot be used further. @override void dispose() {} + + @override + Future renderToSurface(int width, int height, ui.RenderSurface renderSurface, {bool flipVertical = false}) { + throw UnimplementedError('renderToSurface is only implemented for CanvasKit backend'); + } } /// A surface that creates a DOM element for whole app. 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 c36b7ebdff258..5edcc73f63291 100644 --- a/lib/web_ui/lib/src/engine/html/scene_builder.dart +++ b/lib/web_ui/lib/src/engine/html/scene_builder.dart @@ -582,4 +582,9 @@ class SurfaceSceneBuilder implements ui.SceneBuilder { ) { throw UnimplementedError(); } + + @override + ui.BlendEngineLayer pushBlend(int alpha, ui.BlendMode blendMode, {ui.Offset offset = ui.Offset.zero, ui.EngineLayer? oldLayer,}) { + throw UnimplementedError('Blend is not implemented for the html renderer'); + } } diff --git a/lib/web_ui/lib/src/engine/picture.dart b/lib/web_ui/lib/src/engine/picture.dart index 6cc68063c91bf..6c888af5e0731 100644 --- a/lib/web_ui/lib/src/engine/picture.dart +++ b/lib/web_ui/lib/src/engine/picture.dart @@ -109,4 +109,9 @@ class EnginePicture implements ui.Picture { final RecordingCanvas? recordingCanvas; final ui.Rect? cullRect; + + @override + Future renderToSurface(int width, int height, ui.RenderSurface renderSurface, {bool flipVertical = false}) { + throw UnimplementedError('Render to surface is only supported by the CanvasKit renderer.'); + } } diff --git a/lib/web_ui/lib/text.dart b/lib/web_ui/lib/text.dart index ae1cffa923695..3bdd5475a61a4 100644 --- a/lib/web_ui/lib/text.dart +++ b/lib/web_ui/lib/text.dart @@ -754,6 +754,15 @@ abstract class ParagraphBuilder { }); } + +Future unloadFontFamily(String fontFamily) { + if (!engine.useCanvasKit) { + throw Exception('Font unloading is only supported for canvaskit renderer'); + } + + return engine.skiaFontCollection.unloadFontFamily(fontFamily).then((_) => engine.sendFontChangeMessage()); +} + Future loadFontFromList(Uint8List list, {String? fontFamily}) { if (engine.useCanvasKit) { return engine.skiaFontCollection diff --git a/shell/common/rasterizer.cc b/shell/common/rasterizer.cc index 8587d253d1f4d..d8d48227af588 100644 --- a/shell/common/rasterizer.cc +++ b/shell/common/rasterizer.cc @@ -17,11 +17,27 @@ #include "fml/make_copyable.h" #include "third_party/skia/include/core/SkImageEncoder.h" #include "third_party/skia/include/core/SkPictureRecorder.h" +#include "third_party/skia/include/core/SkPromiseImageTexture.h" #include "third_party/skia/include/core/SkSerialProcs.h" #include "third_party/skia/include/core/SkSurface.h" #include "third_party/skia/include/core/SkSurfaceCharacterization.h" #include "third_party/skia/include/utils/SkBase64.h" +#if SK_GL +#include "third_party/skia/include/gpu/gl/GrGLTypes.h" +#endif + +sk_sp get_promise_texture( + SkImage::PromiseImageTextureContext textureContext) { + auto backendTexture = reinterpret_cast(textureContext); + return SkPromiseImageTexture::Make(*backendTexture); +} + +void free_promise_texture(SkImage::PromiseImageTextureContext textureContext) { + auto backendTexture = reinterpret_cast(textureContext); + delete backendTexture; +} + namespace flutter { // The rasterizer will tell Skia to purge cached resources that have not been @@ -460,6 +476,49 @@ sk_sp Rasterizer::ConvertToRasterImage(sk_sp image) { }); } +sk_sp Rasterizer::UploadTexture( + std::shared_ptr& descriptor) { + if (!surface_ || !surface_->GetContext()) { + return nullptr; + } + auto _backendTexture = new GrBackendTexture(descriptor->backendTexure()); + return SkImage::MakePromiseTexture( + surface_->GetContext()->threadSafeProxy(), + _backendTexture->getBackendFormat(), _backendTexture->dimensions(), + GrMipMapped::kNo, kTopLeft_GrSurfaceOrigin, descriptor->colorType(), + kPremul_SkAlphaType, nullptr, get_promise_texture, free_promise_texture, + reinterpret_cast(_backendTexture)); +} + +sk_sp Rasterizer::MakeSurface(int32_t width, + int32_t height, + int64_t raw_texture) { +#if SK_GL + GrGLTextureInfo tInfo; + tInfo.fTarget = 0x0DE1; // GR_GL_TEXTURE2D_2D; + tInfo.fID = raw_texture; + tInfo.fFormat = 0x8058; // GR_GL_RGBA8; + const GrBackendTexture tex(width, height, GrMipmapped::kNo, tInfo); + const auto colorSpace(SkColorSpace::MakeSRGB()); + + sk_sp surface(SkSurface::MakeFromBackendTexture( + surface_->GetContext(), tex, kBottomLeft_GrSurfaceOrigin, 1, + kRGBA_8888_SkColorType, colorSpace, nullptr, nullptr, nullptr)); + + return surface; +#else + return nullptr; +#endif +} + +RasterCache* Rasterizer::GetRasterCache() { + return &compositor_context_->raster_cache(); +} + +GrDirectContext* Rasterizer::GetContext() { + return surface_->GetContext(); +} + fml::Milliseconds Rasterizer::GetFrameBudget() const { return delegate_.GetFrameBudget(); }; @@ -719,6 +778,55 @@ RasterStatus Rasterizer::DrawToSurfaceUnsafe( return RasterStatus::kFailed; } +bool Rasterizer::DrawLayerToSurface(flutter::LayerTree* tree, + OffscreenSurface* snapshot_surface, + bool flip_vertical) { + if (snapshot_surface == nullptr || surface_ == nullptr || + surface_->GetContext() == nullptr) { + return false; + } + // Draw the current layer tree into the snapshot surface. + auto* canvas = snapshot_surface->GetCanvas(); + canvas->save(); + + if (flip_vertical) { + // Get backing surface, this should always return a surface here + SkSurface* surface = canvas->getSurface(); + if (surface == nullptr) { + return false; + } + canvas->translate(0, surface->height()); + canvas->scale(1, -1); + } + + // There is no root surface transformation for the screenshot layer. Reset the + // matrix to identity. + SkMatrix root_surface_transformation; + root_surface_transformation.reset(); + + // snapshot_surface->makeImageSnapshot needs the GL context to be set if the + // render context is GL. frame->Raster() pops the gl context in platforms that + // gl context switching are used. (For example, older iOS that uses GL) We + // reset the GL context using the context switch. + auto context_switch = surface_->MakeRenderContextCurrent(); + + auto frame = compositor_context_->AcquireFrame( + surface_->GetContext(), // skia context + canvas, // canvas + nullptr, // view embedder + root_surface_transformation, // root surface transformation + false, // instrumentation enabled + true, // render buffer readback supported + nullptr, // thread merger + nullptr // display list builder + ); + canvas->clear(SK_ColorTRANSPARENT); + frame->Raster(*tree, false, nullptr); + surface_->GetContext()->flushAndSubmit(true); + canvas->restore(); + return true; +} + static sk_sp ScreenshotLayerTreeAsPicture( flutter::LayerTree* tree, flutter::CompositorContext& compositor_context) { diff --git a/shell/common/rasterizer.h b/shell/common/rasterizer.h index 425b8214e5bd4..51cb995fcf6f3 100644 --- a/shell/common/rasterizer.h +++ b/shell/common/rasterizer.h @@ -486,6 +486,17 @@ class Rasterizer final : public SnapshotDelegate, // |SnapshotDelegate| sk_sp ConvertToRasterImage(sk_sp image) override; + sk_sp UploadTexture( + std::shared_ptr& descriptor) override; + + sk_sp MakeSurface(int32_t width, + int32_t height, + int64_t raw_texture) override; + + RasterCache* GetRasterCache() override; + + GrDirectContext* GetContext() override; + // |Stopwatch::Delegate| /// Time limit for a smooth frame. /// @@ -498,6 +509,10 @@ class Rasterizer final : public SnapshotDelegate, GrDirectContext* surface_context, bool compressed); + bool DrawLayerToSurface(LayerTree* tree, + OffscreenSurface* snapshot_surface, + bool flip_vertical) override; + sk_sp DoMakeRasterSnapshot( SkISize size, std::function draw_callback); diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index edb78d40445d9..67c4bab19cb4e 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -237,6 +237,7 @@ android_java_sources = [ "io/flutter/embedding/engine/renderer/FlutterUiDisplayListener.java", "io/flutter/embedding/engine/renderer/RenderSurface.java", "io/flutter/embedding/engine/renderer/SurfaceTextureWrapper.java", + "io/flutter/embedding/engine/renderer/Task.java", "io/flutter/embedding/engine/systemchannels/AccessibilityChannel.java", "io/flutter/embedding/engine/systemchannels/DeferredComponentChannel.java", "io/flutter/embedding/engine/systemchannels/KeyEventChannel.java", 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_shell_holder.cc b/shell/platform/android/android_shell_holder.cc index 3537de3ea20ee..30208190acf3c 100644 --- a/shell/platform/android/android_shell_holder.cc +++ b/shell/platform/android/android_shell_holder.cc @@ -347,4 +347,15 @@ std::optional AndroidShellHolder::BuildRunConfiguration( return config; } +void AndroidShellHolder::PostTaskOnIOThread(const std::function& task) { + fml::TaskRunner::RunNowOrPostTask(shell_->GetTaskRunners().GetIOTaskRunner(), + task); +} + +void AndroidShellHolder::PostTaskOnRasterThread( + const std::function& task) { + fml::TaskRunner::RunNowOrPostTask( + shell_->GetTaskRunners().GetRasterTaskRunner(), task); +} + } // namespace flutter diff --git a/shell/platform/android/android_shell_holder.h b/shell/platform/android/android_shell_holder.h index bf14db6a24237..7d839a702c244 100644 --- a/shell/platform/android/android_shell_holder.h +++ b/shell/platform/android/android_shell_holder.h @@ -103,6 +103,10 @@ class AndroidShellHolder { return shell_->GetPlatformMessageHandler(); } + void PostTaskOnIOThread(const std::function& task); + + void PostTaskOnRasterThread(const std::function& task); + private: const flutter::Settings settings_; const std::shared_ptr jni_facade_; diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java index 708e3f4a8c241..8d8d03a75a075 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -23,6 +23,7 @@ import io.flutter.embedding.engine.plugins.util.GeneratedPluginRegister; import io.flutter.embedding.engine.renderer.FlutterRenderer; import io.flutter.embedding.engine.renderer.RenderSurface; +import io.flutter.embedding.engine.renderer.Task; import io.flutter.embedding.engine.systemchannels.AccessibilityChannel; import io.flutter.embedding.engine.systemchannels.DeferredComponentChannel; import io.flutter.embedding.engine.systemchannels.LifecycleChannel; @@ -615,4 +616,12 @@ public interface EngineLifecycleListener { */ void onEngineWillDestroy(); } + + public void runOnIOThread(Task task) { + this.flutterJNI.runOnIOThread(task); + } + + public void runOnRasterThread(Task task) { + this.flutterJNI.runOnRasterThread(task); + } } diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java index cbe7a9ceb8ca3..0649f5622f52c 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java @@ -27,6 +27,7 @@ import io.flutter.embedding.engine.mutatorsstack.FlutterMutatorsStack; import io.flutter.embedding.engine.renderer.FlutterUiDisplayListener; import io.flutter.embedding.engine.renderer.SurfaceTextureWrapper; +import io.flutter.embedding.engine.renderer.Task; import io.flutter.plugin.common.StandardMessageCodec; import io.flutter.plugin.localization.LocalizationPlugin; import io.flutter.plugin.platform.PlatformViewsController; @@ -849,6 +850,24 @@ public void setAccessibilityFeatures(int flags) { private native void nativeSetAccessibilityFeatures(long nativeShellHolderId, int flags); // ------ End Accessibility Support ---- + // ------ Start Run on IO Thread Scheduling ----- + + public void runOnIOThread(@NonNull Task task) { + ensureAttachedToNative(); + nativeRunOnIOThread(nativeShellHolderId, task); + } + + private native void nativeRunOnIOThread(long nativeShellHolderId, @NonNull Task task); + + public void runOnRasterThread(@NonNull Task task) { + ensureAttachedToNative(); + nativeRunOnRasterThread(nativeShellHolderId, task); + } + + private native void nativeRunOnRasterThread(long nativeShellHolderId, @NonNull Task task); + + // ------ End Run on IO Thread Scheduling ----- + // ------ Start Texture Registration Support ----- /** * Gives control of a {@link SurfaceTexture} to Flutter so that Flutter can display that texture diff --git a/shell/platform/android/io/flutter/embedding/engine/plugins/FlutterPlugin.java b/shell/platform/android/io/flutter/embedding/engine/plugins/FlutterPlugin.java index c9ed009a44ef1..cbdc07959bee3 100644 --- a/shell/platform/android/io/flutter/embedding/engine/plugins/FlutterPlugin.java +++ b/shell/platform/android/io/flutter/embedding/engine/plugins/FlutterPlugin.java @@ -8,6 +8,7 @@ import androidx.annotation.NonNull; import androidx.lifecycle.Lifecycle; import io.flutter.embedding.engine.FlutterEngine; +import io.flutter.embedding.engine.renderer.Task; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.platform.PlatformViewRegistry; import io.flutter.view.TextureRegistry; @@ -157,6 +158,14 @@ public PlatformViewRegistry getPlatformViewRegistry() { public FlutterAssets getFlutterAssets() { return flutterAssets; } + + public void runOnIOThread(Task task) { + flutterEngine.runOnIOThread(task); + } + + public void runOnRasterThread(Task task) { + flutterEngine.runOnRasterThread(task); + } } /** Provides Flutter plugins with access to Flutter asset information. */ diff --git a/shell/platform/android/io/flutter/embedding/engine/renderer/Task.java b/shell/platform/android/io/flutter/embedding/engine/renderer/Task.java new file mode 100644 index 0000000000000..1eb2662a3cd7e --- /dev/null +++ b/shell/platform/android/io/flutter/embedding/engine/renderer/Task.java @@ -0,0 +1,9 @@ +package io.flutter.embedding.engine.renderer; + +import androidx.annotation.Keep; + +@Keep +public interface Task { + @SuppressWarnings("unused") + public void run(); +} diff --git a/shell/platform/android/platform_view_android_jni_impl.cc b/shell/platform/android/platform_view_android_jni_impl.cc index 6f79175c8f875..ca066d4033b89 100644 --- a/shell/platform/android/platform_view_android_jni_impl.cc +++ b/shell/platform/android/platform_view_android_jni_impl.cc @@ -49,6 +49,8 @@ static fml::jni::ScopedJavaGlobalRef* g_texture_wrapper_class = nullptr; static fml::jni::ScopedJavaGlobalRef* g_java_long_class = nullptr; +static fml::jni::ScopedJavaGlobalRef* g_flutter_task_class = nullptr; + // Called By Native static jmethodID g_flutter_callback_info_constructor = nullptr; @@ -115,6 +117,9 @@ static jmethodID g_overlay_surface_id_method = nullptr; static jmethodID g_overlay_surface_surface_method = nullptr; +// TaskRunner.runOnIOThread() +static jmethodID g_task_run_method = nullptr; + // Mutators static fml::jni::ScopedJavaGlobalRef* g_mutators_stack_class = nullptr; static jmethodID g_mutators_stack_init_method = nullptr; @@ -483,6 +488,28 @@ static jboolean GetIsSoftwareRendering(JNIEnv* env, jobject jcaller) { return FlutterMain::Get().GetSettings().enable_software_rendering; } +static void RunOnIOThread(JNIEnv* env, + jobject jcaller, + jlong shell_holder, + jobject task) { + ANDROID_SHELL_HOLDER->PostTaskOnIOThread( + [task = fml::jni::ScopedJavaGlobalRef(env, task)]() { + JNIEnv* _env = fml::jni::AttachCurrentThread(); + _env->CallVoidMethod(task.obj(), g_task_run_method); + }); +} + +static void RunOnRasterThread(JNIEnv* env, + jobject jcaller, + jlong shell_holder, + jobject task) { + ANDROID_SHELL_HOLDER->PostTaskOnRasterThread( + [task = fml::jni::ScopedJavaGlobalRef(env, task)]() { + JNIEnv* _env = fml::jni::AttachCurrentThread(); + _env->CallVoidMethod(task.obj(), g_task_run_method); + }); +} + static void RegisterTexture(JNIEnv* env, jobject jcaller, jlong shell_holder, @@ -755,6 +782,16 @@ bool RegisterApi(JNIEnv* env) { .signature = "()Z", .fnPtr = reinterpret_cast(&GetIsSoftwareRendering), }, + { + .name = "nativeRunOnIOThread", + .signature = "(JLio/flutter/embedding/engine/renderer/Task;)V", + .fnPtr = reinterpret_cast(&RunOnIOThread), + }, + { + .name = "nativeRunOnRasterThread", + .signature = "(JLio/flutter/embedding/engine/renderer/Task;)V", + .fnPtr = reinterpret_cast(&RunOnRasterThread), + }, { .name = "nativeRegisterTexture", .signature = "(JJLjava/lang/ref/" @@ -1072,6 +1109,20 @@ bool PlatformViewAndroid::Register(JNIEnv* env) { return false; } + g_flutter_task_class = new fml::jni::ScopedJavaGlobalRef( + env, env->FindClass("io/flutter/embedding/engine/renderer/Task")); + if (g_flutter_task_class->is_null()) { + FML_LOG(ERROR) << "Could not locate Task class"; + return false; + } + + g_task_run_method = + env->GetMethodID(g_flutter_task_class->obj(), "run", "()V"); + if (g_task_run_method == nullptr) { + FML_LOG(ERROR) << "Could not locate Task.run() method"; + return false; + } + g_attach_to_gl_context_method = env->GetMethodID( g_texture_wrapper_class->obj(), "attachToGLContext", "(I)V"); @@ -1319,18 +1370,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 +1394,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/linux/fl_view.cc b/shell/platform/linux/fl_view.cc index de676c90b0999..21d659802d2f1 100644 --- a/shell/platform/linux/fl_view.cc +++ b/shell/platform/linux/fl_view.cc @@ -526,6 +526,11 @@ static void fl_view_constructed(GObject* object) { g_signal_connect(rotate, "end", G_CALLBACK(gesture_rotation_end_cb), self); } +G_MODULE_EXPORT bool fl_view_make_render_context_current(FlView* view) { + g_return_val_if_fail(FL_IS_VIEW(view), false); + return fl_renderer_make_current(view->renderer, NULL); +} + static void fl_view_set_property(GObject* object, guint prop_id, const GValue* value, diff --git a/shell/platform/linux/public/flutter_linux/fl_view.h b/shell/platform/linux/public/flutter_linux/fl_view.h index f93f385a4af65..0071dc4850c8b 100644 --- a/shell/platform/linux/public/flutter_linux/fl_view.h +++ b/shell/platform/linux/public/flutter_linux/fl_view.h @@ -56,6 +56,7 @@ FlView* fl_view_new(FlDartProject* project); */ FlEngine* fl_view_get_engine(FlView* view); +gboolean fl_view_make_render_context_current(FlView* view); G_END_DECLS #endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_VIEW_H_ diff --git a/sky/tools/create_macos_gen_snapshots.py b/sky/tools/create_macos_gen_snapshots.py index 7c689063da9ed..58eb03a3a860d 100755 --- a/sky/tools/create_macos_gen_snapshots.py +++ b/sky/tools/create_macos_gen_snapshots.py @@ -19,6 +19,7 @@ def main(): parser.add_argument('--x64-out-dir', type=str) parser.add_argument('--arm64-out-dir', type=str) parser.add_argument('--armv7-out-dir', type=str) + parser.add_argument('--clang-dir', type=str) args = parser.parse_args() diff --git a/sky/tools/flutter_gdb b/sky/tools/flutter_gdb index 78e435eccb44e..744d7f78a04b8 100755 --- a/sky/tools/flutter_gdb +++ b/sky/tools/flutter_gdb @@ -172,6 +172,7 @@ class GdbClient(object): for command in eval_commands: exec_command += ['--eval-command', command] + print(exec_command) os.execv(exec_command[0], exec_command)