借助 C++ 举办 Windows 开拓:Windows 运行时的泛起
副标题#e#
我的上一个专栏中接头了 Windows 运行时 (WinRT) 应用措施模子 (msdn.microsoft.com/magazine/dn342867)。 我演示了如何通过尺度 C++ 和经典 COM 来编写 Windows 应用商店或 Windows Phone 应用措施,个中仅利用了一些 WinRT API 函数。 毫无疑问,您 不必利用 C++/CX 或 C# 这样的语言投射。 可以或许绕过这些抽象观念是一种强大的成果,同时也是一种 相识这项技能事情方法的很好的要领。
我在 2013 年 5 月的专栏中先容了 Direct2D 1.1 并演示了如何利用它在桌面应用措施中举办泛起 (msdn.microsoft.com/magazine/dn198239)。 接下来的专栏先容了 dx.codeplex.com 上提供的 dx.h 库,这可以大幅简化 C++ 中的 DirectX 编程 (msdn.microsoft.com/magazine/dn201741)。
上个专栏中的代码对付实现基于 CoreWindow 的应用措施已经足够,但未提供任何泛起。
本月,我将演示如何操作这种根基的框架并添加泛起支持。 WinRT 应用措施模子针对利用 DirectX 泛起举办了优化。 我将向您演示,如何操作在之前专栏中学到的有关 Direct2D 和 Direct3D 泛起的 内容,将其应用到基于 CoreWindow 的 WinRT 应用措施,详细而言,通过 dx.h 库利用 Direct2D 1.1 。 大大都环境下,岂论您的方针是桌面照旧 Windows 运行时,需要编写的实际 Direct2D 和 Direct3D 绘制呼吁是沟通的。 可是,个中有一些细微的不同,虽然,使其完全运转起来从一开始就有 很大不同。 因此,我将继承上一次的内容,演示如安在屏幕上显示一些像素!
为了正确支持泛起,窗口必需可以或许意识到特定事件。 至少这包罗窗口的可见性和巨细的变动,以及 对用户所选择的逻辑显示 DPI 设置的变动。 在上次专栏中先容的 Activated 事件中,这些新事件都 通过 COM 接口回调陈诉给应用措施。 ICoreWindow 接口提供注册 VisibilityChanged 和 SizeChanged 事件的要领,但首先我需要实现相应的处理惩罚措施。 我需要实现的两个 COM 接口与 Activated 事件处理惩罚措施及其 Microsoft 接口界说语言 (MIDL) 生成的类模板很是相似:
typedef ITypedEventHandler<CoreWindow *, VisibilityChangedEventArgs *>
IVisibilityChangedEventHandler;
typedef ITypedEventHandler<CoreWindow *, WindowSizeChangedEventArgs *>
IWindowSizeChangedEventHandler;
接下来必需实现的 COM 接口称为 IDisplayPropertiesEventHandler,谢天谢地这个接口已经界说 了。 我只需将相关的头文件包罗在个中:
#include <Windows.Graphics.Display.h>
另外,相关范例在以下定名空间中界说:
using namespace ABI::Windows::Graphics::Display;
按照这些界说,我可以更新上次专栏中先容的 SampleWindow 类,也从这三个接口担任:
struct SampleWindow :
…
IVisibilityChangedEventHandler,
IWindowSizeChangedEventHandler,
IDisplayPropertiesEventHandler
同时还需要记着更新我的 QueryInterface 实现以指示对这些接口的支持。 这些内容将让您自行完 成。 虽然,如我上次所说,Windows 运行时并不体贴在那边实现这些 COM 接口回调。 它遵循的原则 是,Windows 运行时不假定我的应用措施 IFrameworkView(SampleWindow 类实现的主要接口)也实现 这些回调接口。 因此,固然 QueryInterface 确实会正确处理惩罚这些接口的查询,不外 Windows 运行时 不会为它们举办查询。 相反,我需要注册相应事件,而最佳位置是在 IFrameworkView Load 要领的实 现中。 提醒一下,Load 要领是应该将所有代码粘贴到这里的要领,以便筹备应用措施举办初始泛起。 接下来在 Load 要领中注册 VisibilityChanged 和 SizeChanged 事件:
EventRegistrationToken token;
HR(m_window->add_VisibilityChanged(this, &token));
HR(m_window->add_SizeChanged(this, &token));
这会明晰汇报 Windows 运行时在那边查找前两个接话柄现。 第三个也是最后一个接口,它针对 LogicalDpiChanged 事件,但此事件注册由 IDisplayPropertiesStatics 接口提供。 此静态接口由 WinRT DisplayProperties 类实现。 我只需利用 GetActivationFactory 函数模板来获取它(在我最 近的专栏中可以找到 GetActivationFactory 的实现):
ComPtr<IDisplayPropertiesStatics> m_displayProperties;
m_displayProperties = GetActivationFactory<IDisplayPropertiesStatics> (
RuntimeClass_Windows_Graphics_Display_DisplayProperties);
成员变量保存此接口指针,在窗口的生命周期中,我需要在差异点上挪用它。 此刻,我可以在 Load 要领中注册 LogicalDpiChanged 事件:
HR(m_displayProperties- >add_LogicalDpiChanged(this, &token));
#p#分页标题#e#
稍后将返回到这三个接口的实现。 此刻该是筹备 DirectX 基本布局的时候了。 我将需要尺度的设 备资源处理惩罚措施集,这些在以前的专栏中已经多次接头过:
void CreateDeviceIndependentResources() {}
void CreateDeviceSizeResources() {}
void CreateDeviceResources() {}
void ReleaseDeviceResources() {}
#p#副标题#e#
在第一个要领中,我可以建设或加载任何并非特定于底层 Direct3D 泛起设备的资源。 接下来两个 用于建设特定于设备的资源。 最好是将特定于窗口巨细的资源与并非特定于窗口巨细的资源脱离开。 最后,必需释放所有设备资源。 剩余的 DirectX 基本布局按照应用措施的特定需求,依赖于应用措施 来正确实现这四个要领。 它在应用措施中为我提供单独的点来打点泛起资源以及这些资源的有效建设 和接纳。
此刻我可以引入 dx.h 来处理惩罚所有的 DirectX 沉重任务:
#include "dx.h"
每个 Direct2D 应用措施都以 Direct2D 工场开始:
Factory1 m_factory;
您可以在 Direct2D 定名空间中找到此项,凡是我回收以下要领包括它:
using namespace KennyKerr;
using namespace KennyKerr::Direct2D;
dx.h 库为 Direct2D、DirectWrite、Direct3D 和 Microsoft DirectX 图形基本布局 (DXGI) 等提供了独立的定名空间。 我的大部门应用措施会频繁利用 Direct2D,因此这对我而言是颇有意义。 虽然,您可以回收任何对您的应用措施有意义的要领来打点定名空间。
m_factory 成员变量暗示 Direct2D 1.1 工场。 它用于建设泛起方针,并按照需要建设其他多种与 设备无关的资源。 我将建设 Direct2D 工场,然后可以在 Load 要领的最后一步中建设与设备无关的 任意资源:
m_factory = CreateFactory();
CreateDeviceIndependentResources();
Load 要领返回后,WinRT CoreApplication 类当即挪用 IFrameworkView Run 要领。
在我的上个专栏中,SampleWindow Run 要领的实现通过在 CoreWindow 调治措施上挪用 ProcessEvents 要领即可阻止。 假如应用措施只需要基于各类事件执行不频繁的泛起,回收这种要领 阻止便已足够。 大概您要实现一个游戏,可能您的应用措施只需要一些高判别率的动画。 另一种极度 环境是利用持续的动画轮回,不外您大概但愿更为智能化一点。 我将实现一些折中处理惩罚这两种环境的 内容。 首先,我添加一个成员变量以便跟踪窗口是否可见。 这可以在窗话柄际上对用户不行见时限制 泛起:
bool m_visible;
SampleWindow() : m_visible(true) {}
接下来,我可以重写 Run 要领,如图 1 中所示。
图 1:动态泛起轮回
auto __stdcall Run() -> HRESULT override
{
ComPtr<ICoreDispatcher> dispatcher;
HR(m_window->get_Dispatcher(dispatcher.GetAddressOf()));
while (true)
{
if (m_visible)
{
Render();
HR(dispatcher->
ProcessEvents (CoreProcessEventsOption_ProcessAllIfPresent));
}
else
{
HR(dispatcher->
ProcessEvents (CoreProcessEventsOption_ProcessOneAndAllPending));
}
}
return S_OK;
}
与之前一样,Run 要领吸收 CoreWindow 调治措施。 然后,它进入无限轮回,持续泛起和处理惩罚行列 中大概存在的任何窗口动静(Windows 运行时称之为“事件”)。 可是,假如窗口不行见 ,则将阻止,直至有动静达到。 应用措施如何得知窗口可见性的变革? 这正是利用 IVisibilityChangedEventHandler 接口的原因。 此刻,我可以实现其 Invoke 要领以更新 m_visible 成员变量:
#p#分页标题#e#
auto __stdcall Invoke(ICoreWindow *,
IVisibilityChangedEventArgs * args) -> HRESULT override
{
unsigned char visible;
HR(args->get_Visible(&visible));
m_visible = 0 != visible;
return S_OK;
}
MIDL 生成的接口利用 unsigned char 作为可移植的布尔数据范例。 我只需利用提供的 IVisibilityChangedEventArgs 接口指针获取窗口当前的可见性,然后相应地更新成员变量。 在窗口 埋没或显示时将激发此事件,这比为桌面应用措施实现这此事件略微简朴,因为在桌面上需要思量多种 景象,包罗应用措施封锁和电源打点,更不消说切换窗口。
接下来,我需要实现通过 Run 要领挪用的 Render 要领,如图 1 中所示。 在此时按需建设泛起堆 栈而且实际执行绘制呼吁。 图 2 中显示了根基框架。
图 2 Render 要领摘要
void Render()
{
if (!m_target)
{
// Prepare render target …
}
m_target.BeginDraw();
Draw();
m_target.EndDraw();
auto const hr = m_swapChain.Present();
if (S_OK != hr && DXGI_STATUS_OCCLUDED != hr)
{
ReleaseDevice();
}
}
Render 要领应该较量眼熟。 它的根基表单与之前在 Direct2D 1.1 中概述的沟通。 开始时按照需 要建设泛起方针。 后头紧跟的是实际绘制呼吁,位于对 BeginDraw 和 EndDraw 的挪用之间。 由于呈 现方针是 Direct2D 设备上下文,实际获取出此刻屏幕上的像素涉及到泛起互换链。 说到这一点,我 需要添加泛起 Direct2D 1.1 设备上下文的 dx.h 范例以及互换链的 DirectX 11.1 版本。 后者在 Dxgi 定名空间中提供:
DeviceContext m_target;
Dxgi::SwapChain1 m_swapChain;
最后,在泛起失败时,Render 要领将挪用 ReleaseDevice:
void ReleaseDevice()
{
m_target.Reset();
m_swapChain.Reset();
ReleaseDeviceResources();
}
这认真释放泛起方针和互换链。 它还挪用 ReleaseDeviceResources 以答允释放任何特定于设备的 资源,譬喻画笔、位图或结果。 此 ReleaseDevice 要领看上去大概无关紧急,但在 DirectX 应用程 序中对付靠得住处理惩罚设备丢失很是重要。 假如不能正确释放所有设备资源(任何由 GPU 支持的资源), 则应用措施将无法从设备丢失中规复,而且会瓦解。
接下来,我需要筹备泛起方针,这是我在图 2 所示的 Render 要领中没有涉及的一点。 首先是创 建 Direct3D 设备(dx.h 库确实也简化了接下来的几个步调):
auto device = Direct3D::CreateDevice();
在利用 Direct3D 设备时,我可以转到 Direct2D 工场以建设 Direct2D 设备和 Direct2D 设备上 下文:
m_target = m_factory.CreateDevice (device).CreateDeviceContext();
接下来,我需要建设窗口的互换链。 我将首先从 Direct3D 设备中检索 DXGI 工场:
auto dxgi = device.GetDxgiFactory();
然后,可觉得应用措施的 CoreWindow 建设一个互换链:
m_swapChain = dxgi.CreateSwapChainForCoreWindow(device, m_window.Get());
这里再次强调,dx.h 库可以自动为我填充 DXGI_SWAP_CHAIN_DESC1 布局,大幅简化了事情。 然后 ,我将挪用 CreateDeviceSwapChainBitmap 要领以建设 Direct2D 位图,该位图将泛起互换链的靠山 缓冲区:
void CreateDeviceSwapChainBitmap()
{
BitmapProperties1 props(BitmapOptions::Target | BitmapOptions::CannotDraw,
PixelFormat(Dxgi::Format::B8G8R8A8_UNORM, AlphaMode::Ignore));
auto bitmap =
m_target.CreateBitmapFromDxgiSurface(m_swapChain, props);
m_target.SetTarget(bitmap);
}
此要领首先需要以 Direct2D 可以领略的要领描写互换链的靠山缓冲区。 BitmapProperties1 是 Direct2D D2D1_BITMAP_PROPERTIES1 布局的 dx.h 版本。 BitmapOptions::Target 常量指示位图将用 作设备上下文的方针。 BitmapOptions::CannotDraw 常量干系到一个实际环境:互换链的靠山缓 冲区只能用作其他绘制操纵的输出,不能用作输入。 PixelFormat 是 Direct2D D2D1_PIXEL_FORMAT 布局的 dx.h 版本。
#p#分页标题#e#
界说位图属性之后,CreateBitmapFromDxgiSurface 要领将检索互换链的靠山缓冲区,并建设 Direct2D 位图来代表它。 回收这种要领,只需通过 SetTarget 定位位图,Direct2D 设备上下文就可 以直接泛起到互换链中。
回到 Render 要领,我只需奉告 Direct2D 如何按照用户的 DPI 设置来缩放任意绘制呼吁:
float dpi;
HR(m_displayProperties->get_LogicalDpi(&dpi));
m_target.SetDpi(dpi);
查察本栏目
然后,我将挪用应用措施的设备资源处理惩罚措施,按照需要建设任意资源。 作为总结,图 3 提供了 Render 要领的完整设备初始化序列。
图 3 筹备泛起方针
void Render()
{
if (!m_target)
{
auto device = Direct3D::CreateDevice();
m_target = m_factory.CreateDevice(device).CreateDeviceContext ();
auto dxgi = device.GetDxgiFactory();
m_swapChain = dxgi.CreateSwapChainForCoreWindow(device, m_window.Get());
CreateDeviceSwapChainBitmap();
float dpi;
HR(m_displayProperties->get_LogicalDpi(&dpi));
m_target.SetDpi(dpi);
CreateDeviceResources();
CreateDeviceSizeResources();
}
// Drawing and presentation …
see Figure 2
固然 DPI 缩放在 Direct2D 设备上下文建设之后当即正确应用,在用户变动了此配置时也需要举办 更新。 可觉得运行的应用措施变动 DPI 缩放的成果是 Windows 8 中的新增成果。 这正是 IDisplayPropertiesEventHandler 接口的浸染。 此刻,我只需实现其 Invoke 要领并相应地更新设备 。 下面是 LogicalDpiChanged 事件处理惩罚措施:
auto __stdcall Invoke(IInspectable *) -> HRESULT override
{
if (m_target)
{
float dpi;
HR(m_displayProperties->get_LogicalDpi(&dpi));
m_target.SetDpi(dpi);
CreateDeviceSizeResources();
Render();
}
return S_OK;
}
假定方针(设备上下文)已建设,它将检索当前逻辑 DPI 值并简朴地将其转发到 Direct2D。 然后 挪用应用措施,在从头泛起之前从头建设任何特定于设备巨细的资源。 回收这种要领,我的应用措施 可以动态地响应显示设备 DPI 设置的变革。 窗口必需动态处理惩罚的最后一种变动是对窗口巨细的变动。 我已经完成事件注册,因此只需添加 IWindowSizeChangedEventHandler Invoke 要领的实现来暗示 SizeChanged 事件处理惩罚措施:
auto __stdcall Invoke(ICoreWindow *,
IWindowSizeChangedEventArgs *) -> HRESULT override
{
if (m_target)
{
ResizeSwapChainBitmap();
Render();
}
return S_OK;
}
独一剩下的任务就是通过 ResizeSwapChainBitmap 要领调解互换链位图的巨细。 再次强调,这是 需要审慎处理惩罚的内容。 调解互换链缓冲区的巨细,只有在正确举办时,才会是有效的操纵。 首先,要 使此操纵乐成,我需要确保已经释放了对这些缓冲区的所有引用。 这些可以是应用措施直接或间接持 有的引用。 在本例中,引用由 Direct2D 设备上下文持有。 方针图像是我建设用于包装互换链的靠山 缓冲区的 Direct2D 位图。 释放此项相当简朴:
m_target.SetTarget();
接下来可以挪用互换链的 ResizeBuffers 要领以执行所有沉重的任务,然后按照需要挪用应用措施 的设备资源处理惩罚措施。 图 4 显示了如何一起完成这些任务。
图 4 互换链巨细调解
#p#分页标题#e#
void ResizeSwapChainBitmap()
{
m_target.SetTarget();
if (S_OK == m_swapChain.ResizeBuffers())
{
CreateDeviceSwapChainBitmap();
CreateDeviceSizeResources();
}
else
{
ReleaseDevice();
}
}
此刻,您可以添加一些绘制呼吁,这些呼吁将由 DirectX 高效地泛起给 CoreWindow 的方针。 举 一个简朴例子,您大概但愿在 CreateDeviceResources 处理惩罚措施中建设一个纯色画笔,并将其分派到 成员变量,如下所示:
SolidColorBrush m_brush;
m_brush = m_target.CreateSolidColorBrush(Color(1.0f, 0.0f, 0.0f));
在窗口的 Draw 要领中,我首先利用白色来排除窗口的配景:
m_target.Clear(Color(1.0f, 1.0f, 1.0f));
然后,可以利用画笔绘制简朴的赤色矩形,如下所示:
RectF rect (100.0f, 100.0f, 200.0f, 200.0f);
m_target.DrawRectangle(rect, m_brush);
为了确保应用措施可以从设备丢失中正常规复,我必需确保应用措施在正确时间释放画笔:
void ReleaseDeviceResources()
{
m_brush.Reset();
}
这就是利用 DirectX 泛起基于 CoreWindow 的应用措施所要采纳的步调。 虽然,将这些内容与我 在 2013 年 5 月的专栏对比,您会惊喜地发明,得益于 dx.h 库,这些事情对比与 DirectX 相关的代 码编写已经简朴了很多。 不外实际上仍有大量的样板代码,主要与实现 COM 接口相关。 在此处可加 入 C++/CX,来简化应用措施中利用的 WinRT API。 它埋没了一部门样板 COM 代码,我在上两期专栏 中已经演示过。