Skip to content

fix: bundle fragment shaders with the Metal runtime stage#34

Merged
MAUstaoglu merged 1 commit into
fluttertv:dev-fixesfrom
thachnb-harman:fix/tvos-metal-shader-runtime-stage
Jul 10, 2026
Merged

fix: bundle fragment shaders with the Metal runtime stage#34
MAUstaoglu merged 1 commit into
fluttertv:dev-fixesfrom
thachnb-harman:fix/tvos-metal-shader-runtime-stage

Conversation

@thachnb-harman

Copy link
Copy Markdown

Problem

TvosCopyFlutterBundle inherits upstream CopyFlutterBundle.build, which hard-codes targetPlatform: TargetPlatform.android for copyAssets. The target platform drives impellerc's runtime-stage flags (ShaderCompiler._shaderTargetsFromTargetPlatform): android yields SkSL/GLES/Vulkan stages, ios yields the Metal stage. Upstream can hard-code android because on real iOS the release asset copy happens in ReleaseIosApplicationBundle, which passes ios — but tvOS bundles assets through CopyFlutterBundle in every build mode, and the tvOS engine renders with Impeller/Metal.

The result: every fragment shader in a tvOS app bundle fails at load time with

Exception: Asset '....frag' does not contain appropriate runtime stage data for current backend (Metal).
Found stages: SkSL OpenGLES OpenGLES3 Vulkan

As a side effect, the build also emits impellerc SkSL-compilation warnings for shaders that can never target SkSL (e.g. ones using fwidth or array initializers), even though that stage is useless on tvOS.

Fix

TvosCopyFlutterBundle.build now mirrors upstream CopyFlutterBundle.build (same maintenance contract as TvosKernelSnapshot: re-mirror on Flutter upgrades) with one change — copyAssets receives targetPlatform: TargetPlatform.ios, so shaders are bundled with the Metal runtime stage. The existing empty-native_assets.json shim is folded into the mirrored body.

Testing

  • flutter/bin/dart analyze lib/ — no new issues vs unmodified main.
  • flutter/bin/dart test — identical results with and without the change (the failures in my environment reproduce byte-for-byte on unmodified main; unrelated).
  • End-to-end with an app using many fragment shaders: before, every FragmentProgram.fromAsset failed with the error above; after, the bundled .frag assets contain the Metal stage (MSL source present, GLES/Vulkan stages absent) and load correctly on the Apple TV simulator. The spurious SkSL impellerc warnings are gone as well.

Note: touches the same import block in lib/build_targets/application.dart as #33, so whichever merges second needs a trivial rebase.

🤖 Generated with Claude Code

@MAUstaoglu MAUstaoglu changed the base branch from main to dev-fixes July 10, 2026 09:42

@MAUstaoglu MAUstaoglu left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Diffed the new TvosCopyFlutterBundle.build line-by-line against upstream CopyFlutterBundle.build in the pinned SDK (e1fd963) — it's a faithful mirror with exactly the two intended deltas:

  1. the empty-native_assets.json shim is written first (folded in from the previous override, since we skip the native-assets targets), and
  2. copyAssets(... targetPlatform: TargetPlatform.ios ...) instead of upstream's hard-coded TargetPlatform.android.

The reasoning holds: targetPlatform drives ShaderCompiler._shaderTargetsFromTargetPlatform, and only the ios target emits the Metal runtime stage the tvOS engine (Impeller/Metal) needs. Upstream can hard-code android here because on real iOS the release asset copy runs through ReleaseIosApplicationBundle with ios; tvOS routes every build mode through this target, which is why the android-stage shaders were failing at load with "does not contain appropriate runtime stage data for current backend (Metal)".

All five added imports (depfile, assets, native_assets, devfs, dart_hook_result) resolve in the pinned SDK and are each used (Depfile, copyAssets, DartBuild.loadHookResult, DevFSContent/DevFSFileContent, DartHooksResult).

The doc comment correctly records the maintenance contract (re-mirror on Flutter upgrades) alongside the existing TvosKernelSnapshot note. As flagged, this shares the import block with #33 — whichever lands second gets a trivial rebase.

Approving.

@MAUstaoglu

Copy link
Copy Markdown
Member

Approved, but this now conflicts with the base after #33 merged into dev-fixes — it's the shared import block in lib/build_targets/application.dart (your native_assets.dart import vs #33's cache.dart import). Could you rebase onto the updated dev-fixes? Keep both imports (alphabetical: build_system/targets/native_assets.dart then cache.dart); the rest of your change is unaffected and merges cleanly. Once it's green I'll merge.

TvosCopyFlutterBundle inherited upstream CopyFlutterBundle.build, which
hard-codes targetPlatform: TargetPlatform.android for copyAssets. The
target platform drives impellerc's runtime-stage flags, so every
fragment shader was bundled with SkSL/GLES/Vulkan stages only — and the
tvOS engine renders with Impeller/Metal, so all shaders failed at
runtime with:

  Exception: Asset '....frag' does not contain appropriate runtime
  stage data for current backend (Metal).
  Found stages: SkSL OpenGLES OpenGLES3 Vulkan

Upstream can hard-code android because on real iOS the release asset
copy happens in ReleaseIosApplicationBundle, which passes ios — but
tvOS bundles assets through CopyFlutterBundle for every build mode.

Mirror upstream CopyFlutterBundle.build in TvosCopyFlutterBundle
(same maintenance contract as TvosKernelSnapshot: re-mirror on Flutter
upgrades) with targetPlatform: TargetPlatform.ios, so shaders get the
Metal runtime stage. This also stops requesting SkSL, which some
shaders (e.g. ones using fwidth or array initializers) can never
compile to anyway.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@thachnb-harman thachnb-harman force-pushed the fix/tvos-metal-shader-runtime-stage branch from c14c034 to 98f4243 Compare July 10, 2026 15:53
@thachnb-harman

Copy link
Copy Markdown
Author

Rebased onto dev-fixes — kept both imports in the shared block, alphabetical as suggested (build_system/targets/native_assets.dart before cache.dart). The rest of the change applied cleanly; dart analyze is clean on the rebased branch and GitHub reports the PR mergeable again. Thanks for the thorough review!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants