Skip to content

feat(build_to_burn): Fluttercon 2026 talk deck and impeller_model GPU cost model - #15

Merged
timcreatedit merged 45 commits into
mainfrom
tim-lehmann/fluttercon-2026-deck-scaffold-8218
Oct 6, 2026
Merged

timcreatedit merged 45 commits into
mainfrom
tim-lehmann/fluttercon-2026-deck-scaffold-8218

Conversation

@timcreatedit

@timcreatedit timcreatedit commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds apps/build_to_burn, the complete Fluttercon 2026 lightning talk by Jesper Bellenbaum & Tim Lehmann, running on motor 2.0 — plus packages/impeller_model, the GPU cost-estimation package built for the talk, its agent skills, a device-validation harness, and a gpu_probe experiment app.

The deck. Cold open on the "you all write this" code slide, then the hook: a feed behind a CupertinoAlertDialog that looks static but pays several render passes per frame and never stops requesting them. The core visualization is the isometric render stack — each pipeline stage lands as its own flat slide (INPUT chip → picture → OUTPUT chip), then shrinks and tilts onto the stack; loops, tool spotlights, frames-in-flight and the feedback arrow are brackets beside the layer list, clamped to landed planes. Second half: live demo of the hook screen's real cost, engine internals from layer tree to Impeller passes, GPU profiling tools, takeaways, docs & skills slides.

The aha. A running Ticker is a full frame every vsync (≈119/s, ≈111 repainting nothing); #192128 is shown as a partial, framework-side drawFrame gate that cuts the caret's arc while a RepaintBoundary spinner keeps pulsing; the fix is to stop or slow the ticker (fixed_ticker).

impeller_model. Ports Impeller's pass logic (pinned Flutter 3.47.1) into a flutter_test binding: estimateGpu reports render passes, cost centers and nominal memory traffic per device class; measureFrameDemand attributes idle-time frame requests to tickers, carets and timers; reports are JSON + HTML + PNG. Every ported engine decision is quoted verbatim next to its Dart port and checked by tool/engine_refs.dart; validation/ compares predictions against real GPU traces on macOS Metal and a Pixel 10 (14/14 scenes). Ships three agent skills (gpu-cost estimate, frame-demand, device gpu-profiling) plus docs.

A stage slide
Frames in flight
Demo video

Motor 2.0. motor is pinned to the rivership motor/2.0 branch at aba0c4c. The workspace has one shared resolution, so the root pubspec.yaml also overrides motor for every package; the ref is repeated in apps/build_to_burn/pubspec.yaml — bump both together.

Workspace migration. On current stable Flutter, main did not resolve: rivership → ^0.4.0 in wnma_talk, physical_ui and gaussian_splatting (requires heroine ^0.7.2, hooks_riverpod ^3.0.0); physical_ui imports StateNotifierProvider from hooks_riverpod/legacy.dart; wnma_talk fixes 3 analyzer findings.

Test plan

  • flutter test in apps/build_to_burn (fonts loaded, every slide and step walked, render-stack scripts tested)
  • flutter test in packages/impeller_model (model cases, report, frame demand)
  • validation/ scenes match real traces on macOS Metal and Pixel 10 (see FEASIBILITY.md)
  • flutter analyze clean for build_to_burn and impeller_model
  • physical_ui depends on the private whynotmake-it/glibber; resolved locally against a stub — verify on a machine with access
  • Render-stack values the spec marks [verify]/[estimate] are unverified

Generated with Devin

