创建背景模糊窗口应使用哪个 Windows API?
结论
对于 C++ 持续生成、带 alpha channel 的画面,可以采用 Direct3D 11 + DXGI + DirectComposition:用纹理保存帧,通过支持 alpha 的 composition swap chain 显示,再用裁剪或透明度控制窗口外观。
Visual layer 文档介绍的 Windows.UI.Composition 也可以用于 Win32 应用,微软的桌面托管教程要求 Windows 10 1803 或更高版本。如果已有 Direct3D 渲染代码,直接使用 DirectComposition 也很合适。Win32 托管教程
窗口后方桌面内容的模糊需要单独实现。Windows 10 没有面向普通 Win32 HWND、具备完整公开支持契约,又能直接设置 CSS blur(Npx) 效果的通用接口。
实现自定义形状也可以不使用 WS_EX_LAYERED,它是实现逐像素透明的一种方式。
为什么选了合成 API,仍然不能直接得到背景模糊?
合成 API 能把图像叠加到窗口上,也能模糊应用提供的图像。要实现 backdrop-filter,还需要取得窗口后方的内容,作为模糊输入。选择 API 时,需要区分这两种能力。
DwmEnableBlurBehindWindow 不适合作为 Windows 10 方案
微软文档明确说明,从 Windows 8 开始,调用 DwmEnableBlurBehindWindow 不再产生模糊效果。因此,Windows Vista、Windows 7 的 Aero Glass 示例不能直接用于实现 Windows 10 背景模糊。API 文档
CreateBackdropBrush 与 CreateHostBackdropBrush 有不同的采样范围
普通 backdrop brush 采样 visual 后方的合成内容;host backdrop brush 采样宿主窗口绘制之前的背景。因此,对应用自己的 visual 做背景模糊,并不代表已经取得了其他窗口和桌面的像素。CreateHostBackdropBrush 文档
CreateHostBackdropBrush 本身在 Windows 10 就已存在,但用于公开允许非 UWP 窗口使用它的 DWMWA_USE_HOSTBACKDROPBRUSH,从 Windows 11 Build 22000 才开始支持。仅凭该方法的最低版本,无法判断普通 Win32 窗口在 Windows 10 上是否具备同样的支持条件。DWM 属性文档
Windows 10 常见的兼容办法是 accent policy
一些项目通过 SetWindowCompositionAttribute 设置 WCA_ACCENT_POLICY,使用 ACCENT_ENABLE_BLURBEHIND 或 ACCENT_ENABLE_ACRYLICBLURBEHIND。这些定义可以在 TranslucentTB 的实现中看到。
SetWindowCompositionAttribute 有微软文档,但上述 accent 策略没有完整的公开支持契约,微软也明确不推荐使用该函数。这条兼容路径需要逐版本验证,无法保证覆盖所有 Windows 10 构建,也无法精确设置 CSS 意义上的模糊半径。函数文档
如果必须使用公开接口,并且需要控制模糊强度,就要另行获取背景图像,再自行模糊和合成。采用屏幕捕获方案时,还要处理捕获到窗口自身造成的反馈、更新延迟和受保护内容,实际工作量超出一次截图操作。
如何组织窗口和渲染代码?
已有 C++ frame buffer 的程序,可以按以下顺序实现:
- 创建无边框的顶层
HWND,例如使用WS_POPUP。 - 建立 Direct3D 11 设备和 composition swap chain,通过 DirectComposition 将其绑定到 HWND。
- 每帧上传或更新图像纹理,绘制到 swap chain,然后调用
Present。 - 实现圆角或自定义形状,并单独接入背景模糊方案。
如果窗口画面完全由合成内容提供,可以使用 WS_EX_NOREDIRECTIONBITMAP,避免依赖传统窗口重定向位图。这个样式本身不会开启模糊。扩展窗口样式文档
CPU frame buffer 通常需要上传到 GPU 纹理。如果帧已经在 GPU 上生成,优先直接使用或共享纹理,减少 GPU 与 CPU 之间的往返复制。
配置支持 alpha 的 swap chain
初始化前,需要创建好以下对象:
device:ID3D11Device*;factory:与设备对应的IDXGIFactory2*;visual:已挂入 HWND 合成目标的IDCompositionVisual*;composition:IDCompositionDevice*。
代码使用 C++/WinRT 的 check_hresult 检查错误,使用 WRL 的 ComPtr 管理接口:
#include <d3d11.h>
#include <dxgi1_2.h>
#include <dcomp.h>
#include <wrl/client.h>
#include <winrt/base.h>
Microsoft::WRL::ComPtr<IDXGISwapChain1> swapChain;
DXGI_SWAP_CHAIN_DESC1 desc{};
desc.Width = width; // 非零,单位为像素
desc.Height = height;
desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
desc.SampleDesc.Count = 1;
desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
desc.BufferCount = 2;
desc.Scaling = DXGI_SCALING_STRETCH;
desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
desc.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED;
winrt::check_hresult(factory->CreateSwapChainForComposition(
device, &desc, nullptr, swapChain.GetAddressOf()));
winrt::check_hresult(visual->SetContent(swapChain.Get()));
winrt::check_hresult(composition->Commit());
// 每帧先完成纹理上传和绘制,再提交画面:
// winrt::check_hresult(swapChain->Present(1, 0));
这段代码用于显示透明图像,尚未实现背景采样和模糊。CreateSwapChainForComposition 与 IDCompositionVisual::SetContent 的绑定方式见 DXGI 文档;设备、目标和 visual 的创建流程见 DirectComposition 初始化文档。
这里使用预乘 alpha。如果原始 frame buffer 保存的是 straight alpha,需要先转换:
Rpremul = R × A
Gpremul = G × A
Bpremul = B × A
各分量按 [0, 1] 理解。还需要确认输入是 RGBA 还是 BGRA,以及纹理格式和行跨度是否匹配。透明区域应写入正确的 alpha;如果整帧都输出 A = 1,后方的模糊层就会被完全遮住。
圆角和自定义形状是否需要 WS_EX_LAYERED?
不需要,可以根据边缘效果和输入需求选择实现方式。
用 SetWindowRgn 设置实际窗口区域
圆角窗口可以使用 CreateRoundRectRgn;更复杂的硬边形状可以用其他 region 表达,再交给 SetWindowRgn,无需使用 WS_EX_LAYERED。SetWindowRgn 文档
#include <windows.h>
// 面向无边框窗口;尺寸和半径均为像素。
bool SetRoundedWindowRegion(HWND hwnd, int radiusPx)
{
RECT rect{};
if (!GetWindowRect(hwnd, &rect))
return false;
HRGN region = CreateRoundRectRgn(
0, 0,
rect.right - rect.left,
rect.bottom - rect.top,
radiusPx * 2,
radiusPx * 2);
if (!region)
return false;
if (SetWindowRgn(hwnd, region, TRUE) == 0)
{
DeleteObject(region);
return false;
}
// 成功后 region 由系统拥有,不再调用 DeleteObject。
return true;
}
region 使用窗口坐标,原点位于整个窗口的左上角。窗口大小或 DPI 变化后,需要重新计算。它适合限定实际窗口范围,但无法表达柔和的半透明边缘。
用合成裁剪或 alpha 表现平滑边缘
要实现抗锯齿圆角、柔和边缘或动态形状,可以在渲染时生成 alpha mask,或使用合成系统的裁剪功能。
这种方式改变的是画面,透明像素是否接受鼠标输入需要另外处理。Visual layer 本身也不会替 Win32 应用完成整个 visual tree 的命中测试。Visual layer 限制
如果模糊由独立的 DWM 背景效果产生,只裁剪前景图像未必能同时裁剪背景模糊。需要让背景和前景使用相同的形状范围,或通过窗口 region 限定整体范围。
什么时候选择 WS_EX_LAYERED?
通过 UpdateLayeredWindow 或 UpdateLayeredWindowIndirect 提交逐像素透明位图时,需要使用 WS_EX_LAYERED。这条路径能实现异形窗口,但不会自动模糊窗口后方的内容。Layered windows 文档
对于持续更新的 frame buffer,可以先评估 GPU 合成路径。如果采用 layered window,则需要根据实际尺寸、更新频率和复制开销,判断是否满足性能要求。
Windows 10 实现时的注意事项
先明确最低支持的 Windows 10 版本。如果使用前述桌面 Visual layer 托管方式,最低版本应按其要求设为 Windows 10 1803;需要兼容更早版本时,可以选择 DirectComposition 承载动态图像。
Windows 11 的 DWMWA_WINDOW_CORNER_PREFERENCE 和 DWMWA_SYSTEMBACKDROP_TYPE 不能作为 Windows 10 的实现基础,后者从 Windows 11 Build 22621 才支持。DWM 属性版本说明
采用 Windows 10 accent 兼容方案时,应在目标系统上检查背景模糊、窗口形状和 alpha 叠加后的效果,并准备纯色背景作为回退。系统材质还可能受透明效果设置或电源策略影响,应用无法保证它始终呈现固定的模糊外观。Host backdrop brush 说明
备注:内容仅供参考。
