Skip to content

fix(video): don't draw the encoder's alignment padding in Firefox (workaround)#272

Draft
rmounce wants to merge 2 commits into
selkies-project:comprehensive-fixesfrom
rmounce:rmounce/cf-ff-coded-crop
Draft

fix(video): don't draw the encoder's alignment padding in Firefox (workaround)#272
rmounce wants to merge 2 commits into
selkies-project:comprehensive-fixesfrom
rmounce:rmounce/cf-ff-coded-crop

Conversation

@rmounce

@rmounce rmounce commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Draft — this is a sub-optimal workaround; posting primarily for the analysis. Firefox renders WebCodecs decoder output incorrectly and, as far as we can tell, undetectably. This PR carries the pragmatic client-side mitigation plus our full findings.

Symptom: with full-frame H.264 through the 2D-canvas paths, Firefox shows an ~8px green bar at the bottom of a 1920x1080 stream from h264_vaapi or NVENC, and renders the picture vertically resampled ("skipped lines" on fine detail). Chromium is unaffected. Reproduced on Intel/VA-API and NVIDIA/NVENC.

Root cause: H.264 codes 16x16 macroblocks, so 1080 rows are always coded as 1088 with 8 padding rows cropped via SPS. The padding content is encoder-specific: x264 edge-replicates (invisible), VA-API/NVENC zero-fill (green in YUV). Firefox's canvas drawImage — and createImageBitmap — scale the frame's full backing surface, padding included, into the nominal 1080 box. So the padding shows, and every full-frame H.264 stream (x264 included) is silently resampled ~0.74% vertically in Firefox.

Why it can't be feature-detected: on these frames Firefox reports codedWidth/Height == displayWidth/Height == visibleRect == 1920x1080, format: BGRX — the padding appears nowhere. And synthetic CPU-backed VideoFrames draw correctly, so a runtime behavior probe passes while real decoder output leaks. We tried, in order: source-rect draw, visibleRect presence gate, synthetic-frame behavior probe, coded>display gate — none can fire. Only a UA gate works.

The workaround: gated on Firefox UA, overscan the draw destination to the padded size computed from macroblock alignment (ceil16), so the visible image maps onto the canvas and the padding lands off-canvas. This eliminates the green bar and corrects the geometry, at the cost of Firefox's internal resample remaining visible at 1:1 (no worse than what Firefox already does to x264 streams). copyTo()+putImageData is the only pixel-exact escape we found (drawing APIs all share the broken conversion), but it costs a ~2M-pixel per-frame CPU copy on top of Firefox's software decode — not included here.

A cheaper full fix may be server-side: if pixelflux padded VA-API/NVENC captures by edge replication (as x264 effectively does), the bar would disappear for all clients with zero client-side cost. Happy to file that on linuxserver/pixelflux#18 if preferred.

Tested on the LSIO-based image against comprehensive-fixes (da1b55e) on Intel/VA-API and NVIDIA/NVENC.

🤖 Generated with Claude Code

ehfd and others added 2 commits July 8, 2026 15:10
Co-authored-by: vishalkadam47 <vishalkadam472@gmail.com>
Firefox's canvas drawImage scales the full BACKING surface of a
decoder-output WebCodecs VideoFrame - the RGB conversion of the decoded
image, alignment padding included - into the frame's nominal box. With
full-frame H.264 painted through the 2D-canvas paths, 1920x1080 coded
as 1920x1088 renders vertically squished with the 8 padding rows
visible at the bottom: a green bar when the encoder zero-fills the
padding (h264_vaapi), invisible when it edge-replicates (x264, which is
why software encoding masked the bug). Chromium is unaffected.

