From 9f4084898e5e6f96ac3dbcad76e3f9e2aef481e7 Mon Sep 17 00:00:00 2001 From: JunsuChoi Date: Wed, 5 Mar 2025 20:28:10 +0900 Subject: [PATCH 01/29] [Tizen] Cherrypicks from flutter-3.27.1 of flutter-tizen/engine --- DEPS | 2 +- engine/src/flutter/common/config.gni | 4 +-- .../src/flutter/display_list/testing/BUILD.gn | 3 ++- engine/src/flutter/impeller/tools/args.gni | 5 ++-- engine/src/flutter/shell/config.gni | 2 +- engine/src/flutter/testing/BUILD.gn | 2 +- engine/src/flutter/tools/gn | 27 ++++++++----------- .../src/flutter/txt/src/txt/platform_linux.cc | 3 +-- 8 files changed, 20 insertions(+), 28 deletions(-) diff --git a/DEPS b/DEPS index 9dadedb1d7ca7..c8c1df8ba3c3c 100644 --- a/DEPS +++ b/DEPS @@ -123,7 +123,7 @@ vars = { 'build_devtools_from_sources': False, # Setup Git hooks by default. - 'setup_githooks': True, + 'setup_githooks': False, # When this is true, the Flutter Engine's configuration files and scripts for # RBE will be downloaded from CIPD. This option is only usable by Googlers. diff --git a/engine/src/flutter/common/config.gni b/engine/src/flutter/common/config.gni index 318d305bd37fc..f3b3c15534a2f 100644 --- a/engine/src/flutter/common/config.gni +++ b/engine/src/flutter/common/config.gni @@ -157,6 +157,4 @@ if (flutter_prebuilt_dart_sdk) { # TODO: We can't build the engine artifacts for arm (32-bit) right now; # see https://github.com/flutter/flutter/issues/74322. build_engine_artifacts = - flutter_build_engine_artifacts && !is_android && - (current_toolchain == host_toolchain || - (is_linux && !is_chromeos && current_cpu != "arm") || is_mac || is_win) + flutter_build_engine_artifacts && current_toolchain == host_toolchain diff --git a/engine/src/flutter/display_list/testing/BUILD.gn b/engine/src/flutter/display_list/testing/BUILD.gn index caab882ad6d84..121bc0d6bd443 100644 --- a/engine/src/flutter/display_list/testing/BUILD.gn +++ b/engine/src/flutter/display_list/testing/BUILD.gn @@ -35,7 +35,8 @@ surface_provider_include_software = !is_android && !is_ios # But, since benchmarks do not run on Windows and rendertests only # runs on SW by default, this restriction currently only limits the # ability to manually cross-check OpenGL on Windows for rendertests -surface_provider_include_gl = !is_fuchsia && !is_ios && !is_win && !is_mac +surface_provider_include_gl = + !is_fuchsia && !is_ios && !is_win && !is_mac && !is_linux # TODO (https://github.com/flutter/flutter/issues/107357): # impeller_enable_vulkan currently requires skia to not use VMA, which in turn diff --git a/engine/src/flutter/impeller/tools/args.gni b/engine/src/flutter/impeller/tools/args.gni index bfdf166e03d71..3daa6aa39dee7 100644 --- a/engine/src/flutter/impeller/tools/args.gni +++ b/engine/src/flutter/impeller/tools/args.gni @@ -18,9 +18,8 @@ declare_args() { enable_unittests) && target_os != "fuchsia" # Whether the Vulkan backend is enabled. - impeller_enable_vulkan = - (is_linux || is_win || is_android || is_mac || is_qnx || - enable_unittests) && target_os != "fuchsia" + impeller_enable_vulkan = (is_win || is_android || is_mac || + enable_unittests) && target_os != "fuchsia" } # Arguments that are combinations of other arguments by default but which can diff --git a/engine/src/flutter/shell/config.gni b/engine/src/flutter/shell/config.gni index 3f498d2c044f7..9ad466439ee2e 100644 --- a/engine/src/flutter/shell/config.gni +++ b/engine/src/flutter/shell/config.gni @@ -17,6 +17,6 @@ declare_args() { test_enable_metal = shell_enable_metal # The Vulkan unittests are combined with the GL unittests. - test_enable_vulkan = is_fuchsia || shell_enable_gl + test_enable_vulkan = is_fuchsia test_enable_software = shell_enable_software } diff --git a/engine/src/flutter/testing/BUILD.gn b/engine/src/flutter/testing/BUILD.gn index 84a11db9a3f4c..f01027e666158 100644 --- a/engine/src/flutter/testing/BUILD.gn +++ b/engine/src/flutter/testing/BUILD.gn @@ -54,7 +54,7 @@ source_set("testing") { if (enable_unittests && is_linux) { # So that we can call gtk_init in main(). - configs += [ "//flutter/shell/platform/linux/config:gtk" ] + # configs += [ "//flutter/shell/platform/linux/config:gtk" ] } public_deps = [ ":testing_lib" ] diff --git a/engine/src/flutter/tools/gn b/engine/src/flutter/tools/gn index 7a2b89e6e8bf1..9a301db81aa2a 100755 --- a/engine/src/flutter/tools/gn +++ b/engine/src/flutter/tools/gn @@ -89,18 +89,7 @@ def to_command_line(gn_args): def is_host_build(args): - # If target_os == None, then this is a host build. - if args.target_os is None: - return True - # For linux arm64 builds, we cross compile from x64 hosts, so the - # target_os='linux' and linux-cpu='arm64' - if args.target_os == 'linux' and args.linux_cpu == 'arm64': - return True - # The Mac and host targets are redundant. Again, necessary to disambiguate - # during cross-compilation. - if args.target_os == 'mac': - return True - return False + return args.target_os is None # Determines whether a prebuilt Dart SDK can be used instead of building one. @@ -542,7 +531,7 @@ def to_gn_args(args): # does not exist. Further, we set the 'host_cpu' so that it shares the # bitwidth of the 32-bit arm target. if sys.platform.startswith( - ('cygwin', 'win')) and args.target_os == 'android' and gn_args['target_cpu'] == 'arm': + ('cygwin', 'win')) and args.target_os != 'win' and gn_args['target_cpu'] == 'arm': gn_args['host_cpu'] = 'x86' gn_args['current_cpu'] = 'x86' @@ -660,7 +649,7 @@ def to_gn_args(args): # Enable Vulkan on all platforms except for iOS. This is just # to save on mobile binary size, as there's no reason the Vulkan embedder # features can't work on these platforms. - if gn_args['target_os'] not in ['ios', 'mac']: + if gn_args['target_os'] not in ['ios', 'mac', 'linux']: gn_args['skia_use_vulkan'] = True gn_args['skia_use_vma'] = False gn_args['shell_enable_vulkan'] = True @@ -768,13 +757,17 @@ def to_gn_args(args): # Enable pointer compression on 64-bit mobile targets. iOS is excluded due to # its inability to allocate address space without allocating memory. - if args.target_os in ['android'] and gn_args['target_cpu'] in ['x64', 'arm64']: + if args.target_os in ['android', 'linux'] and gn_args['target_cpu'] in ['x64', 'arm64']: gn_args['dart_use_compressed_pointers'] = True if args.target_os == 'fuchsia': gn_args['gn_configs_path'] = '//flutter/build/config/fuchsia/gn_configs.gni' gn_args['fuchsia_gn_sdk'] = '//flutter/tools/fuchsia/gn-sdk' + # Don't use the default Linux sysroot when buliding for Linux on macOS. + if sys.platform == 'darwin' and args.target_os == 'linux': + gn_args['use_default_linux_sysroot'] = False + # Flags for Dart features: if args.use_mallinfo2: gn_args['dart_use_mallinfo2'] = args.use_mallinfo2 @@ -799,7 +792,9 @@ def to_gn_args(args): # There is a special case for Android on Windows because there we _only_ build # gen_snapshot, but the build defines otherwise make it look like the build is # for a host Windows build and make GN think we will be building ANGLE. - if is_host_build(args) or (args.target_os == 'android' and get_host_os() == 'win'): + # Angle is not used on Mac hosts as there are no tests for the OpenGL backend. + if (is_host_build(args) and gn_args['host_os'] != 'mac') or (args.target_os == 'linux' and + get_host_os() == 'win'): # Don't include git commit information. gn_args['angle_enable_commit_id'] = False # Do not build unnecessary parts of the ANGLE tree. diff --git a/engine/src/flutter/txt/src/txt/platform_linux.cc b/engine/src/flutter/txt/src/txt/platform_linux.cc index e79a96ada9de1..f0fa21e71b113 100644 --- a/engine/src/flutter/txt/src/txt/platform_linux.cc +++ b/engine/src/flutter/txt/src/txt/platform_linux.cc @@ -20,8 +20,7 @@ namespace txt { std::vector GetDefaultFontFamilies() { - return {"Ubuntu", "Adwaita Sans", "Cantarell", - "DejaVu Sans", "Liberation Sans", "Arial"}; + return {"TizenDefaultFont", "SamsungOneUI"}; } sk_sp GetDefaultFontManager(uint32_t font_initialization_data) { From fcfb313b1d88e9e9badb0166d0b97089886bccfd Mon Sep 17 00:00:00 2001 From: JunsuChoi Date: Wed, 5 Mar 2025 20:50:28 +0900 Subject: [PATCH 02/29] [Tizen] Add CI related files --- .github/workflows/build-docker.yml | 21 ++ .github/workflows/build.yml | 313 ++++++++++++++++++ engine/src/flutter/ci/tizen/.gitignore | 3 + engine/src/flutter/ci/tizen/Dockerfile | 32 ++ engine/src/flutter/ci/tizen/arm.patch | 15 + engine/src/flutter/ci/tizen/build_llvm.sh | 45 +++ .../src/flutter/ci/tizen/generate_sysroot.py | 151 +++++++++ 7 files changed, 580 insertions(+) create mode 100644 .github/workflows/build-docker.yml create mode 100644 .github/workflows/build.yml create mode 100644 engine/src/flutter/ci/tizen/.gitignore create mode 100644 engine/src/flutter/ci/tizen/Dockerfile create mode 100644 engine/src/flutter/ci/tizen/arm.patch create mode 100755 engine/src/flutter/ci/tizen/build_llvm.sh create mode 100755 engine/src/flutter/ci/tizen/generate_sysroot.py diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml new file mode 100644 index 0000000000000..62267c3bbf820 --- /dev/null +++ b/.github/workflows/build-docker.yml @@ -0,0 +1,21 @@ +name: Build Docker + +on: [workflow_dispatch] + +jobs: + builder: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: docker/setup-buildx-action@v3 + - uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + - uses: docker/build-push-action@v5 + with: + context: ci/tizen + file: ci/tizen/Dockerfile + push: true + tags: ghcr.io/${{ github.repository_owner }}/build-engine:latest diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000000000..34d2bdd939119 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,313 @@ +name: Build + +on: [push, pull_request] +jobs: + build: + runs-on: ubuntu-latest + container: + image: ghcr.io/flutter-tizen/build-engine:latest + credentials: + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + strategy: + matrix: + arch: [arm, arm64, x86] + mode: [debug, release, profile] + include: + - arch: arm + triple: arm-linux-gnueabi + - arch: arm64 + triple: aarch64-linux-gnu + - arch: x86 + triple: i686-linux-gnu + exclude: + - arch: x86 + mode: release + - arch: x86 + mode: profile + + steps: + - uses: actions/checkout@v4 + with: + path: flutter + fetch-depth: 1 + + - uses: actions/cache@v4 + with: + path: /tizen_tools/sysroot + key: sysroot + + - name: Install depot_tools + run: | + git clone --depth=1 https://chromium.googlesource.com/chromium/tools/depot_tools.git + echo "$PWD/depot_tools" >> $GITHUB_PATH + export PATH="$PWD/depot_tools:$PATH" + # Install ninja + apt install ninja-build + + - name: Run gclient sync + run: | + cd flutter + gclient config --unmanaged https://github.com/flutter-tizen/flutter + sed -i "s/'flutter'/'.'/g" ./.gclient + gclient setdep --var=download_dart_sdk=False --var=download_android_deps=False --var=download_fuchsia_deps=False --deps-file=DEPS + gclient sync -v --no-history --shallow + + - name: Generate sysroot + run: flutter/engine/src/flutter/ci/tizen/generate_sysroot.py --out /tizen_tools/sysroot + + - name: Build + run: | + cd flutter + # Ignore unsupported linker option. + sed -i "/-Wl,--undefined-version/d" engine/src/build/config/compiler/BUILD.gn + + engine/src/flutter/tools/gn \ + --target-os linux \ + --linux-cpu ${{ matrix.arch }} \ + --no-goma \ + --target-toolchain /tizen_tools/toolchains \ + --target-sysroot /tizen_tools/sysroot/${{ matrix.arch }} \ + --target-triple ${{ matrix.triple }} \ + --runtime-mode ${{ matrix.mode }} \ + --enable-fontconfig \ + --disable-desktop-embeddings \ + --target-dir build + cd engine/src + ninja -C out/build flutter_engine_library + cp -f flutter/third_party/icu/flutter/icudtl.dat out/build + + - name: Build gen_snapshot + if: ${{ matrix.mode != 'debug' }} + run: | + cd flutter + ninja -C engine/src/out/build clang_x64/gen_snapshot + + - uses: actions/upload-artifact@v4 + with: + name: tizen-${{ matrix.arch }}-${{ matrix.mode }} + path: | + flutter/engine/src/out/build/libflutter_engine.so + flutter/engine/src/out/build/icudtl.dat + if-no-files-found: error + + - uses: actions/upload-artifact@v4 + if: ${{ github.event_name == 'push' }} + with: + name: tizen-${{ matrix.arch }}-${{ matrix.mode }}_symbols + path: flutter/engine/src/out/build/so.unstripped/libflutter_engine.so + if-no-files-found: error + + - uses: actions/upload-artifact@v4 + if: ${{ matrix.mode != 'debug' }} + with: + name: tizen-${{ matrix.arch }}-${{ matrix.mode }}_linux-x64 + path: flutter/engine/src/out/build/clang_x64/gen_snapshot + if-no-files-found: error + + windows-build: + runs-on: windows-latest + + strategy: + matrix: + arch: [arm, arm64] + mode: [release, profile] + + steps: + - name: Run git checkout + run: | + mkdir C:\workspace\flutter + cd C:\workspace\flutter + git config --global core.autocrlf true + git init --quiet + git remote add origin https://github.com/${{ github.repository }} + git fetch --depth 1 origin ${{ github.sha }} + git checkout FETCH_HEAD + + - name: Environment setup + run: | + Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem" -Name "LongPathsEnabled" -Value 1 -Force + echo "DEPOT_TOOLS_WIN_TOOLCHAIN=0" >> $Env:GITHUB_ENV + echo "GYP_MSVS_OVERRIDE_PATH=C:\Program Files\Microsoft Visual Studio\2022\Enterprise" >> $Env:GITHUB_ENV + echo "WINDOWSSDKDIR=C:\Program Files (x86)\Windows Kits\10" >> $Env:GITHUB_ENV + + - name: Install depot_tools + run: | + Invoke-WebRequest -Uri https://storage.googleapis.com/chrome-infra/depot_tools.zip -OutFile depot_tools.zip + 7z x -y -o"C:\workspace\depot_tools" .\depot_tools.zip + echo "C:\workspace\depot_tools" >> $Env:GITHUB_PATH + + - name: Run gclient sync + working-directory: C:\workspace\flutter + shell: powershell + run: | + gclient config --unmanaged https://github.com/flutter-tizen/flutter + (Get-Content ".gclient") | ForEach-Object { $_ -replace "'flutter'","'.'" } | Set-Content ".gclient" + # TODO(jsuya) : pipes deprecated in python 3.13. (https://dart-review.googlesource.com/c/sdk/+/307620) + (Get-Content "engine/src/build/vs_toolchain.py") | ForEach-Object { $_ -replace 'import pipes','' } | Set-Content "engine/src/build/vs_toolchain.py" + gclient setdep --var=download_dart_sdk=False --var=download_android_deps=False --var=download_fuchsia_deps=False --deps-file=DEPS + gclient sync -v --no-history --shallow + + - name: Build + working-directory: C:\workspace\flutter\engine\src + run: | + python3 .\flutter\tools\gn ` + --linux ` + --linux-cpu=${{ matrix.arch }} ` + --runtime-mode=${{ matrix.mode }} ` + --no-goma ` + --target-dir build + # TODO(jsuya) : https://github.com/flutter/flutter/issues/163487 + C:\workspace\flutter\engine\src\flutter/third_party/ninja/ninja -C .\out\build gen_snapshot + + - uses: actions/upload-artifact@v4 + with: + name: tizen-${{ matrix.arch }}-${{ matrix.mode }}_windows-x64 + path: C:\workspace\flutter\engine\src\out\build\gen_snapshot.exe + if-no-files-found: error + + macos-build: + runs-on: macos-latest + + strategy: + matrix: + arch: [arm, arm64] + mode: [release, profile] + + steps: + - uses: actions/checkout@v4 + with: + path: flutter + fetch-depth: 1 + + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install depot_tools + run: | + git clone --depth=1 https://chromium.googlesource.com/chromium/tools/depot_tools.git + echo "$PWD/depot_tools" >> $GITHUB_PATH + + - name: Run gclient sync + run: | + cd flutter + gclient config --unmanaged https://github.com/flutter-tizen/flutter + sed -i '' "s/'flutter'/'.'/g" .gclient + gclient setdep --var=download_dart_sdk=False --var=download_android_deps=False --var=download_fuchsia_deps=False --deps-file=DEPS + gclient sync -v --no-history --shallow + + - name: Build + run: | + cd flutter + # Change host_toolchain to mac/clang_arm64. + sed -i "" "s|//build/toolchain/linux:clang_$host_cpu|//build/toolchain/mac:clang_$host_cpu|g" engine/src/build/config/BUILDCONFIG.gn + + # Pass dummy values to prevent using the default (Linux) toolchain. + engine/src/flutter/tools/gn \ + --linux \ + --linux-cpu=${{ matrix.arch }} \ + --no-goma \ + --target-toolchain _ \ + --target-sysroot _ \ + --target-triple _ \ + --runtime-mode=${{ matrix.mode }} \ + --disable-desktop-embeddings \ + --target-dir build + cd engine/src + # TODO(jsuya) : https://github.com/flutter/flutter/issues/163487 + flutter/third_party/ninja/ninja -C out/build clang_arm64/gen_snapshot + + - uses: actions/upload-artifact@v4 + with: + name: tizen-${{ matrix.arch }}-${{ matrix.mode }}_darwin-arm64 + path: flutter/engine/src/out/build/clang_arm64/gen_snapshot + if-no-files-found: error + + macos-intel-build: + runs-on: macos-13 + + strategy: + matrix: + arch: [arm, arm64] + mode: [release, profile] + + steps: + - uses: actions/checkout@v4 + with: + path: flutter + fetch-depth: 1 + + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install depot_tools + run: | + git clone --depth=1 https://chromium.googlesource.com/chromium/tools/depot_tools.git + echo "$PWD/depot_tools" >> $GITHUB_PATH + + - name: Run gclient sync + run: | + cd flutter + gclient config --unmanaged https://github.com/flutter-tizen/flutter + sed -i '' "s/'flutter'/'.'/g" .gclient + gclient setdep --var=download_dart_sdk=False --var=download_android_deps=False --var=download_fuchsia_deps=False --deps-file=DEPS + gclient sync -v --no-history --shallow + + - name: Build + run: | + cd flutter + # Change host_toolchain to mac/clang_x64. + sed -i "" "s|//build/toolchain/linux:clang_$host_cpu|//build/toolchain/mac:clang_$host_cpu|g" engine/src/build/config/BUILDCONFIG.gn + + # Pass dummy values to prevent using the default (Linux) toolchain. + engine/src/flutter/tools/gn \ + --linux \ + --linux-cpu=${{ matrix.arch }} \ + --no-goma \ + --target-toolchain _ \ + --target-sysroot _ \ + --target-triple _ \ + --runtime-mode=${{ matrix.mode }} \ + --disable-desktop-embeddings \ + --target-dir build + cd engine/src + # TODO(jsuya) : https://github.com/flutter/flutter/issues/163487 + flutter/third_party/ninja/ninja -C out/build clang_x64/gen_snapshot + + - uses: actions/upload-artifact@v4 + with: + name: tizen-${{ matrix.arch }}-${{ matrix.mode }}_darwin-x64 + path: flutter/engine/src/out/build/clang_x64/gen_snapshot + if-no-files-found: error + + release: + needs: [build] + if: ${{ github.event_name == 'push' }} + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: actions/download-artifact@v4 + + - name: Create archives + run: | + for name in tizen-*; do + 7z a $name.zip ./$name/* + done + + - name: Set variables + run: | + echo "SHORT_SHA=$(git rev-parse --short $GITHUB_SHA)" >> $GITHUB_ENV + echo "VERSION=$(echo "${{ github.ref_name }}" | cut -d'-' -f2)" >> $GITHUB_ENV + + - uses: softprops/action-gh-release@v2 + with: + name: ${{ env.VERSION }} (${{ env.SHORT_SHA }}) + tag_name: ${{ env.SHORT_SHA }} + target_commitish: ${{ github.sha }} + files: tizen-*.zip + body: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} diff --git a/engine/src/flutter/ci/tizen/.gitignore b/engine/src/flutter/ci/tizen/.gitignore new file mode 100644 index 0000000000000..c23e7e850155f --- /dev/null +++ b/engine/src/flutter/ci/tizen/.gitignore @@ -0,0 +1,3 @@ +/llvm-project*/ +/toolchains*/ +/sysroot/ diff --git a/engine/src/flutter/ci/tizen/Dockerfile b/engine/src/flutter/ci/tizen/Dockerfile new file mode 100644 index 0000000000000..f5d0f1fc5520f --- /dev/null +++ b/engine/src/flutter/ci/tizen/Dockerfile @@ -0,0 +1,32 @@ +############################### +### Stage for building LLVM ### +############################### + +FROM ubuntu:22.04 AS llvm + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update && \ + apt-get install -y git zip build-essential cmake ninja-build clang-11 && \ + apt-get clean + +COPY build_llvm.sh . + +RUN /build_llvm.sh + + +############################## +### Create a release image ### +############################## + +FROM ubuntu:22.04 + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update && \ + apt-get install -y binutils-arm-linux-gnueabi binutils-aarch64-linux-gnu binutils-i686-linux-gnu && \ + apt-get install -y git curl pkg-config ca-certificates python3 python3-pip rpm2cpio cpio && \ + apt-get clean + +# Copy build artifacts from the previous stage. +COPY --from=llvm /toolchains/ /tizen_tools/toolchains/ diff --git a/engine/src/flutter/ci/tizen/arm.patch b/engine/src/flutter/ci/tizen/arm.patch new file mode 100644 index 0000000000000..1dabb3f2803d4 --- /dev/null +++ b/engine/src/flutter/ci/tizen/arm.patch @@ -0,0 +1,15 @@ +diff --git a/usr/include/asm-arm/hwcap.h b/usr/include/asm-arm/hwcap.h +index da85060..adaf619 100644 +--- a/usr/include/asm-arm/hwcap.h ++++ b/usr/include/asm-arm/hwcap.h +@@ -26,5 +26,10 @@ + #define HWCAP_VFPD32 (1 << 19) /* set if VFP has 32 regs (not 16) */ + #define HWCAP_IDIV (HWCAP_IDIVA | HWCAP_IDIVT) + ++#define HWCAP2_AES (1 << 0) ++#define HWCAP2_PMULL (1 << 1) ++#define HWCAP2_SHA1 (1 << 2) ++#define HWCAP2_SHA2 (1 << 3) ++#define HWCAP2_CRC32 (1 << 4) + + #endif /* __ASMARM_HWCAP_H */ diff --git a/engine/src/flutter/ci/tizen/build_llvm.sh b/engine/src/flutter/ci/tizen/build_llvm.sh new file mode 100755 index 0000000000000..507395cea8363 --- /dev/null +++ b/engine/src/flutter/ci/tizen/build_llvm.sh @@ -0,0 +1,45 @@ +#!/bin/bash +# Copyright 2022 Samsung Electronics Co., Ltd. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +set -e + +SCRIPT_DIR="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" +OUTPUT_DIR="$SCRIPT_DIR/toolchains" +cd "$SCRIPT_DIR" + +# Check out the LLVM project source code. +if [ -d llvm-project ]; then + echo "The directory already exists. Skipping download." + cd llvm-project +else + mkdir llvm-project + cd llvm-project + git init + git remote add origin https://github.com/llvm/llvm-project.git + git fetch --depth=1 origin llvmorg-17.0.6 + git checkout FETCH_HEAD +fi + +# Run the ninja build. +mkdir -p build && cd build +cmake -G Ninja \ + -DCLANG_VENDOR="Tizen" \ + -DLLVM_ENABLE_PROJECTS="clang" \ + -DLLVM_TARGETS_TO_BUILD="X86;ARM;AArch64" \ + -DCMAKE_C_COMPILER=clang-11 \ + -DCMAKE_CXX_COMPILER=clang++-11 \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX="$OUTPUT_DIR" \ + ../llvm +ninja install -j$(nproc) + +# Create symbolic links to binutils. +# See build/toolchain/custom/BUILD.gn for more information. +cd "$OUTPUT_DIR/bin" +for name in ar readelf nm strip; do + ln -sf llvm-$name arm-linux-gnueabi-$name + ln -sf llvm-$name aarch64-linux-gnu-$name + ln -sf llvm-$name i686-linux-gnu-$name +done diff --git a/engine/src/flutter/ci/tizen/generate_sysroot.py b/engine/src/flutter/ci/tizen/generate_sysroot.py new file mode 100755 index 0000000000000..c4bdc51f347cf --- /dev/null +++ b/engine/src/flutter/ci/tizen/generate_sysroot.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python3 +# Copyright 2022 Samsung Electronics Co., Ltd. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import argparse +import os +import re +import shutil +import subprocess +import sys +import urllib.parse +import urllib.request +from pathlib import Path + +base_packages = [ + 'gcc', + 'glibc', + 'glibc-devel', + 'libgcc', + 'libstdc++', + 'linux-glibc-devel', + 'zlib-devel', +] + +unified_packages = [ + 'fontconfig', + 'fontconfig-devel', + 'freetype2-devel', + 'libpng-devel', +] + + +def generate_sysroot(sysroot: Path, api_version: float, arch: str, quiet=False): + if arch == 'arm': + tizen_arch = 'armv7l' + elif arch == 'arm64': + tizen_arch = 'aarch64' + elif arch == 'x86': + tizen_arch = 'i686' + else: + sys.exit('Unknown arch: ' + arch) + + base_repo = 'http://download.tizen.org/snapshots/TIZEN/Tizen-{}/Tizen-{}-Base/latest/repos/standard/packages'.format( + api_version, api_version + ) + unified_repo = 'http://download.tizen.org/snapshots/TIZEN/Tizen-{}/Tizen-{}-Unified/latest/repos/standard/packages'.format( + api_version, api_version + ) + + # Retrieve html documents. + documents = {} + for url in ['{}/{}'.format(base_repo, tizen_arch), '{}/{}'.format(base_repo, 'noarch'), + '{}/{}'.format(unified_repo, tizen_arch), '{}/{}'.format(unified_repo, 'noarch')]: + request = urllib.request.Request(url) + with urllib.request.urlopen(request) as response: + documents[url] = response.read().decode('utf-8') + + # Download packages. + download_path = sysroot / '.rpms' + download_path.mkdir(exist_ok=True) + existing_rpms = [f for f in download_path.iterdir() if f.suffix == '.rpm'] + + for package in base_packages + unified_packages: + quoted = urllib.parse.quote(package) + pattern = re.escape(quoted) + '-\\d+\\.[\\d_\\.]+-[\\d\\.]+\\..+\\.rpm' + + if any([re.match(pattern, rpm.name) for rpm in existing_rpms]): + continue + + for parent, doc in documents.items(): + match = re.search('.+?'.format(pattern), doc) + if match: + rpm_url = '{}/{}'.format(parent, match.group(1)) + break + + if match: + if not quiet: + print('Downloading ' + rpm_url) + urllib.request.urlretrieve(rpm_url, download_path / match.group(1)) + else: + sys.exit('Could not find a package named ' + package) + + # Extract files. + for rpm in [f for f in download_path.iterdir() if f.suffix == '.rpm']: + command = 'rpm2cpio {} | cpio -idum --quiet'.format(rpm) + subprocess.run(command, shell=True, cwd=sysroot, check=True) + + # Create symbolic links. + asm = sysroot / 'usr' / 'include' / 'asm' + if not asm.exists(): + os.symlink('asm-' + arch, asm) + pkgconfig = sysroot / 'usr' / 'lib' / 'pkgconfig' + if arch == 'arm64' and not pkgconfig.exists(): + os.symlink('../lib64/pkgconfig', pkgconfig) + + # Copy objects required by the linker, such as crtbeginS.o and libgcc.a. + if arch == 'arm64': + libpath = sysroot / 'usr' / 'lib64' + else: + libpath = sysroot / 'usr' / 'lib' + subprocess.run('cp gcc/*/*/*.o gcc/*/*/*.a .', shell=True, cwd=libpath, check=True) + + # Apply a patch if applicable. + patch = Path(__file__).parent / '{}.patch'.format(arch) + if patch.is_file(): + command = 'patch -p1 -s -d {} < {}'.format(sysroot, patch) + subprocess.run(command, shell=True, check=True) + + +def main(): + # Check dependencies. + for dep in ['rpm2cpio', 'cpio', 'git']: + if not shutil.which(dep): + sys.exit('{} is not installed. To install, run:\n' + ' sudo apt install {}'.format(dep, dep)) + + # Parse arguments. + parser = argparse.ArgumentParser(description='Tizen sysroot generator') + parser.add_argument('-o', '--out', metavar='PATH', type=str, help='Path to the output directory') + parser.add_argument('-f', '--force', action='store_true', help='Force re-downloading of packages') + parser.add_argument('-q', '--quiet', action='store_true', help='Suppress log output') + parser.add_argument( + '--api-version', + metavar='VER', + default=6.0, + type=float, + help='Target API version (defaults to 6.0)' + ) + args = parser.parse_args() + + if args.out: + outpath = Path(args.out) + else: + outpath = Path(__file__).parent / 'sysroot' + outpath.mkdir(exist_ok=True) + + for arch in ['arm', 'arm64', 'x86']: + sysroot = outpath / arch + if args.force and sysroot.is_dir(): + shutil.rmtree(sysroot) + sysroot.mkdir(exist_ok=True) + + if not args.quiet: + print('Generating sysroot for {}...'.format(arch)) + generate_sysroot(sysroot.resolve(), args.api_version, arch, args.quiet) + + +# Execute only if run as a script. +if __name__ == '__main__': + main() From bc922140b6c65adc05ed45e7bc115c32eaa16926 Mon Sep 17 00:00:00 2001 From: JunsuChoi Date: Wed, 12 Mar 2025 12:56:12 +0900 Subject: [PATCH 03/29] [Tizen][impeller] Disable gles3 compile Stop generating the GLES3 impeller shader variants in shaders.gni. Flutter 3.44 changed shell/platform/embedder/embedder_surface_gl_impeller.cc to include the impeller/entity/gles3/* headers and select GLES3 shaders at runtime via GetShaderMappings(is_gles3). With GLES3 generation disabled those headers/symbols do not exist, so adapt that consumer here: drop the gles3 includes and always use the GLES2 shader set (valid on GLES3 contexts too). This keeps the disable-gles3 patch self-consistent. --- engine/src/flutter/impeller/tools/shaders.gni | 23 +---------------- .../embedder/embedder_surface_gl_impeller.cc | 25 ++++--------------- 2 files changed, 6 insertions(+), 42 deletions(-) diff --git a/engine/src/flutter/impeller/tools/shaders.gni b/engine/src/flutter/impeller/tools/shaders.gni index 98aa5a7932170..ddacd814a83bb 100644 --- a/engine/src/flutter/impeller/tools/shaders.gni +++ b/engine/src/flutter/impeller/tools/shaders.gni @@ -122,22 +122,6 @@ template("impeller_shaders") { analyze = analyze } } - - gles3_shaders = "gles3_$target_name" - - impeller_shaders_gles(gles3_shaders) { - name = invoker.name - require_framebuffer_fetch = require_framebuffer_fetch - gles_language_version = 300 - is_300 = true - analyze = false - if (defined(invoker.gles_exclusions)) { - shaders = invoker.shaders - invoker.gles_exclusions - } else { - shaders = invoker.shaders - } - analyze = analyze - } } if (impeller_enable_vulkan) { @@ -171,12 +155,7 @@ template("impeller_shaders") { } if (enable_opengles) { - public_deps += [ ":$gles3_shaders" ] - - # wasm builds only use GLES3, no need to build GLES2 shaders - if (!is_wasm) { - public_deps += [ ":$gles_shaders" ] - } + public_deps += [ ":$gles_shaders" ] } if (impeller_enable_vulkan) { diff --git a/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.cc b/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.cc index 1aad505136eb0..1c603e5147563 100644 --- a/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.cc +++ b/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.cc @@ -10,29 +10,16 @@ #include "impeller/entity/gles/entity_shaders_gles.h" #include "impeller/entity/gles/framebuffer_blend_shaders_gles.h" #include "impeller/entity/gles/modern_shaders_gles.h" -#include "impeller/entity/gles3/entity_shaders_gles.h" -#include "impeller/entity/gles3/framebuffer_blend_shaders_gles.h" -#include "impeller/entity/gles3/modern_shaders_gles.h" #include "impeller/renderer/backend/gles/context_gles.h" #include "impeller/renderer/backend/gles/proc_table_gles.h" namespace flutter { namespace { -std::vector> GetShaderMappings(bool is_gles3) { - if (is_gles3) { - return { - std::make_shared( - impeller_entity_shaders_gles3_data, - impeller_entity_shaders_gles3_length), - std::make_shared( - impeller_modern_shaders_gles3_data, - impeller_modern_shaders_gles3_length), - std::make_shared( - impeller_framebuffer_blend_shaders_gles3_data, - impeller_framebuffer_blend_shaders_gles3_length), - }; - } +std::vector> GetShaderMappings() { + // Tizen: GLES3 impeller shaders are not compiled (see + // impeller/tools/shaders.gni). Always use the GLES2 shader set, which is + // also valid on GLES3 contexts. return { std::make_shared( impeller_entity_shaders_gles_data, @@ -102,9 +89,7 @@ EmbedderSurfaceGLImpeller::EmbedderSurfaceGLImpeller( return; } - const auto is_gles3 = - gl->GetDescription()->GetGlVersion().IsAtLeast(impeller::Version(3)); - const auto shader_mappings = GetShaderMappings(is_gles3); + const auto shader_mappings = GetShaderMappings(); impeller_context_ = impeller::ContextGLES::Create( impeller_flags, std::move(gl), shader_mappings, From 3b4a6ae4b1c353bc77e9ab61926d36e2c7f54397 Mon Sep 17 00:00:00 2001 From: JunsuChoi Date: Fri, 23 May 2025 17:51:34 +0900 Subject: [PATCH 04/29] [Tizen] Upgrade llvm 17 to 18 --- .github/workflows/build-docker.yml | 4 ++-- .github/workflows/build.yml | 15 ++++++++------- engine/src/flutter/ci/tizen/build_llvm.sh | 3 ++- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index 62267c3bbf820..d15fb0f88456b 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -15,7 +15,7 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - uses: docker/build-push-action@v5 with: - context: ci/tizen - file: ci/tizen/Dockerfile + context: engine/src/flutter/ci/tizen + file: engine/src/flutter/ci/tizen/Dockerfile push: true tags: ghcr.io/${{ github.repository_owner }}/build-engine:latest diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 34d2bdd939119..61fd921e209ed 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -119,7 +119,11 @@ jobs: run: | mkdir C:\workspace\flutter cd C:\workspace\flutter - git config --global core.autocrlf true + git config --global core.autocrlf false + git config --global core.filemode false + git config --global core.fscache true + git config --global core.preloadindex true + git config --global depot-tools.allowGlobalGitConfig true git init --quiet git remote add origin https://github.com/${{ github.repository }} git fetch --depth 1 origin ${{ github.sha }} @@ -158,8 +162,7 @@ jobs: --runtime-mode=${{ matrix.mode }} ` --no-goma ` --target-dir build - # TODO(jsuya) : https://github.com/flutter/flutter/issues/163487 - C:\workspace\flutter\engine\src\flutter/third_party/ninja/ninja -C .\out\build gen_snapshot + ninja -C .\out\build gen_snapshot - uses: actions/upload-artifact@v4 with: @@ -216,8 +219,7 @@ jobs: --disable-desktop-embeddings \ --target-dir build cd engine/src - # TODO(jsuya) : https://github.com/flutter/flutter/issues/163487 - flutter/third_party/ninja/ninja -C out/build clang_arm64/gen_snapshot + ninja -C out/build clang_arm64/gen_snapshot - uses: actions/upload-artifact@v4 with: @@ -274,8 +276,7 @@ jobs: --disable-desktop-embeddings \ --target-dir build cd engine/src - # TODO(jsuya) : https://github.com/flutter/flutter/issues/163487 - flutter/third_party/ninja/ninja -C out/build clang_x64/gen_snapshot + ninja -C out/build clang_x64/gen_snapshot - uses: actions/upload-artifact@v4 with: diff --git a/engine/src/flutter/ci/tizen/build_llvm.sh b/engine/src/flutter/ci/tizen/build_llvm.sh index 507395cea8363..7d75d9c6a4252 100755 --- a/engine/src/flutter/ci/tizen/build_llvm.sh +++ b/engine/src/flutter/ci/tizen/build_llvm.sh @@ -18,7 +18,7 @@ else cd llvm-project git init git remote add origin https://github.com/llvm/llvm-project.git - git fetch --depth=1 origin llvmorg-17.0.6 + git fetch --depth=1 origin llvmorg-18.1.8 git checkout FETCH_HEAD fi @@ -29,6 +29,7 @@ cmake -G Ninja \ -DLLVM_ENABLE_PROJECTS="clang" \ -DLLVM_TARGETS_TO_BUILD="X86;ARM;AArch64" \ -DCMAKE_C_COMPILER=clang-11 \ + -DCMAKE_CXX_FLAGS="-mssse3" \ -DCMAKE_CXX_COMPILER=clang++-11 \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX="$OUTPUT_DIR" \ From 7c3d5fd341feee3c65843cb738713b7174563d85 Mon Sep 17 00:00:00 2001 From: JunsuChoi Date: Mon, 23 Jun 2025 10:54:44 +0900 Subject: [PATCH 05/29] [Tizen] Temporary fix to Microsoft STL compiler compatibility checks --- .github/workflows/build.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 61fd921e209ed..0beee16550bbb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -150,6 +150,10 @@ jobs: (Get-Content ".gclient") | ForEach-Object { $_ -replace "'flutter'","'.'" } | Set-Content ".gclient" # TODO(jsuya) : pipes deprecated in python 3.13. (https://dart-review.googlesource.com/c/sdk/+/307620) (Get-Content "engine/src/build/vs_toolchain.py") | ForEach-Object { $_ -replace 'import pipes','' } | Set-Content "engine/src/build/vs_toolchain.py" + + # TODO(jsuya) : Temporary fix to Microsoft STL compiler compatibility checks(https://github.com/flutter-tizen/flutter-tizen/pull/635#issuecomment-2990206946) + (Get-Content "engine/src/build/config/compiler/BUILD.gn") | ForEach-Object { $_; if ($_.Trim() -eq '"__STD_C",') { ' "_ALLOW_COMPILER_AND_STL_VERSION_MISMATCH",' } } | Set-Content "engine/src/build/config/compiler/BUILD.gn" + gclient setdep --var=download_dart_sdk=False --var=download_android_deps=False --var=download_fuchsia_deps=False --deps-file=DEPS gclient sync -v --no-history --shallow From a8f9d40a5b79795081e32b7fe85e81cc39997772 Mon Sep 17 00:00:00 2001 From: xiaowei guan <60122246+xiaowei-guan@users.noreply.github.com> Date: Fri, 29 Aug 2025 09:21:53 +0800 Subject: [PATCH 06/29] [Tizen] Enable vulkan (#11) --- engine/src/build_overrides/vulkan_headers.gni | 2 +- engine/src/flutter/impeller/tools/args.gni | 2 +- engine/src/flutter/shell/config.gni | 2 +- engine/src/flutter/tools/gn | 8 ++++---- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/engine/src/build_overrides/vulkan_headers.gni b/engine/src/build_overrides/vulkan_headers.gni index 8ef7d23936589..8a4144db207b6 100644 --- a/engine/src/build_overrides/vulkan_headers.gni +++ b/engine/src/build_overrides/vulkan_headers.gni @@ -6,6 +6,6 @@ # set anything. if (is_linux) { - vulkan_use_x11 = !is_minimal_linux + vulkan_use_x11 = false vulkan_use_wayland = true } diff --git a/engine/src/flutter/impeller/tools/args.gni b/engine/src/flutter/impeller/tools/args.gni index 3daa6aa39dee7..bf7de3bd4b322 100644 --- a/engine/src/flutter/impeller/tools/args.gni +++ b/engine/src/flutter/impeller/tools/args.gni @@ -18,7 +18,7 @@ declare_args() { enable_unittests) && target_os != "fuchsia" # Whether the Vulkan backend is enabled. - impeller_enable_vulkan = (is_win || is_android || is_mac || + impeller_enable_vulkan = (is_linux || is_win || is_android || is_mac || enable_unittests) && target_os != "fuchsia" } diff --git a/engine/src/flutter/shell/config.gni b/engine/src/flutter/shell/config.gni index 9ad466439ee2e..8aa62e02417ba 100644 --- a/engine/src/flutter/shell/config.gni +++ b/engine/src/flutter/shell/config.gni @@ -7,7 +7,7 @@ declare_args() { # The logic for enabling Vulkan and Metal is in tools/gn. shell_enable_metal = false - shell_enable_vulkan = false + shell_enable_vulkan = true shell_enable_software = true } diff --git a/engine/src/flutter/tools/gn b/engine/src/flutter/tools/gn index 9a301db81aa2a..60c3a25dea2aa 100755 --- a/engine/src/flutter/tools/gn +++ b/engine/src/flutter/tools/gn @@ -649,7 +649,7 @@ def to_gn_args(args): # Enable Vulkan on all platforms except for iOS. This is just # to save on mobile binary size, as there's no reason the Vulkan embedder # features can't work on these platforms. - if gn_args['target_os'] not in ['ios', 'mac', 'linux']: + if gn_args['target_os'] not in ['ios', 'mac']: gn_args['skia_use_vulkan'] = True gn_args['skia_use_vma'] = False gn_args['shell_enable_vulkan'] = True @@ -793,12 +793,12 @@ def to_gn_args(args): # gen_snapshot, but the build defines otherwise make it look like the build is # for a host Windows build and make GN think we will be building ANGLE. # Angle is not used on Mac hosts as there are no tests for the OpenGL backend. - if (is_host_build(args) and gn_args['host_os'] != 'mac') or (args.target_os == 'linux' and - get_host_os() == 'win'): + if (is_host_build(args) and gn_args['host_os'] != 'mac') or args.target_os == 'linux': # Don't include git commit information. gn_args['angle_enable_commit_id'] = False # Do not build unnecessary parts of the ANGLE tree. - gn_args['angle_build_all'] = False + if is_host_build(args) or args.target_os == 'linux': + gn_args['angle_build_all'] = False gn_args['angle_has_astc_encoder'] = False gn_args['angle_enable_libdrm'] = False # Force ANGLE context checks on Windows to prevent crashes. From afaa5166810de1351b7586a0e7c8d3927396f84a Mon Sep 17 00:00:00 2001 From: JunsuChoi Date: Fri, 12 Sep 2025 12:06:23 +0900 Subject: [PATCH 07/29] [Tizen] Add x64 release (#13) flutter-tizen/flutter-tizen#554 Added x64 artifacts for x64 emulator support (Currently, x64 Release mode is only supported on Linux hosts.) --- .github/workflows/build.yml | 41 +++++++++++++++++++++-- engine/src/flutter/ci/tizen/build_llvm.sh | 1 + engine/src/flutter/testing/testing.gni | 3 +- 3 files changed, 41 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0beee16550bbb..e828f82a2b2a3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,7 +12,7 @@ jobs: strategy: matrix: - arch: [arm, arm64, x86] + arch: [arm, arm64, x86, x64] mode: [debug, release, profile] include: - arch: arm @@ -21,6 +21,8 @@ jobs: triple: aarch64-linux-gnu - arch: x86 triple: i686-linux-gnu + - arch: x64 + triple: x86_64-linux-gnu exclude: - arch: x86 mode: release @@ -58,6 +60,7 @@ jobs: run: flutter/engine/src/flutter/ci/tizen/generate_sysroot.py --out /tizen_tools/sysroot - name: Build + if: ${{ matrix.arch != 'x64' }} run: | cd flutter # Ignore unsupported linker option. @@ -78,12 +81,37 @@ jobs: ninja -C out/build flutter_engine_library cp -f flutter/third_party/icu/flutter/icudtl.dat out/build + - name: Build(x64) + if: ${{ matrix.arch == 'x64' }} + run: | + cd flutter + + engine/src/flutter/tools/gn \ + --target-os linux \ + --linux-cpu ${{ matrix.arch }} \ + --no-goma \ + --target-triple ${{ matrix.triple }} \ + --runtime-mode ${{ matrix.mode }} \ + --enable-fontconfig \ + --disable-desktop-embeddings \ + --target-dir build + + cd engine/src + ninja -C out/build flutter_engine_library + cp -f flutter/third_party/icu/flutter/icudtl.dat out/build + - name: Build gen_snapshot - if: ${{ matrix.mode != 'debug' }} + if: ${{ matrix.mode != 'debug' && matrix.arch != 'x64'}} run: | cd flutter ninja -C engine/src/out/build clang_x64/gen_snapshot + - name: Build gen_snapshot(x64) + if: ${{ matrix.mode != 'debug' && matrix.arch == 'x64'}} + run: | + cd flutter + ninja -C engine/src/out/build gen_snapshot + - uses: actions/upload-artifact@v4 with: name: tizen-${{ matrix.arch }}-${{ matrix.mode }} @@ -100,12 +128,19 @@ jobs: if-no-files-found: error - uses: actions/upload-artifact@v4 - if: ${{ matrix.mode != 'debug' }} + if: ${{ matrix.mode != 'debug' && matrix.arch != 'x64'}} with: name: tizen-${{ matrix.arch }}-${{ matrix.mode }}_linux-x64 path: flutter/engine/src/out/build/clang_x64/gen_snapshot if-no-files-found: error + - uses: actions/upload-artifact@v4 + if: ${{ matrix.mode != 'debug' && matrix.arch == 'x64'}} + with: + name: tizen-${{ matrix.arch }}-${{ matrix.mode }}_linux-x64 + path: flutter/engine/src/out/build/gen_snapshot + if-no-files-found: error + windows-build: runs-on: windows-latest diff --git a/engine/src/flutter/ci/tizen/build_llvm.sh b/engine/src/flutter/ci/tizen/build_llvm.sh index 7d75d9c6a4252..de27efb73a9b6 100755 --- a/engine/src/flutter/ci/tizen/build_llvm.sh +++ b/engine/src/flutter/ci/tizen/build_llvm.sh @@ -43,4 +43,5 @@ for name in ar readelf nm strip; do ln -sf llvm-$name arm-linux-gnueabi-$name ln -sf llvm-$name aarch64-linux-gnu-$name ln -sf llvm-$name i686-linux-gnu-$name + ln -sf llvm-$name x86_64-linux-gnu-$name done diff --git a/engine/src/flutter/testing/testing.gni b/engine/src/flutter/testing/testing.gni index 5771aeb8b4ab3..67f162f35984b 100644 --- a/engine/src/flutter/testing/testing.gni +++ b/engine/src/flutter/testing/testing.gni @@ -15,7 +15,8 @@ declare_args() { # * host_toolchain: non-cross-compile. We can run tests on the host. # * Fuchsia: build unittests for testing on device. # * macOS: arm64 builds can run x64 binaries. - enable_unittests = current_toolchain == host_toolchain || is_fuchsia || is_mac + # Disable unit test + enable_unittests = false # Build iOS target device unit tests. # From f1351a59c3445b3598831b5366ad66aa01ac02b4 Mon Sep 17 00:00:00 2001 From: JunsuChoi Date: Wed, 17 Sep 2025 13:02:27 +0900 Subject: [PATCH 08/29] [Tizen] Disable x64 symbol generation temporary (#14) --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e828f82a2b2a3..0eb01ef09b086 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -121,7 +121,7 @@ jobs: if-no-files-found: error - uses: actions/upload-artifact@v4 - if: ${{ github.event_name == 'push' }} + if: ${{ github.event_name == 'push' && matrix.arch != 'x64' }} with: name: tizen-${{ matrix.arch }}-${{ matrix.mode }}_symbols path: flutter/engine/src/out/build/so.unstripped/libflutter_engine.so From c44f113998fca5c9848343f6bd1af8ea7a6a7524 Mon Sep 17 00:00:00 2001 From: JunsuChoi Date: Thu, 18 Sep 2025 17:48:28 +0900 Subject: [PATCH 09/29] [Tizen] Update sysroot download URL (#15) https://github.com/flutter-tizen/embedder/pull/118#discussion_r2357867428 --- engine/src/flutter/ci/tizen/generate_sysroot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/engine/src/flutter/ci/tizen/generate_sysroot.py b/engine/src/flutter/ci/tizen/generate_sysroot.py index c4bdc51f347cf..210f7e9ecf9e7 100755 --- a/engine/src/flutter/ci/tizen/generate_sysroot.py +++ b/engine/src/flutter/ci/tizen/generate_sysroot.py @@ -41,10 +41,10 @@ def generate_sysroot(sysroot: Path, api_version: float, arch: str, quiet=False): else: sys.exit('Unknown arch: ' + arch) - base_repo = 'http://download.tizen.org/snapshots/TIZEN/Tizen-{}/Tizen-{}-Base/latest/repos/standard/packages'.format( + base_repo = 'http://download.tizen.org/snapshots/TIZEN/Tizen-{}/Tizen-{}-Base/reference/repos/standard/packages'.format( api_version, api_version ) - unified_repo = 'http://download.tizen.org/snapshots/TIZEN/Tizen-{}/Tizen-{}-Unified/latest/repos/standard/packages'.format( + unified_repo = 'http://download.tizen.org/snapshots/TIZEN/Tizen-{}/Tizen-{}-Unified/reference/repos/standard/packages'.format( api_version, api_version ) From 2aa9cfa41105097ae6c6efeff8b396e653904bc8 Mon Sep 17 00:00:00 2001 From: JunsuChoi Date: Fri, 19 Sep 2025 18:15:14 +0900 Subject: [PATCH 10/29] [ci] Fix window version to 2022 flutter engine's window build uses Window SDKs 10.0.22621.0. window-2025 runner in gitHub actions no longer supports the 10.0.22621.0 SDK. --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0eb01ef09b086..cb97449ae2e30 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -142,7 +142,7 @@ jobs: if-no-files-found: error windows-build: - runs-on: windows-latest + runs-on: windows-2022 strategy: matrix: From e93058fe334dd8a5469beca0f8a5a744cfaf4946 Mon Sep 17 00:00:00 2001 From: xiaowei guan <60122246+xiaowei-guan@users.noreply.github.com> Date: Sat, 11 Oct 2025 10:42:02 +0800 Subject: [PATCH 11/29] [Tizen] Support render texture for vulkan + impeller (#17) Add new struct FlutterVulkanTexture for embedder : ``` typedef struct { /// Handle to the VkImage that is owned by the embedder. The engine will /// bind this image for writing the frame. FlutterVulkanImageHandle image; /// The VkDeviceMemory that backs the iamge. FlutterVulkanDeviceMemoryHandle image_memory; /// The VkFormat of the image (for example: VK_FORMAT_R8G8B8A8_UNORM). uint32_t format; /// User data to be returned on the invocation of the destruction callback. void* user_data; /// Callback invoked (on an engine managed thread) that asks the embedder to /// collect the texture. VoidCallback destruction_callback; /// Optional parameters for texture height/width, default is 0, non-zero means /// the texture has the specified width/height. /// Width of the texture. size_t width; /// Height of the texture. size_t height; } FlutterVulkanTexture; ``` The implement of [texture source](https://github.com/flutter-tizen/flutter/pull/17/files#diff-7955a8522a753162869f2e8ca0017a83f4854b60800c844202e70c3aa00ff0c9R5-R204) refer to the solution of android platform(https://github.com/flutter-tizen/flutter/blob/flutter-3.35.3/engine/src/flutter/impeller/renderer/backend/vulkan/android/ahb_texture_source_vk.cc) and I have submitted the code [Support render texture for embedder](https://github.com/xiaowei-guan/embedder/commit/6a94746b5a006730e6f5280d5a4fecf8eef53b4d), I will create a new PR after[ support vulkan backend](https://github.com/flutter-tizen/embedder/pull/110) PR released. --- .../flutter/shell/platform/embedder/BUILD.gn | 4 + .../shell/platform/embedder/embedder.cc | 21 ++ .../shell/platform/embedder/embedder.h | 39 +++- .../embedder_external_texture_resolver.cc | 19 ++ .../embedder_external_texture_resolver.h | 12 + ...embedder_external_texture_source_vulkan.cc | 206 ++++++++++++++++++ .../embedder_external_texture_source_vulkan.h | 72 ++++++ .../embedder_external_texture_vulkan.cc | 196 +++++++++++++++++ .../embedder_external_texture_vulkan.h | 64 ++++++ 9 files changed, 632 insertions(+), 1 deletion(-) create mode 100644 engine/src/flutter/shell/platform/embedder/embedder_external_texture_source_vulkan.cc create mode 100644 engine/src/flutter/shell/platform/embedder/embedder_external_texture_source_vulkan.h create mode 100644 engine/src/flutter/shell/platform/embedder/embedder_external_texture_vulkan.cc create mode 100644 engine/src/flutter/shell/platform/embedder/embedder_external_texture_vulkan.h diff --git a/engine/src/flutter/shell/platform/embedder/BUILD.gn b/engine/src/flutter/shell/platform/embedder/BUILD.gn index c2325147c145a..90398d2d2ebbe 100644 --- a/engine/src/flutter/shell/platform/embedder/BUILD.gn +++ b/engine/src/flutter/shell/platform/embedder/BUILD.gn @@ -167,6 +167,8 @@ template("embedder_source_set") { if (embedder_enable_vulkan) { sources += [ + "embedder_external_texture_vulkan.cc", + "embedder_external_texture_vulkan.h", "embedder_surface_vulkan.cc", "embedder_surface_vulkan.h", ] @@ -175,6 +177,8 @@ template("embedder_source_set") { sources += [ "embedder_surface_vulkan_impeller.cc", "embedder_surface_vulkan_impeller.h", + "embedder_external_texture_source_vulkan.cc", + "embedder_external_texture_source_vulkan.h", ] } diff --git a/engine/src/flutter/shell/platform/embedder/embedder.cc b/engine/src/flutter/shell/platform/embedder/embedder.cc index d7f02e79018cf..48e960fb4c13e 100644 --- a/engine/src/flutter/shell/platform/embedder/embedder.cc +++ b/engine/src/flutter/shell/platform/embedder/embedder.cc @@ -2385,6 +2385,27 @@ FlutterEngineResult FlutterEngineInitialize(size_t version, external_texture_metal_callback); } } +#endif +#ifdef SHELL_ENABLE_VULKAN + if (config->type == kVulkan) { + const FlutterVulkanRendererConfig* vulkan_config = &config->vulkan; + if (auto callback = SAFE_ACCESS(vulkan_config, + external_texture_frame_callback, nullptr)) { + auto external_texture_vulkan_callback = + [ptr = callback, user_data]( + int64_t texture_identifier, size_t width, + size_t height) -> std::unique_ptr { + std::unique_ptr texture = + std::make_unique(); + if (!ptr(user_data, texture_identifier, width, height, texture.get())) { + return nullptr; + } + return texture; + }; + external_texture_resolver = std::make_unique( + external_texture_vulkan_callback); + } + } #endif auto custom_task_runners = SAFE_ACCESS(args, custom_task_runners, nullptr); auto thread_config_callback = [&custom_task_runners]( diff --git a/engine/src/flutter/shell/platform/embedder/embedder.h b/engine/src/flutter/shell/platform/embedder/embedder.h index e9f3769963b2f..37734abfe9908 100644 --- a/engine/src/flutter/shell/platform/embedder/embedder.h +++ b/engine/src/flutter/shell/platform/embedder/embedder.h @@ -930,6 +930,9 @@ typedef void* FlutterVulkanQueueHandle; /// Alias for VkImage. typedef uint64_t FlutterVulkanImageHandle; +/// Alias for VkDeviceMemory. +typedef uint64_t FlutterVulkanDeviceMemoryHandle; + typedef struct { /// The size of this struct. Must be sizeof(FlutterVulkanImage). size_t struct_size; @@ -958,6 +961,36 @@ typedef bool (*FlutterVulkanPresentCallback)( void* /* user data */, const FlutterVulkanImage* /* image */); +typedef struct { + /// Handle to the VkImage that is owned by the embedder. The engine will + /// bind this image for writing the frame. + FlutterVulkanImageHandle image; + /// The VkDeviceMemory that backs the iamge. + FlutterVulkanDeviceMemoryHandle image_memory; + /// The VkFormat of the image (for example: VK_FORMAT_R8G8B8A8_UNORM). + uint32_t format; + /// User data to be returned on the invocation of the destruction callback. + void* user_data; + /// Callback invoked (on an engine managed thread) that asks the embedder to + /// collect the texture. + VoidCallback destruction_callback; + /// Optional parameters for texture height/width, default is 0, non-zero means + /// the texture has the specified width/height. + /// Width of the texture. + size_t width; + /// Height of the texture. + size_t height; +} FlutterVulkanTexture; + +/// Callback to provide an external texture for a given texture_id. +/// See: external_texture_frame_callback. +typedef bool (*FlutterVulkanTextureFrameCallback)( + void* /* user data */, + int64_t /* texture identifier */, + size_t /* width */, + size_t /* height */, + FlutterVulkanTexture* /* texture out */); + typedef struct { /// The size of this struct. Must be sizeof(FlutterVulkanRendererConfig). size_t struct_size; @@ -1021,7 +1054,11 @@ typedef struct { /// without any additional synchronization. /// Not used if a FlutterCompositor is supplied in FlutterProjectArgs. FlutterVulkanPresentCallback present_image_callback; - + /// When the embedder specifies that a texture has a frame available, the + /// engine will call this method (on an internal engine managed thread) so + /// that external texture details can be supplied to the engine for subsequent + /// composition. + FlutterVulkanTextureFrameCallback external_texture_frame_callback; } FlutterVulkanRendererConfig; typedef struct { diff --git a/engine/src/flutter/shell/platform/embedder/embedder_external_texture_resolver.cc b/engine/src/flutter/shell/platform/embedder/embedder_external_texture_resolver.cc index 4e4b41b8d4ad2..2dcef86b2061c 100644 --- a/engine/src/flutter/shell/platform/embedder/embedder_external_texture_resolver.cc +++ b/engine/src/flutter/shell/platform/embedder/embedder_external_texture_resolver.cc @@ -21,6 +21,12 @@ EmbedderExternalTextureResolver::EmbedderExternalTextureResolver( : metal_callback_(std::move(metal_callback)) {} #endif +#ifdef SHELL_ENABLE_VULKAN +EmbedderExternalTextureResolver::EmbedderExternalTextureResolver( + EmbedderExternalTextureVulkan::ExternalTextureCallback vulkan_callback) + : vulkan_callback_(std::move(vulkan_callback)) {} +#endif + std::unique_ptr EmbedderExternalTextureResolver::ResolveExternalTexture(int64_t texture_id) { #ifdef SHELL_ENABLE_GL @@ -37,6 +43,13 @@ EmbedderExternalTextureResolver::ResolveExternalTexture(int64_t texture_id) { } #endif +#ifdef SHELL_ENABLE_VULKAN + if (vulkan_callback_) { + return std::make_unique(texture_id, + vulkan_callback_); + } +#endif + return nullptr; } @@ -53,6 +66,12 @@ bool EmbedderExternalTextureResolver::SupportsExternalTextures() { } #endif +#ifdef SHELL_ENABLE_VULKAN + if (vulkan_callback_) { + return true; + } +#endif + return false; } diff --git a/engine/src/flutter/shell/platform/embedder/embedder_external_texture_resolver.h b/engine/src/flutter/shell/platform/embedder/embedder_external_texture_resolver.h index 412301736e566..1079a006d6359 100644 --- a/engine/src/flutter/shell/platform/embedder/embedder_external_texture_resolver.h +++ b/engine/src/flutter/shell/platform/embedder/embedder_external_texture_resolver.h @@ -17,6 +17,10 @@ #include "flutter/shell/platform/embedder/embedder_external_texture_metal.h" #endif +#ifdef SHELL_ENABLE_VULKAN +#include "flutter/shell/platform/embedder/embedder_external_texture_vulkan.h" +#endif + namespace flutter { class EmbedderExternalTextureResolver { public: @@ -34,6 +38,11 @@ class EmbedderExternalTextureResolver { EmbedderExternalTextureMetal::ExternalTextureCallback metal_callback); #endif +#ifdef SHELL_ENABLE_VULKAN + explicit EmbedderExternalTextureResolver( + EmbedderExternalTextureVulkan::ExternalTextureCallback vulkan_callback); +#endif + std::unique_ptr ResolveExternalTexture(int64_t texture_id); bool SupportsExternalTextures(); @@ -47,6 +56,9 @@ class EmbedderExternalTextureResolver { EmbedderExternalTextureMetal::ExternalTextureCallback metal_callback_; #endif +#ifdef SHELL_ENABLE_VULKAN + EmbedderExternalTextureVulkan::ExternalTextureCallback vulkan_callback_; +#endif FML_DISALLOW_COPY_AND_ASSIGN(EmbedderExternalTextureResolver); }; } // namespace flutter diff --git a/engine/src/flutter/shell/platform/embedder/embedder_external_texture_source_vulkan.cc b/engine/src/flutter/shell/platform/embedder/embedder_external_texture_source_vulkan.cc new file mode 100644 index 0000000000000..565a48abf6477 --- /dev/null +++ b/engine/src/flutter/shell/platform/embedder/embedder_external_texture_source_vulkan.cc @@ -0,0 +1,206 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/shell/platform/embedder/embedder_external_texture_source_vulkan.h" + +#include "impeller/renderer/backend/vulkan/allocator_vk.h" +#include "impeller/renderer/backend/vulkan/context_vk.h" +#include "impeller/renderer/backend/vulkan/texture_source_vk.h" +#include "impeller/renderer/backend/vulkan/yuv_conversion_library_vk.h" +#include "vulkan/vulkan.hpp" + +namespace flutter { + +bool RequiresYCBCRConversion(impeller::vk::Format format) { + switch (format) { + case impeller::vk::Format::eG8B8R83Plane420Unorm: + case impeller::vk::Format::eG8B8R82Plane420Unorm: + case impeller::vk::Format::eG8B8R83Plane422Unorm: + case impeller::vk::Format::eG8B8R82Plane422Unorm: + case impeller::vk::Format::eG8B8R83Plane444Unorm: + return true; + default: + // NOTE: NOT EXHAUSTIVE. + break; + } + return false; +} + +EmbedderExternalTextureSourceVulkan::EmbedderExternalTextureSourceVulkan( + const std::shared_ptr& p_context, + FlutterVulkanTexture* embedder_desc) + : TextureSourceVK(ToTextureDescriptor(embedder_desc)) { + const auto& context = impeller::ContextVK::Cast(*p_context); + const auto& device = context.GetDevice(); + texture_image_ = + impeller::vk::Image(reinterpret_cast(embedder_desc->image)); + texture_device_memory_ = impeller::vk::DeviceMemory( + reinterpret_cast(embedder_desc->image_memory)); + + needs_yuv_conversion_ = RequiresYCBCRConversion( + static_cast(embedder_desc->format)); + std::shared_ptr yuv_conversion; + if (needs_yuv_conversion_) { + // Figure out how to perform YUV conversions. + yuv_conversion = CreateYUVConversion(context, embedder_desc); + if (!yuv_conversion || !yuv_conversion->IsValid()) { + VALIDATION_LOG << "Fail to create yuv conversion"; + return; + } + } + + // Create image view for the newly created image. + if (!CreateTextureImageView(device, embedder_desc, yuv_conversion)) { + VALIDATION_LOG << "Fail to create texture image view"; + return; + } + + yuv_conversion_ = std::move(yuv_conversion); + is_valid_ = true; +} + +impeller::PixelFormat ToPixelFormat(int32_t vk_format) { + switch (vk_format) { + case VK_FORMAT_UNDEFINED: + return impeller::PixelFormat::kUnknown; + case VK_FORMAT_R8G8B8A8_UNORM: + return impeller::PixelFormat::kR8G8B8A8UNormInt; + case VK_FORMAT_R8G8B8A8_SRGB: + return impeller::PixelFormat::kR8G8B8A8UNormIntSRGB; + case VK_FORMAT_B8G8R8A8_UNORM: + return impeller::PixelFormat::kB8G8R8A8UNormInt; + case VK_FORMAT_B8G8R8A8_SRGB: + return impeller::PixelFormat::kB8G8R8A8UNormIntSRGB; + case VK_FORMAT_R32G32B32A32_SFLOAT: + return impeller::PixelFormat::kR32G32B32A32Float; + case VK_FORMAT_R16G16B16A16_SFLOAT: + return impeller::PixelFormat::kR16G16B16A16Float; + case VK_FORMAT_S8_UINT: + return impeller::PixelFormat::kS8UInt; + case VK_FORMAT_D24_UNORM_S8_UINT: + return impeller::PixelFormat::kD24UnormS8Uint; + case VK_FORMAT_D32_SFLOAT_S8_UINT: + return impeller::PixelFormat::kD32FloatS8UInt; + case VK_FORMAT_R8_UNORM: + return impeller::PixelFormat::kR8UNormInt; + case VK_FORMAT_R8G8_UNORM: + return impeller::PixelFormat::kR8G8UNormInt; + default: + return impeller::PixelFormat::kUnknown; + } +} + +impeller::TextureDescriptor +EmbedderExternalTextureSourceVulkan::ToTextureDescriptor( + FlutterVulkanTexture* embedder_desc) { + const auto size = + impeller::ISize{static_cast(embedder_desc->width), + static_cast(embedder_desc->height)}; + impeller::TextureDescriptor desc; + desc.storage_mode = impeller::StorageMode::kDevicePrivate; + desc.format = ToPixelFormat(embedder_desc->format); + desc.size = size; + desc.type = impeller::TextureType::kTexture2D; + desc.sample_count = impeller::SampleCount::kCount1; + desc.compression_type = impeller::CompressionType::kLossless; + desc.mip_count = 1u; + desc.usage = impeller::TextureUsage::kRenderTarget; + return desc; +} + +std::shared_ptr +EmbedderExternalTextureSourceVulkan::CreateYUVConversion( + const impeller::ContextVK& context, + FlutterVulkanTexture* embedder_desc) { + impeller::YUVConversionDescriptorVK conversion_chain; + auto& conversion_info = conversion_chain.get(); + + conversion_info.format = + static_cast(embedder_desc->format); + conversion_info.ycbcrModel = + impeller::vk::SamplerYcbcrModelConversion::eYcbcr709; + conversion_info.ycbcrRange = impeller::vk::SamplerYcbcrRange::eItuFull; + conversion_info.components = {impeller::vk::ComponentSwizzle::eIdentity, + impeller::vk::ComponentSwizzle::eIdentity, + impeller::vk::ComponentSwizzle::eIdentity, + impeller::vk::ComponentSwizzle::eIdentity}; + conversion_info.xChromaOffset = impeller::vk::ChromaLocation::eCositedEven; + conversion_info.yChromaOffset = impeller::vk::ChromaLocation::eCositedEven; + conversion_info.chromaFilter = impeller::vk::Filter::eNearest; + conversion_info.forceExplicitReconstruction = false; + return context.GetYUVConversionLibrary()->GetConversion(conversion_chain); +} + +bool EmbedderExternalTextureSourceVulkan::CreateTextureImageView( + const impeller::vk::Device& device, + FlutterVulkanTexture* embedder_desc, + const std::shared_ptr& yuv_conversion_wrapper) { + impeller::vk::StructureChain + view_chain; + auto& view_info = view_chain.get(); + view_info.image = texture_image_; + view_info.viewType = impeller::vk::ImageViewType::e2D; + view_info.format = static_cast(embedder_desc->format); + view_info.subresourceRange.aspectMask = + impeller::vk::ImageAspectFlagBits::eColor; + view_info.subresourceRange.baseMipLevel = 0u; + view_info.subresourceRange.baseArrayLayer = 0u; + view_info.subresourceRange.levelCount = 1; + view_info.subresourceRange.layerCount = 1; + + if (RequiresYCBCRConversion( + static_cast(embedder_desc->format))) { + view_chain.get().conversion = + yuv_conversion_wrapper->GetConversion(); + } else { + view_chain.unlink(); + } + auto image_view = device.createImageViewUnique(view_info); + if (image_view.result != impeller::vk::Result::eSuccess) { + return false; + } + texture_image_view_ = std::move(image_view.value); + return true; +} + +// |TextureSourceVK| +EmbedderExternalTextureSourceVulkan::~EmbedderExternalTextureSourceVulkan() = + default; + +bool EmbedderExternalTextureSourceVulkan::IsValid() const { + return is_valid_; +} + +// |TextureSourceVK| +impeller::vk::Image EmbedderExternalTextureSourceVulkan::GetImage() const { + return texture_image_; +} + +// |TextureSourceVK| +impeller::vk::ImageView EmbedderExternalTextureSourceVulkan::GetImageView() + const { + return texture_image_view_.get(); +} + +// |TextureSourceVK| +impeller::vk::ImageView +EmbedderExternalTextureSourceVulkan::GetRenderTargetView( + uint32_t mip_level, + uint32_t array_layer) const { + return texture_image_view_.get(); +} + +// |TextureSourceVK| +bool EmbedderExternalTextureSourceVulkan::IsSwapchainImage() const { + return is_swapchain_image_; +} + +// |TextureSourceVK| +std::shared_ptr +EmbedderExternalTextureSourceVulkan::GetYUVConversion() const { + return needs_yuv_conversion_ ? yuv_conversion_ : nullptr; +} + +} // namespace flutter diff --git a/engine/src/flutter/shell/platform/embedder/embedder_external_texture_source_vulkan.h b/engine/src/flutter/shell/platform/embedder/embedder_external_texture_source_vulkan.h new file mode 100644 index 0000000000000..b223b694f4d5a --- /dev/null +++ b/engine/src/flutter/shell/platform/embedder/embedder_external_texture_source_vulkan.h @@ -0,0 +1,72 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_SHELL_PLATFORM_EMBEDDER_EMBEDDER_EXTERNAL_TEXTURE_SOURCE_VULKAN_H_ +#define FLUTTER_SHELL_PLATFORM_EMBEDDER_EMBEDDER_EXTERNAL_TEXTURE_SOURCE_VULKAN_H_ + +#include "flutter/shell/platform/embedder/embedder.h" +#include "impeller/renderer/backend/vulkan/context_vk.h" +#include "impeller/renderer/backend/vulkan/texture_source_vk.h" +#include "impeller/renderer/backend/vulkan/vk.h" +#include "impeller/renderer/backend/vulkan/yuv_conversion_vk.h" + +namespace flutter { + +class ContextVK; + +class EmbedderExternalTextureSourceVulkan final + : public impeller::TextureSourceVK { + public: + EmbedderExternalTextureSourceVulkan( + const std::shared_ptr& context, + FlutterVulkanTexture* embedder_desc); + + // |TextureSourceVK| + ~EmbedderExternalTextureSourceVulkan() override; + + // |TextureSourceVK| + impeller::vk::Image GetImage() const override; + + // |TextureSourceVK| + impeller::vk::ImageView GetImageView() const override; + + // |TextureSourceVK| + impeller::vk::ImageView GetRenderTargetView( + uint32_t mip_level, + uint32_t array_layer) const override; + + bool IsValid() const; + + // |TextureSourceVK| + bool IsSwapchainImage() const override; + + // |TextureSourceVK| + std::shared_ptr GetYUVConversion() const override; + + private: + bool CreateTextureImageView( + const impeller::vk::Device& device, + FlutterVulkanTexture* embedder_desc, + const std::shared_ptr& yuv_conversion_wrapper); + impeller::TextureDescriptor ToTextureDescriptor( + FlutterVulkanTexture* embedder_desc); + std::shared_ptr CreateYUVConversion( + const impeller::ContextVK& context, + FlutterVulkanTexture* embedder_desc); + std::shared_ptr yuv_conversion_ = {}; + bool needs_yuv_conversion_ = false; + bool is_swapchain_image_ = false; + bool is_valid_ = false; + impeller::vk::Image texture_image_; + impeller::vk::DeviceMemory texture_device_memory_; + impeller::vk::UniqueImageView texture_image_view_ = {}; + EmbedderExternalTextureSourceVulkan( + const EmbedderExternalTextureSourceVulkan&) = delete; + EmbedderExternalTextureSourceVulkan& operator=( + const EmbedderExternalTextureSourceVulkan&) = delete; +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_EMBEDDER_EMBEDDER_EXTERNAL_TEXTURE_SOURCE_VULKAN_H_ diff --git a/engine/src/flutter/shell/platform/embedder/embedder_external_texture_vulkan.cc b/engine/src/flutter/shell/platform/embedder/embedder_external_texture_vulkan.cc new file mode 100644 index 0000000000000..8ed1d13295423 --- /dev/null +++ b/engine/src/flutter/shell/platform/embedder/embedder_external_texture_vulkan.cc @@ -0,0 +1,196 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/shell/platform/embedder/embedder_external_texture_vulkan.h" + +#include "flutter/display_list/image/dl_image_skia.h" +#include "flutter/fml/logging.h" +#include "flutter/impeller/display_list/dl_image_impeller.h" +#include "flutter/impeller/renderer/backend/vulkan/command_buffer_vk.h" +#include "flutter/shell/platform/embedder/embedder_external_texture_source_vulkan.h" +#include "impeller/core/texture_descriptor.h" +#include "impeller/display_list/aiks_context.h" +#include "impeller/renderer/backend/vulkan/texture_vk.h" +#include "impeller/renderer/context.h" +#include "include/core/SkCanvas.h" +#include "include/core/SkPaint.h" +#include "third_party/skia/include/core/SkAlphaType.h" +#include "third_party/skia/include/core/SkColorSpace.h" +#include "third_party/skia/include/core/SkColorType.h" +#include "third_party/skia/include/core/SkImage.h" +#include "third_party/skia/include/core/SkSize.h" +#include "third_party/skia/include/gpu/ganesh/GrBackendSurface.h" +#include "third_party/skia/include/gpu/ganesh/GrDirectContext.h" +#include "third_party/skia/include/gpu/ganesh/SkImageGanesh.h" +#include "third_party/skia/include/gpu/ganesh/vk/GrVkBackendSurface.h" +#include "third_party/skia/include/gpu/ganesh/vk/GrVkTypes.h" + +namespace flutter { +EmbedderExternalTextureVulkan::EmbedderExternalTextureVulkan( + int64_t texture_identifier, + const ExternalTextureCallback& callback) + : Texture(texture_identifier), external_texture_callback_(callback) { + FML_DCHECK(external_texture_callback_); +} + +// |flutter::Texture| +void EmbedderExternalTextureVulkan::Paint(PaintContext& context, + const DlRect& bounds, + bool freeze, + const DlImageSampling sampling) { + if (last_image_ == nullptr) { + last_image_ = + ResolveTexture(Id(), context.gr_context, context.aiks_context, + SkISize::Make(bounds.GetWidth(), bounds.GetHeight())); + } + + DlCanvas* canvas = context.canvas; + const DlPaint* paint = context.paint; + + if (last_image_) { + DlRect image_bounds = DlRect::Make(last_image_->GetBounds()); + if (bounds != image_bounds) { + canvas->DrawImageRect(last_image_, image_bounds, bounds, sampling, paint); + } else { + canvas->DrawImage(last_image_, bounds.GetOrigin(), sampling, paint); + } + } +} + +sk_sp EmbedderExternalTextureVulkan::ResolveTexture( + int64_t texture_id, + GrDirectContext* context, + impeller::AiksContext* aiks_context, + const SkISize& size) { + if (!!aiks_context) { + return ResolveTextureImpeller(texture_id, aiks_context, size); + } else { + return ResolveTextureSkia(texture_id, context, size); + } +} + +sk_sp EmbedderExternalTextureVulkan::ResolveTextureSkia( + int64_t texture_id, + GrDirectContext* context, + const SkISize& size) { + context->flushAndSubmit(); + context->resetContext(kAll_GrBackendState); + std::unique_ptr texture = + external_texture_callback_(texture_id, size.width(), size.height()); + + if (!texture) { + return nullptr; + } + + size_t width = size.width(); + size_t height = size.height(); + + if (texture->width != 0 && texture->height != 0) { + width = texture->width; + height = texture->height; + } + + GrVkImageInfo image_info = { + .fImage = reinterpret_cast(texture->image), + .fImageTiling = VK_IMAGE_TILING_OPTIMAL, + .fImageLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .fFormat = static_cast(texture->format), + .fImageUsageFlags = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | + VK_IMAGE_USAGE_TRANSFER_SRC_BIT | + VK_IMAGE_USAGE_TRANSFER_DST_BIT | + VK_IMAGE_USAGE_SAMPLED_BIT, + .fSampleCount = 1, + .fLevelCount = 1, + }; + + auto gr_backend_texture = + GrBackendTextures::MakeVk(width, height, image_info); + SkImages::TextureReleaseProc release_proc = texture->destruction_callback; + auto image = + SkImages::BorrowTextureFrom(context, // context + gr_backend_texture, // texture handle + kTopLeft_GrSurfaceOrigin, // origin + kRGB_888x_SkColorType, // color type + kPremul_SkAlphaType, // alpha type + nullptr, // colorspace + release_proc, // texture release proc + texture->user_data // texture release context + ); + + if (!image) { + // In case Skia rejects the image, call the release proc so that + // embedders can perform collection of intermediates. + if (release_proc) { + release_proc(texture->user_data); + } + return nullptr; + } + + return DlImageSkia::Make(std::move(image)); +} + +sk_sp EmbedderExternalTextureVulkan::ResolveTextureImpeller( + int64_t texture_id, + impeller::AiksContext* aiks_context, + const SkISize& size) { + std::unique_ptr texture_desc = + external_texture_callback_(texture_id, size.width(), size.height()); + if (!texture_desc) { + return nullptr; + } + + const auto& impeller_context = + impeller::ContextVK::Cast(*aiks_context->GetContext()); + + auto texture_source = std::make_shared( + aiks_context->GetContext(), texture_desc.get()); + + auto texture = std::make_shared( + aiks_context->GetContext(), texture_source); + // Transition the layout to shader read. + { + auto buffer = impeller_context.CreateCommandBuffer(); + impeller::CommandBufferVK& buffer_vk = + impeller::CommandBufferVK::Cast(*buffer); + + impeller::BarrierVK barrier; + barrier.cmd_buffer = buffer_vk.GetCommandBuffer(); + barrier.src_access = impeller::vk::AccessFlagBits::eColorAttachmentWrite | + impeller::vk::AccessFlagBits::eTransferWrite; + barrier.src_stage = + impeller::vk::PipelineStageFlagBits::eColorAttachmentOutput | + impeller::vk::PipelineStageFlagBits::eTransfer; + barrier.dst_access = impeller::vk::AccessFlagBits::eShaderRead; + barrier.dst_stage = impeller::vk::PipelineStageFlagBits::eFragmentShader; + + barrier.new_layout = impeller::vk::ImageLayout::eShaderReadOnlyOptimal; + + if (!texture_source->SetLayout(barrier).ok()) { + return nullptr; + } + if (!impeller_context.GetCommandQueue()->Submit({buffer}).ok()) { + return nullptr; + } + } + + return impeller::DlImageImpeller::Make(texture); +} + +EmbedderExternalTextureVulkan::~EmbedderExternalTextureVulkan() = default; + +// |flutter::Texture| +void EmbedderExternalTextureVulkan::OnGrContextCreated() {} + +// |flutter::Texture| +void EmbedderExternalTextureVulkan::OnGrContextDestroyed() {} + +// |flutter::Texture| +void EmbedderExternalTextureVulkan::MarkNewFrameAvailable() { + last_image_ = nullptr; +} + +// |flutter::Texture| +void EmbedderExternalTextureVulkan::OnTextureUnregistered() {} + +} // namespace flutter diff --git a/engine/src/flutter/shell/platform/embedder/embedder_external_texture_vulkan.h b/engine/src/flutter/shell/platform/embedder/embedder_external_texture_vulkan.h new file mode 100644 index 0000000000000..07611b4b729bf --- /dev/null +++ b/engine/src/flutter/shell/platform/embedder/embedder_external_texture_vulkan.h @@ -0,0 +1,64 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_SHELL_PLATFORM_EMBEDDER_EMBEDDER_EXTERNAL_TEXTURE_VULKAN_H_ +#define FLUTTER_SHELL_PLATFORM_EMBEDDER_EMBEDDER_EXTERNAL_TEXTURE_VULKAN_H_ + +#include "flutter/common/graphics/texture.h" +#include "flutter/fml/macros.h" +#include "flutter/shell/platform/embedder/embedder.h" +#include "include/core/SkTypes.h" +#include "include/gpu/vk/VulkanTypes.h" +#include "third_party/skia/include/core/SkSize.h" + +namespace flutter { + +class EmbedderExternalTextureVulkan : public flutter::Texture { + public: + using ExternalTextureCallback = std::function< + std::unique_ptr(int64_t, size_t, size_t)>; + EmbedderExternalTextureVulkan(int64_t texture_identifier, + const ExternalTextureCallback& callback); + + ~EmbedderExternalTextureVulkan(); + + private: + const ExternalTextureCallback& external_texture_callback_; + + sk_sp last_image_; + + sk_sp ResolveTexture(int64_t texture_id, + GrDirectContext* context, + impeller::AiksContext* aiks_context, + const SkISize& size); + sk_sp ResolveTextureSkia(int64_t texture_id, + GrDirectContext* context, + const SkISize& size); + sk_sp ResolveTextureImpeller(int64_t texture_id, + impeller::AiksContext* aiks_context, + const SkISize& size); + + // |flutter::Texture| + void Paint(PaintContext& context, + const DlRect& bounds, + bool freeze, + const DlImageSampling sampling) override; + + // |flutter::Texture| + void OnGrContextCreated() override; + + // |flutter::Texture| + void OnGrContextDestroyed() override; + + // |flutter::Texture| + void MarkNewFrameAvailable() override; + + // |flutter::Texture| + void OnTextureUnregistered() override; + + FML_DISALLOW_COPY_AND_ASSIGN(EmbedderExternalTextureVulkan); +}; +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_EMBEDDER_EMBEDDER_EXTERNAL_TEXTURE_VULKAN_H_ From fb31c9205bfde2bcf18fe4688864254911bda946 Mon Sep 17 00:00:00 2001 From: xiaowei guan <60122246+xiaowei-guan@users.noreply.github.com> Date: Mon, 24 Nov 2025 10:57:50 +0800 Subject: [PATCH 12/29] [Tizen] Fix external texture gl crash issue (#18) --- .../shell/platform/embedder/embedder_external_texture_gl.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/engine/src/flutter/shell/platform/embedder/embedder_external_texture_gl.cc b/engine/src/flutter/shell/platform/embedder/embedder_external_texture_gl.cc index b533f4394483f..a4bd5ee991fe3 100644 --- a/engine/src/flutter/shell/platform/embedder/embedder_external_texture_gl.cc +++ b/engine/src/flutter/shell/platform/embedder/embedder_external_texture_gl.cc @@ -71,8 +71,10 @@ sk_sp EmbedderExternalTextureGL::ResolveTexture( const SkISize& size) { if (!!aiks_context) { return ResolveTextureImpeller(texture_id, aiks_context, size); - } else { + } else if (!!context) { return ResolveTextureSkia(texture_id, context, size); + } else { + return nullptr; } } From 8ff5c617f59355d2f6d13485258a96d0acd91075 Mon Sep 17 00:00:00 2001 From: JunsuChoi Date: Fri, 5 Dec 2025 14:47:59 +0900 Subject: [PATCH 13/29] [Tizen][ci] Add ignore linker option --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cb97449ae2e30..2d4597630f465 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -65,6 +65,7 @@ jobs: cd flutter # Ignore unsupported linker option. sed -i "/-Wl,--undefined-version/d" engine/src/build/config/compiler/BUILD.gn + sed -i "s/Wno-nontrivial-memcall/Wno-nontrivial-memaccess/g" engine/src/build/config/compiler/BUILD.gn engine/src/flutter/tools/gn \ --target-os linux \ From 641d821c40b9d145af5b5f7af934957569b246e8 Mon Sep 17 00:00:00 2001 From: JunsuChoi Date: Fri, 5 Dec 2025 14:55:30 +0900 Subject: [PATCH 14/29] [Tizen] Remove unnecessary CI --- .github/release.yml | 68 --------------------------------------------- 1 file changed, 68 deletions(-) delete mode 100644 .github/release.yml diff --git a/.github/release.yml b/.github/release.yml deleted file mode 100644 index 5c6decc52930e..0000000000000 --- a/.github/release.yml +++ /dev/null @@ -1,68 +0,0 @@ -changelog: - exclude: - authors: - - engine-flutter-autoroll - - fluttergithubbot - labels: - - passed first triage - - passed secondary triage - - "team: flakes" - - revert - categories: - - title: Framework - labels: - - framework - - "a: text input" - - "f: scrolling" - - "f: routes" - - "f: selection" - - "f: gestures" - exclude: - labels: - - "f: material design" - - title: Material - labels: - - "f: material design" - # Mobile - - title: iOS - labels: - - platform-ios - - "f: cupertino" - - title: Android - labels: - - platform-android - # Desktop - - title: macOS - labels: - - platform-mac - - title: Windows - labels: - - platform-windows - - title: Linux - labels: - - platform-linux - # Web - - title: Web - labels: - - platform-web - - "browser: chrome-desktop" - - "browser: edge" - - "browser: firefox" - - "browser: safari-macos" - # Misc - - title: Tooling - labels: - - tool - - title: DevTools - labels: - - "d: devtools" - - "d: intellij" - - "d: tools_metadata" - - "f: inspector" - - title: Documentation - labels: - - "d: examples" - - "d: api docs" - - title: Other Changes - labels: - - '*' From 518e03bdc6eaf1877b9b09ab6bddd86dabdbc9aa Mon Sep 17 00:00:00 2001 From: JunsuChoi Date: Fri, 5 Dec 2025 17:07:40 +0900 Subject: [PATCH 15/29] [Tizen] Disable lto option --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2d4597630f465..0fee350a485cd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -71,6 +71,7 @@ jobs: --target-os linux \ --linux-cpu ${{ matrix.arch }} \ --no-goma \ + --no-lto \ --target-toolchain /tizen_tools/toolchains \ --target-sysroot /tizen_tools/sysroot/${{ matrix.arch }} \ --target-triple ${{ matrix.triple }} \ From ca7711f70dd84671ddb70a8d9d035f71f7436f83 Mon Sep 17 00:00:00 2001 From: JunsuChoi Date: Fri, 12 Dec 2025 19:02:34 +0900 Subject: [PATCH 16/29] [Tizen][ci] Update macos runnner for intel mac Replaced macos-13 with macos-15-intel https://github.com/actions/runner-images/issues/13046 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0fee350a485cd..67a4a1cfcec32 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -269,7 +269,7 @@ jobs: if-no-files-found: error macos-intel-build: - runs-on: macos-13 + runs-on: macos-15-intel strategy: matrix: From 77fefd1c8ed230a134a63d5a9970b5a2649ed961 Mon Sep 17 00:00:00 2001 From: xiaowei guan <60122246+xiaowei-guan@users.noreply.github.com> Date: Tue, 23 Dec 2025 16:30:24 +0800 Subject: [PATCH 17/29] [Tizen] Fix GPU memory leak issue (#21) Because Tizen platform don't support hardware key, so we can't create a cache source to store the gpu resource. so we should reset the resouce before the frame end. --- .../platform/embedder/embedder_external_texture_vulkan.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/engine/src/flutter/shell/platform/embedder/embedder_external_texture_vulkan.cc b/engine/src/flutter/shell/platform/embedder/embedder_external_texture_vulkan.cc index 8ed1d13295423..0c2f05177cbaf 100644 --- a/engine/src/flutter/shell/platform/embedder/embedder_external_texture_vulkan.cc +++ b/engine/src/flutter/shell/platform/embedder/embedder_external_texture_vulkan.cc @@ -140,7 +140,7 @@ sk_sp EmbedderExternalTextureVulkan::ResolveTextureImpeller( return nullptr; } - const auto& impeller_context = + auto& impeller_context = impeller::ContextVK::Cast(*aiks_context->GetContext()); auto texture_source = std::make_shared( @@ -173,7 +173,7 @@ sk_sp EmbedderExternalTextureVulkan::ResolveTextureImpeller( return nullptr; } } - + impeller_context.DisposeThreadLocalCachedResources(); return impeller::DlImageImpeller::Make(texture); } From fe13bd4508e2134f6549279d9501da3c70d3223c Mon Sep 17 00:00:00 2001 From: JunsuChoi Date: Wed, 11 Mar 2026 17:18:27 +0900 Subject: [PATCH 18/29] [Tizen] Add syscall compat header for custom Linux toolchains Inject a small compat header into the custom cross-toolchain command line so Linux targets built with Tizen sysroots still see __NR_getrandom even when the sysroot headers do not export it. This keeps third_party/dart untouched and fixes builds across common Tizen architectures: - x86: 355 - x64: 318 - arm: 384 - arm64: 278 --- engine/src/build/toolchain/custom/BUILD.gn | 9 +++-- .../toolchain/custom/tizen_syscall_compat.h | 39 +++++++++++++++++++ 2 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 engine/src/build/toolchain/custom/tizen_syscall_compat.h diff --git a/engine/src/build/toolchain/custom/BUILD.gn b/engine/src/build/toolchain/custom/BUILD.gn index c5d2a60af752b..5011fa2ae6d63 100644 --- a/engine/src/build/toolchain/custom/BUILD.gn +++ b/engine/src/build/toolchain/custom/BUILD.gn @@ -25,6 +25,9 @@ toolchain("custom") { target_triple_flags = "--target=${custom_target_triple}" sysroot_flags = "--sysroot ${custom_sysroot}" + syscall_compat_header = + rebase_path("//build/toolchain/custom/tizen_syscall_compat.h", root_build_dir) + syscall_compat_flags = "-include ${syscall_compat_header}" custom_lib_flags = "-L${custom_toolchain}/lib" @@ -34,7 +37,7 @@ toolchain("custom") { tool("cc") { depfile = "{{output}}.d" - command = "$cc -MD -MF $depfile $target_triple_flags $sysroot_flags {{defines}} {{include_dirs}} {{cflags}} {{cflags_c}} -c {{source}} -o {{output}}" + command = "$cc -MD -MF $depfile $target_triple_flags $sysroot_flags $syscall_compat_flags {{defines}} {{include_dirs}} {{cflags}} {{cflags_c}} -c {{source}} -o {{output}}" depsformat = "gcc" description = "CC {{output}}" outputs = @@ -43,7 +46,7 @@ toolchain("custom") { tool("cxx") { depfile = "{{output}}.d" - command = "$cxx -MD -MF $depfile $target_triple_flags $sysroot_flags {{defines}} {{include_dirs}} {{cflags}} {{cflags_cc}} -c {{source}} -o {{output}}" + command = "$cxx -MD -MF $depfile $target_triple_flags $sysroot_flags $syscall_compat_flags {{defines}} {{include_dirs}} {{cflags}} {{cflags_cc}} -c {{source}} -o {{output}}" depsformat = "gcc" description = "CXX {{output}}" outputs = @@ -52,7 +55,7 @@ toolchain("custom") { tool("asm") { depfile = "{{output}}.d" - command = "$cc -MD -MF $depfile $target_triple_flags $sysroot_flags {{defines}} {{include_dirs}} {{asmflags}} {{cflags}} {{cflags_c}} -c {{source}} -o {{output}}" + command = "$cc -MD -MF $depfile $target_triple_flags $sysroot_flags $syscall_compat_flags {{defines}} {{include_dirs}} {{asmflags}} {{cflags}} {{cflags_c}} -c {{source}} -o {{output}}" depsformat = "gcc" description = "ASM {{output}}" outputs = diff --git a/engine/src/build/toolchain/custom/tizen_syscall_compat.h b/engine/src/build/toolchain/custom/tizen_syscall_compat.h new file mode 100644 index 0000000000000..b6de9aeb0265e --- /dev/null +++ b/engine/src/build/toolchain/custom/tizen_syscall_compat.h @@ -0,0 +1,39 @@ +// Copyright 2026 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 BUILD_TOOLCHAIN_CUSTOM_TIZEN_SYSCALL_COMPAT_H_ +#define BUILD_TOOLCHAIN_CUSTOM_TIZEN_SYSCALL_COMPAT_H_ + +// Some Tizen cross-compilation sysroots do not expose __NR_getrandom even +// though the target Linux ABI supports the syscall. Dart's crypto_linux.cc +// references the syscall number directly, so provide the missing arch-specific +// values for custom Linux toolchains without modifying third_party sources. +#if defined(__linux__) + +#if defined(__i386__) && !defined(__NR_getrandom) +#define __NR_getrandom 355 +#endif + +#if defined(__x86_64__) && !defined(__NR_getrandom) +#define __NR_getrandom 318 +#endif + +#if defined(__arm__) && !defined(__NR_getrandom) +#define __NR_getrandom 384 +#endif + +#if defined(__aarch64__) && !defined(__NR_getrandom) +#define __NR_getrandom 278 +#endif + +// Some Tizen sysroots ship a pre-3.17 UAPI header that +// predates the getrandom(2) flags. BoringSSL's urandom.cc references +// GRND_NONBLOCK directly, so provide the missing flag for custom toolchains. +#if !defined(GRND_NONBLOCK) +#define GRND_NONBLOCK 0x0001 +#endif + +#endif // defined(__linux__) + +#endif // BUILD_TOOLCHAIN_CUSTOM_TIZEN_SYSCALL_COMPAT_H_ From 0516eb7e742c3ed0a1be51d5a7e92e99de431915 Mon Sep 17 00:00:00 2001 From: JunsuChoi Date: Tue, 24 Mar 2026 16:10:20 +0900 Subject: [PATCH 19/29] [Tizen] Remove unnecessary CI (#28) Remove unnecessary CI --- .github/workflows/lock.yaml | 35 ----------------------------------- 1 file changed, 35 deletions(-) delete mode 100644 .github/workflows/lock.yaml diff --git a/.github/workflows/lock.yaml b/.github/workflows/lock.yaml deleted file mode 100644 index e1b9355be94dd..0000000000000 --- a/.github/workflows/lock.yaml +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright 2013 The Flutter Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -# Configuration for Lock Threads - https://github.com/dessant/lock-threads - -name: 'Lock Threads' - -# By specifying the access of one of the scopes, all of those that are not -# specified are set to 'none'. -permissions: - issues: write - -on: - schedule: - - cron: '0 * * * *' - -jobs: - lock: - permissions: - issues: write - runs-on: ubuntu-latest - if: ${{ github.repository == 'flutter/flutter' }} - steps: - - uses: dessant/lock-threads@7266a7ce5c1df01b1c6db85bf8cd86c737dadbe7 - with: - process-only: 'issues' - github-token: ${{ github.token }} - # Number of days of inactivity before a closed issue is locked. - issue-inactive-days: '14' - issue-comment: > - This thread has been automatically locked since there has not been - any recent activity after it was closed. If you are still experiencing a - similar issue, please open a new bug, including the output of - `flutter doctor -v` and a minimal reproduction of the issue. From a46bcb761f42063bfaa713b54f5d062a8391ee0e Mon Sep 17 00:00:00 2001 From: JunsuChoi Date: Fri, 27 Mar 2026 13:37:14 +0900 Subject: [PATCH 20/29] [Tizen] CI host artifact builds for macOS (#29) Adds artifact generation for x64 builds in a macOS environment. I tested the build on macOS 26.3 (Tahoe) and M4 Pro (Arm64) using the x64 lib --- .github/workflows/build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 67a4a1cfcec32..d18770b52b733 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -216,7 +216,7 @@ jobs: strategy: matrix: - arch: [arm, arm64] + arch: [arm, arm64, x64] mode: [release, profile] steps: @@ -273,7 +273,7 @@ jobs: strategy: matrix: - arch: [arm, arm64] + arch: [arm, arm64, x64] mode: [release, profile] steps: @@ -326,7 +326,7 @@ jobs: if-no-files-found: error release: - needs: [build] + needs: [build, macos-build, macos-intel-build] if: ${{ github.event_name == 'push' }} runs-on: ubuntu-latest From 1319efc2ef33ba194a6f884ec72d98c0a88dc4ca Mon Sep 17 00:00:00 2001 From: JunsuChoi Date: Fri, 3 Apr 2026 16:42:45 +0900 Subject: [PATCH 21/29] [Tizen] CI host artifact builds for Windows x64 (#30) --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d18770b52b733..7c4027fcfcda0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -148,7 +148,7 @@ jobs: strategy: matrix: - arch: [arm, arm64] + arch: [arm, arm64, x64] mode: [release, profile] steps: @@ -326,7 +326,7 @@ jobs: if-no-files-found: error release: - needs: [build, macos-build, macos-intel-build] + needs: [build, windows-build, macos-build, macos-intel-build] if: ${{ github.event_name == 'push' }} runs-on: ubuntu-latest From 6535d0515810d8f22bd24997a1cfa1ce0796bd62 Mon Sep 17 00:00:00 2001 From: JunsuChoi Date: Mon, 8 Jun 2026 12:15:12 +0900 Subject: [PATCH 22/29] [Tizen] Fix VkImage leak in EmbedderExternalTextureSourceVulkan Store the embedder's destruction_callback / user_data and invoke it in the destructor to reclaim the VkImage; the Impeller path never released it. Reset the image view before the callback frees the image. --- .../embedder_external_texture_source_vulkan.cc | 12 +++++++++--- .../embedder_external_texture_source_vulkan.h | 2 ++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/engine/src/flutter/shell/platform/embedder/embedder_external_texture_source_vulkan.cc b/engine/src/flutter/shell/platform/embedder/embedder_external_texture_source_vulkan.cc index 565a48abf6477..23c50eb025be6 100644 --- a/engine/src/flutter/shell/platform/embedder/embedder_external_texture_source_vulkan.cc +++ b/engine/src/flutter/shell/platform/embedder/embedder_external_texture_source_vulkan.cc @@ -30,7 +30,9 @@ bool RequiresYCBCRConversion(impeller::vk::Format format) { EmbedderExternalTextureSourceVulkan::EmbedderExternalTextureSourceVulkan( const std::shared_ptr& p_context, FlutterVulkanTexture* embedder_desc) - : TextureSourceVK(ToTextureDescriptor(embedder_desc)) { + : TextureSourceVK(ToTextureDescriptor(embedder_desc)), + destruction_callback_(embedder_desc->destruction_callback), + destruction_callback_user_data_(embedder_desc->user_data) { const auto& context = impeller::ContextVK::Cast(*p_context); const auto& device = context.GetDevice(); texture_image_ = @@ -166,8 +168,12 @@ bool EmbedderExternalTextureSourceVulkan::CreateTextureImageView( } // |TextureSourceVK| -EmbedderExternalTextureSourceVulkan::~EmbedderExternalTextureSourceVulkan() = - default; +EmbedderExternalTextureSourceVulkan::~EmbedderExternalTextureSourceVulkan() { + texture_image_view_.reset(); + if (destruction_callback_) { + destruction_callback_(destruction_callback_user_data_); + } +} bool EmbedderExternalTextureSourceVulkan::IsValid() const { return is_valid_; diff --git a/engine/src/flutter/shell/platform/embedder/embedder_external_texture_source_vulkan.h b/engine/src/flutter/shell/platform/embedder/embedder_external_texture_source_vulkan.h index b223b694f4d5a..1aee11fb835f9 100644 --- a/engine/src/flutter/shell/platform/embedder/embedder_external_texture_source_vulkan.h +++ b/engine/src/flutter/shell/platform/embedder/embedder_external_texture_source_vulkan.h @@ -61,6 +61,8 @@ class EmbedderExternalTextureSourceVulkan final impeller::vk::Image texture_image_; impeller::vk::DeviceMemory texture_device_memory_; impeller::vk::UniqueImageView texture_image_view_ = {}; + VoidCallback destruction_callback_ = nullptr; + void* destruction_callback_user_data_ = nullptr; EmbedderExternalTextureSourceVulkan( const EmbedderExternalTextureSourceVulkan&) = delete; EmbedderExternalTextureSourceVulkan& operator=( From ded1372db5f18ef7da12a3165f8aff410f9fd4d3 Mon Sep 17 00:00:00 2001 From: JunsuChoi Date: Mon, 8 Jun 2026 14:07:42 +0900 Subject: [PATCH 23/29] [Tizen] Validate external Vulkan texture before use Reject a null embedder image handle in the texture source constructor and skip wrapping an invalid source into a TextureVK, returning nullptr instead of rendering with a half-initialized texture. --- .../embedder/embedder_external_texture_source_vulkan.cc | 4 ++++ .../platform/embedder/embedder_external_texture_vulkan.cc | 3 +++ 2 files changed, 7 insertions(+) diff --git a/engine/src/flutter/shell/platform/embedder/embedder_external_texture_source_vulkan.cc b/engine/src/flutter/shell/platform/embedder/embedder_external_texture_source_vulkan.cc index 23c50eb025be6..e17f849c6c93d 100644 --- a/engine/src/flutter/shell/platform/embedder/embedder_external_texture_source_vulkan.cc +++ b/engine/src/flutter/shell/platform/embedder/embedder_external_texture_source_vulkan.cc @@ -33,6 +33,10 @@ EmbedderExternalTextureSourceVulkan::EmbedderExternalTextureSourceVulkan( : TextureSourceVK(ToTextureDescriptor(embedder_desc)), destruction_callback_(embedder_desc->destruction_callback), destruction_callback_user_data_(embedder_desc->user_data) { + if (!embedder_desc->image) { + VALIDATION_LOG << "Vulkan image handle is null"; + return; + } const auto& context = impeller::ContextVK::Cast(*p_context); const auto& device = context.GetDevice(); texture_image_ = diff --git a/engine/src/flutter/shell/platform/embedder/embedder_external_texture_vulkan.cc b/engine/src/flutter/shell/platform/embedder/embedder_external_texture_vulkan.cc index 0c2f05177cbaf..12589b0e31198 100644 --- a/engine/src/flutter/shell/platform/embedder/embedder_external_texture_vulkan.cc +++ b/engine/src/flutter/shell/platform/embedder/embedder_external_texture_vulkan.cc @@ -145,6 +145,9 @@ sk_sp EmbedderExternalTextureVulkan::ResolveTextureImpeller( auto texture_source = std::make_shared( aiks_context->GetContext(), texture_desc.get()); + if (!texture_source->IsValid()) { + return nullptr; + } auto texture = std::make_shared( aiks_context->GetContext(), texture_source); From 597589b9a908b0d9d4e2c14338af9efc5a32549b Mon Sep 17 00:00:00 2001 From: JunsuChoi Date: Tue, 9 Jun 2026 19:55:41 +0900 Subject: [PATCH 24/29] [Tizen] Support GL_TEXTURE_EXTERNAL_OES for impeller external texture The flutter-tizen embedder passes target=GL_TEXTURE_EXTERNAL_OES, so set the descriptor type to kTextureExternalOES instead of the default 2D. --- .../shell/platform/embedder/embedder_external_texture_gl.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/engine/src/flutter/shell/platform/embedder/embedder_external_texture_gl.cc b/engine/src/flutter/shell/platform/embedder/embedder_external_texture_gl.cc index a4bd5ee991fe3..404c8ac5b50e0 100644 --- a/engine/src/flutter/shell/platform/embedder/embedder_external_texture_gl.cc +++ b/engine/src/flutter/shell/platform/embedder/embedder_external_texture_gl.cc @@ -156,6 +156,9 @@ sk_sp EmbedderExternalTextureGL::ResolveTextureImpeller( impeller::TextureDescriptor desc; desc.size = impeller::ISize(texture->width, texture->height); desc.format = impeller::PixelFormat::kR8G8B8A8UNormInt; + if (texture->target == GL_TEXTURE_EXTERNAL_OES) { + desc.type = impeller::TextureType::kTextureExternalOES; + } impeller::ContextGLES& context = impeller::ContextGLES::Cast(*aiks_context->GetContext()); From c9d5f03626fa799444180ec5c4deacc665252360 Mon Sep 17 00:00:00 2001 From: JunsuChoi Date: Thu, 2 Jul 2026 20:35:33 +0900 Subject: [PATCH 25/29] [Tizen] Use HTTPS and avoid shell=True in sysroot generator Download packages from download.tizen.org over HTTPS instead of plain HTTP, and run the patch command with an argument list and stdin redirection instead of shell=True string interpolation. --- engine/src/flutter/ci/tizen/generate_sysroot.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/engine/src/flutter/ci/tizen/generate_sysroot.py b/engine/src/flutter/ci/tizen/generate_sysroot.py index 210f7e9ecf9e7..8aa3ccd211396 100755 --- a/engine/src/flutter/ci/tizen/generate_sysroot.py +++ b/engine/src/flutter/ci/tizen/generate_sysroot.py @@ -41,10 +41,10 @@ def generate_sysroot(sysroot: Path, api_version: float, arch: str, quiet=False): else: sys.exit('Unknown arch: ' + arch) - base_repo = 'http://download.tizen.org/snapshots/TIZEN/Tizen-{}/Tizen-{}-Base/reference/repos/standard/packages'.format( + base_repo = 'https://download.tizen.org/snapshots/TIZEN/Tizen-{}/Tizen-{}-Base/reference/repos/standard/packages'.format( api_version, api_version ) - unified_repo = 'http://download.tizen.org/snapshots/TIZEN/Tizen-{}/Tizen-{}-Unified/reference/repos/standard/packages'.format( + unified_repo = 'https://download.tizen.org/snapshots/TIZEN/Tizen-{}/Tizen-{}-Unified/reference/repos/standard/packages'.format( api_version, api_version ) @@ -104,8 +104,9 @@ def generate_sysroot(sysroot: Path, api_version: float, arch: str, quiet=False): # Apply a patch if applicable. patch = Path(__file__).parent / '{}.patch'.format(arch) if patch.is_file(): - command = 'patch -p1 -s -d {} < {}'.format(sysroot, patch) - subprocess.run(command, shell=True, check=True) + with open(patch) as file: + subprocess.run(['patch', '-p1', '-s', '-d', str(sysroot)], + stdin=file, check=True) def main(): From 5dae7503472810cca00f2f8005932ea012c99d3a Mon Sep 17 00:00:00 2001 From: JunsuChoi Date: Thu, 2 Jul 2026 20:35:51 +0900 Subject: [PATCH 26/29] [Tizen] Harden Vulkan external texture resolution - Null-check the command buffer returned by CreateCommandBuffer() before dereferencing it in the Impeller path. It can be null when the context is being torn down. - Fall back to the engine-provided size when the embedder does not populate the texture width/height, matching the Skia path. --- .../platform/embedder/embedder_external_texture_vulkan.cc | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/engine/src/flutter/shell/platform/embedder/embedder_external_texture_vulkan.cc b/engine/src/flutter/shell/platform/embedder/embedder_external_texture_vulkan.cc index 12589b0e31198..528f16ee1a7a4 100644 --- a/engine/src/flutter/shell/platform/embedder/embedder_external_texture_vulkan.cc +++ b/engine/src/flutter/shell/platform/embedder/embedder_external_texture_vulkan.cc @@ -140,6 +140,11 @@ sk_sp EmbedderExternalTextureVulkan::ResolveTextureImpeller( return nullptr; } + if (texture_desc->width == 0 || texture_desc->height == 0) { + texture_desc->width = size.width(); + texture_desc->height = size.height(); + } + auto& impeller_context = impeller::ContextVK::Cast(*aiks_context->GetContext()); @@ -154,6 +159,9 @@ sk_sp EmbedderExternalTextureVulkan::ResolveTextureImpeller( // Transition the layout to shader read. { auto buffer = impeller_context.CreateCommandBuffer(); + if (!buffer) { + return nullptr; + } impeller::CommandBufferVK& buffer_vk = impeller::CommandBufferVK::Cast(*buffer); From 1bb4478a7c0bbfb6236533073061551812a2144c Mon Sep 17 00:00:00 2001 From: xiaowei guan <60122246+xiaowei-guan@users.noreply.github.com> Date: Fri, 10 Jul 2026 08:42:54 +0800 Subject: [PATCH 27/29] [Impeller] Use the IO context for OpenGL program setup (#37) Fix https://github.com/flutter-tizen/embedder/issues/157 Impeller eagerly sets up the entire set of pipeline state objects (PSOs) it may need during renderer setup. This works fine on mid to high-end devices as the setup completes well before the first frame is rendered. However on low-end devices, not all PSOs may be ready before the first frame is rendered. Low-end device usually don't have as much available concurrency and are slower to boot. On these devices, the rendering thread had to wait for the background compile job to be completed. It was also observed that the relatively higher priority render thread was waiting on a background thread to finish a PSO compile job. The PSO compile job could also be stuck behind another job that was not immediately needed. This made the situation on low end devices less than ideal. https://github.com/flutter/flutter/pull/180022, this PR fix PSO stuck issue for Vulkan on low end devices. Current PR can fix the PSO stuck issue for GLES on low end devices. Related issue : https://github.com/flutter/flutter/issues/176657 This PR can improve app startup time for impeller+GLES on low-end devices. https://github.com/flutter/flutter/pull/180022 has provided solution for impeller + vulkan. Impeller+GLES has a special case: resource loading cannot be arbitrarily placed on other threads; it needs to be done on the I/O thread because this thread shares the context with the GPU thread. Therefore, this PR introduces the I/O task runner into PipelineCompileQueueGLES. However, since the I/O task runner may handle other tasks, we cannot put all jobs into the I/O thread at once. My current solution is to wait for the previous job to finish before putting the next one into the I/O thread. Main changes: Introduce fml::RefPtrfml::TaskRunner into PipelineCompileQueueGLES. Extract new class PipelineCompileQueueVulkan for vulkan, because the runners are on different threads. (cherry picked from commit 92a84309326c027c621dddd962485a920a9a5a2a) --- engine/src/flutter/impeller/renderer/BUILD.gn | 1 + .../impeller/renderer/backend/gles/BUILD.gn | 4 + .../renderer/backend/gles/context_gles.cc | 15 +- .../renderer/backend/gles/context_gles.h | 6 +- .../gles/pipeline_compile_queue_gles.cc | 72 +++++++ .../gles/pipeline_compile_queue_gles.h | 63 ++++++ .../backend/gles/pipeline_library_gles.cc | 51 +++-- .../backend/gles/pipeline_library_gles.h | 8 +- .../pipeline_compile_queue_gles_unittests.cc | 185 ++++++++++++++++++ .../impeller/renderer/backend/vulkan/BUILD.gn | 3 + .../vulkan/pipeline_compile_queue_vulkan.cc | 45 +++++ .../vulkan/pipeline_compile_queue_vulkan.h | 56 ++++++ .../backend/vulkan/pipeline_library_vk.cc | 2 +- .../backend/vulkan/pipeline_library_vk.h | 4 +- ...pipeline_compile_queue_vulkan_unittests.cc | 181 +++++++++++++++++ .../renderer/pipeline_compile_queue.cc | 50 ++--- .../renderer/pipeline_compile_queue.h | 71 +++++-- .../pipeline_compile_queue_unittests.cc | 96 +++++++++ .../common/shell_test_platform_view_gl.cc | 3 +- .../android_context_dynamic_impeller.cc | 8 +- .../android_context_dynamic_impeller.h | 4 +- .../android/android_context_gl_impeller.cc | 21 +- .../android/android_context_gl_impeller.h | 5 +- .../platform/android/platform_view_android.cc | 13 +- .../shell/platform/embedder/embedder.cc | 1 + .../embedder/embedder_surface_gl_impeller.cc | 3 +- .../embedder/embedder_surface_gl_impeller.h | 1 + .../embedder_test_surface_gl_impeller.cc | 6 +- 28 files changed, 886 insertions(+), 92 deletions(-) create mode 100644 engine/src/flutter/impeller/renderer/backend/gles/pipeline_compile_queue_gles.cc create mode 100644 engine/src/flutter/impeller/renderer/backend/gles/pipeline_compile_queue_gles.h create mode 100644 engine/src/flutter/impeller/renderer/backend/gles/test/pipeline_compile_queue_gles_unittests.cc create mode 100644 engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_compile_queue_vulkan.cc create mode 100644 engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_compile_queue_vulkan.h create mode 100644 engine/src/flutter/impeller/renderer/backend/vulkan/test/pipeline_compile_queue_vulkan_unittests.cc create mode 100644 engine/src/flutter/impeller/renderer/pipeline_compile_queue_unittests.cc diff --git a/engine/src/flutter/impeller/renderer/BUILD.gn b/engine/src/flutter/impeller/renderer/BUILD.gn index 4c9190707e339..d92239a3bf43c 100644 --- a/engine/src/flutter/impeller/renderer/BUILD.gn +++ b/engine/src/flutter/impeller/renderer/BUILD.gn @@ -108,6 +108,7 @@ template("renderer_unittests_component") { "blit_pass_unittests.cc", "capabilities_unittests.cc", "device_buffer_unittests.cc", + "pipeline_compile_queue_unittests.cc", "pipeline_descriptor_unittests.cc", "pipeline_library_unittests.cc", "pool_unittests.cc", diff --git a/engine/src/flutter/impeller/renderer/backend/gles/BUILD.gn b/engine/src/flutter/impeller/renderer/backend/gles/BUILD.gn index deec3f1c2fd69..6ae9f50baec3d 100644 --- a/engine/src/flutter/impeller/renderer/backend/gles/BUILD.gn +++ b/engine/src/flutter/impeller/renderer/backend/gles/BUILD.gn @@ -25,6 +25,7 @@ impeller_component("gles_unittests") { "test/mock_gles.cc", "test/mock_gles.h", "test/mock_gles_unittests.cc", + "test/pipeline_compile_queue_gles_unittests.cc", "test/pipeline_library_gles_unittests.cc", "test/proc_table_gles_unittests.cc", "test/reactor_unittests.cc", @@ -35,6 +36,7 @@ impeller_component("gles_unittests") { ] deps = [ ":gles", + "//flutter/fml", "//flutter/impeller/playground:playground_test", "//flutter/testing:testing_lib", ] @@ -69,6 +71,8 @@ impeller_component("gles") { "gpu_tracer_gles.h", "handle_gles.cc", "handle_gles.h", + "pipeline_compile_queue_gles.cc", + "pipeline_compile_queue_gles.h", "pipeline_gles.cc", "pipeline_gles.h", "pipeline_library_gles.cc", diff --git a/engine/src/flutter/impeller/renderer/backend/gles/context_gles.cc b/engine/src/flutter/impeller/renderer/backend/gles/context_gles.cc index 849e574cce7ca..d7efc6abb329b 100644 --- a/engine/src/flutter/impeller/renderer/backend/gles/context_gles.cc +++ b/engine/src/flutter/impeller/renderer/backend/gles/context_gles.cc @@ -22,16 +22,19 @@ std::shared_ptr ContextGLES::Create( const Flags& flags, std::unique_ptr gl, const std::vector>& shader_libraries, - bool enable_gpu_tracing) { - return std::shared_ptr(new ContextGLES( - flags, std::move(gl), shader_libraries, enable_gpu_tracing)); + bool enable_gpu_tracing, + fml::RefPtr io_task_runner) { + return std::shared_ptr( + new ContextGLES(flags, std::move(gl), shader_libraries, + enable_gpu_tracing, std::move(io_task_runner))); } ContextGLES::ContextGLES( const Flags& flags, std::unique_ptr gl, const std::vector>& shader_libraries_mappings, - bool enable_gpu_tracing) + bool enable_gpu_tracing, + fml::RefPtr io_task_runner) : Context(flags) { reactor_ = std::make_shared(std::move(gl)); if (!reactor_->IsValid()) { @@ -52,8 +55,8 @@ ContextGLES::ContextGLES( // Create the pipeline library. { - pipeline_library_ = - std::shared_ptr(new PipelineLibraryGLES(reactor_)); + pipeline_library_ = std::shared_ptr( + new PipelineLibraryGLES(reactor_, std::move(io_task_runner))); } // Create allocators. diff --git a/engine/src/flutter/impeller/renderer/backend/gles/context_gles.h b/engine/src/flutter/impeller/renderer/backend/gles/context_gles.h index 1f23b762a119d..174434133f760 100644 --- a/engine/src/flutter/impeller/renderer/backend/gles/context_gles.h +++ b/engine/src/flutter/impeller/renderer/backend/gles/context_gles.h @@ -28,7 +28,8 @@ class ContextGLES final : public Context, const Flags& flags, std::unique_ptr gl, const std::vector>& shader_libraries, - bool enable_gpu_tracing); + bool enable_gpu_tracing, + fml::RefPtr io_task_runner = nullptr); // |Context| ~ContextGLES() override; @@ -64,7 +65,8 @@ class ContextGLES final : public Context, const Flags& flags, std::unique_ptr gl, const std::vector>& shader_libraries, - bool enable_gpu_tracing); + bool enable_gpu_tracing, + fml::RefPtr io_task_runner = nullptr); // |Context| std::string DescribeGpuModel() const override; diff --git a/engine/src/flutter/impeller/renderer/backend/gles/pipeline_compile_queue_gles.cc b/engine/src/flutter/impeller/renderer/backend/gles/pipeline_compile_queue_gles.cc new file mode 100644 index 0000000000000..eeb971ab09751 --- /dev/null +++ b/engine/src/flutter/impeller/renderer/backend/gles/pipeline_compile_queue_gles.cc @@ -0,0 +1,72 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "impeller/renderer/backend/gles/pipeline_compile_queue_gles.h" + +#include "flutter/fml/logging.h" +#include "flutter/fml/trace_event.h" +#include "impeller/base/validation.h" + +namespace impeller { + +std::shared_ptr PipelineCompileQueueGLES::Create( + fml::RefPtr worker_task_runner) { + if (!worker_task_runner) { + return nullptr; + } + return std::shared_ptr( + new PipelineCompileQueueGLES(std::move(worker_task_runner))); +} + +PipelineCompileQueueGLES::PipelineCompileQueueGLES( + fml::RefPtr worker_task_runner) + : worker_task_runner_(std::move(worker_task_runner)) {} + +PipelineCompileQueueGLES::~PipelineCompileQueueGLES() = default; + +void PipelineCompileQueueGLES::OnJobAdded() { + // To prevent potential deadlocks and reduce lock contention, avoid calling + // external or virtual methods (such as DrainPendingJobs, which posts tasks + // to the task runner) while holding a mutex. Instead, minimize the scope of + // the lock by using a local boolean flag to trigger the draining process + // outside the lock block. + bool should_drain = false; + { + Lock lock(processing_mutex_); + if (!is_processing_) { + is_processing_ = true; + should_drain = true; + } + } + if (should_drain) { + DrainPendingJobs(); + } +} + +void PipelineCompileQueueGLES::PostJob(const fml::closure& job) { + if (!job) { + return; + } + + worker_task_runner_->PostTask(job); +} + +void PipelineCompileQueueGLES::DrainPendingJobs() { + PostJob([weak_queue = weak_from_this()]() { + if (auto queue = std::static_pointer_cast( + weak_queue.lock())) { + queue->DoOneJob(); + { + Lock lock(queue->processing_mutex_); + if (!queue->HasPendingJobs()) { + queue->is_processing_ = false; + return; + } + } + queue->DrainPendingJobs(); + } + }); +} + +} // namespace impeller diff --git a/engine/src/flutter/impeller/renderer/backend/gles/pipeline_compile_queue_gles.h b/engine/src/flutter/impeller/renderer/backend/gles/pipeline_compile_queue_gles.h new file mode 100644 index 0000000000000..138b5398ea301 --- /dev/null +++ b/engine/src/flutter/impeller/renderer/backend/gles/pipeline_compile_queue_gles.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_IMPELLER_RENDERER_BACKEND_GLES_PIPELINE_COMPILE_QUEUE_GLES_H_ +#define FLUTTER_IMPELLER_RENDERER_BACKEND_GLES_PIPELINE_COMPILE_QUEUE_GLES_H_ + +#include "flutter/fml/closure.h" +#include "flutter/fml/task_runner.h" +#include "impeller/base/thread.h" +#include "impeller/renderer/pipeline_compile_queue.h" + +namespace impeller { + +//------------------------------------------------------------------------------ +/// @brief A task queue designed for managing compilation of pipeline state +/// objects for OpenGL ES backend. +/// +/// This subclass uses a fml::TaskRunner as the worker task runner +/// and implements a sequential job processing mechanism to prevent +/// blocking the IO task runner. +/// +/// Key characteristics: +/// - Uses fml::RefPtr for worker_task_runner_ +/// - Processes jobs sequentially: loads one job at a time before +/// proceeding to the next, preventing IO task runner blocking +/// - Uses DrainPendingJobs() to recursively process jobs one by one +/// - Employs is_processing_ flag and processing_mutex_ to control +/// sequential processing +/// +/// The sequential processing ensures that pipeline compilation jobs +/// do not overwhelm the task runner, which is particularly +/// important for GLES backend where resource loading patterns +/// differ from Vulkan. +/// +class PipelineCompileQueueGLES : public PipelineCompileQueue { + public: + static std::shared_ptr Create( + fml::RefPtr worker_task_runner); + + ~PipelineCompileQueueGLES() override; + + PipelineCompileQueueGLES(const PipelineCompileQueueGLES&) = delete; + + PipelineCompileQueueGLES& operator=(const PipelineCompileQueueGLES&) = delete; + + void PostJob(const fml::closure& job) override; + + void OnJobAdded() override; + + private: + explicit PipelineCompileQueueGLES( + fml::RefPtr worker_task_runner); + void DrainPendingJobs(); + + fml::RefPtr worker_task_runner_; + Mutex processing_mutex_; + bool is_processing_ IPLR_GUARDED_BY(processing_mutex_) = false; +}; + +} // namespace impeller + +#endif // FLUTTER_IMPELLER_RENDERER_BACKEND_GLES_PIPELINE_COMPILE_QUEUE_GLES_H_ diff --git a/engine/src/flutter/impeller/renderer/backend/gles/pipeline_library_gles.cc b/engine/src/flutter/impeller/renderer/backend/gles/pipeline_library_gles.cc index 240891dfc4b60..3cad5620e143c 100644 --- a/engine/src/flutter/impeller/renderer/backend/gles/pipeline_library_gles.cc +++ b/engine/src/flutter/impeller/renderer/backend/gles/pipeline_library_gles.cc @@ -16,8 +16,12 @@ namespace impeller { -PipelineLibraryGLES::PipelineLibraryGLES(std::shared_ptr reactor) - : reactor_(std::move(reactor)) {} +PipelineLibraryGLES::PipelineLibraryGLES( + std::shared_ptr reactor, + fml::RefPtr io_task_runner) + : reactor_(std::move(reactor)), + compile_queue_( + PipelineCompileQueueGLES::Create(std::move(io_task_runner))) {} static std::string GetShaderInfoLog(const ProcTableGLES& gl, GLuint shader) { GLint log_length = 0; @@ -296,17 +300,34 @@ PipelineFuture PipelineLibraryGLES::GetPipeline( PipelineFuture{descriptor, promise->get_future()}; pipelines_[descriptor] = pipeline_future; - const auto result = reactor_->AddOperation([promise, // - weak_this = weak_from_this(), // - descriptor, // - vert_function, // - frag_function, // - threadsafe // - ](const ReactorGLES& reactor) { - promise->set_value(CreatePipeline(weak_this, descriptor, vert_function, - frag_function, threadsafe)); - }); - FML_CHECK(result); + auto weak_this = weak_from_this(); + auto reactor = reactor_; + auto generation_task = [promise, weak_this, descriptor, vert_function, + frag_function, threadsafe, reactor]() { + auto thiz = weak_this.lock(); + if (!thiz) { + promise->set_value(nullptr); + return; + } + const auto result = reactor->AddOperation([promise, // + weak_this, // + descriptor, // + vert_function, // + frag_function, // + threadsafe // + ](const ReactorGLES& reactor) { + promise->set_value(CreatePipeline(weak_this, descriptor, vert_function, + frag_function, threadsafe)); + }); + FML_CHECK(result); + }; + + if (async && compile_queue_) { + compile_queue_->PostJobForDescriptor(descriptor, + std::move(generation_task)); + } else { + generation_task(); + } return pipeline_future; } @@ -373,4 +394,8 @@ void PipelineLibraryGLES::SetProgramForKey( programs_[key] = std::move(program); } +PipelineCompileQueue* PipelineLibraryGLES::GetPipelineCompileQueue() const { + return compile_queue_.get(); +} + } // namespace impeller diff --git a/engine/src/flutter/impeller/renderer/backend/gles/pipeline_library_gles.h b/engine/src/flutter/impeller/renderer/backend/gles/pipeline_library_gles.h index 25c51c8113d38..765cff60c7152 100644 --- a/engine/src/flutter/impeller/renderer/backend/gles/pipeline_library_gles.h +++ b/engine/src/flutter/impeller/renderer/backend/gles/pipeline_library_gles.h @@ -9,7 +9,9 @@ #include #include "flutter/fml/hash_combine.h" +#include "flutter/fml/task_runner.h" #include "impeller/base/thread.h" +#include "impeller/renderer/backend/gles/pipeline_compile_queue_gles.h" #include "impeller/renderer/backend/gles/reactor_gles.h" #include "impeller/renderer/backend/gles/unique_handle_gles.h" #include "impeller/renderer/pipeline_library.h" @@ -91,8 +93,10 @@ class PipelineLibraryGLES final PipelineMap pipelines_; Mutex programs_mutex_; ProgramMap programs_ IPLR_GUARDED_BY(programs_mutex_); + std::shared_ptr compile_queue_; - explicit PipelineLibraryGLES(std::shared_ptr reactor); + explicit PipelineLibraryGLES(std::shared_ptr reactor, + fml::RefPtr io_task_runner); // |PipelineLibrary| bool IsValid() const override; @@ -127,6 +131,8 @@ class PipelineLibraryGLES final void SetProgramForKey(const ProgramKey& key, std::shared_ptr program); + // |PipelineLibrary| + PipelineCompileQueue* GetPipelineCompileQueue() const override; }; } // namespace impeller diff --git a/engine/src/flutter/impeller/renderer/backend/gles/test/pipeline_compile_queue_gles_unittests.cc b/engine/src/flutter/impeller/renderer/backend/gles/test/pipeline_compile_queue_gles_unittests.cc new file mode 100644 index 0000000000000..325254cadf02d --- /dev/null +++ b/engine/src/flutter/impeller/renderer/backend/gles/test/pipeline_compile_queue_gles_unittests.cc @@ -0,0 +1,185 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "impeller/renderer/backend/gles/pipeline_compile_queue_gles.h" + +#include +#include +#include + +#include "flutter/fml/synchronization/count_down_latch.h" +#include "flutter/fml/task_runner.h" +#include "flutter/fml/thread.h" +#include "flutter/testing/testing.h" +#include "impeller/renderer/pipeline_descriptor.h" + +namespace impeller { +namespace testing { + +TEST(PipelineCompileQueueGLESTest, CreateReturnsNullWithNullTaskRunner) { + auto queue = PipelineCompileQueueGLES::Create(nullptr); + EXPECT_EQ(queue, nullptr); +} + +TEST(PipelineCompileQueueGLESTest, CreateSucceedsWithValidTaskRunner) { + fml::Thread thread; + auto queue = PipelineCompileQueueGLES::Create(thread.GetTaskRunner()); + EXPECT_NE(queue, nullptr); + thread.Join(); +} + +TEST(PipelineCompileQueueGLESTest, PostJobDoesNothingWithNullClosure) { + fml::Thread thread; + auto queue = PipelineCompileQueueGLES::Create(thread.GetTaskRunner()); + ASSERT_NE(queue, nullptr); + queue->PostJob(nullptr); + thread.Join(); +} + +TEST(PipelineCompileQueueGLESTest, OnJobAddedProcessesJobsSequentially) { + fml::Thread thread; + auto queue = PipelineCompileQueueGLES::Create(thread.GetTaskRunner()); + ASSERT_NE(queue, nullptr); + + std::atomic completed_jobs{0}; + fml::CountDownLatch latch(3); + + PipelineDescriptor desc1; + desc1.SetSampleCount(SampleCount::kCount1); + desc1.SetCullMode(CullMode::kNone); + + PipelineDescriptor desc2; + desc2.SetSampleCount(SampleCount::kCount1); + desc2.SetCullMode(CullMode::kFrontFace); + + PipelineDescriptor desc3; + desc3.SetSampleCount(SampleCount::kCount1); + desc3.SetCullMode(CullMode::kBackFace); + + queue->PostJobForDescriptor(desc1, [&]() { + std::this_thread::sleep_for(std::chrono::milliseconds(80)); + completed_jobs++; + latch.CountDown(); + }); + + queue->PostJobForDescriptor(desc2, [&]() { + std::this_thread::sleep_for(std::chrono::milliseconds(80)); + completed_jobs++; + latch.CountDown(); + }); + + queue->PostJobForDescriptor(desc3, [&]() { + std::this_thread::sleep_for(std::chrono::milliseconds(80)); + completed_jobs++; + latch.CountDown(); + }); + + latch.Wait(); + + EXPECT_EQ(completed_jobs, 3); + + thread.Join(); +} + +TEST(PipelineCompileQueueGLESTest, + PostJobForDescriptorWithDuplicateRunsEagerly) { + fml::Thread thread; + auto queue = PipelineCompileQueueGLES::Create(thread.GetTaskRunner()); + ASSERT_NE(queue, nullptr); + + std::atomic first_job_count{0}; + std::atomic second_job_count{0}; + fml::CountDownLatch latch(2); + + PipelineDescriptor desc; + + queue->PostJobForDescriptor(desc, [&]() { + first_job_count++; + latch.CountDown(); + }); + + queue->PostJobForDescriptor(desc, [&]() { + second_job_count++; + latch.CountDown(); + }); + + latch.Wait(); + + EXPECT_EQ(first_job_count, 1); + EXPECT_EQ(second_job_count, 1); + thread.Join(); +} + +TEST(PipelineCompileQueueGLESTest, IsProcessingResetsAfterAllJobsComplete) { + fml::Thread thread; + auto queue = PipelineCompileQueueGLES::Create(thread.GetTaskRunner()); + ASSERT_NE(queue, nullptr); + + fml::CountDownLatch latch(1); + + queue->PostJobForDescriptor(PipelineDescriptor{}, + [&]() { latch.CountDown(); }); + + latch.Wait(); + + fml::CountDownLatch latch2(1); + queue->PostJobForDescriptor(PipelineDescriptor{}, + [&]() { latch2.CountDown(); }); + + latch2.Wait(); + + SUCCEED(); + thread.Join(); +} + +TEST(PipelineCompileQueueGLESTest, DestroyQueueWithPendingTasks) { + fml::Thread thread; + std::atomic completed_jobs{0}; + fml::CountDownLatch latch(3); + + { + auto queue = PipelineCompileQueueGLES::Create(thread.GetTaskRunner()); + ASSERT_NE(queue, nullptr); + + PipelineDescriptor desc1; + desc1.SetSampleCount(SampleCount::kCount1); + desc1.SetCullMode(CullMode::kNone); + + PipelineDescriptor desc2; + desc2.SetSampleCount(SampleCount::kCount1); + desc2.SetCullMode(CullMode::kFrontFace); + + PipelineDescriptor desc3; + desc3.SetSampleCount(SampleCount::kCount1); + desc3.SetCullMode(CullMode::kBackFace); + + queue->PostJobForDescriptor(desc1, [&]() { + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + completed_jobs++; + latch.CountDown(); + }); + + queue->PostJobForDescriptor(desc2, [&]() { + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + completed_jobs++; + latch.CountDown(); + }); + + queue->PostJobForDescriptor(desc3, [&]() { + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + completed_jobs++; + latch.CountDown(); + }); + + // Queue will be destroyed here with pending jobs. + // The destructor should block and wait for all jobs to complete. + } + + // All jobs should be completed before the queue is destroyed. + EXPECT_EQ(completed_jobs, 3); + thread.Join(); +} + +} // namespace testing +} // namespace impeller diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/BUILD.gn b/engine/src/flutter/impeller/renderer/backend/vulkan/BUILD.gn index a7860e7b823c7..5704e6df71309 100644 --- a/engine/src/flutter/impeller/renderer/backend/vulkan/BUILD.gn +++ b/engine/src/flutter/impeller/renderer/backend/vulkan/BUILD.gn @@ -41,6 +41,7 @@ impeller_component("vulkan_unittests") { "surface_context_vk_unittests.cc", "test/gpu_tracer_unittests.cc", "test/mock_vulkan_unittests.cc", + "test/pipeline_compile_queue_vulkan_unittests.cc", "test/sampler_library_vk_unittests.cc", "test/swapchain_unittests.cc", ] @@ -94,6 +95,8 @@ impeller_component("vulkan") { "pipeline_cache_data_vk.h", "pipeline_cache_vk.cc", "pipeline_cache_vk.h", + "pipeline_compile_queue_vulkan.cc", + "pipeline_compile_queue_vulkan.h", "pipeline_library_vk.cc", "pipeline_library_vk.h", "pipeline_vk.cc", diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_compile_queue_vulkan.cc b/engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_compile_queue_vulkan.cc new file mode 100644 index 0000000000000..aed83a4cebdeb --- /dev/null +++ b/engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_compile_queue_vulkan.cc @@ -0,0 +1,45 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "impeller/renderer/backend/vulkan/pipeline_compile_queue_vulkan.h" + +#include "flutter/fml/logging.h" +#include "flutter/fml/trace_event.h" + +namespace impeller { + +std::shared_ptr PipelineCompileQueueVulkan::Create( + std::shared_ptr worker_task_runner) { + if (!worker_task_runner) { + return nullptr; + } + return std::shared_ptr( + new PipelineCompileQueueVulkan(std::move(worker_task_runner))); +} + +PipelineCompileQueueVulkan::PipelineCompileQueueVulkan( + std::shared_ptr worker_task_runner) + : PipelineCompileQueue(), + worker_task_runner_(std::move(worker_task_runner)) {} + +PipelineCompileQueueVulkan::~PipelineCompileQueueVulkan() = default; + +void PipelineCompileQueueVulkan::OnJobAdded() { + PostJob([weak_queue = weak_from_this()]() { + if (auto queue = std::static_pointer_cast( + weak_queue.lock())) { + queue->DoOneJob(); + } + }); +} + +void PipelineCompileQueueVulkan::PostJob(const fml::closure& job) { + if (!job) { + return; + } + + worker_task_runner_->PostTask(job); +} + +} // namespace impeller diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_compile_queue_vulkan.h b/engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_compile_queue_vulkan.h new file mode 100644 index 0000000000000..5a65c0ebc4997 --- /dev/null +++ b/engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_compile_queue_vulkan.h @@ -0,0 +1,56 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_IMPELLER_RENDERER_BACKEND_VULKAN_PIPELINE_COMPILE_QUEUE_VULKAN_H_ +#define FLUTTER_IMPELLER_RENDERER_BACKEND_VULKAN_PIPELINE_COMPILE_QUEUE_VULKAN_H_ + +#include "flutter/fml/closure.h" +#include "flutter/fml/task_runner.h" +#include "impeller/renderer/pipeline_compile_queue.h" + +namespace impeller { + +//------------------------------------------------------------------------------ +/// @brief A task queue designed for managing compilation of pipeline state +/// objects for Vulkan backend. +/// +/// This subclass uses a fml::BasicTaskRunner as the worker task +/// runner and dispatches compile jobs directly without sequential +/// processing constraints. +/// +/// Key characteristics: +/// - Uses std::shared_ptr for +/// worker_task_runner_ +/// - Dispatches jobs directly to the task runner in OnJobAdded() +/// - Does not implement sequential processing like GLES version +/// +/// The Vulkan backend benefits from the parallel nature of pipeline +/// compilation, allowing multiple compile jobs to be processed +/// concurrently through the task runner. +/// +class PipelineCompileQueueVulkan : public PipelineCompileQueue { + public: + static std::shared_ptr Create( + std::shared_ptr worker_task_runner); + + ~PipelineCompileQueueVulkan() override; + + PipelineCompileQueueVulkan(const PipelineCompileQueueVulkan&) = delete; + + PipelineCompileQueueVulkan& operator=(const PipelineCompileQueueVulkan&) = + delete; + + void PostJob(const fml::closure& job) override; + + void OnJobAdded() override; + + private: + explicit PipelineCompileQueueVulkan( + std::shared_ptr worker_task_runner); + std::shared_ptr worker_task_runner_; +}; + +} // namespace impeller + +#endif // FLUTTER_IMPELLER_RENDERER_BACKEND_VULKAN_PIPELINE_COMPILE_QUEUE_VULKAN_H_ diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_library_vk.cc b/engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_library_vk.cc index 95b3351049b39..6e8d9544f7dc5 100644 --- a/engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_library_vk.cc +++ b/engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_library_vk.cc @@ -27,7 +27,7 @@ PipelineLibraryVK::PipelineLibraryVK( device_holder, std::move(cache_directory))), worker_task_runner_(std::move(worker_task_runner)), - compile_queue_(PipelineCompileQueue::Create(worker_task_runner_)) { + compile_queue_(PipelineCompileQueueVulkan::Create(worker_task_runner_)) { FML_DCHECK(worker_task_runner_); if (!pso_cache_->IsValid() || !worker_task_runner_) { return; diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_library_vk.h b/engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_library_vk.h index 3ead3c7dc700d..4304db9b58293 100644 --- a/engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_library_vk.h +++ b/engine/src/flutter/impeller/renderer/backend/vulkan/pipeline_library_vk.h @@ -13,10 +13,10 @@ #include "impeller/base/thread.h" #include "impeller/renderer/backend/vulkan/compute_pipeline_vk.h" #include "impeller/renderer/backend/vulkan/pipeline_cache_vk.h" +#include "impeller/renderer/backend/vulkan/pipeline_compile_queue_vulkan.h" #include "impeller/renderer/backend/vulkan/pipeline_vk.h" #include "impeller/renderer/backend/vulkan/vk.h" #include "impeller/renderer/pipeline.h" -#include "impeller/renderer/pipeline_compile_queue.h" #include "impeller/renderer/pipeline_library.h" namespace impeller { @@ -49,7 +49,7 @@ class PipelineLibraryVK final PipelineKey pipeline_key_ IPLR_GUARDED_BY(pipelines_mutex_) = 1; bool is_valid_ = false; bool cache_dirty_ = false; - std::shared_ptr compile_queue_; + std::shared_ptr compile_queue_; PipelineLibraryVK( const std::shared_ptr& device_holder, diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/test/pipeline_compile_queue_vulkan_unittests.cc b/engine/src/flutter/impeller/renderer/backend/vulkan/test/pipeline_compile_queue_vulkan_unittests.cc new file mode 100644 index 0000000000000..6ee373535fd39 --- /dev/null +++ b/engine/src/flutter/impeller/renderer/backend/vulkan/test/pipeline_compile_queue_vulkan_unittests.cc @@ -0,0 +1,181 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "impeller/renderer/backend/vulkan/pipeline_compile_queue_vulkan.h" + +#include +#include +#include +#include + +#include "flutter/fml/synchronization/count_down_latch.h" +#include "flutter/fml/task_runner.h" +#include "flutter/testing/testing.h" +#include "impeller/renderer/pipeline_descriptor.h" + +namespace impeller { +namespace testing { + +TEST(PipelineCompileQueueVulkanTest, CreateSucceedsWithValidTaskRunner) { + auto loop = fml::ConcurrentMessageLoop::Create(); + auto queue = PipelineCompileQueueVulkan::Create(loop->GetTaskRunner()); + EXPECT_NE(queue, nullptr); +} + +TEST(PipelineCompileQueueVulkanTest, PostJobDoesNothingWithNullClosure) { + auto loop = fml::ConcurrentMessageLoop::Create(); + auto queue = PipelineCompileQueueVulkan::Create(loop->GetTaskRunner()); + ASSERT_NE(queue, nullptr); + + queue->PostJob(nullptr); +} + +TEST(PipelineCompileQueueVulkanTest, OnJobAddedProcessesJobsInParallel) { + auto loop = fml::ConcurrentMessageLoop::Create(); + auto queue = PipelineCompileQueueVulkan::Create(loop->GetTaskRunner()); + ASSERT_NE(queue, nullptr); + + std::atomic concurrent_jobs{0}; + std::atomic max_concurrent{0}; + fml::CountDownLatch latch(3); + + PipelineDescriptor desc1; + desc1.SetSampleCount(SampleCount::kCount1); + desc1.SetCullMode(CullMode::kNone); + + PipelineDescriptor desc2; + desc2.SetSampleCount(SampleCount::kCount1); + desc2.SetCullMode(CullMode::kFrontFace); + + PipelineDescriptor desc3; + desc3.SetSampleCount(SampleCount::kCount1); + desc3.SetCullMode(CullMode::kBackFace); + + queue->PostJobForDescriptor(desc1, [&]() { + int current = ++concurrent_jobs; + int prev_max = max_concurrent.load(); + while (current > prev_max && + !max_concurrent.compare_exchange_weak(prev_max, current)) { + } + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + concurrent_jobs--; + latch.CountDown(); + }); + + queue->PostJobForDescriptor(desc2, [&]() { + int current = ++concurrent_jobs; + int prev_max = max_concurrent.load(); + while (current > prev_max && + !max_concurrent.compare_exchange_weak(prev_max, current)) { + } + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + concurrent_jobs--; + latch.CountDown(); + }); + + queue->PostJobForDescriptor(desc3, [&]() { + int current = ++concurrent_jobs; + int prev_max = max_concurrent.load(); + while (current > prev_max && + !max_concurrent.compare_exchange_weak(prev_max, current)) { + } + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + concurrent_jobs--; + latch.CountDown(); + }); + + latch.Wait(); + + EXPECT_GE(max_concurrent.load(), 1); +} + +TEST(PipelineCompileQueueVulkanTest, + PostJobForDescriptorWithDuplicateRunsEagerly) { + auto loop = fml::ConcurrentMessageLoop::Create(); + auto queue = PipelineCompileQueueVulkan::Create(loop->GetTaskRunner()); + ASSERT_NE(queue, nullptr); + + std::atomic first_job_count{0}; + std::atomic second_job_count{0}; + fml::CountDownLatch latch(2); + + PipelineDescriptor desc; + + queue->PostJobForDescriptor(desc, [&]() { + first_job_count++; + latch.CountDown(); + }); + + queue->PostJobForDescriptor(desc, [&]() { + second_job_count++; + latch.CountDown(); + }); + + latch.Wait(); + + EXPECT_EQ(first_job_count, 1); + EXPECT_EQ(second_job_count, 1); +} + +TEST(PipelineCompileQueueVulkanTest, MultipleJobsCompleteSuccessfully) { + auto loop = fml::ConcurrentMessageLoop::Create(); + auto queue = PipelineCompileQueueVulkan::Create(loop->GetTaskRunner()); + ASSERT_NE(queue, nullptr); + + std::atomic completed_jobs{0}; + fml::CountDownLatch latch(5); + + PipelineDescriptor desc1; + desc1.SetSampleCount(SampleCount::kCount1); + desc1.SetCullMode(CullMode::kNone); + + PipelineDescriptor desc2; + desc2.SetSampleCount(SampleCount::kCount1); + desc2.SetCullMode(CullMode::kFrontFace); + + PipelineDescriptor desc3; + desc3.SetSampleCount(SampleCount::kCount1); + desc3.SetCullMode(CullMode::kBackFace); + + PipelineDescriptor desc4; + desc4.SetSampleCount(SampleCount::kCount4); + desc4.SetCullMode(CullMode::kNone); + + PipelineDescriptor desc5; + desc5.SetSampleCount(SampleCount::kCount4); + desc5.SetCullMode(CullMode::kFrontFace); + + // Post 5 jobs with distinct descriptors + queue->PostJobForDescriptor(desc1, [&]() { + completed_jobs++; + latch.CountDown(); + }); + + queue->PostJobForDescriptor(desc2, [&]() { + completed_jobs++; + latch.CountDown(); + }); + + queue->PostJobForDescriptor(desc3, [&]() { + completed_jobs++; + latch.CountDown(); + }); + + queue->PostJobForDescriptor(desc4, [&]() { + completed_jobs++; + latch.CountDown(); + }); + + queue->PostJobForDescriptor(desc5, [&]() { + completed_jobs++; + latch.CountDown(); + }); + + latch.Wait(); + + EXPECT_EQ(completed_jobs, 5); +} + +} // namespace testing +} // namespace impeller diff --git a/engine/src/flutter/impeller/renderer/pipeline_compile_queue.cc b/engine/src/flutter/impeller/renderer/pipeline_compile_queue.cc index 124e11e177346..da10b9be100b8 100644 --- a/engine/src/flutter/impeller/renderer/pipeline_compile_queue.cc +++ b/engine/src/flutter/impeller/renderer/pipeline_compile_queue.cc @@ -9,16 +9,6 @@ namespace impeller { -std::shared_ptr PipelineCompileQueue::Create( - std::shared_ptr worker_task_runner) { - return std::shared_ptr( - new PipelineCompileQueue(std::move(worker_task_runner))); -} - -PipelineCompileQueue::PipelineCompileQueue( - std::shared_ptr worker_task_runner) - : worker_task_runner_(std::move(worker_task_runner)) {} - PipelineCompileQueue::~PipelineCompileQueue() { FinishAllJobs(); } @@ -29,31 +19,33 @@ bool PipelineCompileQueue::PostJobForDescriptor(const PipelineDescriptor& desc, return false; } - { - Lock lock(pending_jobs_mutex_); - auto insertion_result = pending_jobs_.insert(std::make_pair(desc, job)); - if (!insertion_result.second) { - // This bit is being extremely conservative. If insertion did not take - // place, someone gave the compile queue a job for the same description. - // This is highly unusual but technically not impossible. Just run the job - // eagerly. - FML_LOG(ERROR) << "Got multiple compile jobs for the same descriptor. " + if (!AddJob(desc, job)) { + // This bit is being extremely conservative. If insertion did not take + // place, someone gave the compile queue a job for the same description. + // This is highly unusual but technically not impossible. Just run the job + // eagerly. + FML_LOG(WARNING) << "Got multiple compile jobs for the same descriptor. " "Running eagerly."; - // Don't invoke the job here has there are we have currently acquired a - // mutex. - worker_task_runner_->PostTask(job); - return true; - } + PostJob(job); + return true; } - worker_task_runner_->PostTask([weak_queue = weak_from_this()]() { - if (auto queue = weak_queue.lock()) { - queue->DoOneJob(); - } - }); + OnJobAdded(); return true; } +bool PipelineCompileQueue::AddJob(const PipelineDescriptor& desc, + const fml::closure& job) { + Lock lock(pending_jobs_mutex_); + auto insertion_result = pending_jobs_.insert(std::make_pair(desc, job)); + return insertion_result.second; +} + +bool PipelineCompileQueue::HasPendingJobs() { + Lock lock(pending_jobs_mutex_); + return !pending_jobs_.empty(); +} + fml::closure PipelineCompileQueue::TakeNextJob() { Lock lock(pending_jobs_mutex_); if (pending_jobs_.empty()) { diff --git a/engine/src/flutter/impeller/renderer/pipeline_compile_queue.h b/engine/src/flutter/impeller/renderer/pipeline_compile_queue.h index d166679b3798f..901ac76977431 100644 --- a/engine/src/flutter/impeller/renderer/pipeline_compile_queue.h +++ b/engine/src/flutter/impeller/renderer/pipeline_compile_queue.h @@ -39,13 +39,12 @@ namespace impeller { /// entirely optional. The queue skipping mechanism all assume the /// optional availability of a compile queue. /// -class PipelineCompileQueue final +class PipelineCompileQueue : public std::enable_shared_from_this { public: - static std::shared_ptr Create( - std::shared_ptr worker_task_runner); + PipelineCompileQueue() = default; - ~PipelineCompileQueue(); + virtual ~PipelineCompileQueue(); PipelineCompileQueue(const PipelineCompileQueue&) = delete; @@ -72,26 +71,68 @@ class PipelineCompileQueue final /// void PerformJobEagerly(const PipelineDescriptor& desc); + protected: + //---------------------------------------------------------------------------- + /// @brief Post a compilation job to the worker task runner. + /// + /// This is a pure virtual function that must be implemented by + /// subclasses. It is responsible for actually dispatching the + /// job closure to the appropriate task runner for execution. + /// + /// @param[in] job The compilation job closure to post + /// + virtual void PostJob(const fml::closure& job) = 0; + + //---------------------------------------------------------------------------- + /// @brief Called by PostJobForDescriptor after a job has been + /// successfully added to the queue. Subclasses must implement + /// this to define their scheduling strategy. + /// + /// The default implementation for duplicate descriptors is to + /// run the job eagerly. Subclasses can override this behavior + /// by checking for duplicates before calling the base class. + /// + virtual void OnJobAdded() = 0; + + //---------------------------------------------------------------------------- + /// @brief Execute one pending compilation job from the queue. + /// + /// This method retrieves and executes a single job from the + /// pending jobs queue. It is typically called by subclasses + /// when they are ready to process the next job in the queue. + /// + void DoOneJob(); + + //---------------------------------------------------------------------------- + /// @brief Add a compilation job to the pending queue for the specified + /// descriptor. + /// + /// @param[in] desc The pipeline descriptor that identifies the job + /// @param[in] job The compilation job closure to add + /// + /// @return True if the job was successfully added to the queue, false + /// if a job for this descriptor already exists. + /// + bool AddJob(const PipelineDescriptor& desc, const fml::closure& job); + + //---------------------------------------------------------------------------- + /// @brief Check if there are any pending compilation jobs in the queue. + /// + /// @return True if there are pending jobs waiting to be processed, + /// false otherwise. + /// + bool HasPendingJobs(); + private: - std::shared_ptr worker_task_runner_; Mutex pending_jobs_mutex_; - size_t priorities_elevated_ = {}; - std::unordered_map, ComparableEqual> pending_jobs_ IPLR_GUARDED_BY(pending_jobs_mutex_); - - explicit PipelineCompileQueue( - std::shared_ptr worker_task_runner); - + size_t priorities_elevated_ = {}; fml::closure TakeJob(const PipelineDescriptor& desc); - fml::closure TakeNextJob(); - - void DoOneJob(); - void FinishAllJobs(); }; diff --git a/engine/src/flutter/impeller/renderer/pipeline_compile_queue_unittests.cc b/engine/src/flutter/impeller/renderer/pipeline_compile_queue_unittests.cc new file mode 100644 index 0000000000000..14be51dcd1556 --- /dev/null +++ b/engine/src/flutter/impeller/renderer/pipeline_compile_queue_unittests.cc @@ -0,0 +1,96 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "impeller/renderer/pipeline_compile_queue.h" + +#include + +#include "flutter/fml/closure.h" +#include "flutter/testing/testing.h" + +namespace impeller { +namespace testing { + +class TestPipelineCompileQueue : public PipelineCompileQueue { + public: + void PostJob(const fml::closure& job) override { + if (job) { + job(); + } + } + + void OnJobAdded() override {} + + bool AddJobForTest(const PipelineDescriptor& desc, const fml::closure& job) { + return AddJob(desc, job); + } + + bool HasPendingJobsForTest() { return HasPendingJobs(); } +}; + +TEST(PipelineCompileQueueTest, AddJobReturnsTrueForNewDescriptor) { + TestPipelineCompileQueue queue; + PipelineDescriptor desc; + bool job_executed = false; + fml::closure job = [&job_executed]() { job_executed = true; }; + + bool result = queue.AddJobForTest(desc, job); + EXPECT_TRUE(result); +} + +TEST(PipelineCompileQueueTest, AddJobReturnsFalseForDuplicateDescriptor) { + TestPipelineCompileQueue queue; + PipelineDescriptor desc; + bool job1_executed = false; + bool job2_executed = false; + fml::closure job1 = [&job1_executed]() { job1_executed = true; }; + fml::closure job2 = [&job2_executed]() { job2_executed = true; }; + + bool result1 = queue.AddJobForTest(desc, job1); + bool result2 = queue.AddJobForTest(desc, job2); + + EXPECT_TRUE(result1); + EXPECT_FALSE(result2); +} + +TEST(PipelineCompileQueueTest, HasPendingJobsReturnsCorrectState) { + TestPipelineCompileQueue queue; + PipelineDescriptor desc; + fml::closure job = []() {}; + + EXPECT_FALSE(queue.HasPendingJobsForTest()); + + queue.AddJobForTest(desc, job); + EXPECT_TRUE(queue.HasPendingJobsForTest()); +} + +TEST(PipelineCompileQueueTest, PerformJobEagerlyExecutesJob) { + TestPipelineCompileQueue queue; + PipelineDescriptor desc; + bool job_executed = false; + fml::closure job = [&job_executed]() { job_executed = true; }; + + queue.AddJobForTest(desc, job); + queue.PerformJobEagerly(desc); + + EXPECT_TRUE(job_executed); + EXPECT_FALSE(queue.HasPendingJobsForTest()); +} + +TEST(PipelineCompileQueueTest, FinishAllJobsDrainsQueue) { + auto queue = std::make_shared(); + PipelineDescriptor desc; + bool job_executed = false; + fml::closure job = [&job_executed]() { job_executed = true; }; + + queue->AddJobForTest(desc, job); + EXPECT_TRUE(queue->HasPendingJobsForTest()); + + queue.reset(); + + EXPECT_TRUE(job_executed); +} + +} // namespace testing +} // namespace impeller diff --git a/engine/src/flutter/shell/common/shell_test_platform_view_gl.cc b/engine/src/flutter/shell/common/shell_test_platform_view_gl.cc index 5ee5e7b18a935..c7da40ba45d1b 100644 --- a/engine/src/flutter/shell/common/shell_test_platform_view_gl.cc +++ b/engine/src/flutter/shell/common/shell_test_platform_view_gl.cc @@ -61,7 +61,8 @@ ShellTestPlatformViewGL::ShellTestPlatformViewGL( return; } impeller_context_ = impeller::ContextGLES::Create( - impeller::Flags{}, std::move(gl), ShaderLibraryMappings(), true); + impeller::Flags{}, std::move(gl), ShaderLibraryMappings(), true, + task_runners.GetIOTaskRunner()); } } diff --git a/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.cc b/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.cc index 77358627808df..9dc01e595af01 100644 --- a/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.cc +++ b/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.cc @@ -145,9 +145,11 @@ GetActualRenderingAPIForImpeller( } // namespace AndroidContextDynamicImpeller::AndroidContextDynamicImpeller( - const AndroidContext::ContextSettings& settings) + const AndroidContext::ContextSettings& settings, + fml::RefPtr io_task_runner) : AndroidContext(AndroidRenderingAPI::kImpellerVulkan), - settings_(settings) {} + settings_(settings), + io_task_runner_(std::move(io_task_runner)) {} AndroidContextDynamicImpeller::~AndroidContextDynamicImpeller() = default; @@ -191,7 +193,7 @@ void AndroidContextDynamicImpeller::SetupImpellerContext() { if (!vk_context_) { gl_context_ = std::make_shared( std::make_unique(), - settings_.enable_gpu_tracing); + settings_.enable_gpu_tracing, io_task_runner_); } } diff --git a/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.h b/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.h index 4de037bfa77ba..b6e59b65d0dae 100644 --- a/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.h +++ b/engine/src/flutter/shell/platform/android/android_context_dynamic_impeller.h @@ -23,7 +23,8 @@ namespace flutter { class AndroidContextDynamicImpeller : public AndroidContext { public: explicit AndroidContextDynamicImpeller( - const AndroidContext::ContextSettings& settings); + const AndroidContext::ContextSettings& settings, + fml::RefPtr io_task_runner); ~AndroidContextDynamicImpeller(); @@ -51,6 +52,7 @@ class AndroidContextDynamicImpeller : public AndroidContext { const AndroidContext::ContextSettings settings_; std::shared_ptr gl_context_; std::shared_ptr vk_context_; + fml::RefPtr io_task_runner_; FML_DISALLOW_COPY_AND_ASSIGN(AndroidContextDynamicImpeller); }; diff --git a/engine/src/flutter/shell/platform/android/android_context_gl_impeller.cc b/engine/src/flutter/shell/platform/android/android_context_gl_impeller.cc index 79c0ec66bd25b..ce7bb0bf23eba 100644 --- a/engine/src/flutter/shell/platform/android/android_context_gl_impeller.cc +++ b/engine/src/flutter/shell/platform/android/android_context_gl_impeller.cc @@ -51,7 +51,8 @@ class AndroidContextGLImpeller::ReactorWorker final static std::shared_ptr CreateImpellerContext( const std::shared_ptr& worker, - bool enable_gpu_tracing) { + bool enable_gpu_tracing, + fml::RefPtr io_task_runner) { auto proc_table = std::make_unique( impeller::egl::CreateProcAddressResolver()); @@ -85,11 +86,11 @@ static std::shared_ptr CreateImpellerContext( auto context = impeller::ContextGLES::Create( impeller::Flags{}, std::move(proc_table), is_gles3 ? gles3_shader_mappings : gles2_shader_mappings, - enable_gpu_tracing); + enable_gpu_tracing, io_task_runner); #else - auto context = - impeller::ContextGLES::Create(impeller::Flags{}, std::move(proc_table), - gles2_shader_mappings, enable_gpu_tracing); + auto context = impeller::ContextGLES::Create( + impeller::Flags{}, std::move(proc_table), gles2_shader_mappings, + enable_gpu_tracing, io_task_runner); #endif // !SLIMPELLER if (!context) { @@ -107,10 +108,12 @@ static std::shared_ptr CreateImpellerContext( AndroidContextGLImpeller::AndroidContextGLImpeller( std::unique_ptr display, - bool enable_gpu_tracing) + bool enable_gpu_tracing, + fml::RefPtr io_task_runner) : AndroidContext(AndroidRenderingAPI::kImpellerOpenGLES), reactor_worker_(std::shared_ptr(new ReactorWorker())), - display_(std::move(display)) { + display_(std::move(display)), + io_task_runner_(std::move(io_task_runner)) { if (!display_ || !display_->IsValid()) { FML_LOG(ERROR) << "Could not create context with invalid EGL display."; return; @@ -174,8 +177,8 @@ AndroidContextGLImpeller::AndroidContextGLImpeller( return; } - auto impeller_context = - CreateImpellerContext(reactor_worker_, enable_gpu_tracing); + auto impeller_context = CreateImpellerContext( + reactor_worker_, enable_gpu_tracing, io_task_runner_); if (!impeller_context) { FML_LOG(ERROR) << "Could not create Impeller context."; diff --git a/engine/src/flutter/shell/platform/android/android_context_gl_impeller.h b/engine/src/flutter/shell/platform/android/android_context_gl_impeller.h index e6d68d8444e38..792c8236bb106 100644 --- a/engine/src/flutter/shell/platform/android/android_context_gl_impeller.h +++ b/engine/src/flutter/shell/platform/android/android_context_gl_impeller.h @@ -6,6 +6,7 @@ #define FLUTTER_SHELL_PLATFORM_ANDROID_ANDROID_CONTEXT_GL_IMPELLER_H_ #include "flutter/fml/macros.h" +#include "flutter/fml/task_runner.h" #include "flutter/impeller/toolkit/egl/display.h" #include "flutter/shell/platform/android/context/android_context.h" @@ -14,7 +15,8 @@ namespace flutter { class AndroidContextGLImpeller : public AndroidContext { public: AndroidContextGLImpeller(std::unique_ptr display, - bool enable_gpu_tracing); + bool enable_gpu_tracing, + fml::RefPtr io_task_runner); ~AndroidContextGLImpeller(); @@ -42,6 +44,7 @@ class AndroidContextGLImpeller : public AndroidContext { std::unique_ptr onscreen_context_; std::unique_ptr offscreen_context_; bool is_valid_ = false; + fml::RefPtr io_task_runner_; FML_DISALLOW_COPY_AND_ASSIGN(AndroidContextGLImpeller); }; diff --git a/engine/src/flutter/shell/platform/android/platform_view_android.cc b/engine/src/flutter/shell/platform/android/platform_view_android.cc index 02ed4df41e5c1..466c4d916edcc 100644 --- a/engine/src/flutter/shell/platform/android/platform_view_android.cc +++ b/engine/src/flutter/shell/platform/android/platform_view_android.cc @@ -108,7 +108,8 @@ static std::shared_ptr CreateAndroidContext( const flutter::TaskRunners& task_runners, AndroidRenderingAPI android_rendering_api, bool enable_opengl_gpu_tracing, - const AndroidContext::ContextSettings& settings) { + const AndroidContext::ContextSettings& settings, + const fml::RefPtr& io_task_runner) { switch (android_rendering_api) { #if !SLIMPELLER case AndroidRenderingAPI::kSoftware: @@ -123,11 +124,12 @@ static std::shared_ptr CreateAndroidContext( return std::make_unique(settings); case AndroidRenderingAPI::kImpellerOpenGLES: return std::make_unique( - std::make_unique(), - enable_opengl_gpu_tracing); + std::make_unique(), enable_opengl_gpu_tracing, + io_task_runner); case AndroidRenderingAPI::kImpellerAutoselect: // Determine if we're using GL or Vulkan. - return std::make_unique(settings); + return std::make_unique(settings, + io_task_runner); } FML_UNREACHABLE(); } @@ -145,7 +147,8 @@ PlatformViewAndroid::PlatformViewAndroid( task_runners, rendering_api, delegate.OnPlatformViewGetSettings().enable_opengl_gpu_tracing, - CreateContextSettings(delegate.OnPlatformViewGetSettings()))) {} + CreateContextSettings(delegate.OnPlatformViewGetSettings()), + task_runners.GetIOTaskRunner())) {} PlatformViewAndroid::PlatformViewAndroid( PlatformView::Delegate& delegate, diff --git a/engine/src/flutter/shell/platform/embedder/embedder.cc b/engine/src/flutter/shell/platform/embedder/embedder.cc index 48e960fb4c13e..35d358ec80aaf 100644 --- a/engine/src/flutter/shell/platform/embedder/embedder.cc +++ b/engine/src/flutter/shell/platform/embedder/embedder.cc @@ -495,6 +495,7 @@ InferOpenGLPlatformViewCreationCallback( shell.GetTaskRunners(), // task runners std::make_unique( gl_dispatch_table, fbo_reset_after_present, view_embedder, + shell.GetTaskRunners().GetIOTaskRunner(), impeller_flags), // embedder_surface platform_dispatch_table, // embedder platform dispatch table view_embedder // external view embedder diff --git a/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.cc b/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.cc index 1c603e5147563..3aad279bcdcf8 100644 --- a/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.cc +++ b/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.cc @@ -65,6 +65,7 @@ EmbedderSurfaceGLImpeller::EmbedderSurfaceGLImpeller( EmbedderSurfaceGLSkia::GLDispatchTable gl_dispatch_table, bool fbo_reset_after_present, std::shared_ptr external_view_embedder, + fml::RefPtr io_task_runner, impeller::Flags impeller_flags) : gl_dispatch_table_(std::move(gl_dispatch_table)), fbo_reset_after_present_(fbo_reset_after_present), @@ -93,7 +94,7 @@ EmbedderSurfaceGLImpeller::EmbedderSurfaceGLImpeller( impeller_context_ = impeller::ContextGLES::Create( impeller_flags, std::move(gl), shader_mappings, - /*enable_gpu_tracing=*/false); + /*enable_gpu_tracing=*/false, std::move(io_task_runner)); if (!impeller_context_) { FML_LOG(ERROR) << "Could not create Impeller context."; diff --git a/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.h b/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.h index 598925f44b483..e7a92f95df97e 100644 --- a/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.h +++ b/engine/src/flutter/shell/platform/embedder/embedder_surface_gl_impeller.h @@ -33,6 +33,7 @@ class EmbedderSurfaceGLImpeller final : public EmbedderSurface, EmbedderSurfaceGLSkia::GLDispatchTable gl_dispatch_table, bool fbo_reset_after_present, std::shared_ptr external_view_embedder, + fml::RefPtr io_task_runner, impeller::Flags impeller_flags = {}); ~EmbedderSurfaceGLImpeller() override; diff --git a/engine/src/flutter/shell/platform/embedder/tests/embedder_test_surface_gl_impeller.cc b/engine/src/flutter/shell/platform/embedder/tests/embedder_test_surface_gl_impeller.cc index eda11d2ccb0ba..278d9380fbc5d 100644 --- a/engine/src/flutter/shell/platform/embedder/tests/embedder_test_surface_gl_impeller.cc +++ b/engine/src/flutter/shell/platform/embedder/tests/embedder_test_surface_gl_impeller.cc @@ -39,7 +39,8 @@ TEST(EmbedderSurfaceGLImpellerTest, GLES3ContextHasGLES3Shaders) { StubDispatchTable(/* version */ "OpenGL ES 3.0"); const auto surface = EmbedderSurfaceGLImpeller( gl_dispatch_table, /* fbo_reset_after_present */ false, - /* external_view_embedder */ nullptr); + /* external_view_embedder */ nullptr, + /* io_task_runner */ nullptr); const std::shared_ptr context = surface.CreateImpellerContext(); @@ -61,7 +62,8 @@ TEST(EmbedderSurfaceGLImpellerTest, GLES2ContextDoesNotHaveGLES3Shaders) { StubDispatchTable(/* version */ "OpenGL ES 2.0"); const auto surface = EmbedderSurfaceGLImpeller( gl_dispatch_table, /* fbo_reset_after_present */ false, - /* external_view_embedder */ nullptr); + /* external_view_embedder */ nullptr, + /* io_task_runner */ nullptr); const std::shared_ptr context = surface.CreateImpellerContext(); From 0b516ca3b72d4702d34b3ecbfc2a8b9cbc37048d Mon Sep 17 00:00:00 2001 From: xiaowei guan <60122246+xiaowei-guan@users.noreply.github.com> Date: Tue, 11 Aug 2026 10:01:08 +0800 Subject: [PATCH 28/29] Support vulkan pileline cache (#42) --- .../impeller/renderer/backend/vulkan/context_vk.cc | 8 ++++++++ .../flutter/impeller/renderer/backend/vulkan/context_vk.h | 6 ++++++ .../renderer/backend/vulkan/surface_context_vk.cc | 7 +------ .../src/flutter/shell/gpu/gpu_surface_vulkan_impeller.cc | 6 +++++- engine/src/flutter/shell/platform/embedder/embedder.cc | 5 ++++- engine/src/flutter/shell/platform/embedder/embedder.h | 5 +++++ .../platform/embedder/embedder_surface_vulkan_impeller.cc | 8 +++++++- .../platform/embedder/embedder_surface_vulkan_impeller.h | 3 ++- 8 files changed, 38 insertions(+), 10 deletions(-) diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/context_vk.cc b/engine/src/flutter/impeller/renderer/backend/vulkan/context_vk.cc index e6f20de1b68d0..d518b7a3e9db3 100644 --- a/engine/src/flutter/impeller/renderer/backend/vulkan/context_vk.cc +++ b/engine/src/flutter/impeller/renderer/backend/vulkan/context_vk.cc @@ -731,6 +731,14 @@ void ContextVK::DisposeThreadLocalCachedResources() { command_pool_recycler_->Dispose(); } +void ContextVK::MarkFrameEnd() { + if (auto pipeline_library = GetPipelineLibrary()) { + PipelineLibraryVK::Cast(*pipeline_library).DidAcquireSurfaceFrame(); + } + DisposeThreadLocalCachedResources(); + GetResourceAllocator()->DebugTraceMemoryStatistics(); +} + const std::shared_ptr& ContextVK::GetYUVConversionLibrary() const { return yuv_conversion_library_; diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/context_vk.h b/engine/src/flutter/impeller/renderer/backend/vulkan/context_vk.h index b300d8657e92f..4f7c200c87974 100644 --- a/engine/src/flutter/impeller/renderer/backend/vulkan/context_vk.h +++ b/engine/src/flutter/impeller/renderer/backend/vulkan/context_vk.h @@ -227,6 +227,12 @@ class ContextVK final : public Context, // |Context| void DisposeThreadLocalCachedResources() override; + /// @brief Perform frame-end maintenance: persist the pipeline cache to + /// disk periodically and dispose thread-local cached resources. + /// Called by SurfaceContextVK::MarkFrameEnd and directly by + /// embedder paths that bypass AcquireNextSurface. + void MarkFrameEnd(); + /// @brief Whether the Android Surface control based swapchain should be /// enabled bool GetShouldEnableSurfaceControlSwapchain() const; diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/surface_context_vk.cc b/engine/src/flutter/impeller/renderer/backend/vulkan/surface_context_vk.cc index 8c68dff7c210a..3141eb3142b08 100644 --- a/engine/src/flutter/impeller/renderer/backend/vulkan/surface_context_vk.cc +++ b/engine/src/flutter/impeller/renderer/backend/vulkan/surface_context_vk.cc @@ -99,12 +99,7 @@ std::unique_ptr SurfaceContextVK::AcquireNextSurface() { } void SurfaceContextVK::MarkFrameEnd() { - if (auto pipeline_library = parent_->GetPipelineLibrary()) { - impeller::PipelineLibraryVK::Cast(*pipeline_library) - .DidAcquireSurfaceFrame(); - } - parent_->DisposeThreadLocalCachedResources(); - parent_->GetResourceAllocator()->DebugTraceMemoryStatistics(); + parent_->MarkFrameEnd(); } void SurfaceContextVK::UpdateSurfaceSize(const ISize& size) const { diff --git a/engine/src/flutter/shell/gpu/gpu_surface_vulkan_impeller.cc b/engine/src/flutter/shell/gpu/gpu_surface_vulkan_impeller.cc index c5cd2e9d0074f..fc06184bdde08 100644 --- a/engine/src/flutter/shell/gpu/gpu_surface_vulkan_impeller.cc +++ b/engine/src/flutter/shell/gpu/gpu_surface_vulkan_impeller.cc @@ -161,7 +161,11 @@ std::unique_ptr GPUSurfaceVulkanImpeller::AcquireFrame( impeller::ContextVK& context_vk = impeller::ContextVK::Cast(*impeller_context_); - context_vk.DisposeThreadLocalCachedResources(); + // The embedder path does not go through + // SurfaceContextVK::AcquireNextSurface (which calls MarkFrameEnd). + // Call MarkFrameEnd directly to ensure the Vulkan pipeline cache is + // periodically persisted to disk and thread-local resources are cleaned up. + context_vk.MarkFrameEnd(); impeller::vk::Image vk_image = impeller::vk::Image(reinterpret_cast(flutter_image.image)); diff --git a/engine/src/flutter/shell/platform/embedder/embedder.cc b/engine/src/flutter/shell/platform/embedder/embedder.cc index 35d358ec80aaf..1398f1dae17b4 100644 --- a/engine/src/flutter/shell/platform/embedder/embedder.cc +++ b/engine/src/flutter/shell/platform/embedder/embedder.cc @@ -630,6 +630,8 @@ InferVulkanPlatformViewCreationCallback( } #ifdef SHELL_ENABLE_VULKAN + const FlutterVulkanRendererConfig* vulkan_config = &config->vulkan; + std::function vulkan_get_instance_proc_address = [ptr = config->vulkan.get_instance_proc_address_callback, user_data]( @@ -688,7 +690,8 @@ InferVulkanPlatformViewCreationCallback( static_cast(config->vulkan.device), config->vulkan.queue_family_index, static_cast(config->vulkan.queue), vulkan_dispatch_table, - view_embedder, impeller_flags); + view_embedder, impeller_flags, + SAFE_ACCESS(vulkan_config, cache_path, nullptr)); return fml::MakeCopyable( [embedder_surface = std::move(embedder_surface), diff --git a/engine/src/flutter/shell/platform/embedder/embedder.h b/engine/src/flutter/shell/platform/embedder/embedder.h index 37734abfe9908..058f2259a237f 100644 --- a/engine/src/flutter/shell/platform/embedder/embedder.h +++ b/engine/src/flutter/shell/platform/embedder/embedder.h @@ -1059,6 +1059,11 @@ typedef struct { /// that external texture details can be supplied to the engine for subsequent /// composition. FlutterVulkanTextureFrameCallback external_texture_frame_callback; + /// The path to the Vulkan pipeline cache data. + /// The string can be collected after the call to `FlutterEngineRun` returns. + /// The string must be NULL terminated. + /// If NULL, Impeller will not use the Vulkan pipeline cache. + const char* cache_path; } FlutterVulkanRendererConfig; typedef struct { diff --git a/engine/src/flutter/shell/platform/embedder/embedder_surface_vulkan_impeller.cc b/engine/src/flutter/shell/platform/embedder/embedder_surface_vulkan_impeller.cc index d8e8018ab38a9..24c4423c0558b 100644 --- a/engine/src/flutter/shell/platform/embedder/embedder_surface_vulkan_impeller.cc +++ b/engine/src/flutter/shell/platform/embedder/embedder_surface_vulkan_impeller.cc @@ -6,6 +6,7 @@ #include +#include "flutter/fml/file.h" #include "flutter/impeller/entity/vk/entity_shaders_vk.h" #include "flutter/impeller/entity/vk/framebuffer_blend_shaders_vk.h" #include "flutter/impeller/entity/vk/modern_shaders_vk.h" @@ -30,7 +31,8 @@ EmbedderSurfaceVulkanImpeller::EmbedderSurfaceVulkanImpeller( VkQueue queue, const VulkanDispatchTable& vulkan_dispatch_table, std::shared_ptr external_view_embedder, - impeller::Flags impeller_flags) + impeller::Flags impeller_flags, + const char* cache_path) : vk_(fml::MakeRefCounted( vulkan_dispatch_table.get_instance_proc_address)), vulkan_dispatch_table_(vulkan_dispatch_table), @@ -56,6 +58,10 @@ EmbedderSurfaceVulkanImpeller::EmbedderSurfaceVulkanImpeller( settings.proc_address_callback = vulkan_dispatch_table.get_instance_proc_address; settings.flags = impeller_flags; + if (cache_path != nullptr) { + settings.cache_directory = + fml::OpenDirectory(cache_path, false, fml::FilePermission::kReadWrite); + } impeller::ContextVK::EmbedderData data; data.instance = instance; diff --git a/engine/src/flutter/shell/platform/embedder/embedder_surface_vulkan_impeller.h b/engine/src/flutter/shell/platform/embedder/embedder_surface_vulkan_impeller.h index d51918166455d..2f90c28f3dbc2 100644 --- a/engine/src/flutter/shell/platform/embedder/embedder_surface_vulkan_impeller.h +++ b/engine/src/flutter/shell/platform/embedder/embedder_surface_vulkan_impeller.h @@ -42,7 +42,8 @@ class EmbedderSurfaceVulkanImpeller final : public EmbedderSurface, VkQueue queue, const VulkanDispatchTable& vulkan_dispatch_table, std::shared_ptr external_view_embedder, - impeller::Flags impeller_flags = {}); + impeller::Flags impeller_flags = {}, + const char* cache_path = nullptr); ~EmbedderSurfaceVulkanImpeller() override; From 18a16d2d827a51d7e9bc3ca405b21bdc47eb7db9 Mon Sep 17 00:00:00 2001 From: JunsuChoi Date: Mon, 24 Aug 2026 19:14:29 +0900 Subject: [PATCH 29/29] [Tizen] Fix Vulkan external texture resolution issues - Return null instead of crashing when no rendering context is available - Derive the Skia color type from the supplied VkFormat instead of hardcoding kRGB_888x - Use kR8G8B8A8UNormInt as the descriptor format for YUV images so layout transitions emit a valid aspect mask --- .../embedder_external_texture_source_vulkan.cc | 5 +++++ .../embedder_external_texture_vulkan.cc | 17 +++++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/engine/src/flutter/shell/platform/embedder/embedder_external_texture_source_vulkan.cc b/engine/src/flutter/shell/platform/embedder/embedder_external_texture_source_vulkan.cc index e17f849c6c93d..06bc76fdabb5c 100644 --- a/engine/src/flutter/shell/platform/embedder/embedder_external_texture_source_vulkan.cc +++ b/engine/src/flutter/shell/platform/embedder/embedder_external_texture_source_vulkan.cc @@ -106,6 +106,11 @@ EmbedderExternalTextureSourceVulkan::ToTextureDescriptor( impeller::TextureDescriptor desc; desc.storage_mode = impeller::StorageMode::kDevicePrivate; desc.format = ToPixelFormat(embedder_desc->format); + if (RequiresYCBCRConversion( + static_cast(embedder_desc->format))) { + // Color-aspect placeholder for YUV images, as in AHBTextureSourceVK. + desc.format = impeller::PixelFormat::kR8G8B8A8UNormInt; + } desc.size = size; desc.type = impeller::TextureType::kTexture2D; desc.sample_count = impeller::SampleCount::kCount1; diff --git a/engine/src/flutter/shell/platform/embedder/embedder_external_texture_vulkan.cc b/engine/src/flutter/shell/platform/embedder/embedder_external_texture_vulkan.cc index 528f16ee1a7a4..bae5f36231f8a 100644 --- a/engine/src/flutter/shell/platform/embedder/embedder_external_texture_vulkan.cc +++ b/engine/src/flutter/shell/platform/embedder/embedder_external_texture_vulkan.cc @@ -65,8 +65,10 @@ sk_sp EmbedderExternalTextureVulkan::ResolveTexture( const SkISize& size) { if (!!aiks_context) { return ResolveTextureImpeller(texture_id, aiks_context, size); - } else { + } else if (!!context) { return ResolveTextureSkia(texture_id, context, size); + } else { + return nullptr; } } @@ -91,6 +93,17 @@ sk_sp EmbedderExternalTextureVulkan::ResolveTextureSkia( height = texture->height; } + SkColorType color_type; + switch (static_cast(texture->format)) { + case VK_FORMAT_B8G8R8A8_UNORM: + case VK_FORMAT_B8G8R8A8_SRGB: + color_type = kBGRA_8888_SkColorType; + break; + default: + color_type = kRGBA_8888_SkColorType; + break; + } + GrVkImageInfo image_info = { .fImage = reinterpret_cast(texture->image), .fImageTiling = VK_IMAGE_TILING_OPTIMAL, @@ -111,7 +124,7 @@ sk_sp EmbedderExternalTextureVulkan::ResolveTextureSkia( SkImages::BorrowTextureFrom(context, // context gr_backend_texture, // texture handle kTopLeft_GrSurfaceOrigin, // origin - kRGB_888x_SkColorType, // color type + color_type, // color type kPremul_SkAlphaType, // alpha type nullptr, // colorspace release_proc, // texture release proc