本文章是由機器翻譯。

Windows 與 C++

轉譯 Windows 執行階段

Kenny Kerr

Kenny Kerr我的最後一列,審查了 Windows 運行庫 (WinRT) 應用程式模型 (msdn.microsoft.com/magazine/dn342867)。我教你如何寫一個 Windows 存儲區或 Windows Phone 的應用程式與標準 c + + 和經典 COM,使用只有少量的 WinRT API 函數。當然沒有任何規定要求您必須使用一個語言投影等 C + + / CX 或 C#。能到周圍這些抽象的步驟是一個強大的功能,是偉大的方式來瞭解這種技術的工作原理。

我的 2013 年 5 月專欄介紹了 Direct2D 1.1,並向您展示如何使用它在桌面應用程式中的呈現 (msdn.microsoft.com/magazine/dn198239)。下一列仲介紹了 dx.h 庫 — — 可從 dx.codeplex.com— — 這極大地簡化了 c + + 中的 DirectX 程式設計 (msdn.microsoft.com/magazine/dn201741)。

我的最後一列中的代碼是足夠的把基於 CoreWindow 的 app 的生活,但它不提供任何呈現。

這個月,我會教你怎麼把這個基本的骨架和添加對呈現的支援。WinRT 應用程式模型的渲染與 DirectX 優化。我會教你怎麼把你學到了在我以前的專欄關於 Direct2D 和 Direct3D 渲染的和將其應用到您的基於 CoreWindow 的 WinRT 應用程式 — — 具體地使用 Direct2D 1.1,通過 dx.h 圖書館。大多數情況下,你需要寫的實際 Direct2D 和 Direct3D 繪圖命令是無論你目標的就在桌面上或 Windows 運行時相同的。然而,有一些微小的差異,並且放在第一位當然獲取它所有掛鉤是相當不同。於是我拿起停下來最後一次的地方向您展示如何在螢幕上得到一些圖元 !

為了支援正確,呈現視窗必須知道的某些事件。至少,這些包括更改視窗的可見度和大小,以及對使用者所選的邏輯顯示 DPI 配置的更改。與活性炭事件我涵蓋最後一次,這些新事件都報告給應用程式通過 COM 介面回檔。ICoreWindow 介面提供的方法註冊的 VisibilityChanged 和 SizeChanged 事件,但首先我需要實現各自的處理常式。我需要實現的兩個 COM 介面非常象被啟動事件處理常式用其 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 方法的執行。 作為一個提醒,負載是哪裡你應該堅持任何和所有的代碼,您的應用程式準備初步的演示文稿。 我就可以註冊的 VisibilityChanged 和 SizeChanged 的事件裡面,Load 方法為:

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);

成員變數保存到此介面指標,因為我需要在視窗的生命週期期間致電各點。 現在,我只是可以註冊的載入方法內的 LogicalDpiChanged 事件:

HR(m_displayProperties->add_LogicalDpiChanged(this, &token));

我會回到一會兒這些三個介面的實現。 現在是時候準備的 DirectX 基礎設施。 我要去需要設備資源處理常式,我在以前的專欄中討論過無數次的一組標準:

void CreateDeviceIndependentResources() {}
void CreateDeviceSizeResources() {}
void CreateDeviceResources() {}
void ReleaseDeviceResources() {}

第一是我可以在其中創建或載入不是特定于基礎的 Direct3D 渲染設備的任何資源。 接下來的兩是用於創建特定于設備的資源。 它最好單獨的資源從那些不是具體到視窗的大小。 最後,必須釋放所有的設備資源。 剩餘的 DirectX 基礎設施依賴于執行這四種方法正確基於應用程式的特定需要的應用程式。 它提供了在應用程式中為我管理呈現資源和高效地創建和這些資源的回收利用的離散點。

現在我可以讓 dx.h 來照顧所有的 DirectX 重活:

#include "dx.h"

每個 Direct2D 應用程式開始與 Direct2D 工廠:

Factory1 m_factory;

您可以找到這在 Direct2D 命名空間,並如下通常包括它:

using namespace KennyKerr;
using namespace KennyKerr::Direct2D;

Dx.h 庫都有單獨的命名空間為 Direct2D,直接­編寫,Direct3D,Microsoft DirectX 圖形基礎設施 (DXGI),等等。 我的應用程式的大部分使用 Direct2D 沉重,所以這對我來說是有道理。 你當然可以,管理中任何的方式使您的應用程式有意義的命名空間。

M_factory 成員變數表示 Direct2D 1.1 工廠。 它用於創建呈現目標,以及各種其他所需的獨立于設備資源。 我會創建 Direct2D 工廠,然後允許任何要作為 Load 方法的最後一步創建的獨立于設備資源:

m_factory = CreateFactory();
CreateDeviceIndependentResources();

Load 方法返回後,在 WinRT CoreApplication 類立即調用 IFrameworkView Run 方法。

我簡單地阻止通過對 CoreWindow 調度程式調用 ProcessEvents 方法的最後一列從 SampleWindow 運行方法的實現。 以這種方式阻止是足夠的如果您的應用程式只會執行基於各種事件很少發生呈現。 您可能正在推行一個遊戲或只是需要一些高解析度的動畫,為您的應用程式。 另一個極端是使用連續動畫的迴圈,但也許你想要什麼有點更智慧化。 我要去實現的折衷辦法,這兩個職位的東西。 首先,我將添加一個成員變數來跟蹤的視窗是否可見。 這會讓我當視窗不是物理上使用者可見的油門呈現:

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;
}

