描述 Bug
在 HDR 显示器上,当不透明度 < 100% 时,每次按键模型都会瞬间变亮一下。
三个条件缺一不可:
| 条件 |
去掉后 |
| HDR 开启 |
不再闪 |
| 不透明度 < 100% |
不再闪 |
| 有按键输入 |
不再闪 |
这大概率就是 #582 说的那个现象。那条 issue 里作者回复「你录的视频里我没看到透明度发生变化」,报告者自己怀疑显卡驱动就关掉了 —— 如果作者的显示器没开 HDR,是确实复现不出来的。
重现步骤
- Windows 上把显示器 HDR 打开(设置 → 系统 → 屏幕 → HDR)
- BongoCat 偏好设置 → 猫咪 → 不透明度,调到 100% 以下(我用的 68%)
- 正常打字,盯着模型看 —— 每次按键会闪一下变亮
- 把 HDR 关掉,重复第 3 步 —— 闪烁消失
预期行为
按键不应该影响模型亮度。
原因分析
主窗口用根节点的 CSS opacity 做半透明,而按键图是动态增删的 DOM 图层:
<div :style="{ opacity: catStore.window.opacity / 100 }">
<img v-if="backgroundImagePath" />
<canvas id="live2dCanvas" />
<img v-for="path in modelStore.pressedKeys" :src="convertFileSrc(path)" /> <!-- 按键时增删 -->
</div>
按键导致 opacity 组内的合成层数量变化 → Chromium 重新评估合成策略。而在 HDR 下,
SDR 白点的映射在不同合成路径上不一致,于是表现为一帧的亮度跳变。
这属于 Chromium / WebView2 已知的 HDR 合成问题,不是本项目代码本身的缺陷:
但本项目可以规避它。
已验证的修法
Windows 下把不透明度交给系统合成器,而不是 CSS:WebView 内部始终保持全不透明,
Chromium 的合成路径就不再随图层增删变化。
窗口本来就带 WS_EX_LAYERED(Tauri 透明窗口的默认行为),只需补一个恒定 alpha:
#[command]
pub async fn set_window_opacity<R: Runtime>(window: WebviewWindow<R>, opacity: u8) {
let Ok(hwnd) = window.hwnd() else { return };
unsafe {
let ex_style = GetWindowLongPtrW(hwnd, GWL_EXSTYLE);
let layered = WS_EX_LAYERED.0 as isize;
if ex_style & layered == 0 {
SetWindowLongPtrW(hwnd, GWL_EXSTYLE, ex_style | layered);
}
let _ = SetLayeredWindowAttributes(hwnd, COLORREF(0), opacity, LWA_ALPHA);
}
}
前端相应改为:
// Windows 走系统级窗口 alpha,其余平台保持原有 CSS 行为
if (isWindows) {
watch(() => catStore.window.opacity, setWindowOpacity, { immediate: true })
}
:style="{
- opacity: catStore.window.opacity / 100,
+ opacity: isWindows ? 1 : catStore.window.opacity / 100,
borderRadius: `${catStore.window.radius}%`,
}"
windows crate 现有的 feature 就够,不需要加依赖。macOS / Linux 我没有环境验证,
保持原样未改。
实测结果
环境:RTX 4060 Laptop / 2560×1600 @ 240Hz / HDR 开启 / 不透明度 68%
- 绝大部分闪烁消失
- 验证补丁确实生效:
GetLayeredWindowAttributes 读到 alpha=173, flags=0x2 (LWA_ALPHA),
173 正好是 68% × 255
残留(如实说明):模型自身带加算混合的小面积高光仍有极轻微闪动 ——
实测到的是角尖的辉光和玻璃杯的透光。因为按键图层的增删本身并没有被消除,
只是窗口内部不再半透明,所以只剩下模型自己半透明的那部分像素还会受影响。
根治需要把按键图渲染进 Live2D 的 canvas(让整个窗口永远只有一个合成层,
按键时只有像素变化、没有图层变化),本次没做 —— 改动面大,且需要重新评估性能。
软件信息
{
"appName": "BongoCat",
"appVersion": "1.1.0",
"platform": "windows",
"platformArch": "x86_64",
"platformVersion": "10.0.26200",
"webview2Runtime": "153.0.4234.48",
"display": "2560x1600 @ 240Hz, HDR on",
"gpu": "NVIDIA GeForce RTX 4060 Laptop GPU"
}
对应源码为 master 分支当前状态(44f44bc,2026-04-28)。
附加信息
相关:#582「设置增加"关闭闪烁"功能」。那条 issue 描述的是「透明度 != 100 时窗口透明度会随机闪动」,
触发路径是鼠标在 Edge 顶部栏晃动;本 issue 的触发路径是按键。两者机制应该相同 ——
都是图层/合成状态变化在 HDR 下引起的亮度跳变。
建议把 HDR 作为这类问题的必要复现条件写进排查清单,否则没有 HDR 显示器的人永远复现不出来。
描述 Bug
在 HDR 显示器上,当不透明度 < 100% 时,每次按键模型都会瞬间变亮一下。
三个条件缺一不可:
这大概率就是 #582 说的那个现象。那条 issue 里作者回复「你录的视频里我没看到透明度发生变化」,报告者自己怀疑显卡驱动就关掉了 —— 如果作者的显示器没开 HDR,是确实复现不出来的。
重现步骤
预期行为
按键不应该影响模型亮度。
原因分析
主窗口用根节点的 CSS
opacity做半透明,而按键图是动态增删的 DOM 图层:按键导致 opacity 组内的合成层数量变化 → Chromium 重新评估合成策略。而在 HDR 下,
SDR 白点的映射在不同合成路径上不一致,于是表现为一帧的亮度跳变。
这属于 Chromium / WebView2 已知的 HDR 合成问题,不是本项目代码本身的缺陷:
但本项目可以规避它。
已验证的修法
Windows 下把不透明度交给系统合成器,而不是 CSS:WebView 内部始终保持全不透明,
Chromium 的合成路径就不再随图层增删变化。
窗口本来就带
WS_EX_LAYERED(Tauri 透明窗口的默认行为),只需补一个恒定 alpha:前端相应改为:
:style="{ - opacity: catStore.window.opacity / 100, + opacity: isWindows ? 1 : catStore.window.opacity / 100, borderRadius: `${catStore.window.radius}%`, }"windowscrate 现有的 feature 就够,不需要加依赖。macOS / Linux 我没有环境验证,保持原样未改。
实测结果
环境:RTX 4060 Laptop / 2560×1600 @ 240Hz / HDR 开启 / 不透明度 68%
GetLayeredWindowAttributes读到alpha=173, flags=0x2 (LWA_ALPHA),173 正好是 68% × 255
残留(如实说明):模型自身带加算混合的小面积高光仍有极轻微闪动 ——
实测到的是角尖的辉光和玻璃杯的透光。因为按键图层的增删本身并没有被消除,
只是窗口内部不再半透明,所以只剩下模型自己半透明的那部分像素还会受影响。
根治需要把按键图渲染进 Live2D 的 canvas(让整个窗口永远只有一个合成层,
按键时只有像素变化、没有图层变化),本次没做 —— 改动面大,且需要重新评估性能。
软件信息
对应源码为 master 分支当前状态(
44f44bc,2026-04-28)。附加信息
相关:#582「设置增加"关闭闪烁"功能」。那条 issue 描述的是「透明度 != 100 时窗口透明度会随机闪动」,
触发路径是鼠标在 Edge 顶部栏晃动;本 issue 的触发路径是按键。两者机制应该相同 ——
都是图层/合成状态变化在 HDR 下引起的亮度跳变。
建议把 HDR 作为这类问题的必要复现条件写进排查清单,否则没有 HDR 显示器的人永远复现不出来。