Repository navigation
Conversation
…cit LOD On ANGLE's Direct3D backend (Impeller on Windows) a lit pipeline's first use runs the HLSL compiler, and nothing keeps the result between launches. StandardFragment took 14 s to link on a Ryzen 5 2600 with a Radeon RX 580, and the application is frozen meanwhile. SampleShadow expanded SampleCascade once per cascade through _TRY_CASCADE(0..3), so its three filters and the blocker search were compiled four times: the generated OpenGL ES source read the shadow atlas 103 times. It now walks the cascades in a loop bounded by the cascade count. The matrix and box size are still selected with literal indices, the loop has a single exit, and the arithmetic and the cascade order are unchanged. Two texture() reads were still inside loops: the band atlas in SamplePrefilteredRadiance, behind an early return that SPIRV-Cross emits as a one-iteration loop, and the LTC table in the punctual light loop. ANGLE marks such a loop [loop] and the branches around it [flatten], and reads the texture at LOD 0 anyway. Both textures have a single level, so textureLod(..., 0.0) returns the same texel on every backend. Compile and link on ANGLE, one program per process: StandardFragment 14.0 s to 5.2 s, StandardLeanFragment 6.2 s to 2.1 s, StandardNoShadowFragment 2.8 s to 1.7 s, StandardLeanNoShadowFragment 0.79 s to 0.51 s. Warming up a ten-primitive scene with shadows and ambient occlusion went from 23.6 s to 9.5 s. The 45 smoke frames are identical pixel for pixel on Windows before and after. Frame time at 2048 x 2048 on the same machine is unchanged within the 4 % noise of the measurement, for every directional filter; it was not measured on a mobile GPU. lit_shader_compile_cost_test.dart checks both patterns on the source the build hook's compile produces.
The test now takes its impellerc arguments from shaderBundleImpellercArguments and the GLES version from the engine hook's constant, so it cannot drift from the build. Adds the CHANGELOG entry.
|
The latest updates on your projects. Learn more about Argos notifications ↗︎
|
scalz
marked this pull request as ready for review
October 13, 2026 07:13
Picking the cascade's matrix through an if-chain made Mali flatten it and load all four matrices on every iteration, so shadowed frames on a Galaxy A16 drew 4 to 5% slower than with the unrolled cascades. Vulkan and Metal now index the array with the loop counter. GLES keeps literal indices, since some GLES drivers misread a dynamically indexed uniform array, and transforms inside each branch so a flattened chain still reads one matrix.
bdero
force-pushed
the
lit-shader-compile-time
branch
from
October 13, 2026 12:02
08c7eff to
55d62a6
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
On Windows (Impeller on ANGLE's Direct3D backend), the first use of a lit pipeline freezes the application for seconds, and nothing keeps the compiled program between launches, so every launch pays it again (flutter/flutter#191860).
StandardFragmentalone takes 14 s to link on a Ryzen 5 2600 with a Radeon RX 580.This change takes the four standard lit entries from 23.7 s to 9.5 s in total, with pixel-identical output on ANGLE, by removing two patterns from the generated OpenGL ES source:
_TRY_CASCADE(0..3)expandedSampleCascade(three filters, the blocker search, the probes) once per cascade.SampleShadownow walks the cascades in a loop bounded by the cascade count, so the sampler is compiled once. The matrix and box size are still selected with literal indices, and the loop has a single exit (nobreak), as the existing Direct3D note asks. The arithmetic and the order of the cascades are unchanged.texture()reads sat inside loops. ANGLE marks a loop that contains one[loop]and every branch around it[flatten], and reads the texture at LOD 0 anyway. Two places were left after Sample the shadow atlas and punctual light tables with an explicit LOD #497: the band atlas inSamplePrefilteredRadiance(inside the one-iterationdo { } while (false)that SPIRV-Cross emits for the function's earlyreturn) and the LTC table in the rect area light branch (inside the punctual light loop). Both textures have a single level (createPrefilterAtlasTexture(mipLayout: false),buildBrdfLutTexture), sotextureLod(..., 0.0)returns the same texel on every backend.This fixes #501 .
Measurements
Flutter stable 3.47.6 (engine 692136cb65), Ryzen 5 2600, Radeon RX 580 (driver 31.0.21923.11000), Windows 11 25H2. Base is
1f3fc8a9f2.Compile and link of one program on ANGLE (D3D11), each in a fresh process, with the generated OpenGL ES source of each entry and
UnskinnedVertex. Flutter links ANGLE statically, so this uses the ANGLE DLLs that ship with VS Code; the times agree with the application's within 5 %. One measurement each:StandardFragmentStandardLeanFragmentStandardNoShadowFragmentStandardLeanNoShadowFragmentIn an application (ten primitives with
PhysicallyBasedMaterial,Scene.warmUpcalled with one more feature each time, release build, three launches each, median):The smoke suite on the same machine ran in 4 min 06 s instead of 5 min 56 s (one run each).
Where the time went
fxc /T ps_5_0 /O2on the HLSL that ANGLE generates forStandardFragmentat base, which reproduces the link time:ANGLE_ENABLE_LOOP_FLATTEN=1)[loop]and[flatten]attributes/Odinstead of/O2So about 5 s came from the attributes, and most of the rest from the size of
main(5,000 lines once everything is inlined), of which the four cascade copies were the largest part: once the attributes are gone, keeping a single_TRY_CASCADEtakes the entry from 9.0 s to 4.8 s.Testing
examples/smoke_renderon Windows (ANGLE, hardware D3D11), before and after: all 50 tests pass and the 45 frames are identical pixel for pixel.test/lit_shader_compile_cost_test.dartcompiles the four entries the way the build hook does and checks, on the generated OpenGL ES source, that no implicit-LOD texture read is inside a loop and that the shadow atlas is read from one copy of the cascade sampler. It fails on base (103 reads, and the reads in the two places above) and passes here. It needs onlyimpellerc, not a GPU.test/physical_material_sources_test.dart: two assertions on the source text ofSampleShadowfollow the new loop.flutter testinpackages/flutter_scene: the same 8 tests fail on this Windows checkout with and without the change (bloom_pass_test,engine_stamp_test,source_scene_loader_test,generated_target_isolation_test,shader_precision_test, and three inphysical_material_sources_test); none is related.Not verified
17296f49da. The loop replaces four guarded blocks with one loop of at most four iterations, with the same texture reads. On the RX 580 I rendered a shadowed scene (a ground plane filling the view, 48 casters) off screen at 2048 x 2048 with a read-back per frame, 80 frames per configuration, for 8 configurations (no shadows, each of the four filters with four cascades, one cascade, a 0.3 cascade overlap, and the full shader), base and this PR in alternation, three runs each, then again in the opposite order. On frames of 6.5 to 13 ms, this PR is 0.2 to 0.6 ms slower in the first order and within 0.3 ms either way in the second, and the configuration without shadows moves as much as the shadowed ones. I read that as noise of about 5 %: it rules out a large change on this desktop GPU and nothing finer..fmatphysical materials include the same files and should gain as much; I did not time them.Left out on purpose
Three
[loop]or[flatten]sites remain in the lit entries on ANGLE:SpecularAARoughnessandTangentFramereturn early before screen-space derivatives. Giving them a single return removes the sites and saves a further 0.1 to 0.2 s per entry, but it changes 8 of the 45 smoke frames on ANGLE. In the two I looked at closely,dielectric_speculardiffers by a one-pixel rim at the silhouettes, andphysical_layeredshows a pinch near a pole of the sphere at base that the single-return version does not. That looks like a fix, but it is a visible change on one backend and I have no frame from another backend to compare with, so it belongs in its own pull request.Also untouched:
ResolveFragmenttook 0.27 s in 0.24.3 and takes 0.85 s sincebfb788dca7, which callsDisplay()from three places.🤖 Generated with Claude Code