Repository navigation
fix(core): keep the colour grade on a graded video's first rendered frame - #5575
Merged
Merged
Conversation
…rame A graded clip rendered ungraded for one frame wherever a capture session first drew it: the clip's own first frame, a parallel worker's first frame inside it, or a capture segment's first frame inside it. The grade dropped the clip while its injected frame was still decoding, so the redraw after decode had nothing to draw. Part of #5548
terencechotai2-commits
approved these changes
Oct 14, 2026
terencechotai2-commits
left a comment
There was a problem hiding this comment.
Approved at 5a4b151d. The one-line fix is right, and the real-render test checks pixel levels, not just that nothing crashed.
Checked
- The fix.
hasInjectedRenderFramenow asks only whether the injected render-frame<img>exists and is visible (findInjectedRenderFrame). It no longer requires it to be decoded (findRenderFrameImage→isDrawableSource). That check only decides whether the DOM observer keeps or drops the clip's grading entry (:3672) and whatstatus()reports (:3799). The draw path,getDrawableSource→findRenderFrameImage, still requires a decoded frame, so nothing draws from a half-loaded image. Preview injects no frames, so it's unaffected. - Every first-frame path goes through this one check. A segment start, a parallel worker's first frame, a seek-in, and a clip's hard-cut first frame are all "a capture session shows this video's injected frame for the first time while it decodes". The integration test covers the segment, the second worker and the clip's own first frame.
- No double grade. There's one entry per element (
upsert), and the injected frame is the raw video frame, so keeping the entry can't grade twice. The integration test would catch it if it did: every clip frame in all three render shapes must be within 3 levels of the graded reference, while the ungraded grey is more than 30 levels away. A second grade would land outside that band. - Per-frame cost: none added. The check now skips the decode test, so it's slightly cheaper, and it still runs only in observer callbacks and
status(), not per captured frame.
Low (unverified)
- If an injected frame stays visible but never decodes (a broken frame file), the entry is now kept rather than dropped, and the redraw has no drawable source. I didn't check whether the grading canvas then keeps the previous frame's pixels above the new image. In a render the producer waits for the decode, so this only matters on a failed decode.
Tests run
npx vitest run src/runtime/colorGrading.test.ts(core): 63/63. Locally this neededparsers/lintbuilt first.- Mutant: restoring
findRenderFrameImageinhasInjectedRenderFramefails the new test (1 failed). - I didn't run the integration test locally. In CI,
Producer: integration testspasses on this head. - CI when I posted: 67 pass, 8 skipping, 1 pending (
Studio: edit accuracy (2/5)), none failing.
— tai2
Contributor
Edit accuracy: accurate 435 (base branch 435), smooth 298 of thoseThe gate passes. Quarantined, measured but not gated (0) |
4 tasks done
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.
Part of #5548
What changes for users
A video with
data-color-gradingnow keeps its grade on every rendered frame. Before, one frame could come out ungraded (brighter, cooler, no vignette):HF_SEGMENTED_CAPTURE=true), which is the case reported in WSL2: render always falls back to SwiftShader (probe: "WebGL unavailable"; /dev/dri/renderD128 not accessible), Windows-native --browser-gpu ~5x faster; segmented capture hangs with >1 worker on Windows #5548,--workers 2or more, no segments), andCause
When a graded video first appears on a page during a render, the producer inserts its frame image and waits for it to decode. While it decodes, the grading runtime's DOM observer re-checks the video. The video itself is hidden and the frame image is not decoded yet, so the runtime decided the clip was off screen and dropped its grade. The redraw that runs after the decode then had no graded clip to draw, so the raw frame was captured. On the next frame the clip was picked up again, so the result was a one-frame flash.
A segment start or a worker's first frame hits this because that capture session has never shown the video before. This is not a timing problem, so waiting longer before the capture would not help.
Fix
The runtime's check for "this video is shown through an injected render frame" now counts a frame that is still decoding. The clip stays graded, and the post-decode redraw draws the right frame. This is one line in
hasInjectedRenderFrame. Preview has no injected frames and is unchanged.Tests
colorGrading.test.ts: the DOM observer runs while the frame decodes, then the redraw has to draw that frame. Fails on main (redraw()returns 0) and passes with the fix.segmentedColorGrading.integration.test.ts(integration lane, real Chrome and ffmpeg): renders a graded clip three ways: one worker with no segments (the reference), segmented with 2 workers, and 2 workers with no segments. In the last two, the second session starts inside the clip. Without the fix, the segment's first frame, the second worker's first frame and the clip's own first frame are 64 levels brighter than the graded value (3 of 4 checks fail). With the fix, all 4 pass, 3 runs in a row.Checked by hand with a 4 s composition at
HF_SEGMENT_FRAMES=45. On main, the segment's first frame scores 19.5 dB PSNR against the unsegmented render. With the fix, the worst frame scores 48.5 dB, which is ordinary encoder noise at a segment edge.