fix: window.opacity transparency on Windows (#1905)
* fix: window.opacity rendering through wgpu PreMultiplied surfaces On Windows the wgpu surface picks `PreMultiplied` alpha mode (the only non-Opaque mode the DX12 / Vulkan WSI paths offer), so DWM interprets framebuffer RGB as already multiplied by alpha. The grid bg pass emits `(0,0,0,0)` for default-bg cells and lets the cleared color show through, but `LoadOp::Clear` was writing the raw `(bg, opacity)` value instead of premultiplied `(bg*opacity, opacity)`. The compositor then treated the dark background as if it were premultiplied, which made `window.opacity < 1` render almost fully transparent. Premultiply the clear color in `Sugarloaf::render_wgpu` for any alpha mode that isn't `PostMultiplied` (only mode that wants straight RGBA). Promote `WgpuContext::alpha_mode` to `pub` so the renderer can branch on it. Also fix the cfg gating in `screen/mod.rs` that selected the CPU backend on default Windows builds. The `wgpu` crate feature in rioterm isn't on by default — only `--features wgpu` enables it — but the sugarloaf + rio-backend deps are already forced to `features = ["wgpu"]` on Windows via the target dependency override. Without this OS-level fallback the `Backend::Webgpu => SugarloafBackend::Cpu` arm fired and the user fell into the CPU rasterizer (which doesn't write per-pixel alpha), so `with_transparent(true)` + `DwmEnableBlurBehindWindow` showed a fully-transparent window regardless of the configured opacity. * fix(cpu): preserve framebuffer alpha so window.opacity works The CPU rasterizer wrote 0x00RRGGBB pixels (alpha = 0) and every blend function masked the alpha byte back out (`& 0x00ff_ffff`, or `pack_opaque` that omits the upper byte). That was fine on platforms where the OS ignores swap-chain alpha, but with `with_transparent(true)` plus `DwmEnableBlurBehindWindow` (engaged when `window.opacity < 1`), DWM treats the framebuffer alpha as the per-pixel transparency. The whole window therefore came out 100% see-through on the CPU backend whenever opacity was set, regardless of the configured value. Switch the CPU framebuffer to premultiplied RGBA throughout: - `pack_opaque` now writes `alpha = 0xff` (no-op for OSes that ignore it). - bg fill in `render_cpu` uses `pack_premul((R,G,B)·a, a)` so the cleared color carries `window.opacity` into the alpha byte. - All four blend variants in `renderer/cpu.rs` (scalar SWAR + 3 SIMD flavours) now compute the A channel via the same `(x*inv) / 255` SWAR trick used for G, and stop stripping alpha from the source/result. The premultiplied invariant (RGB ≤ A) keeps the byte-wise add safe from carry across channels. - `grid/cpu.rs::blend_over` and `text.rs::blend_premul_over` (separate scalar paths used by the grid bg/text and UI text overlays) get the same A-channel computation. New `pack_premul` helper added next to each `pack_opaque`. - Caller sites that splatted `(src_premul & 0x00ff_ffff)` and post-masked the SIMD result with `& mask_rgb_*` are updated to splat the full premultiplied src and drop the post-mask. Default-bg cells emit `(0,0,0,0)`; with `sa == 0` the early-out branch returns `dst` unchanged, so the cleared bg's alpha bleeds through and the window renders translucent at the configured opacity. * opacity: alpha-capable adapter, paired swar, alpha tests * libsugarloaf: default new renderer field * test var_src kernel at extreme alphas too * review fixes: premul gating, adapter fallback, shared premul module --------- Co-authored-by: KOGA Mitsuhiro <shiena.jp@gmail.com>
R
Raphael Amorim committed
357281638216876a2406c46e50033e7143256175
Parent: 482414f
Committed by GitHub <noreply@github.com>
on 8/28/2026, 9:52:25 PM