The bug cannot be detected at runtime: the frame's metadata is fully
clean (codedWidth/Height == displayWidth/Height == visibleRect, the
padding is nowhere reported), and synthetic CPU-backed frames draw
correctly, so a visibleRect presence check and a drawn-pixel behavior
probe both pass while real decoder output still leaks. Gate on the UA
instead, and compute the padded backing size from H.264's 16x16
macroblock alignment: overscan the destination to that size so the
visible image maps 1:1 onto the canvas and the padding lands
off-canvas. Other browsers keep the plain draw, which crops to the
visible region natively. The first drawn frame's geometry and the
overscan decision are logged once per context for diagnosis. Applied at
every full-frame draw site (the worker's OffscreenCanvas present and
the main-thread canvas fallbacks); striped and JPEG paths are untouched
(stripes are encoder-aligned by construction, JPEG carries exact
dimensions).

Co-authored-by: Claude <noreply@anthropic.com>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request addresses a Firefox-specific rendering issue where canvas.drawImage scales the full backing surface of a decoder-output VideoFrame, including alignment padding, into the frame's nominal box. To resolve this, the changes introduce helper functions to detect Firefox, calculate the padded backing size based on H.264's 16x16 macroblock alignment, and overscan the destination so that the padding falls off-canvas and is clipped. These helpers are applied to both the main thread and the video worker. There are no review comments, so I have no feedback to provide.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

@ehfd ehfd force-pushed the comprehensive-fixes branch 2 times, most recently from 32a3087 to 6f4e5c7 Compare July 8, 2026 09:13
@ehfd

ehfd commented Jul 8, 2026

Copy link
Copy Markdown
Member

A cheaper full fix may be server-side: if pixelflux padded VA-API/NVENC captures by edge replication (as x264 effectively does), the bar would disappear for all clients with zero client-side cost. Happy to file that on linuxserver/pixelflux#18 if preferred.

I went down this rabbit hole and concluded this was not it. The encoders are not at fault; it's a Firefox bug.

So your fix is the correct one with the least computational cost.

@ehfd

ehfd commented Jul 8, 2026

Copy link
Copy Markdown
Member

@rmounce This is a hardware decode artifact. You should see if software decode solves the problem. If so, I can prefer software decode for Firefox.

@ehfd

ehfd commented Jul 8, 2026

Copy link
Copy Markdown
Member

Moreover, did you try Firefox 151 or later? I don't reproduce there on software decode.

@ehfd ehfd force-pushed the comprehensive-fixes branch from 6f4e5c7 to 978c4d0 Compare July 8, 2026 11:24
@rmounce

rmounce commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

My testing was on Ubuntu 24.04 on Intel laptop with Firefox via snap (I did not note the version, I refreshed within the last week so probably 152).
I believe it was software decode with Firefox on the basis that CPU load was higher (about 65% vs 10% for Chrome) and via intel_gpu_top the “Video” usage was 0 (it was used by Chrome). Render/3D was non-zero for FF but I attributed this to compositing.

I am now out of the office for a few days so I cannot check further details or do more testing until next week.

I do suspect this is a Firefox bug and I intend to try and report it upstream if so. This draft PR was a way to capture the workaround that helps to characterise the underlying bug. So I’m fine if we don’t adopt this workaround.

I’ve been making the most of promotional access to Claude Fable 5 to accelerate testing and proposed bug fixes, which is now over so I’ll be back down to Opus with more manual guidance next week. I hope you don’t mind the prolific submissions. I’ve been trying to mark all AI generated output as such, but Fable is very autonomous so there are were a few generated comments without attribution.

@ehfd

ehfd commented Jul 8, 2026

Copy link
Copy Markdown
Member

I’ve been making the most of promotional access to Claude Fable 5 to accelerate testing and proposed bug fixes, which is now over so I’ll be back down to Opus with more manual guidance next week. I hope you don’t mind the prolific submissions. I’ve been trying to mark all AI generated output as such, but Fable is very autonomous so there are were a few generated comments without attribution.

It's fine. I agree about Fable. Everything else was critically important.

Nonetheless, my Playwright validations on Ubuntu 24.04 actually didn't find the line at the bottom, so I think it's worth more deliberate evaluation of what the cause is.

Thank you so much for the test and bug hunts!

@ehfd ehfd force-pushed the comprehensive-fixes branch 9 times, most recently from 3bab531 to 4de4c8a Compare July 9, 2026 07:07
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.

2 participants