本文章是由機器翻譯。

Windows 與 C++

適合 DirectX 程式設計的新式 C++ 程式庫

Kenny Kerr

下載代碼示例

Kenny Kerr我已經寫了很多 DirectX 代碼,我已經寫過 DirectX 的廣泛。我甚至所製作 DirectX 線上培訓課程。它真的不是像一些開發商把它寫成的那麼難。當然的學習曲線,但一旦你恢復了,是不難理解如何以及為什麼 DirectX 的工作方式就不會。仍然,我承認 DirectX 家庭的 Api 可能更便於使用。

幾天前,我決定要糾正此問題。我通宵未睡,寫了一個小小的標頭檔。幾天以後,它接近 5,000 的程式碼。我的目標是提供的東西,你可以使用來構建應用程式可以更輕鬆地 Direct2D 和挑戰所有的"c + + 很難"或"DirectX 是硬"的論據是如此普遍的今天。我不想產生 DirectX 的另一重包裝。相反,我決定利用 C + + 11 出示,以一種更簡單的 API 供 DirectX 沒有施加任何空間和時間無意中聽到了 DirectX API 的核心。你可以找到我開發了在此庫 dx.codeplex.com

庫本身完全由組成的單個標標頭檔在 CodePlex 上調用 dx.h—the 的原始程式碼,該代碼的其餘部分提供了使用它的例子。

在本專欄中,我就會顯示你如何使用圖書館要更輕鬆地執行各種常見的 DirectX 有關的活動。我還將介紹此庫的設計,讓你瞭解如何 C + + 11 可以説明您使經典 COM Api 而不是訴諸 Microsoft.NET 框架等高影響包裝更美味。

很明顯,重點是 Direct2D。它仍然是最簡單和最有效的方式來利用 DirectX 的最廣泛的類的應用程式和遊戲。許多開發人員似乎落入兩個對立的陣營。有的鐵杆的 DirectX 開發人員在各個版本的 DirectX API 的削減了他們的牙齒。他們多年的 DirectX 演變硬化,可與高酒吧的入口,一個俱樂部,很少開發人員可能會加入專屬俱樂部高興。在其他陣營是那些聽到的消息 DirectX 是硬的開發人員,不想再與它的事情。他們也往往拒絕作為理所當然的 c + +。

我不屬於這些營地之一。我相信 c + + 和 DirectX 不一定要硬。在上個月的列 (msdn.microsoft.com/magazine/dn198239),我介紹了 Direct2D 1.1 和必備 Direct3D 和 DirectX 圖形基礎設施 (DXGI) 的代碼來創建一個設備和管理交換鏈。為創建 Direct3D 設備用 D3D11CreateDevice 函數適合 GPU 或 CPU 渲染代碼來到大約 35 行代碼。然而,我小小的標頭檔的説明下,它相當於這:

auto device = CreateDevice();

CreateDevice 函數返回一個 device1 中的物件。 所有的 Direct3D 定義都是在 Direct3D 的命名空間,所以我可能會更加明確和寫它,如下所示:

Direct3D::Device1 device = Direct3D::CreateDevice();

Device1 中的物件是簡單包裝 ID3D11­device1 中 COM 介面指標,介紹了與 DirectX 11.1 發行的 Direct3D 設備介面。 在 device1 中的類派生自的設備類,反過來是原始的 ID3D11Device 介面的包裝。 它表示一個引用,並添加沒有額外的系統開銷相比,只堅持自己的介面指標。 請注意 device1 中和其父類的設備,是定期 c + + 類而不是介面。 你可以認為他們是智慧指標,但是那未免過於簡單化。 當然,他們處理的引用計數,並提供"->"運算子來直接調用該方法的選擇,但他們真的開始閃耀當你開始使用 dx.h 庫所提供的許多非虛方法。

以下就是範例。 通常,您可能需要在 Direct3D 設備 DXGI 介面要傳遞給一些其他方法或函數。 你能做到硬的方式:

auto device = Direct3D::CreateDevice();
wrl::ComPtr<IDXGIDevice2> dxdevice;
HR(device->QueryInterface(dxdevice.GetAddressOf()));

那當然有效,但現在您還必須直接處理 DXGI 設備介面。 您還需要記住的 IDXGIDevice2 介面是 DXGI 設備介面的 DirectX 11.1 版本。 相反,您可以只調用 AsDxgi 方法:

auto device = Direct3D::CreateDevice();
auto dxdevice = device.AsDxgi();

產生的 Device2 物件,在 Dxgi 的命名空間中定義這一次,包裝,IDXGIDevice2 COM 介面指標,提供其自己的非虛方法集。 作為另一個例子,你可能想要使用 DirectX"物件模型",使自己的 DXGI 工廠的方式:

auto device   = Direct3D::CreateDevice();
auto dxdevice = device.AsDxgi();
auto adapter  = dxdevice.GetAdapter();
auto factory  = adapter.GetParent();

