Skip to content

Run smoke-render on Linux and fix OpenGL ES rendering#144

Merged
bdero merged 10 commits into
masterfrom
bdero/smoke-linux
May 25, 2026
Merged

Run smoke-render on Linux and fix OpenGL ES rendering#144
bdero merged 10 commits into
masterfrom
bdero/smoke-linux

Conversation

@bdero
Copy link
Copy Markdown
Owner

@bdero bdero commented May 25, 2026

Adds a Linux job to the smoke-render check and fixes two flutter_scene rendering bugs it surfaced on OpenGL ES. Replaces the hosted-macOS job, which can't produce frames on CI runners.

flutter_scene:

  • Shaders now compile as conformant GLSL ES 1.00. The shadow PCF Poisson kernel was a const array, which compiles to a construct some OpenGL ES drivers reject; it is now unrolled.
  • Renders right-side up on OpenGL ES. The renderer probes the backend's render-to-texture Y orientation once (renders a known pattern and reads it back) and flips its output only when the backend stores bottom-up. Output is byte-identical on backends that already store top-down.

smoke-render check:

  • New Linux job (software rendering under a virtual display); the web job is unchanged, the hosted-macOS job is removed.
  • Adds a directional-light shadow scene and loosens one uniformity threshold for software rendering.

Verified: byte-identical output on the unaffected backends; Linux renders upright with both jobs green.

bdero added 10 commits May 24, 2026 15:40
Hosted macOS runners have no display, so the integration test's first
frame never pumps (confirmed: even a bare pumpWidget hangs). Linux has
Xvfb, which gives the GTK window a virtual display so frames flow, and
Flutter GPU is enabled cross-platform via the --enable-flutter-gpu engine
switch, so flutter_scene's native path can run on Linux under Mesa
software GL (llvmpipe).

Replace the unfixable hosted-macOS job with a linux job (xvfb-run +
llvmpipe + --enable-impeller --enable-flutter-gpu, Argos build-name
linux), and add the linux/ desktop scaffolding. Revert the macOS hang
diagnostics and the window-activation attempt; macos/ scaffolding stays
for local validation.
The standard fragment shader held its 16-tap Poisson disk in a const array.
impellerc/SPIRV-Cross emits a const array as a GLSL array constructor
(`vec2[](...)`) in its `#version 100` GLES output, which is invalid ES 1.00,
so the shader fails to compile on conformant ES drivers (Mesa/llvmpipe under
headless Linux CI). Lenient drivers accept it, which is why it went unnoticed.
Even an element-wise-filled array is folded back into a const array by the
SPIR-V optimizer, so the only robust fix is to remove the array entirely.

Unroll the PCF loop with the kernel as inline literals (a ShadowTap helper
plus a local macro), leaving no array for the optimizer to materialize.
Verified conformant ES 1.00 with glslangValidator and byte-identical output
on macOS/Metal. This is a temporary workaround; the real fix belongs upstream
in impellerc (emit valid ES 1.00, or target ES 3.00 for the bundle's GLES
stage). See the TODO in flutter_scene_standard.frag.
On Linux CI (llvmpipe software GL) the low-roughness metallic scene
renders correctly but with only 43 distinct colors, below the >100 gate.
That metric is the weakest sanity check (noisy, edge-AA-dominated) and
coverage + foreground luma are the real blank detectors, so lower the
threshold to 24: still catches a flat/uniform fill, tolerates software
output.
Temporary: lets us pull the actual rendered PNGs to verify software-Mesa
fidelity (the metallic scene in particular) before landing.
flutter_scene (like Impeller's Metal/Vulkan backends) assumes render-to-
texture content is stored top-down. Impeller's OpenGL ES backend stores it
bottom-up, and the upstream fix that makes GLES match (flutter/flutter#186556)
is not yet in the engines we build against, so flutter_scene rendered upside
down on native GLES (e.g. Linux desktop, headless CI).

Absorb the difference in flutter_scene as a temporary workaround, gated on a
GL-backend proxy (no offscreen-MSAA support; Flutter GPU exposes no backend
query). New src/render/y_flip.dart:
- negates gl_Position.y in every offscreen pass (matrix premultiply for the
  matrix-based scene/shadow passes; a FlipInfo uniform for the full-screen
  tonemap/prefilter passes), storing targets top-down;
- inverts cull winding to compensate (the Y negation reverses screen winding);
- so the existing render-target sampling flips become the top-down value on
  every backend (tonemap flip_y 0.0, render_target_flip_y 1.0).

Gated off for Metal/Vulkan and the WebGL2 shim (which does its own flip), so
those paths are byte-identical (verified on macOS/Metal and web). TODO: remove
once the GLES top-down fix lands upstream; see the note in y_flip.dart.
…proxy

The offscreen-MSAA proxy is true on the Linux runner (llvmpipe supports
GLES3 MSAA), so the Y-flip stayed gated off. Dump capabilities to find a
signal that distinguishes the GLES backend from Metal/Vulkan.
…pabilities

The offscreen-MSAA proxy is unreliable: it is true on GLES3/llvmpipe (the
Linux CI runner), and no Flutter GPU capability cleanly separates GLES from
Vulkan, so a format-based guess risks flipping Vulkan. Instead, measure the
orientation directly: on the first Scene.render, render a known top/bottom
pattern to an offscreen texture and read it back (asImage + toByteData). Top
row red means top-down (Metal/Vulkan/web shim); otherwise bottom-up (OpenGL
ES) and backendFlipsRenderTargetY becomes true.

The probe runs during a frame (the GLES context is only up after the first
frame) and reads back asynchronously, so the first frame uses the default
(no flip) and later frames use the measured value. Verified byte-identical on
macOS/Metal and web (probe measures top-down, flip stays off).
The vertex-stage flip stores the scene top-down, but asImage presents the
swapchain as-is on native (unlike the web shim's present-time blit flip), so
the result landed upside down. Flip the drawImageRect blit vertically when
backendFlipsRenderTargetY to match.
Remove the backendFlipsRenderTargetY debug print and the per-run frame
artifact upload now that the Y-flip workaround is validated (Linux renders
right-side up, matching macOS; web and Metal unchanged).
@bdero bdero merged commit 9a88072 into master May 25, 2026
2 checks passed
@bdero bdero deleted the bdero/smoke-linux branch May 25, 2026 04:25
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.

1 participant