65.9K
CodeProject 正在变化。 阅读更多。
Home

非托管 Vista 功能

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.94/5 (100投票s)

2007 年 2 月 23 日

公共领域

10分钟阅读

viewsIcon

283775

downloadIcon

3009

如何在不使用 WPF 和 .NET 的情况下访问 Vista 和 DWM 的强大功能

Screenshot - umvistad3d.jpg

引言

本文将演示如何在不使用“托管”语言以及最重要的是,完全不使用 .NET 的情况下,访问 Vista 新的硬件加速窗口系统的强大功能。

所提供的应用程序使用传统的非托管 C++ 和 DirectX 9 Ex,在一个 Aero Glass 窗口上创建一个抗锯齿、半透明的 3D 旋转立方体,其运行与 Vista 下的任何其他应用程序一样流畅。

背景

我不是大学毕业的程序员,也不是 DirectX 专家。我是自学成才的;一个对 DirectX 产生了浓厚兴趣的电脑爱好者。

我等待 Windows 中的硬件加速界面已经很久了。它终于来了,但我却被互联网上充斥的信息所困扰,这些信息暗示我必须使用 .NET 和 Windows Presentation Foundation (WPF) 才能利用这个硬件加速界面。

我在编程方面有控制欲,因此我不喜欢 .NET 和/或托管编程语言。我被迫使用 .NET 才能在 Vista 中做任何特殊事情的想法让我非常不舒服,所以我开始尽可能多地寻找关于 Vista 如何实现这些功能的信息,以便我能够绕过 .NET。事实证明,微软并没有向非托管开发者隐藏这些功能。他们只是非常成功地暗示 .NET 和 WPF 是唯一的解决方案,并且为了强化这种暗示,没有提供(我能找到的)任何关于如何在没有 .NET 和 WPF 的情况下做任何事情的信息。

WPF 为开发者提供了一些相当酷的功能。我们终于获得了 GDI+ 的功能,但具有硬件加速,此外 WPF 甚至引入了真正的 3D 控件的概念。这两个功能都很出色,但如果我不得不使用托管语言和 .NET 来获得它们,那我可就太失望了。

我通过浏览微软开发者博客开始寻找信息。一段时间后,我偶然发现了桌面窗口管理器 (DWM) 开发团队成员 Greg Schechter 的博客。Greg 分享了许多关于 Vista 技术非常有用的信息,但最重要的是,他相当详细地描述了 DWM 的工作原理。他的博客给了我足够的信息,让我相信这绝对可以做到。

DWM 是在 Vista 中提供硬件加速窗口系统的应用程序。简而言之,DWM 是一个全屏 Direct3D 应用程序,它为每个“窗口”创建一个纹理四边形,在 3D 空间中排列这些四边形,并将它们与一些性感的像素着色器效果(Aero Glass)一起渲染到屏幕上。标准桌面视图看起来是传统的 2D,但快速按下 WinKey+TAB 将证明这绝非如此。

微软成功地为我们提供了这个新的窗口环境,同时没有破坏任何之前的软件(好吧,只要是遵循 Win32 设计规则的软件都没有破坏)。你可以使用标准的 Win32 库编写一个 Vista 应用程序,它会像你期望的那样工作。微软为此成就获得十分,但因让非 .NET 开发者对如何实现 WPF 的功能一无所知而扣除十分。

我在网上找到的大多数针对 Vista 的编程示例都使用 .NET 框架,少数使用 MFC 或 WTL 等较旧的类库,更少的仍然使用传统的原生 Win32。在自定义图形方面,用于特殊效果编程(如真正的 alpha 混合图形等)的是 WPF 或 GDI+。我没有找到任何一个使用 Direct3D 的示例;而 Direct3D 正是 WPF 和 DWM 原生使用的系统。我的意图是改变这种状况。

乍一看,这个示例应用程序可能显得平淡无奇;“哦,是的,它是一个在窗口上旋转的立方体”。请暂时不要感到平淡无奇,并考虑一下我们这里拥有的,是一个 Direct3D 渲染的旋转立方体,带有抗锯齿和渐变半透明,通过实时生成的 alpha 通道无缝合成。现在是否更令人印象深刻了?没有?那么,请停下来再思考几分钟,并意识到这个应用程序所演示的,是无限大冰山的一小部分尖端。您不再受 .NET 和 WPF 能力的限制,而只受 DirectX 的限制,我的朋友们,这太棒了。

因此,事不宜迟,让我们深入探讨细节。

代码

