Repository navigation
perf(producer): cloud render chunks hand Chrome each video near the size it is shown - #6001
Conversation
…ize it is shown A chunk now measures the largest box each video is drawn in over its own frames, in device pixels, before extracting that video's frames, and halves the frames while they still cover that box. Graded videos, pages with a canvas, and object-fit none/scale-down keep full-size frames.
somanshreddy
left a comment
There was a problem hiding this comment.
Re-review at e625e9f3. The delta from 3f1fcf37 has no behaviour change:
- three comments removed for the ratchet;
shownSizeScaleFilterrenamed tohalveToCoverScaleFilter;--added before the frame path in two test ffprobe calls.
One leftover nit: the old /** Extracts the given frames of deferred videos… */ JSDoc now documents shownDeferredFrameIndexes, so the two docs are effectively swapped. My review below stands.
Reviewed at 3f1fcf37. No blockers; approving once required CI is green.
- The measured box is conservative in every direction I could find.
getBoundingClientRect() × devicePixelRatioincludes transforms, and a rotated element's bounding rect is larger than its drawn size.- The filter uses
max(bw/iw, bh/ih), which is the right scale forcoverand an over-estimate forcontain. - Halving stops while the frame still covers the box, rounds sides up to even, and never upscales.
- A video with a zero-size rect at every sampled time (
display:none, never laid out) is filtered out and stays full size.
- Fail-safe paths keep full size:
- grading attribute,
object-fit: none | scale-down, or any non-grading<canvas>at a sampled time; - missing
__hf.seek; - the parallel-BeginFrame branch, where
shownBoxesstays undefined; - HDR, PNG and alpha frames, because the filter is only pushed in the SDR
jpgbranch.
- grading attribute,
- It's exact per chunk: each chunk measures at exactly its own frame times, so a zoom crossing chunks is full size where it's big. I checked that chunk range extraction (
extractVideoFramesRange) doesn't go through the content-keyed extract cache (that's onlyextractAllVideoFrames), so a shrunk extraction can't be reused by another chunk or render that needs full size. - Same inputs to session boot.
skipReadinessVideoIdsand the remote metadata hints are now derived from the plan (shownDeferredFrameIndexes, and deferred entries ∩ remote IDs). They give the same sets as the old extraction-output-derived ones, since a deferred video with no shown frames had an emptyframePaths. Remote videos still get their plan-sized<video>, so the measured rect isn't a 300×150 default.
Non-blocking:
- An optimization that can fail a chunk.
measureVideoShownBoxesthrowing, from apage.evaluateprotocol timeout or an exception in a seek, now fails the chunk. Before, this step didn't exist. Wrapping it withcatch → undefined(full size, plus a perf-sidecar note) would make the optimization fail open. - Canvas detection only sees DOM canvases. An
OffscreenCanvas, or a canvas created but never appended, that samples a video (drawImage→toDataURLinto an<img>) isn't detected, so it would read a halved frame. That's rare, but worth a line in the JSDoc. - Misplaced doc comment. In
chunkFrames.ts, the old/** Extracts the given frames of deferred videos… */now sits directly aboveshownDeferredFrameIndexes's own JSDoc, soextractDeferredVideoFramesForChunklost its doc. Moving it back also helps the non-requiredCommentsratchet, which is red onchunkFrames.ts(+2 lines) andvideoFrameExtractor.ts(+3). - Extra seek pass. The measurement adds one seek per chunk frame before capture. Your fixture shows a net win; on a page with a costly seek and only a full-frame video, it's pure overhead. Skipping the pass when every shown deferred video's plan-time size already fits the viewport would avoid it.
— Somu
vanceingalls
left a comment
There was a problem hiding this comment.
Reviewed at e625e9f3. There are no reviews on GitHub yet. Somu's thread pre-review at 3f1fcf37 covered the escape hatches and raised three non-blocking points (a measure throw fails the chunk, OffscreenCanvas, and the moved JSDoc); I'm not repeating them.
What holds
- The measured box is conservative. It is
getBoundingClientRect × devicePixelRatio, taken over every frame of the chunk, and it agrees with capture: the viewport DSF andclip.scaleare bothdeviceScaleFactor(screenshotService.ts:227-228,frameCapture.ts:1526-1530). The halving fitmax(W/iw, H/ih)is thecoverscale, so it is never too small forcontainorfilleither. The parallel BeginFrame branch never measures, so it stays full size. - The canvas check returns on the first seek, so pages with incremental canvas or rAF drawing are never seeked out of order before capture.
- Verified locally with ffmpeg 7.0.2 and headless Chrome:
- the new extractor test and both
shownBoxesbrowser tests pass; renderChunk.test.tsgives 28/29 here, and the shown-size chunk test runs rather than skips. The one failure is the byte-range test'sserved.bytes < size/3; it flakes the same way on the basee44f456b, and CI's Test is green.
- the new extractor test and both
- Mutation checks (each turns tests red): the extractor ignoring the box, measuring without DPR, dropping the canvas check, dropping the grading check, and dropping the
object-fit: none | scale-downcheck.
Blocker: rounding to even changes the aspect, which draws a 1 px background column on each side of a default-fit video (videoFrameExtractor.ts:271, 2*ceil(${dim}/pow(2, halvings)/2))
-
1080p and 4K sources halve to an odd height once the box is under 240 device px wide: 1920x1080 → 240x135, which becomes 240x136. With real ffmpeg the filter gives 240x136 for a 1920x1080 or 3840x2160 source and a 200x112 or 230x130 box.
-
The replacement
<img>copies the video'sobject-fit(screenshotService.ts:802), and a<video>'s default iscontain(I checkedgetComputedStylein the headless shell). A 240x136 frame in a 16:9 box is letterboxed. -
Repro in Chrome: a white frame drawn into a 200x112.5
object-fit: containbox, minimum value of the edge columns (0, 1, 198, 199):So a small camera bubble from a 1080p or 4K source, with the default fit, gets a full-strength column of whatever is behind it on both sides, on every frame. That is the PR's target case, and "picture unchanged" doesn't hold for it. A 1 px column is too small to fall below the 0.97 in-box SSIM bar, which would explain why the fixtures didn't catch it.
coverandfillare unaffected. -
Fix: drop the even rounding,
ceil(${dim}/pow(2, halvings)). The JPEG path doesn't need even sizes: the same filter chain with/8encodes 240x135yuvj420pcleanly, and 1080p, 4K and 720p sources then halve exactly. With that change the extractor test gets 16x9 for the 64x36 source instead of 16x10, so its expectation would change with it.
Non-blocking
- No test checks that
renderChunkpasses the measured boxes on to extraction. WithshownBoxes: undefinedatrenderChunk.ts:788the chunk suite still passes, because the shown-size test only checks the sidecar, which records the measurement whether or not extraction used it, and that fixture renderspng-sequence. Asserting a deferred frame's decoded size in that test would pin the wiring.
CI: the eight required checks are green at e625e9f3.
Verdict: REQUEST CHANGES
Reasoning: The measurement and the escape hatches are sound and pinned by tests, but rounding the halved size to even letterboxes default-fit videos from 1080p and 4K sources by a pixel on each side. The fix is a one-line change plus the test expectation.
— Via
Dismissing my approval at e625e9f: Via's blocker is real. The halving filter rounds each side up to even (1080 halved 3 times = 135, which becomes 136), which changes the aspect ratio and leaves 1 px gaps under object-fit: contain. I listed that rounding as a positive in my review and didn't check the aspect ratio. I'll re-check the fixed head. — Somu

