Skip to content

Fix GL window bleed-through on Win32 with Intel GPUs#4845

Open
andrewachen wants to merge 1 commit intoXpra-org:masterfrom
andrewachen:fix/intel-dwm-alpha-bleedthrough
Open

Fix GL window bleed-through on Win32 with Intel GPUs#4845
andrewachen wants to merge 1 commit intoXpra-org:masterfrom
andrewachen:fix/intel-dwm-alpha-bleedthrough

Conversation

@andrewachen
Copy link
Copy Markdown
Contributor

Problem

On Win32 with Intel integrated GPUs, OpenGL windows show content from windows behind them bleeding through character/text backgrounds. Affects both native WGL and GtkGLArea rendering paths.

Root cause: Intel's ChoosePixelFormat returns a pixel format with 8 alpha bits even when cAlphaBits=0 is requested. DWM reads undefined alpha from the window surface as transparent (freedesktop.org bug #14165).

Confirmed on Intel(R) Graphics, driver build 32.0.101.8508. Does not affect NVIDIA, AMD, or Adreno (ARM64/GLon12).

Fix

The workaround is Intel+Win32-specific, gated on GL_VENDOR containing "Intel". The bulk of the workaround logic lives in platform/win32/gl_context.py to keep platform-specific hacks out of the shared OpenGL code. The shared backing.py only adds a no-op _fixup_present_alpha() hook that the Win32 code overrides.

Intel+Win32 workarounds:

  • Force RGBA visual on GL windows so the GDI surface has a proper alpha channel (client_window.py)
  • Force alpha in GtkGLArea readback so the surface gets alpha=255 (glarea_backing.py)
  • Force alpha=1 via glColorMask+glClear before each present, with a one-time glReadPixels to prime the Intel driver — Intel's glClear doesn't properly respect glColorMask without a prior readback (gl_context.py:setup_intel_workarounds)

Platform-independent:

  • Clear non-alpha FBOs with alpha=1 instead of alpha=0 (backing.py)
  • Add _fixup_present_alpha hook in do_present_fbo (backing.py)
  • Set app_paintable on GL DrawingArea — matches the Cairo path (drawing_area.py)

Testing

Tested on Win32 x64 with Intel(R) Graphics (driver 32.0.101.8508):

  • opengl=force (native WGL path) — no bleedthrough
  • opengl=force:glarea (GtkGLArea path) — no bleedthrough
  • opengl=off (Cairo) — unaffected, still works

Tested on Win32 ARM64 with Adreno X1-85 (GLon12) — no regression, workaround not activated.

Confirmed WS_EX_LAYERED is NOT set on any decorated GL window after the RGBA visual change.

References

Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com
Sponsored-By: Netflix

On Win32 with Intel integrated GPUs, ChoosePixelFormat returns a
pixel format with 8 alpha bits even when none are requested. DWM
reads undefined alpha as transparent, causing window bleed-through
(freedesktop.org bug #14165).

Intel+Win32 workarounds (in platform/win32/gl_context.py):
- Override _fixup_present_alpha to force alpha=1 via glColorMask
  before each present, with a one-time glReadPixels to prime the
  Intel driver (Intel's glClear doesn't respect glColorMask without
  a prior readback)

Intel+Win32 workarounds (in client/gtk3/opengl/):
- Force RGBA visual on GL windows (client_window.py)
- Force alpha in GtkGLArea readback (glarea_backing.py)

Platform-independent fixes:
- Clear non-alpha FBOs with alpha=1 instead of alpha=0 (backing.py)
- Add _fixup_present_alpha hook in do_present_fbo (backing.py)
- Set app_paintable on GL DrawingArea (drawing_area.py)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sponsored-By: Netflix
@andrewachen
Copy link
Copy Markdown
Contributor Author

This feels a little invasive and the Intel machine is not my daily driver, so I won't feel terrible if you decide not to merge this -- it's unfortunate that the Intel integrated GPU needs such things.

@andrewachen andrewachen marked this pull request as ready for review March 30, 2026 16:11
@totaam
Copy link
Copy Markdown
Collaborator

totaam commented Apr 21, 2026

Claude "max effort" review confirms some of my concerns:

Freedesktop #14165 is real but different. It's a 2009 Mesa/i965 DRI bug — glClear() with glColorMask() caused assertion failures in intel_clear_tris() on Linux. Fixed by restricting that path to front/back-left buffers only. It confirms Intel has a history of glClear+glColorMask misbehaviour, but it's a Linux Mesa driver fix, not the Windows ICD.

2009 on Linux...

The "readback priming" claim has no documented basis found. No Mesa commits, no bug reports, no vendor documentation describes needing a glReadPixels to prime driver state before glColorMask+glClear works correctly on Windows Intel drivers. It appears to be empirically discovered — someone found it worked and shipped it.

Is this absolutely needed?

The ChoosePixelFormat alpha-bits issue is well-known (the DWM bleed-through mechanism is documented), but the specific glReadPixels-as-primer workaround for Windows Intel isn't.

If it is well known, I would expect workarounds to be more common and easier than readback!

Worth noting in the PR review: the code comment/docstring says the primer is needed because "Intel's glClear doesn't properly respect glColorMask without a prior readback", but no reference is cited for that specific claim on Windows. Given the 2009 Mesa fix, it's plausible there's a related Windows driver bug, but the PR would be stronger with a test case or driver version that demonstrates the primer is actually necessary — otherwise it's expensive insurance for an unverified assumption.

Claude was making good progress, but then:

⎿  You've hit your limit · resets 2am

totaam added a commit that referenced this pull request Apr 22, 2026
@totaam
Copy link
Copy Markdown
Collaborator

totaam commented Apr 22, 2026

@andrewachen can you try running 88d8abc with XPRA_OPENGL_ALWAYS_RGBA=1?

Claude said:
So ALWAYS_RGBA=1 paired with the PR's FBO clear changes (alpha=0 → alpha=1) could in principle fix the bleed-through without any per-frame workaround — if Intel's glBlitFramebuffer correctly copies the alpha channel from the RGBA8 FBO to the window framebuffer. That's the unknown. If the blit doesn't propagate alpha (which is
plausible for the same class of Intel driver bug), ALWAYS_RGBA alone still leaves the window framebuffer alpha undefined after the blit, and you're back to needing the glColorMask+glClear trick.

Worth testing: it's a much lighter change than the per-frame workaround, but it may or may not be sufficient depending on whether Intel's blit honours the alpha source.


I found an Intel card, but it stopped working after a reboot!

totaam added a commit that referenced this pull request Apr 22, 2026
that's the point of using a drawing area after all
@totaam
Copy link
Copy Markdown
Collaborator

totaam commented Apr 22, 2026

The Intel card is working again, turns out that this GPU is so big that it bent the SATA connectors out of their sockets, which prevented the OS from loading...

Anyway, I can test with Intel now, but I'm not seeing any issues. Which app can I used to reproduce the problem and how?

totaam added a commit that referenced this pull request Apr 24, 2026
that's the point of using a drawing area after all
totaam added a commit that referenced this pull request Apr 24, 2026
that's the point of using a drawing area after all
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