所提供的应用程序源代码尽可能简单。它有一些错误检查,但任何错误只会导致静默失败。比这更复杂的东西都会使代码过于复杂。我只在文章中插入了与所描述的技术直接相关的小代码片段。请查阅可下载的源代码以获取更多详细信息。

获得我们想要的结果只是正确设置窗口和 Direct3D 的问题,其余的将由 DWM 处理。没有未文档化的 API 需要逆向工程,没有“高手”技巧,只是在正确的时间,将正确的东西放在正确的位置。

我们做的第一件事是像往常一样创建一个窗口。声明并填充 WNDCLASSEX 结构,注册它,然后创建一个窗口。

CreateWindowEx 调用的第一个参数包含一个重要因素,这对于让所有这些都正常工作至关重要。

hWnd = CreateWindowEx(WS_EX_COMPOSITED,      // dwExStyle
                      g_wcpAppName,          // lpClassName
                      g_wcpAppName,          // lpWindowName
                      WS_POPUP | WS_SIZEBOX, // dwStyle
                      CW_USEDEFAULT,         // x
                      CW_USEDEFAULT,         // y
                      g_iWidth,              // nWidth
                      g_iHeight,             // nHeight
                      NULL,                  // hWndParent
                      NULL,                  // hMenu
                      hInstance,             // hInstance
                      NULL);                 // lpParam

这里的关键因素是 WS_EX_COMPOSITED 扩展窗口样式。如果您不提供此标志,您的窗口在移动时将会出现严重的滞后,稍后会详细介绍。WS_POPUPWS_SIZEBOX 窗口样式为我们提供了带有标准边框的简单窗口表面。

我使用了 dwmapi.dll 来扩展我们窗口的非客户区,使其覆盖整个区域。有许多其他文章详细介绍了这种简单技术,因此我在此不再赘述。

// Extend glass to cover whole window
DwmExtendFrameIntoClientArea(hWnd, &g_mgDWMMargins);

现在我们有了窗口,可以启动并运行 Direct3D。这里重要的事项是指定一个带 alpha 通道的后台缓冲区格式,并显然设置 Windowed 标志。我们进行一些硬件检查以确定抗锯齿的可用性,并设置我们能达到的最高质量。

// Create a Direct3D object
if(FAILED(Direct3DCreate9Ex(D3D_SDK_VERSION, &g_pD3D))) return E_FAIL;

// Setup presentation parameters
ZeroMemory(&pp, sizeof(pp));
pp.Windowed = TRUE;
pp.SwapEffect = D3DSWAPEFFECT_DISCARD; // Required for multi sampling
pp.BackBufferFormat = D3DFMT_A8R8G8B8; // Back buffer with alpha channel

// Set highest quality non-maskable AA available or none if not
if(SUCCEEDED(g_pD3D->CheckDeviceMultiSampleType(D3DADAPTER_DEFAULT,
                                                D3DDEVTYPE_HAL,
                                                D3DFMT_A8R8G8B8,
                                                TRUE,
                                                D3DMULTISAMPLE_NONMASKABLE,
                                                &msqAAQuality
                                                )))
{
  // Set AA quality
  pp.MultiSampleType = D3DMULTISAMPLE_NONMASKABLE;
  pp.MultiSampleQuality = msqAAQuality - 1;
}
else
{
  // No AA
  pp.MultiSampleType = D3DMULTISAMPLE_NONE;
}

// Create a Direct3D device object
if(FAILED(g_pD3D->CreateDeviceEx(D3DADAPTER_DEFAULT,
                                 D3DDEVTYPE_HAL,
                                 hWnd,
                                 D3DCREATE_HARDWARE_VERTEXPROCESSING,
                                 &pp,
                                 NULL,
                                 &g_pD3DDevice
                                 ))) return E_FAIL;

[开始推测]

当窗口和关联的 DirectX 对象配置正确时,会发生一些神奇的事情。似乎在正确的条件下,DWM 会认为你知道你在做什么,并且在设备创建时不会创建一个新的后台缓冲区,而是给你它创建的用于渲染你的窗口的缓冲区(纹理)。因此,你直接渲染到 DWM 将映射到 Vista 桌面 3D 空间中的一个四边形上的纹理。当 WS_EX_COMPOSITED 扩展窗口样式被移除时,突然之间,与其他 Vista 窗口相比,我们的窗口变得卡顿,这是由于双重 Direct3D 操作造成的。你渲染的每一帧都被渲染到你的离屏后台缓冲区,然后该缓冲区又被复制到你的“窗口”中,而这个“窗口”实际上是一个 DWM 管理的纹理,然后被映射到一个四边形并与屏幕上的其他窗口进行合成。

[结束推测]

