HUD 窗口 64 位 (DWM 合成)






4.59/5 (9投票s)
DWM 合成
引言
HUD 窗口 的目的是创建基于桌面窗口管理器 (DWM) 的合成演示,DWM 是自 Vista 以来操作系统的重要组成部分。它基于创建 Vista Pro 中的DreamScenes所使用的相同概念。
它受到《阿凡达》、《钢铁侠》和《遗落战境》等电影的启发,这些电影大量使用透明显示,就像在现代喷气式战斗机驾驶舱中发现的那些 HUD 应用程序一样。
本文与使用第三方插件(BASS.dll、GDImage.dll、WinLIFT.dll)密切相关,但我会尽力解释这个概念,因为我不知道有其他工具仅使用核心 API 就能做到这一点,但如果有,我很乐意了解它们。
它混合了多种语言:C++、PowerBASIC、OpenGL,整个应用程序是32位和64位模块协同工作的结果。
桌面窗口管理器
桌面合成功能在 Windows Vista 中引入,从根本上改变了应用程序在屏幕上显示像素的方式。启用桌面合成后,各个窗口不再像以前版本的 Windows 那样直接绘制到屏幕或主显示设备上。相反,它们的绘制被重定向到视频内存中的脱屏表面,然后这些表面被渲染成桌面图像并显示在屏幕上。
在 Windows 8 中,桌面窗口管理器 (DWM) 始终处于开启状态,并且无法由最终用户和应用程序禁用。与 Windows 7 一样,DWM 用于合成桌面。除了 Windows 7 中启用的体验外,DWM 桌面合成现在还为所有主题启用桌面合成、支持立体 3D,以及管理、分离和保护 Windows 应用商店应用的体验。
在 Windows Vista 和 Windows 7 中,桌面合成仅通过 AERO Glass 主题启用。
使用 DWM 时,所有内容最终都渲染到 DirectDraw 曲面,使用 GPU 而不是 CPU。这为能够使用它的图形引擎提供了丰富的可能性。为了说明本文,我使用了我的 GDImage 7.00 和 WinLIFT 5.00 的免费试用版,因为它们都设计为使用 GPU 而不是 CPU,并且能够将大多数图形技术结合在一起。
合成模式
为了在合成模式下工作,所有绘图都必须以 32 位完成,因为 alpha 通道的 ব্যবহার 是处理子控件的各个可变透明度的强制要求(我们不使用分层窗口,因为我们希望保留子控件的各个可变透明度)。
在 Windows 8 中
- Windows 7 中存在的禁用桌面合成的所有选项均已删除
- 桌面合成负责合成所有主题
- 应用程序无法使用
DwmEnableComposition
禁用桌面合成。为了保持向后兼容性,对此 API 的调用将成功返回;但是,桌面合成不会被禁用 - 已删除“禁用桌面合成”的兼容性补丁
- 已从应用程序属性对话框的兼容性选项卡中删除“禁用桌面合成”选项。
- 已删除 AERO 模糊模式。
在 Vista 和 Seven 中,这里有一些用于启用/禁用合成的函数
#include <Dwmapi.h>
#define long_proc typedef long (__stdcall *zProc)
// Check for the DWMAPI
HMODULE LoadDWM () {
static HMODULE hLib;
static long nChecked;
if (nChecked == 0) { nChecked = -1; hLib = LoadLibrary (L"DWMAPI"); }
return hLib;
}
// Enable DWM composition
long zDwmEnableComposition (IN long uCompositionAction) {
long nRet = 0;
HMODULE hLib = LoadDWM();
if (hLib) {
long_proc (long);
zProc hProc = (zProc) GetProcAddress(hLib, "DwmEnableComposition");
if (hProc) { nRet = hProc(uCompositionAction); }
}
return nRet;
}
// Check if DWM composition is enabled
long zDwmIsCompositionEnabled () {
long nRet = 0;
HMODULE hLib = LoadDWM();
if (hLib) {
long_proc (BOOL*);
zProc hProc = (zProc) GetProcAddress(hLib, "DwmIsCompositionEnabled");
if (hProc) {
BOOL bAero = FALSE;
if (hProc(&bAero) == 0) {
if (bAero) { nRet = -1; }
}
}
}
return nRet;
}
// This is the most IMPORTANT API to create a transparent window
// We fool DWM with a fake region, because we want to use the client, and the non-client area,
// just like with a transparent layered window.
// This one must also be used with Windows 8+
void zSetCrystalBehindMode (IN HWND hWnd, IN BOOL nBOOL) {
static FARPROC hProc;
HMODULE hLib = LoadDWM ();
if (hLib) {
if (zDwmIsCompositionEnabled()) {
long_proc (HWND, DWM_BLURBEHIND*);
zProc hProc = (zProc) GetProcAddress(hLib,
"DwmEnableBlurBehindWindow");
if (hProc) {
LRESULT nRet = S_OK;
HRGN hRgnBlur = 0;
DWM_BLURBEHIND bb = {0};
// Create and populate the BlurBehind structure.
// Set Blur Behind and Blur Region.
bb.fEnable = nBOOL;
bb.dwFlags = DWM_BB_ENABLE | DWM_BB_BLURREGION;
bb.fTransitionOnMaximized = 0;
// Fool DWM with a fake region
if (nBOOL) { hRgnBlur = CreateRectRgn(-1, -1, 0, 0); }
bb.hRgnBlur = hRgnBlur;
// Set Blur Behind mode.
nRet = hProc(hWnd, &bb);
}
}
}
}
完全控制你的窗口
为了在 DirectDraw 曲面上以合成模式进行绘制,您不能再使用 GDI32,主要是因为 DirectDraw(以及 OpenGL)存储 32 位 ARGB 分量的方式不同,并且 RGB(0,0,0) 不会产生纯黑色的笔刷,而是透明的。解决方法是使用 GDIPLUS 而不是 GDI32,并在直接绘制到 DD 曲面时(例如创建 OpenGL 纹理时)进行红色和绿色排列。
另一个注意事项是,必须严格按照 z 顺序,从下到上绘制所有控件和对象,以正确渲染可变透明度。这意味着您不应让 Windows 自己执行默认的绘制过程,而是将所有内容绘制到 DIB 内存中,然后一旦所有重叠图层已在缓存内存中绘制完成,就将生成的合成图像 blit 到 DD 曲面上(这是应该委托给 SkinEngine 的难点)。
为了完全控制弹出窗口,我编写了一个教程,解释了创建简单 SkinEngine 的基本步骤。教程是用 BASIC 编写的,以便于阅读代码,但由于它仅使用低级 SDK API,因此很容易从一种语言翻译到另一种语言。完整的教程可以在这里找到:http://www.jose.it-berater.org/smfforum/index.php?topic=1164.0。
对于那些想弄清楚与 DWM 兼容的 Skin Engine 可以做什么,而无需重新输入/翻译所有代码的人,请使用本文末尾的链接下载 C++ 演示项目,该项目随附了 WinLIFT/GDImage C++ 64 位试用版的副本。
关于演示
该演示完全不寻常,因为它在同一个应用程序中混合使用了 32 位和 64 位,以及多种语言(C++ 和 PowerBASIC),低级编码部分是用 PowerBASIC 32 位编写的,而主 EXE 是用 C++ 64 位编写的。
保留低级编码部分为 32 位的原因是,相同的代码可以与最终的 EXE 结合使用,该 EXE 无论是 32 位还是 64 位。
ZWP.exe 播放器
为了在 64 位和 32 位编码部分之间执行进程内通信,我们使用了一个名为 ZWP.exe 的运行时,它负责监视和显示用于执行后台动画的 (BassBox) OpenGL 插件。
ZWP.exe 本身是用 PowerBASIC 编写的(源代码在 ZIP 文件中),可以生成非常小的 EXE(25 Kb),无需运行时要求,也没有额外的依赖项。它使用一个弹出窗口,并带有 WS_EX_TOOLWINDOW
扩展样式,以避免在 Windows 任务栏上显示自己。
ZWP {子弹出窗口} 必须始终位于主 HUD 窗口下方,并且必须使用与其父窗口相同的大小和位置,这部分代码由下面的 ZWP_SizeMove 过程处理。
void ZWP_SizeMove() {
RECT lpw;
int x, y, w, h;
HWND hWnd, hPopup;
GetWindowRect(gh_Main, &lpw);
x = lpw.left + skGetSystemMetrics(SK_DWM_LEFT);
y = lpw.top + skGetSystemMetrics(SK_DWM_TOP);
w = lpw.right - skGetSystemMetrics(SK_DWM_RIGHT) - x;
h = lpw.bottom - skGetSystemMetrics(SK_DWM_BOTTOM) - y;
// Use this to move the window in case of AERO shake
// because setting the new location with SetWindowPos
// may fail!
hWnd = ZWP_Handle();
if (hWnd) {
hPopup = skGetDWMregion(gh_Main); // This is the region drawn behind the child controls
if (hPopup == 0) { hPopup = gh_Main; }
SetWindowPos(hWnd, GetWindow(hPopup, GW_HWNDNEXT), 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOOWNERZORDER | SWP_NOACTIVATE | SWP_ASYNCWINDOWPOS);
MoveWindow(hWnd, x, y, w, h, 0);
}
}
skGetDWMregion
是 SkinEngine 的一个特殊过程,它在 Combo 的编辑部分后面绘制一个黑色笔刷区域,因为当使用 GDI32 笔刷创建 ComboBox 来绘制背景时,Windows 仍然使用旧的编辑控件,这使得它在合成模式下透明。
到目前为止,我还没有找到一种简单的方法来解决在重绘整个场景时出现的这种行为。因此,我选择仅在隐藏视觉插件时重绘该区域。
为了与 ZWP.exe 通信,我们定义了自己的私有消息 WM_ZWPNOTIFICATION
,并使用 SendMessageA
和 WM_SETTEXT
来交换参数,使用这组 API(详情请参阅 C++ 项目源代码)。
- ZWP_Destroy,终止 ZWP 子进程。
- ZWP_Handle,检索 ZWP 窗口句柄。
- ZWP_Hide,隐藏 ZWP 窗口。
- ZWP_Message,与 ZWP 子进程通信。
- ZWP_Play,播放特定音频文件。
- ZWP_Show,显示 ZWP 窗口。
- ZWP_SizeMove,调整 ZWP 窗口的大小/移动以跟随主 EXE 的大小/移动。
- ZWP_Stop,停止播放音频。
- ZWP_UsePlugin,指定要使用的视觉 OpenGL 插件。
ZWP.exe 本身使用 freeware BassBox 音频播放器引擎的一个子集,该引擎与 Ian Luck 的 BASS.dll (www.un4seeen.com) 密切合作。
它的目的是与主窗口应用程序完全并发地播放视觉插件和音频,而不是像子线程那样。我这样做是因为 Windows 不以相同的优先级级别处理线程和独立进程。
注意:ZWP.bas 的完整 PowerBASIC 源代码与 C++ 项目一起提供在 ZIP 文件中。
视觉插件
它们都是真正的艺术编程作品,Matrix 可能是你见过的最好的版本之一,Attractor 也是如此,它从不产生两次相同的显示。所有插件都是小的独立 32 位 DLL,它们的目的是基于音频信号的分析实时渲染动画。
我还可以与感兴趣的人分享更多视觉插件,如果需要,还可以解释如何创建它们。您可以在此处了解 BassBox 视觉插件的概念:这里。
同时使用视觉插件和音频文件
演示项目附带一组存储在“\Audio”子文件夹中的音频文件,您可以在其中添加自己的音频文件,可以是音轨音乐(xm, it, mod, s3m, mtm)或音频流格式(mp3, ogg, wav)。要播放或停止音频,只需单击“使用视觉插件”复选框。您可以从第一个组合框中选择一首歌曲,或者如果您愿意,可以从 Windows Explorer 拖放来播放特定的音频文件。
要更改视觉插件,请从第二个组合框中选择一个新的。
GDImage 7.00
为了播放动画,我不得不使用 GDImage 7.00 版本,因为它能够在完全 32 位合成模式下播放静态动画(与 gif 动画相同)。
所有动画都可以用鼠标在图形容器内拖动。每个图形组件都可以固定在特定位置,就像窗口中的子控件一样。
文本“DWM composition”使用了一个私有字体,以确保它在所有计算机上看起来都一样,而无需先安装缺失的字体。
主居中动画可以从窗口底部显示的任何圆盘旋钮以任何角度旋转(右键单击任何旋钮控件即可恢复初始角度)。
窗口右侧的垂直滑块可用于即时调整主动画的大小。(注意:64 位试用版禁用了一些 GDImage 功能)。
WinLIFT 5.00
是一个 SkinEngine,专门设计用于与 GDImage 协作,以合成模式渲染整个界面。
创建自己的 SkinEngine 的基本原理在名为“控制你的窗口”的系列文章中进行了详细解释,可在此处找到:这里。
分层背景
SkinEngine 能够使用多个分层合成背景,其中一些在“\Background”子文件夹中提供。
要更改它们,请单击左上角的钢铁侠图标的左键或右键。
为了更好地看到细微分层背景的变化,最好使用深色壁纸作为主 Windows 背景。
透明 HUD 模式
选中此选项时,不会在图形容器后面应用分层合成背景,以便更好地查看在 z 顺序底部播放的视觉插件动画。
旋转动画
该项目使用静态动画,称为“spinner”,其最初目的是显示分层动画,该动画的功能与 GIF 动画类似,但它基于透明的 .PNG 或 .SKI 图像(专有的 GDImage 格式),可以在完全合成模式下使用。
您可以在此处了解有关使用 Spinner 控件来通知用户正在运行耗时或关键任务的更多信息:这里。
这里有一个 PNG 文件示例,可用于执行静态动画
重要提示:每个帧都必须完全适合正方形尺寸,并使用从左到右的水平对齐方式,以简化计算。
硬件要求
由于演示的多媒体性质,它旨在运行在带有良好显卡(Nvidia 或 AMD/ATI)的 I7 多核处理器上。
在现代硬件上使用时,由于使用了 GPU,HUDplus.exe 应该使用少于 2% 的 CPU,而 ZWP.exe 使用 0%,除非使用最耗资源的 Matrix 插件,那时 CPU 资源占用可能会上升到 10%。
这些值来自一台配备 GEFORCE GT 650M 和 2 GB 显存的 ASUS N76V CORE i7。
如果您的硬件配置与上述规格匹配,则可以使用内置的线程动画,而不是计时器,因为这允许在保持动画运行的同时平滑拖动精灵。
long StartAnimation (IN long Delay) {
long nRet = LB_ERR;
gn_Delay = Delay;
DWORD dwThreadId = 0;
HANDLE hThread = CreateThread(NULL, // default security attributes
0, // use default stack size
(LPTHREAD_START_ROUTINE ) Animate, // thread function name
(LPVOID) Delay, // argument to thread function
0, // use default creation flags
&dwThreadId); // returns the thread identifier
if (hThread) { nRet = 0; Sleep(100); }
CloseHandle(hThread);
return nRet;
}
源代码
项目源代码使用 UNICODE,它以纯 SDK 编码风格提供,如Charles Petzold 第五版(SDK 编码圣经)中所述。
为了与其他正在运行的应用程序协同工作,它不使用游戏循环,而是使用多核 i7 上的专用线程,在较旧的机器上可以替换为简单的计时器。
为了实现跨语言兼容性,本项目仅使用过程式编码风格和核心 API。
屏幕截图
此屏幕截图仅是 DWM 合成的一个有限子集预览。
这种 HUD 窗口是混合 2D 和 3D 的唯一方法,可用于创建复杂的游戏界面。
DreamScenes
如果有兴趣,我可以发布另一个演示项目,该项目使用视频而不是 OpenGL 插件,并且工作方式与 Vista Pro 中的 DreamScenes 完全相同,但侵扰性较小,并且特定于单个应用程序,而不是使用整个桌面。
它使用与 C# 项目中使用的 API 相同的 API,此处。
SDK 编程
我为什么使用它
- 底层编程是完全访问核心 Windows API 功能的唯一方法。
- 它不使用专有语法,也不会向您隐藏幕后发生的事情。
- 它是所有编译器(包括 .NET)都能理解的通用分母。
- 它能保持代码小巧且快速。
- 它允许您编写不比 OOP 更难学的过程式代码。
- 它允许您使用所有 Windows 控件。
- 由于 DLL 是 SDK 的核心,因此很容易与第三方 DLL 一起使用。
- 它处理所有 Windows 消息和结构。
- 它不是死胡同,因为尽管微软这么说,但它一直在不断改进。
- 当您需要时,您不必依赖任何人来编写自己的控件。
- 摆脱了 {现代} 编程语言中所有额外的臃肿依赖项。
下载项目链接
由于 ZIP 文件的大小超出了 CodeProject 允许的限制,您必须在此处下载完整的项目:此处。 (28 171 Kb)
历史
这是 1.00 版本,发布于 2014 年 1 月 2 日。