當然,這是常見的 Direct3D 設備類提供的 GetDxgiFactory 方法作為一個方便的快捷方式:

auto d3device = Direct3D::CreateDevice();
auto dxfactory = d3device.GetDxgiFactory();

所以,除了幾個方便的方法和功能,如 GetDxgiFactory,非虛方法一對一地映射到底層的 DirectX 介面方法和函數。 這似乎並不多,但許多技術結合在一起以產生一個更方便、 更富有成效的程式設計模型的 DirectX。 第一是限定了作用域的枚舉的使用。 DirectX 家庭的 Api 定義了令人眼花繚亂的常量,其中許多都是傳統的枚舉、 標誌或常量。 他們不強型別的他們很難找到,和他們好不要玩Visual StudioIntelliSense。 工廠的選項忽略了一會兒,這裡的什麼你需要創建一個 Direct2D 工廠:

wrl::ComPtr<ID2D1Factory1> factory;
HR(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED,
                     factory.GetAddressOf()));

D2D1CreateFactory 函數的第一個參數是一個枚舉,但因為它不是一個限定了作用域的枚舉,很難發現與Visual StudioIntelliSense。 這些舊學校枚舉提供型別安全,但不太多。 充其量,你會領略到 E_INVALIDARG 結果代碼在運行時。 我不知道你的事,但我會在編譯時,而是捕捉這種錯誤或更好的是,完全避免:

auto factory = CreateFactory(FactoryType::MultiThreaded);

再次,我不需要花時間翻找出什麼叫 Direct2D 工廠介面的最新版本。 當然,在這裡最大的好處是生產力。 當然,DirectX API 是有關多個創建和調用 COM 介面方法。 許多平原舊資料結構,用於捆綁各種屬性和參數。 交換鏈的描述是一個很好的例子。 同其所有成員令人困惑,我永遠都不記得這種結構應準備方式,更不用提平臺細節。 在這裡,再次,圖書館進來方便通過提供的令人生畏的 DXGI_SWAP_CHAIN_DESC1 結構的替代品:

SwapChainDescription1 description;

在這種情況下,提供二進位相容替換,確保 DirectX 看到相同的類型,但你可以用更多一點實際的東西。 這不是不同于 Microsoft.NET 框架提供的 P/Invoke 包裝。 預設的建構函式為大多數桌面和 Windows 應用商店的應用程式提供適當的預設值。 例如,您可能想在來重寫此為桌面應用程式以產生更平滑渲染時調整大小:

SwapChainDescription1 description;
description.SwapEffect = SwapEffect::Discard;

此交換效果,順便說一句,也是需要時針對 Windows Phone 8,但它不允許在 Windows 應用商店的應用程式。 自己去琢磨吧!

最好的圖書館的許多導致你工作解決方案快速、 輕鬆地。 讓我們考慮一個具體的例子。 Direct2D 提供一個線性漸層畫筆。 創建這種畫筆涉及三個邏輯步驟:定義漸變停止點,創建漸變停止點集合,然後創建線性漸層畫筆給該集合。 圖 1 顯示這可能看起來像直接使用 Direct2D API。

圖 1 創建一個線性漸層畫筆硬的

D2D1_GRADIENT_STOP stops[] =
{
  { 0.0f, COLOR_WHITE },
  { 1.0f, COLOR_BLUE },
};
wrl::ComPtr<ID2D1GradientStopCollection> collection;
HR(target->CreateGradientStopCollection(stops,
   _countof(stops),
   collection.GetAddressOf()));
wrl::ComPtr<ID2D1LinearGradientBrush> brush;
HR(target->CreateLinearGradientBrush(D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES(),
   collection.Get(),
   brush.GetAddressOf()));

Dx.h 的説明它成為一堆更直觀:

GradientStop stops[] =
{
  GradientStop(0.0f, COLOR_WHITE),
  GradientStop(1.0f, COLOR_BLUE),
};
auto collection = target.CreateGradientStopCollection(stops);
auto brush = target.CreateLinearGradientBrush(collection);

雖然這不是大大短于中的代碼圖 1、 當然容易寫,和你搞錯第一次,尤其是與IntelliSense的説明下不太可能。 圖書館使用各種技術來生成一個更令人愉快的程式設計模型。 在這種情況下,CreateGradientStopCollection 方法被重載與函數範本,在編譯時推斷 GradientStop 陣列的大小,所以您不需要使用 _countof 宏。

錯誤處理呢? 嗯,生產這種簡潔的程式設計模型的前提條件之一是通過的誤差傳播的例外情況。 考慮我之前提到的 Direct3D 設備 AsDxgi 方法的定義:

inline auto Device::AsDxgi() const -> Dxgi::Device2
{
  Dxgi::Device2 result;
  HR(m_ptr.CopyTo(result.GetAddressOf()));
  return result;
}