cursoragent and others added 30 commits September 24, 2026 17:04
Override motor with the top of the rivership motor 2.0 PR stack
(whynotmake-it/rivership#325, pinned to e3fd207) for the whole workspace.

The old pins no longer resolve on current stable Flutter: rivership
0.3.x caps material_color_utilities below what the SDK pins. Bump
rivership to ^0.4.0, which brings heroine ^0.7.2 and hooks_riverpod 3,
and import StateNotifierProvider from riverpod's legacy library.

Co-authored-by: Tim Lehmann <hi@timcreated.it>
Co-authored-by: Tim Lehmann <hi@timcreated.it>
A new talk app with a title slide and placeholder openers for the hook,
frame pipeline, painting vs compositing, GPU profiling, fixes, and
production + AI skill sections. The look follows motor 2.0's example
gallery: its palette, Archivo and JetBrains Mono, and the shared logo
glyph from example_design.

Co-authored-by: Tim Lehmann <hi@timcreated.it>
…d README

Co-authored-by: Tim Lehmann <hi@timcreated.it>
Co-authored-by: Tim Lehmann <hi@timcreated.it>
One slide per file, each with its own route and speaker notes, listed
explicitly in main.dart; shared widgets in lib/shared; wnma_talk as a
path dependency; a closing thanks slide. The top bar shows the speaker
from wnma_talk's speaker-notes header convention.

Co-authored-by: Tim Lehmann <hi@timcreated.it>
…sualizations

Standalone, self-scaling motor 2.0 components that later slides can place
and restyle freely:
- FramePipeline: one frame on UI thread, raster thread and GPU lanes over
  a 120 Hz vsync ruler, revealed per FrameStage.
- PaintVsComposite: a BackdropFilter search sheet twice, showing what
  repaints versus what re-renders, next to GPU work = how often x how
  hard, driven by a BlurScenario.

Co-authored-by: Tim Lehmann <hi@timcreated.it>
One plain placeholder per agenda section with the facts in the speaker
notes, plus the cold open and paint-vs-composite slides, which step
through the two visualizations with deck navigation.

Co-authored-by: Tim Lehmann <hi@timcreated.it>
…nder stack

Tim's core concept: widget code at the bottom, pixels at the top, each
level an isometric plane. One steppable motor 2.0 component shows planes
building up, opening to show their contents, highlights, per-plane inputs
and outputs, the CPU/GPU border, looping repaint/frame pulses, and a
frame N / N+1 pipeline. Content is placeholder data in
render_stack_content.dart until the spec lands.

Co-authored-by: Tim Lehmann <hi@timcreated.it>
One data-driven motor 2.0 widget following
docs/render-stack-visualization.md: nine tiers in four bands (plus the
Scene handoff tray and a system-compositor cap), expandable tiers with
their example content, off/dim/hot lighting, the thread, CPU/GPU and
present borders, loop arcs pulsing at their real rate slowed x10, tool
spotlights, and a frame token that climbs to the demo's pixels.

Ships build-up steps 1 (cold open), 3 (UI half), 4 (raster half),
5 (aha: loops A, C and Scene only, RepaintBoundary, #192128) and
8 (profiling tools) as five slides.

Co-authored-by: Tim Lehmann <hi@timcreated.it>
…able

Give the thin borders one label each so they don't collide with the
CPU/GPU border, leave more room above an expanded tier, and expand the
UI half's tiers one at a time.

Co-authored-by: Tim Lehmann <hi@timcreated.it>
…tiles and feedback

Aha: a running Ticker means a full frame every vsync. Loop T (Ticker
frame) is the bold arc from the vsync, counted at the measured ~119
frames/s with ~111 repainting nothing; the repaint loop C (~8/s) is a
thin side branch; #192128 cuts T at tier 5.

Tiers 2-4 follow the real dumps: picture 2 includes the text field's
white box and hairline border, and the layer tree is the curated one
with app-shell and helper chips and the FollowerLayer. Plane text is
larger and more spaced, and planes above an expanded tier are more
transparent.

New build-up steps: the hook (the stack dims behind the demo on a phone
with a live blur, a fading caret and the vote), why a blur costs (tile
memory fills, flushes T0 to DRAM and re-seeds from it), and the
back-pressure and completion arrows across the CPU/GPU border.

Co-authored-by: Tim Lehmann <hi@timcreated.it>
Expanded planes now show a schematic of their detail in 3D, and the
readable text sits in a flat card at the bottom left with a leader to
its plane. The slide shows each step's caption above the stack, so the
bottom left stays free for loop arcs.

Co-authored-by: Tim Lehmann <hi@timcreated.it>
No-repaint ticker frames build no Scene, so the ticker arc is cut below
the handoff. Captions and notes give the measured caret drop on master
(~119 to ~7.9 Scenes/s) and 3.50 stable as the expected release.

Co-authored-by: Tim Lehmann <hi@timcreated.it>
Co-authored-by: Tim Lehmann <hi@timcreated.it>
…r the arcs

Co-authored-by: Tim Lehmann <hi@timcreated.it>
Co-authored-by: Tim Lehmann <hi@timcreated.it>
…vote

Co-authored-by: Tim Lehmann <hi@timcreated.it>
Following Tim's build-up (spec section 7.1): the cold open is the code
slide alone; in the pipeline sections each stage appears as its own
flat slide with its input (the previous stage's output) and its output,
then tilts and shrinks onto the stack as the next plane. After the last
stage, frames in flight show N+1, N and N-1 on different planes at once,
the 2-slot queue and 3 drawables, then what doesn't overlap (spec
section 6), then the four-band zoom-out.

The label list shows only number and title; the focused stage expands
with its input, output and thread. Loops, borders, tool spotlights,
frames and the feedback arrows are brackets beside the list, and rows
light up with their planes. The renderer is split into part files.

Co-authored-by: Tim Lehmann <hi@timcreated.it>
…tack

Co-authored-by: Tim Lehmann <hi@timcreated.it>
… cut

A fifth aha step: after the caret's ticker arc is cut, a custom spinner
inside a RepaintBoundary keeps pulsing at full rate, since it paints one
small picture every tick. Captions and notes call #192128 a partial,
framework-side skip and lead with the main fix: stop or slow the ticker.

Co-authored-by: Tim Lehmann <hi@timcreated.it>
…entered stack

- The focused row's input, output and thread are readable and wrap;
  rows below it move down to make room.
- Landed planes keep a faint schematic of their stage; only the top
  plane shows the demo pixels.
- The stack and list are centered vertically on their visible extent,
  gliding as planes land.
- Frames in flight spread the stack so each frame pill sits on its own
  band, level with its bracket.

Co-authored-by: Tim Lehmann <hi@timcreated.it>
The previous pin (e3fd207) became unreachable after the rivership branch
was rebased. The new pin is the head of the motor/2.0 integration branch,
kept reachable by the rivership tag talks/motor-2.0-pin-aba0c4c.

- motor, fixed_ticker and example_design resolve at aba0c4c
- fixed_ticker is overridden to the same ref because motor there needs
  ^0.2.1, which is not on pub.dev yet
- FontMotionConverter now extends MotionConverter so it inherits the new
  lerp default instead of having to implement it
- top bar: drop the motor logo and the section eyebrow; the speaker is
  now only the subtle colored dot, colored like the other talks
  (tim blueAccent, jesper green, shared via SlideSpeaker.color)
- title slide: drop the speaker names, keep whynotmake.it
- remove the section parameter from RenderStackSlide/SkeletonSlide
- scaffold the linux runner so the deck runs as a desktop debug build
- Split the pipeline into one deck slide per layer; advancing lands the
  current card on the stack, then the next layer's card comes in.
- Chained stack slides hand off live track values and velocities, so
  motion continues across slides instead of restarting from zero.
- Landing runs on snappy springs: the card shrinks, then its corners fly
  to the live plane one by one, in the order that keeps the quad convex.
- Slides headed "N: title" per layer; drop the progress strip and slide
  count.
- Analyzer excludes for build output, macOS CocoaPods setup, drop slide
  tests per AGENTS.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d frame slots

- Cold open shows the demo on a phone next to its code; the hook adds the vote.
- Drop the full-screen code slide and the "Widgets by color" slide.
- Render objects slide: second step opens the official docs' trees picture
  as a dialog, with a link and QR code to the architectural overview.
- New "Inside paint()" slide with trimmed paint() methods from the Flutter
  sources, marking the FFI call into C++.
- Paint calls slide: drop the two note lines.
- Scene slide: label both frame-pipeline slots and explain why there are two.
- Pipeline limits slide: show both slots taken and the next frame waiting.
- Clear the speaker notes down to their speaker header.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
JesperBellenbaum and others added 14 commits October 3, 2026 22:19
A standalone package (outside the melos workspace) that captures a
widget-test frame — recorded canvas ops + the debugLayer tree — and
replays it through a Dart port of Impeller's Canvas::SaveLayer /
FlipBackdrop / blur-pass decision logic across four capability
profiles. Emits per-pass timelines (JSON + HTML) with sizes, reasons,
flips and a memory-traffic estimate, so "did my widget change add a
pass" becomes a deterministic widget-test signal instead of a noisy
on-device benchmark.

Engine-semantics fidelity work (verified against flutter 3.47.1
engine sources): saveLayer content-bound rewriting, unbounded/cull
accumulation with has_valid_clip tracking, collective opacity/filter
distribution via renderable-state flags, backdrop-group snapshots,
per-pass MSAA traffic accounting, content-based picture↔layer
matching, and scope-balanced op streams.

FEASIBILITY.md documents accuracy, limitations, the upstream hooks
needed, and a recommendation. CI runs the package's tests + analyzer
on a pinned Flutter 3.47.1 (the model depends on private engine
semantics that float on stable).

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…and reports

- Pass model ported from Impeller 3.47.1 with every engine decision quoted
  (tool/engine_refs.dart verifies 143 quotes, --diff-to lists what an SDK
  upgrade touches).
- Validated against real GPU traces: 14/14 scenes on macOS Metal (Metal
  System Trace) and on a Pixel 10 (Vulkan pass logger); predict_test fails
  when the model drifts from the committed results.
- Memory traffic: resolve-only MSAA, BGRA10_XR (8 B/px) on iOS, per-device
  screen-equivalents; tests at device preset sizes.
- Frame demand: sources, tickers and identical frames, measured on each
  device's platform.
- estimateGpu API with device presets, JSON/HTML report and snaptest
  screenshot; example app with the talk's hook screen.
- Agent skills: gpu-cost, frame-demand, gpu-profiling; engine-upgrade
  maintainer skill. README, MAINTAINING and FEASIBILITY rewritten.
- CI runs the quote check, example and validation predictions.

Thread: https://delta.dev/join/thread_01m41zp98qhmvxdcvyfaqswm86
The layer walk read Flutter's layer tree by type, so a package's own Layer
subclass that pushes clips or filters in addToScene (liquid_glass_renderer's
LiquidGlassCapture) was estimated as an empty container. The binding now
wraps createSceneBuilder, records every push it forwards, and rebuilds
custom layers from what they pushed. Their framework children keep their
widget origins. A custom layer whose pushes cannot be found is reported in
unmodeledLayers, only when the scene holds a push nobody accounts for.

Also moves to snaptest 1.0.0-dev.3 (setTestViewForDevice, snap(device:)).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The report keeps only what a developer can act on: passes and memory traffic
per device, the widgets that cause them with a one-line fix per cause, the
frames drawn while idle with who requested them, and warnings when part of
the estimate is a guess (approximate passes, uncounted pictures by location,
unreadable custom layers). Tabs, cards, the pass timeline, the GPU capture
table and the frame strip are gone from the page; the JSON keeps them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A custom layer whose engine layer came from an unrecorded scene (a toImage
  snapshot) is now reported as unmodeled instead of silently estimated as a
  plain container.
- Pushes no layer accounts for are reported even when a custom layer was
  read from the scene; the check is skipped when the recorded scene no
  longer matches the tree (a snapshot replaced engine layers after it).
- Leaf layer subclasses (PictureLayer, TextureLayer, ...) draw like their
  framework type instead of being dropped.
- Backdrops compare by BackdropKey whether framework- or scene-read.
- estimateGpu takes the screenshot after the frame demand, so its toImage
  cannot hide custom layers from the demand's estimates.
- The report says when idle frames do not stop on their own.
- Framework layers are indexed once per walk; the owner's origin is computed
  once per custom layer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Merge the layer tree and Scene into one stack tier and one slide: the
  pictures cross to C++ first, then the SceneBuilder copies the layers
- New animated DisplayList and render passes slides; the GPU chapter
  shows one frame and moves before the passes slide
- Renumber the render stack to 8 tiers
- Short titles instead of subtitles; trim the title slide
- F toggles per-slide flow notes (question, takeaway, transition)
- Speaker dots: 1-3 Tim, 4-6 Jesper, 7+ Tim

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every pass of one frame in order, with its size, share of the screen, cause
and what ended it early; the pass role's explanation shows on hover.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Slide 10 shows the finished pass plan and announces the live demo
- New slides: live demo, every frame, quiz answer, fast is not cheap
- Ticker slide: expected 8 frames a second, then 120 in reality
- Profiling slide shows our own captures of each tool next to the stack
- Remove the GPU tiles, pixels, zoom-out and blur-cost slides; frames in
  flight and pipeline limits move to backup
- Slow, readable line steps on the DisplayList slide, with a reading dot

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- New docs and skills slide with QR codes to the Impeller profiling guides
  and the impeller_model agent skills
- Thank you slide as the end
- Remove slides that didn't make the final deck: GPU tiles, pixels, zoom
  out, why blur costs, fixes, production, Monday checklist
- Profiling slide: repaint highlighting as a looping clip, no frame
  capture step, no bracket labels next to the compressed stack

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lls slides

- Title: "Beyond DevTools", "What is Flutter doing on my GPU?".
- Hook: Xcode's energy report of the demo as a dialog, then the vote. The
  demo phone shows its keyboard; the quiz names the circles underneath,
  the fading cursor and the rounded clip.
- Stage 5 ends on the written, flat DisplayList (two steps).
- New takeaways slide (cost = frames x price per frame) and a skills slide
  that replaces docs and skills.
- Work in progress on the GPU chapter, engine stage and render stack cards.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Redesign impeller_model HTML report

Restructure the GPU estimate report with:
- Device picker tabs instead of a table
- Screenshot display in hero section
- Visual bars for pass costs and frame strips
- Updated styling with CSS variables for light/dark modes
- Reorganized content layout for better scanning
- Updated README to reflect new report structure
@timcreatedit timcreatedit changed the title feat(build_to_burn): scaffold the Fluttercon 2026 deck on motor 2.0 feat(build_to_burn): Fluttercon 2026 talk deck and impeller_model GPU cost model Oct 6, 2026
@timcreatedit
timcreatedit marked this pull request as ready for review October 6, 2026 08:49
@timcreatedit
timcreatedit merged commit af15a30 into main Oct 6, 2026
3 of 5 checks passed
@timcreatedit
timcreatedit deleted the tim-lehmann/fluttercon-2026-deck-scaffold-8218 branch October 6, 2026 10:13
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.

3 participants