我对上述说法很可能是错的,但目前这是我根据从几个 MS 开发者博客中收集到的信息,对我正在发生的事情的最佳猜测。如果有人知道这件事的真实情况,我会很感兴趣。

现在我们有了窗口和 Direct3D 对象可以操作,剩下的就是循环渲染帧,以及当我们收到明确的 WM_PAINTWM_ERASEBKGND 消息时进行渲染。在 Vista 下,由于 DWM 的工作方式,我们不应该收到太多这些消息,但我们仍然会在窗口大小调整等情况下收到它们。

在渲染每一帧之前,请务必将后台缓冲区清除为透明。

// Clear the back buffer to transparent
g_pD3DDevice->Clear(0,
                    NULL, D3DCLEAR_TARGET,
                    ARGB_TRANS, // 0x00000000
                    1.0f,
                    0);

作为渲染函数中的最后一步,我们使用另一个新的 DirectX9 函数 PresentEx。在我们的需求背景下,PresentEx 和旧的 Present 之间唯一的区别是需要传递另一个 NULL;五个 NULL 而不是四个。

// Update display
g_pD3DDevice->PresentEx(NULL, NULL, NULL, NULL, NULL);

关注点

那么,如果你不想要玻璃面板呢?如果你只是想直接渲染到屏幕上呢?考虑一下 Vista 自带的“气泡”屏保。当然可以!如果你从窗口类中移除 WM_SIZEBOX 样式标志,就会发生一些非常酷的事情。你现在已经有效地告诉 DWM,它不需要绘制非客户区,因此它只是直接合成你的客户区内容,包括 alpha 通道。窗口本身似乎消失了(视觉上),你只留下一个完美合成的 3D 旋转立方体。让窗口覆盖整个桌面,并渲染 50 个这样的立方体四处弹跳,你就有了自己的气泡风格屏保。好吧,实际上,“气泡”屏保确实会先截取桌面(为了兼容没有桌面合成的 Vista 机器,毫无疑问也是出于所谓的“安全考虑”),但原理是相同的。

如果你正在阅读这篇文章,心想“哇!完全的 DirectX 访问意味着我们可以像 Aero Glass 那样自己进行窗口像素着色”,那么很抱歉,你可能会失望。这项技术不允许你做的一件事是为你的窗口创建自己的 Glass 类型像素着色器。更准确地说,你将无法以任何现实的性能方式做到这一点。微软的开发人员已经解释过,在新 DWM 下从“屏幕”读取或写入是一项令人难以置信的昂贵操作(毕竟每次都需要强制合成整个工作区),应完全避免。请记住,你的应用程序不知道它下面有什么,只有 DWM 合成引擎知道。只要你不需要知道窗口下面有什么,那么天空就是极限。

结论

就这样了。您可以在 Vista 下开发高效的应用程序,无需 .NET 和托管语言,这些应用程序将与 DWM 完全协作,无需任何危险的黑客手段。这不仅对 C/C++ 开发者是个好消息,对老派的汇编语言开发者也是如此。您仍然可以编写纯 Win32 ASM 应用程序,并获得所有新的优点。

我将把这里提供的信息的应用留给你们无疑非常有能力的想象力,但当你们梦想时,请记住几点。仅仅因为你能做某事并不意味着你应该做。是的,有了这些信息,你可以去编写一个应用程序,其效果相当于将《半衰期 2》的录制演示作为桌面背景运行,但这并不明智。请记住,你现在正在亲密地与操作系统 GUI 共享 CPU 和显卡的 3D 功能。不仅如此,在 Windows 的历史上,第一次“桌面”在所有运行 Windows 的计算机上都不是平等的。现在,桌面环境与硬件能力紧密相关,就像 3D 游戏一样。你的机器可能每秒能处理 100 亿个像素着色多边形,并且仍然有 DWM 的喘息空间,但隔壁的 Joe Blogs 可能 barely 能够运行 Aero 界面。

该示例应用程序是在 Vista 64 上的 Visual Studio 2005 中开发的。硬件包括 AthlonFX62 双核处理器、ATI Radeon X1900XTX 显卡和 2GB RAM。我成功地将此代码编译为 32 位和 64 位二进制文件,并将其与源代码一起包含在内。但是,我强烈建议您使用源代码设置一个项目并自行构建。您将需要最新的 Windows SDK、最新的 DirectX SDK,当然还有启用 Aero 的 Windows Vista。我不知道这在功能较弱的系统上会如何运行,但我非常乐意了解。

我不会止步于此,这只是一个开始,但我认为我应该尽快将这些信息公之于众,让其他人开始在全新的语境中体验 Direct3D。

© . All rights reserved.