這是一個非常常見的模式在庫中。 首先,請注意該方法是 const。 幾乎所有的庫中的方法都是 const,作為唯一的資料成員是基礎 ComPtr 並且有無需修改它。 在方法體中,您可以看到所產生的設備物件如何來到生活。 所有庫的類提供了移動語義,所以即使這可能似乎執行數量的副本 — — 由此推論,AddRef/釋放對的數目 — — 事實上是沒有這發生在運行時。 在中間包裝此運算式的 HR 是內聯函數,如果結果不是則返回 S_OK,將引發異常。 最後,圖書館將始終嘗試返回的最特定的類,以避免調用方無需執行額外的要求,向 QueryInterface,暴露更多的功能。

前面的示例中使用的 ComPtr CopyTo 方法,有效地只是調用 QueryInterface。 這裡是從 Direct2D 的另一個示例:

inline auto BitmapBrush::GetBitmap() const -> Bitmap
{
  Bitmap result;
  (*this)->GetBitmap(result.GetAddressOf());
  return result;
}

這是稍有不同,因為直接調用基礎 COM 介面上的一種方法。 這種模式其實是什麼構成的庫代碼的大部分。 在這裡我回的畫筆繪製的點陣圖。 許多 Direct2D 方法返回 void,正如在這裡,所以無需為人力資源職能檢查結果。 GetBitmap 方法所導致的間接定址可能不那麼明顯,但是。

正樣機早期版本的庫,我不得不之間作出選擇,編譯器,玩弄和演奏技巧與 com。 涉及我早企圖玩弄把戲與 c + + 使用的範本,具體鍵入性狀,但也編譯器類型性狀 (也稱為內部類型特徵)。 這是因為好玩,但它很快變得明顯我正在為自己取得更多的工作。

你看,圖書館模型"是一個"具體的類作為 COM 介面之間的關係。 COM 介面僅可直接從一個其他介面繼承。 除了本身的 IUnknown,每個 COM 介面必須從一個其他介面直接繼承。 最終,這導致所有路回 IUnknown 類型層次結構中向上。 我開始通過定義一個類的每個 COM 介面。 呈現器目標類包含一個 ID2D1RenderTarget 介面指標。 DeviceCoNtext 類包含一個 ID2D1DeviceCoNtext 介面指標。 直到您想要將 DeviceCoNtext 視為呈現器目標,這似乎不夠合理。 畢竟,ID2D1DeviceCoNtext 介面派生自的 ID2D1RenderTarget 介面。 它是相當合理的有 DeviceCoNtext 和想要將它傳遞給需要呈現器目標作為傳址參數的方法的。

不幸的是,c + + 類型系統不會看到這一點。 使用這種方法,DeviceCoNtext 不能實際派生自呈現器目標否則它將舉行兩個引用。 我試著移動語義和內部類型特徵來正確地根據需要左右移動引用的組合。 它幾乎奏效了,但有一個額外的 AddRef/釋放對被介紹了的情況。 最終,它證明了太複雜,並需要一個更簡單的解決方案。

與 c + +,不同的是 COM 有一個非常明確的二進位合同。 這就是,畢竟,COM 是什麼所有有關。 只要你堅持商定的規則,COM 不會讓你失望。 你可以玩了 COM,這麼說,並使用 c + + 到你的優勢,而不是與它作鬥爭。 這意味著每個 c + + 類不能保持一個強型別的 COM 介面指標,但只是一般性地提到 IUnknown。 C + + 中添加型別安全回來,和其規則類的繼承 — — 和最近,移動語義 — — 讓我再一次治療這些 COM"物件"作為 c + + 類。 從概念上講,我開始用這個:

class RenderTarget { ComPtr<ID2D1RenderTarget> ptr; };
class DeviceContext { ComPtr<ID2D1DeviceContext> ptr; };

面對這:

class Object { ComPtr<IUnknown> ptr; };
class RenderTarget : public Object {};
class DeviceContext : public RenderTarget {};

COM 介面和它們之間的關係所隱含的邏輯層次結構現在一個 c + + 物件模型所體現的作為一個整體的程式設計模型是遠更自然和便於使用。 有很多對它,和我鼓勵你去看看原始程式碼,該代碼更密切。 寫這篇文章,它涵蓋了幾乎所有的 Direct2D 和 Windows 動畫管理員,以及有用的 DirectWrite、 Windows 圖像元件 (WIC)、 Direct3D 和 DXGI 塊。 此外,我定期添加更多的功能,所以請定期檢查。 好好享受吧!

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

由於以下的技術專家對本文的審閱: Worachai Chaoweeraprasit (Microsoft)
Worachai Chaoweeraprasit (Microsoft) wchao@microsoft.com

Worachai Chaoweeraprasitis Direct2D 和 DirectWrite 的發展領導。 他是文本的迷戀速度和品質的 2D 向量圖形以及螢幕上的可讀性。 在他的業餘時間他喜歡被壟斷了,在家裡的兩個小小的人。