What
Cloud render chunks (Lambda and Cloud Run both run
renderChunk) now hand Chrome each video's frames at close to the size the video is drawn, instead of always at the source's full resolution. A 4K camera shown as a small picture-in-picture bubble now reaches Chrome as a much smaller image; a video that fills the frame is unchanged.Why
Profiling a heavy 4K chunk (full-screen screen recording plus a camera bubble) showed capture dominating the chunk's time, and inside capture the biggest single cost was Chrome shrinking a full 4K camera frame into its small box, in software, on every frame. Reading, base64-encoding and delivering the frames over CDP was a small fraction of that; x264 encode was also small. Giving Chrome an image near its drawn size removes most of that per-frame work, so chunks finish sooner and cloud renders use less compute.
Related work
Refs #5952 (local renders encode while capturing; independent of this change).
How
renderChunknow works out which frames each deferred video shows from the plan, boots its preflight browser session, and calls the new engine helpermeasureVideoShownBoxes: it seeks the page through the chunk's own frames and records each video's largest drawn box in device pixels (getBoundingClientRect() x devicePixelRatio, so transforms and animated zooms count). Extraction runs after that, with the box. The capture options that used to come from extraction output (which videos to skip readiness for, metadata hints for videos read by URL) now come from the plan; they are the same values.shownBoxoption (SDR JPEG frames only) halves frames, with 2x2 area averaging, as many times as they still cover the box; the ffmpeg expression works from the decoded, rotated size and never upscales. An exact resize to the box was tried first: it made frames sharper and noisier than Chrome's own downscale (which averages through power-of-two levels), and output files grew noticeably. Halving keeps the drawn picture and file size close to before, at the same speed.object-fit: none | scale-down, a video never laid out in the chunk, and HDR / PNG / alpha extraction.videoShownBoxesso a render shows which videos were shrunk.Test plan
Parity bar, set before measuring: per-frame SSIM against main of at least 0.99 (min) and 0.995 (mean) over the full frame, and at least 0.97 (min) inside the box the shrunk video is drawn in.
videoFrameExtractor.test.ts: real ffmpeg; a 64x36 source with boxes 16x9 / 20x12 / 40x20 gives 16x10 / 32x18 / 64x36 frames.videoFrameInjector-shownBoxes.browser.test.ts: real Chrome; largest box across seeks at device scale 2, a video laid out only late, graded andobject-fit: noneexcluded, a page canvas keeps everything full size.renderChunk.test.ts:extractDeferredVideoFramesForChunkhonours the box; a real chunk render records the measured boxes (32x18 bubble, 128x72 full) in its sidecar. The existing chunk tests (skip list, video read by URL renders the same chunk) still pass.Not exercised: H.264 sources on a cloud render (HEVC sources were); Cloud Run end to end (same
renderChunkcode path).