mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-14 05:06:16 -08:00
perf(tooling): compositing probe, report command and route-wide clip-mask guard (#1858)
* perf(tooling): compositing probe, report command and route-wide clip-mask guard Make the instrument that found the dashboard's clip masks part of the repository: `src/performance/compositing-probe.ts` reads the layer tree over CDP (bounds in device pixels, compositing reasons and owner nodes, waiting for Blink's layer debug info), classifies synthesized clip masks (no owner node and no compositing reason) and reads the renderer's `cc/tile_memory` from a memory-infra dump. `pnpm run perf:compositing` seeds one profile against the Xtream mock on a dedicated port, serves the artwork from memory, fixes the window at 1600x1000 and records tile and image memory, layer counts, the largest layers, masks and tile warnings for Live TV, the movie and series details, the dashboard (idle, crossfade, scrolled) and settings into dist/performance/compositing/<timestamp>/summary.json and a console table. Both helpers have node:test coverage in the performance harness. The dashboard-only compositing E2E becomes `compositing.e2e.ts` and guards Live TV, a movie detail, a series detail and the dashboard in one launch, with the backdrop-filtered controls confirmed as composited layers on the dashboard and the movie detail. The rule now lives where it triggers: a Rendering Cost section in the theme/style skill, a validation line in the UI design skill, a context map row, and a Compositing budget section in the performance journeys contract (budget, mask mechanism, instruments, why megabytes are evidence rather than a ratchet). Validation map and README mention the command. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * perf(tooling): sample the dashboard idle reading with rotation paused The hero advances after 8 s, so a 9 s wait sampled the next slide one second into its zoom (backdrop scale 1.116 instead of 1.1, the previous slide's tiles still pooled): 224 MB read as idle, 122 MB with rotation paused and the zoom and tile pool settled. The warning delta and its checkpoint are now taken at one boundary after the sampling, so a "tile memory limits exceeded" line logged while the layers or the dump are read counts for that route instead of for none. The report's test artifacts go under dist/test-results like the other configs'. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * perf(tooling): retry a memory dump that carries no renderer tile memory One reading of seven came back without `cc/tile_memory`: the dump's trace events reach the buffer shortly after the request resolves, and a process can skip a dump it is busy for. The capture now waits briefly before ending the trace and retries up to three times before a reading stays null. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * perf(tooling): run the compositing report with motion enabled With the OS set to reduced motion the hero neither rotates nor renders its pause button, so the report's click timed out before the summary was written. The report now emulates `prefers-reduced-motion: no-preference`, which also keeps the idle and crossfade readings on the same animation set on every machine. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * perf(tooling): sample the crossfade frozen, count split stderr lines, drop stale layer samples Review findings on the pushed head, plus the CI failure: - A layer that vanished between the LayerTree snapshot and its reasons query was kept with empty reasons; without an owner node it would pass for a synthesized mask. Such a snapshot is now discarded and retaken. - The crossfade reading sampled layers, owners and memory across the 700 ms fade, so the row depended on how long the sampling took. The page's animations are frozen 300 ms into the fade while the reading is taken, resumed afterwards, and the incoming slide's zoom is allowed to finish before the scrolled reading. - A stderr chunk can end mid-line; the warning counter now keeps the unfinished line for the next chunk. - The mock server's launch spec lists the new Playwright config. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
8030ced95a
commit
e29f36426b
18 files changed
+1374
-159
No files matched your search
@@ -1392,6 +1392,68 @@ threshold on Node 22 and inside Electron 43, so D2 regex prefilters were not
|
||||
applied. Rerun the benchmark after changing either parser or when a user
|
||||
reports slow imports of non-Latin playlists.
|
||||
|
||||
## Compositing budget
|
||||
|
||||
Chromium rasterizes every composited layer into tiles and gives a desktop
|
||||
renderer 512 MB of tile memory (1 GB on a display at least 3500 device pixels
|
||||
wide). Tiles needed for the visible frame that do not fit log
|
||||
`cc/tiles/tile_manager.cc: WARNING: tile memory limits exceeded, some content
|
||||
may not draw` and leave blank tiles while scrolling. On a 2x display a layer
|
||||
the size of the content area costs about 24 MB, so a dozen full-size layers
|
||||
spend the budget. The expensive layers are rarely the obvious ones: a
|
||||
`border-radius` on a scroller makes Blink clip every composited effect inside
|
||||
it (`backdrop-filter`, `filter` with a render surface, a running transform or
|
||||
opacity animation, `isolation: isolate`, `will-change`) through a mask layer
|
||||
synthesized per effect, each the size of the whole clipped area, because its
|
||||
shader path for rounded clips needs four equal radii on macOS and a
|
||||
translation-only transform between the clip and the effect
|
||||
(`PropertyTreeManager::ShaderBasedRRect`). The workspace shell therefore
|
||||
paints its content corner instead of clipping it
|
||||
([workspace shell](workspace-shell.md#content-surface-corner)).
|
||||
|
||||
Two instruments share `src/performance/compositing-probe.ts`, which reads
|
||||
the layer tree over the Chrome DevTools Protocol (`LayerTree`: bounds in
|
||||
device pixels, compositing reasons and owner nodes, available only after
|
||||
Blink's first layerization with the domain enabled) and the renderer's
|
||||
`cc/tile_memory` from a memory-infra dump (`Tracing.requestMemoryDump`). A
|
||||
synthesized clip mask is a drawing layer with no owner node and no
|
||||
compositing reason; a content layer always carries a reason, and may lack an
|
||||
owner node when its first paint chunk belongs to an anonymous box.
|
||||
|
||||
- `src/compositing.e2e.ts` is the deterministic guard, run with the Electron
|
||||
E2E suite on every platform: on Live TV, a movie detail, a series detail
|
||||
and the dashboard no synthesized mask may span half the content area, and
|
||||
the backdrop-filtered controls must really be composited layers, so the
|
||||
check cannot pass on a page with nothing to clip. Blink synthesizes the
|
||||
masks before any GPU work; under `--disable-gpu` (the Linux CI launch) the
|
||||
old scroller radius still produced seven.
|
||||
- `pnpm run perf:compositing` (`electron-backend-e2e:compositing-report`,
|
||||
`playwright.compositing.config.ts`, Xtream mock on `127.0.0.1:3233`,
|
||||
override with `IPTVNATOR_COMPOSITING_XTREAM_MOCK_PORT`) measures. One fresh
|
||||
profile is seeded through the app's dialogs with a live, a movie and a
|
||||
series favourite, artwork is served from memory, and the window is 1600x1000
|
||||
CSS pixels. For Live TV, the movie and series details, the dashboard (idle,
|
||||
during a crossfade, scrolled) and settings it records tile and image
|
||||
memory, composited layer counts, the eight largest layers with their
|
||||
owners and reasons, synthesized masks and the `tile memory limits
|
||||
exceeded` lines on stderr, into
|
||||
`dist/performance/compositing/<YYYYMMDDTHHMMSSZ>/summary.json` and a table on
|
||||
the console. The pool keeps freed tiles for a few seconds, so each reading
|
||||
waits `TILE_POOL_SETTLE_MS` after its route settles.
|
||||
|
||||
Megabytes depend on the window size, the device scale factor and the GPU
|
||||
path, so they are evidence for a PR, not a ratchet: the Linux runner at 1x
|
||||
cannot reproduce a 2x laptop. Layer and mask counts on the fixed mock are
|
||||
comparable across machines; wiring them into the [ratchet](#ratchet) needs a
|
||||
baseline measured on the canonical runner first. A PR that adds or changes
|
||||
`backdrop-filter`, `filter`, `will-change`, `isolation`, a transform or
|
||||
opacity animation, a `mask` or `clip-path`, or a `border-radius` on an
|
||||
element with `overflow`, runs the report before and after and quotes the
|
||||
affected routes. Reference, 2026-10-09 on a 16" MacBook Pro at 2x before and
|
||||
after the painted corner: dashboard idle 537 to 242 MB, during a slide switch
|
||||
598 to 303 MB, scrolled 655 to 165 MB; movie detail 110 to 89 MB; series
|
||||
detail 187 to 143 MB.
|
||||
|
||||
## Adding a counter
|
||||
|
||||
1. Produce the value from the built output or from a deterministic probe, not
|
||||
|
||||
@@ -277,6 +277,7 @@ pnpm run perf:initial-bytes # breakdown only
|
||||
pnpm run perf:initial-bytes:check # measure, then compare with the committed baseline
|
||||
pnpm nx test performance-tools
|
||||
pnpm run perf:journeys # J1 launch, J2 open-source and J3 playback journeys, one dist/performance/journeys/<timestamp>/summary.json
|
||||
pnpm run perf:compositing # tile memory and composited layers per route, dist/performance/compositing/<timestamp>/summary.json
|
||||
```
|
||||
|
||||
`perf:initial-bytes` reads the built `dist/apps/web/index.html` and sums the
|
||||
@@ -306,7 +307,10 @@ not through `pnpm exec`: under `pnpm nx`, a nested `pnpm exec` can run from the
|
||||
workspace root instead of the target `cwd` and miss cwd-relative specs, globs
|
||||
and configs. The contract, what
|
||||
counts and how to add a counter or a journey are in the
|
||||
[performance journeys](performance-journeys.md) document.
|
||||
[performance journeys](performance-journeys.md) document. `perf:compositing`
|
||||
measures tile memory and composited layers on the artwork-heavy routes; the
|
||||
deterministic guard is `compositing.e2e.ts` in the Electron E2E suite
|
||||
([compositing budget](performance-journeys.md#compositing-budget)).
|
||||
|
||||
## Logging
|
||||
|
||||
|
||||
@@ -121,7 +121,7 @@ translation-only transform between the clip and the effect, and the fallback
|
||||
mask is the size of the whole clipped area, about 24 MB of tile memory at 2x
|
||||
on a 16" display. A dozen of them took the dashboard past Chromium's 512 MB
|
||||
tile budget (`tile memory limits exceeded`, blank tiles while scrolling).
|
||||
`apps/electron-backend-e2e/src/dashboard-compositing.e2e.ts` guards it: no
|
||||
`apps/electron-backend-e2e/src/compositing.e2e.ts` guards it: no
|
||||
drawing layer without an owner node and without a compositing reason (a
|
||||
synthesized mask, unlike any content layer) may span half the content area,
|
||||
and the backdrop-filtered controls must really be composited, so the check
|
||||
|
||||
@@ -38,6 +38,7 @@ are not prerequisites for reading repository contracts.
|
||||
| Web engines, controls, tracks, PiP, radio and display sleep; `libs/ui/playback` | [Player controls](../architecture/player-controls-contract.md), [inline playback/diagnostics/recovery](../architecture/embedded-inline-playback.md) | Read the contract directly |
|
||||
| Embedded MPV, platform engines, addon, pinned runtime and packaging; Electron native services, `tools/embedded-mpv` | [Native MPV](../architecture/embedded-mpv-native.md), [runtime build and licensing](../../tools/embedded-mpv/README.md) | Read the contract directly |
|
||||
| Live panels, keyboard focus, grid/layout conventions; shared UI and portal views | [UI guidelines](../architecture/iptvnator-ui-guidelines.md), [detail navigation](../architecture/portal-detail-navigation.md) | [UI design](../../.codex/skills/iptvnator-ui-design/SKILL.md), [theme/style](../../.codex/skills/iptvnator-theme-style/SKILL.md) |
|
||||
| Rendering cost of UI: `backdrop-filter`, `filter`, `will-change`, `isolation`, transform/opacity animations, rounded or path-clipped scrollers; tile memory | [Compositing budget](../architecture/performance-journeys.md#compositing-budget), [workspace shell](../architecture/workspace-shell.md#content-surface-corner) | [Theme/style](../../.codex/skills/iptvnator-theme-style/SKILL.md) |
|
||||
| Workspace routes, title bar, switcher, collections and dashboard; `libs/workspace` | [Workspace shell](../architecture/workspace-shell.md), [dashboard](../architecture/workspace-dashboard.md), [collection/detail navigation](../architecture/portal-detail-navigation.md) | UI/theme skills for visible changes |
|
||||
| Remote control, playback queue, channel return and shortcuts; `libs/ui/remote-control`, `apps/remote-control-web` | [Remote control](../architecture/remote-control.md) | Provider skill when queue ownership changes |
|
||||
| Parental lock: PIN, per-category locks, worker-side filtering; `libs/services/src/lib/parental-lock`, category/group dialogs, `apps/electron-backend/src/app/database/parental-lock-state.ts` | [Parental lock](../architecture/parental-lock.md), affected provider contract | Read the affected provider skill |
|
||||
|
||||
Reference in new issue
Block a user