和以前一樣,運行方法檢索 CoreWindow 調度員。 它然後進入一個無限迴圈,不斷地呈現和處理任何視窗消息 (稱為"事件"由 Windows 運行時),可能會在佇列中。 如果,然而,視窗不可見,它將阻塞直到有消息到達。 這款應用程式如何知道該視窗的可見度更改時? IVisibilityChangedEventHandler 介面就用用來解決這個問題。 我現在可以實現其調用方法來更新的 m_visible 成員變數:

auto __stdcall Invoke(ICoreWindow *,
  IVisibilityChangedEventArgs * args) -> HRESULT override
{
  unsigned char visible;
  HR(args->get_Visible(&visible));
  m_visible = 0 != visible;
  return S_OK;
}

MIDL 生成的介面使用無符號的 char 作為可擕式布林資料類型。 我現在就可以使用所提供的 IVisibilityChangedEventArgs 介面指標的視窗的當前可見度並相應地更新的成員變數。 視窗是顯示或隱藏和有點簡單比執行這對於桌面應用程式,它負責處理的幾個方案包括 app 關機和電源管理,更不用提切換視窗時,將引發此事件。

接下來,我需要實現 Render 方法調用 Run 方法中的圖 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 設備上下文,實際上越來越呈現到螢幕上的圖元涉及提出交換鏈。 說到這,我需要添加 dx.h 類型表示的 Direct2D 1.1 設備上下文,以及交換鏈的 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。 它從開始創建 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 結構,在為我。 要創建一個將代表交換鏈的背景緩衝區的 Direct2D 點陣圖的 CreateDeviceSwapChainBitmap 方法,我們然後會呼籲:

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 常數指示該點陣圖將被用作設備上下文的目標。 點陣圖­Options::CannotDraw 常量是關乎交換鏈的回的事實作為輸出,而不是其他的繪圖操作的輸入,可以只使用緩衝區。 PixelFormat 是 Direct2D D2D1_PIXEL_FORMAT 結構的 dx.h 版本。

點陣圖屬性定義、 CreateBitmapFromDxgiSurface 方法檢索交換鏈的背景緩衝區,並創建一個 Direct2D 點陣圖來表示它。 這種方式,Direct2D 設備上下文可以簡單地通過針對通過 SetTarget 方法點陣圖呈現直接到交換鏈。

回來的 Render 方法中,我只被要告訴 Direct2D 如何擴大規模根據該使用者的 DPI 配置任何繪圖命令:

float dpi;
HR(m_displayProperties->get_LogicalDpi(&dpi));
m_target.SetDpi(dpi);

我然後會喊到應用程式的設備資源的處理常式來創建所需的任何資源。 若要匯總, 圖 3 提供完整的設備初始化序列的渲染方法。

圖 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 介面的進來。 現在,我可以簡單地實現其調用方法並相應地更新設備。 這裡是 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。 然後調用到 app 重新創建任何設備-大小-­前重新呈現的特定資源。 這種方式,我的應用程式可以動態地回應中,顯示器的 DPI 配置的更改。 最後更改視窗必須動態地處理是對視窗的大小的更改。 我已經過釣事件註冊,所以我只是需要添加的 IWindowSizeChangedEventHandler Invoke 方法執行代表的 SizeChanged 事件處理常式:

auto __stdcall Invoke(ICoreWindow *,
  IWindowSizeChangedEventArgs *) -> HRESULT override
{
  if (m_target)
  {
    ResizeSwapChainBitmap();
    Render();
  }
  return S_OK;
}

剩下要做的唯一事情是調整通過 ResizeSwapChainBitmap 方法交換鏈點陣圖的大小。 再次,這是需要,必須謹慎處理的東西。 調整大小交換鏈緩衝區可以並應有效運作,但是,只有當正確進行。 第一,為了使此操作才能成功,我需要確保所有對這些緩衝區的引用也被釋放。 這些可能是 app 直接和間接持有的引用。 在這種情況下,引用是舉行由 Direct2D 設備上下文。 靶心圖表像是 Direct2D 點陣圖創建包交換鏈的背景緩衝區。 釋放這非常簡單:

m_target.SetTarget();

我可以然後調用交換鏈的 ResizeBuffers 方法來做所有的重物,然後到應用程式的設備資源處理常式根據需要請。 圖 4 向您顯示如何這彙集一起。

圖 4 調整大小交換鏈

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));

在視窗的繪製方法,內部就能通過清除視窗的背景用白色:

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 月的專欄,你應該感到驚喜地驚訝的如何變得更加簡單,DirectX 相關代碼工作要感謝 dx.h 圖書館。 但由於它站立,仍有大量的樣板代碼,主要涉及到實現的 COM 介面。 這是在 C + + / CX 進來,並簡化了在您的應用程式內部的 WinRT Api 使用。 它會隱藏一些樣板檔我已經給你們這些最後兩列中的 COM 代碼。

Kenny Kerr 是設在加拿大的一位作者為 Pluralsight 和微軟最有價值球員一個電腦程式員。在他博客 kennykerr.ca 你可以跟著他在 Twitter 上 twitter.com/kennykerr