Skip to content

fix(engine): render text without ClearType colour fringes on Windows - #7663

Merged
miguel-heygen merged 1 commit into
mainfrom
fix/disable-lcd-text
Nov 13, 2026
Merged

miguel-heygen merged 1 commit into
mainfrom
fix/disable-lcd-text

Conversation

@miguel-heygen

@miguel-heygen miguel-heygen commented Nov 13, 2026 •

Copy link
Copy Markdown
Collaborator

What

Always launch Chrome with --disable-lcd-text, so text is rasterised with greyscale antialiasing instead of LCD subpixel antialiasing.

Why

On Windows with ClearType on, glyph edges in renders, snapshots and captures get a red/blue colour split, including text on an opaque page background in an MP4. Subpixel antialiasing is tuned for one physical display, so in a video frame it is just a colour error baked into the pixels. --disable-lcd-text removes it with no glyph movement, only colour. Linux output is unchanged (see below).

Related work

Refs #7661. This takes the issue's first proposal as the default for every caller. It does not add a way to pass arbitrary Chrome flags.

How

One line in buildChromeArgs (packages/engine/src/services/browserManager.ts), next to --font-render-hinting=none. Render, snapshot, capture, validate, layout and the studio thumbnails all build their Chrome args through it, so every path gets the flag.

Test plan

fails without the fix: packages/engine/src/services/browserManager.test.ts › buildChromeArgs browser GPU mode › always disables LCD text (beginframe|screenshot|drawelement)

  • Unit test: buildChromeArgs contains --disable-lcd-text in every capture mode, and on win32. Fails without the fix (3 failed, 121 passed), passes with it (124 passed).

  • Windows, measured on a GitHub windows-latest runner with ClearType turned on (SPI_GETFONTSMOOTHING 1, SPI_GETFONTSMOOTHINGTYPE 2). Fixture: 22 px Consolas, #f0f0f0 on #202020, 1280x720. Same build, only this line removed for "before". Split = sum of |R−B| over sum of |G−background| on glyph pixels (0 = neutral grey):

    snapshot PNG render MP4
    before 0.335 0.248
    after 0 0

    Run: https://github.com/heygen-com/hyperframes/actions/runs/43492623591 (artifact lcd-out has the before/after frames and 6x crops).

  • Linux, no regression: the PR's regression shards are green. The full producer suite (80 fixtures, the CI Docker test image on linux/amd64) passed 74 with this change. The 6 that failed are all in shard-schedule.json's "never run in CI" list, and fail the same way without this change: the same failed frame counts and the same PSNR to two decimals for fast-capture-gsap, heygen-promo-preview-assets and page-side-shader-compositor-render-compat; 99 failed frames at PSNR about 12.3 either way for webgl-video-texture-render-compat (it varies by 0.05 between runs); and the same 45 s setup timeout for d3-adapter and maplibre-adapter. No baseline moved.

@github-actions

github-actions Bot commented Nov 13, 2026 •

Copy link
Copy Markdown
Contributor

Edit accuracy: accurate 267 (base branch 267), smooth 101 of those

The gate passes.
Smoothness is reported in the artifact, not gated. A case fails only if it fails 2 of 3 runs.

Quarantined, measured but not gated (8)

@heygengenesis heygengenesis 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.

Reviewed at aa409f13. The code change is correct: every render, snapshot, capture, validate, layout, studio and audio-fx path launches Chrome through buildChromeArgs, so a single --disable-lcd-text entry covers all of them. The Windows before/after numbers and the green Linux regression shards support it. I found no blocker in the code.

Requesting changes for one reason: the PR body is missing the required fails without the fix: line. A fix PR has to name the test that fails without the fix on a line of its own. The test plan currently says "Fails without the fix (3 failed, 121 passed)" but never names the test. Please add something like:

fails without the fix: packages/engine/src/services/browserManager.test.ts › buildChromeArgs browser GPU mode › always disables LCD text (beginframe|screenshot|drawelement)

I'll approve once the body has that line. No code change is needed.

Non-blocking: packages/producer/src/parity-harness.ts:332 and packages/player/tests/perf/runner.ts:94 still build their own arg lists with --font-render-hinting=none and no --disable-lcd-text. Linux CI is unaffected. If the parity harness is ever run on Windows, though, it would compare against fringed text. You could add the flag there too, either in this PR or in a follow-up.

@heygengenesis heygengenesis 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.

Re-reviewed at aa409f13. The head is unchanged, and the body now names the test that fails without the fix: browserManager.test.ts › buildChromeArgs browser GPU mode › always disables LCD text. That test checks for --disable-lcd-text in the args, so it can't pass without the new line. My earlier review found no problems in the code. Approving. The parity harness and perf runner note from before is still optional.

@miguel-heygen
miguel-heygen added this pull request to the merge queue Nov 13, 2026
Merged via the queue into main with commit 273672a Nov 13, 2026
210 of 211 checks passed
@miguel-heygen
miguel-heygen deleted the fix/disable-lcd-text branch November 13, 2026 21: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.

1 participant