Windows 合成エンジンを使った高パフォーマンスのレイヤード ウィンドウ
Windows XP で初めて目にしてからレイヤード ウィンドウに魅了されています。レイヤード ウィンドウにより、デスクトップ ウィンドウは四角形 (ほぼ四角形) であるという従来の考え方から離れられることにいつも大きな魅力を感じていました。そして、Windows Vista の登場です。この Windows のリリースの評判は最悪でしたが、いずれ胸躍るような使いやすい機能が登場するかもしれないことを予感させるものでした。Windows Vista で始まったことは Windows 8 が登場してようやく評価されるようになりましたが、レイヤード ウィンドウが除々に衰退する契機にもなったともいえます。
Windows Vista では、デスクトップ ウィンドウ マネージャーというサービスが導入されました。この名前は、当時から現在まで誤解を招き続けています。デスクトップ ウィンドウ マネージャーは、Windows 合成エンジン (合成ツール) と考えてください。この合成エンジンにより、アプリケーション ウィンドウをデスクトップに表示する方法がまったく変わりました。各ウィンドウを直接ディスプレイ (ディスプレイ アダプター) にレンダリングするのではなく、オフスクリーン サーフェス (バッファー) にレンダリングします。システムは、このオフスクリーン サーフェイスをトップレベル ウィンドウ 1 つあたり 1 つずつ割り当てます。すべての GDI グラフィックス、Direct3D グラフィックス、Direct2D グラフィックスはこのサーフェスにレンダリングします。このオフスクリーン サーフェスは、リダイレクト サーフェスとも呼ばれます。これは、GDI 描画コマンドだけでなく、Direct3D スワップ チェーンの表示要求も、このサーフェスに (GPU 内で) リダイレクトまたはコピーされるためです。
合成エンジンは、ある時点で、任意の特定のウィンドウとは無関係に、最新の変更バッチでデスクトップを合成するタイミングを決めます。このとき、これらのリダイレクト サーフェスをすべて合成し、非クライアント領域 (通常ウィンドウ クロムと呼ぶ) を追加し、場合によっては陰影などの効果を施し、最終結果をディスプレイ アダプターに表示します。
この合成処理には多くのすばらしいメリットがあります。このメリットを今後数か月にわたって Windows 合成について詳しく説明していく中で紹介していきます。ただし、大きな制限事項も 1 つあります。それは、リダイレクト サーフェスが不透明だということです。大半のアプリケーションではまったく問題にならず、アルファ ブレンドの高い負荷を考えれば、パフォーマンスの点からも非常に理にかなっていることは間違いありません。しかし、その結果、レイヤード ウィンドウが主役の座を去ることになります。
レイヤード ウィンドウを使用するのであれば、パフォーマンスの低下を受け入れなければなりません。具体的な設計上の制限事項については、以前のコラム「Direct2D によるレイヤード ウィンドウ」(msdn.microsoft.com/magazine/ee819134) で説明しました。簡単にまとめておくと、CPU がレイヤード ウィンドウを処理し、主にアルファ ブレンドが行われたピクセルのヒット テストに対応します。つまり、CPU にはレイヤード ウィンドウのサーフェス領域を構成するピクセルのコピーが必要です。CPU でレンダリングする場合は、通常 GPU レンダリングよりも大幅に時間がかかり、GPU でレンダリングする場合は、レンダリング対象をすべてビデオ メモリからシステム メモリにコピーする必要があるため、バスの帯域幅を消費することになります。先ほど示したコラムでは、Direct2D を最大限に活用してシステムで最高のパフォーマンスを引き出す方法を説明しています。これは、CPU レンダリングと GPU レンダリングのいずれかを選択できるのは Direct2D のみだからです。レイヤード ウィンドウが必ずシステム メモリに常駐するとしても、合成エンジンがこれをすぐにビデオ メモリにコピーし、レイヤード ウィンドウの実際の合成を引き続きハードウェアが主導するようにすることが重要です。
従来のレイヤード ウィンドウが再び勢いを取り戻すとは思えませんが、明るい兆しもいくつかあります。従来のレイヤード ウィンドウには、2 つの興味深い機能があります。1 つは、ピクセル単位のアルファ ブレンドです。レイヤード ウィンドウにレンダリングしたものはすべて、デスクトップやその時点のウィンドウの背景とアルファ ブレンドされます。もう 1 つは、User32 がピクセルのアルファ値に基づきレイヤード ウィンドウでのヒット テストを可能にします。これにより、特定の点のピクセルが透明であれば、マウス メッセージが発生しなくなります。Windows 8 でも Windows 8.1 でも User32 はあまり変わっていません。ただし、ピクセル単位のアルファ ブレンドがすべて GPU でサポートされるようになり、ウィンドウ サーフェスをシステム メモリに転送するコストが必要なくなりました。つまり、ピクセル単位のヒット テストを必要としないという条件を付ければ、パフォーマンスを犠牲にすることなく、レイヤード ウィンドウの効果を発揮できるようになります。ヒット テストは、ウィンドウ全体で一様に行われます。ヒット テストを犠牲にすれば、当然システムの負荷が下がるので魅力的ではありますが、アプリケーションでヒット テストを可能にすることは考えたことがありませんでした。ここからは、この方法について説明します。
これを実現するためには、Windows 合成エンジンを使用する必要があります。合成エンジンは、当初 Windows Vista でデスクトップ ウィンドウ マネージャーとして登場しました。この時点では、API に制限があり、有名な Aero グラス効果を提供しました。Windows 8 の登場と共に DirectComposition API が導入されました。これは、同じ合成エンジンの API を単に拡張したものです。合成エンジンが登場してから随分時間が経ちましたが、Windows 8 のリリースと共にようやくマイクロソフトはサードパーティ開発者にこの合成エンジンの使用を許可しました。当然、Direct2D などの Direct3D 対応のグラフィックス API を利用する必要が出てきますが、まず不透明のリダイレクト サーフェスに取り組む必要があります。
既に述べましたが、システムはトップレベル ウィンドウ 1 つあたり 1 つリダイレクト サーフェスを割り当てます。Windows 8 では、トップレベル ウィンドウを作成できるうえ、そのトップレベル ウィンドウをリダイレクト サーフェスなしで作成するよう要求できるようになりました。厳密に言うと、レイヤード ウィンドウとは無関係なので、WS_EX_LAYERED 拡張ウィンドウ スタイルを使用しません (実際にはレイヤード ウィンドウのサポートは Windows 8 で少し機能強化されていますが、これについては次回のコラムで詳しく取り上げます)。代わりに、ウィンドウ用にリダイレクト サーフェスを割り当てないように合成エンジンに指示する WS_EX_NOREDIRECTIONBITMAP 拡張ウィンドウ スタイルを使用します。まずは、従来のシンプルなデスクトップ ウィンドウから着手します。図 1 に、WNDCLASS 構造体の設定、ウィンドウ クラスの登録、ウィンドウの作成、およびウィンドウ メッセージの表示を行う例を示します。目新しいものはありませんが、これらの基本が重要であることには変わりありません。ウィンドウ変数はまだ使用していませんが、すぐに必要になります。このコードを Visual Studio の Visual C++ プロジェクトにコピーするか、単に次のようにコマンド プロンプトからコンパイルします。
cl /W4 /nologo Sample.cpp
図 1 従来のウィンドウの作成
#ifndef UNICODE
#define UNICODE
#endif
#include <windows.h>
#pragma comment(lib, "user32.lib")
int __stdcall wWinMain(HINSTANCE module, HINSTANCE, PWSTR, int)
{
WNDCLASS wc = {};
wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
wc.hInstance = module;
wc.lpszClassName = L"window";
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc =
[] (HWND window, UINT message, WPARAM wparam,
LPARAM lparam) -> LRESULT
{
if (WM_DESTROY == message)
{
PostQuitMessage(0);
return 0;
}
return DefWindowProc(window, message, wparam, lparam);
};
RegisterClass(&wc);
HWND const window = CreateWindow(wc.lpszClassName, L"Sample",
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
nullptr, nullptr, module, nullptr);
MSG message;
while (BOOL result = GetMessage(&message, 0, 0, 0))
{
if (-1 != result) DispatchMessage(&message);
}
}
これをデスクトップで表示すると図 2 のようになります。特に目をひくものはありません。この例では塗りつぶしとレンダリングのコマンドを指定していませんが、ウィンドウのクライアント領域は不透明になり、合成エンジンが非クライアント領域、境界線、およびタイトル バーを追加します。WS_EX_NOREDIRECTIONBITMAP 拡張ウィンドウ スタイルを適用して不透明なリダイレクト サーフェス (クライアント領域) を取り除くには、CreateWindow 関数を CreateWindowEx 関数に置き換え、最初のパラメーターに拡張ウィンドウ スタイルを渡すだけです。
HWND const window = CreateWindowEx(WS_EX_NOREDIRECTIONBITMAP,
wc.lpszClassName, L"Sample",
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
nullptr, nullptr, module, nullptr);
図 2 デスクトップに表示された従来のウィンドウ
変更点は、最初の引数として WS_EX_NOREDIRECTIONBITMAP 拡張ウィンドウ スタイルを追加し、CreateWindow 関数の代わりに CreateWindowEx 関数を使用しただけです。しかし、結果は大きく異なり、デスクトップ上にウィンドウが図 3 に示すように表示されます。ウィンドウのクライアント領域が完全に透明になります。ウィンドウを移動してみるとよくわかります。背景でビデオを再生することさえ可能で、ビデオがウィンドウに遮られることは一切ありません。一方、ヒット テストはウィンドウ全体で一様に行われるため、クライアント領域内をクリックしてもウィンドウのフォーカスは失われません。つまり、ヒット テストとマウス入力を処理するサブシステムは、クライアント領域が透明であることを認識していません。
図 3 リダイレクト サーフェスを使用しないウィンドウ
「合成エンジンに提供するリダイレクト サーフェスが存在しないのに、どのようにしてウィンドウにレンダリングできるのだろうか」という疑問が当然浮かびます。答えは DirectComposition API と、DirectX Graphics Infrastructure (DXGI) との密接な統合にあります。Windows 8.1 XAML の実装を可能にして XAML アプリケーション内で非常に高パフォーマンスのコンテンツ合成を提供するのと同じテクニックです。Internet Explorer Trident レンダリング エンジンは、タッチ操作によるパンとズームに加え、CSS3 アニメーション、移動、および変形に DirectComposition を幅広く使用します。
今回は DirectComposition を使用して、ピクセル単位にプリマルチプライ済みアルファ値を使って透明性をサポートするスワップ チェーンを構成し、デスクトップの残りの部分と合成します。従来の DirectX アプリケーションは、通常 DXGI ファクトリの CreateSwapChainForHwnd メソッドを使ってスワップ チェーンを作成します。このスワップ チェーンは、バッファーのペアまたはコレクションを表示中に効率よく切り替えるという考えに基づき、その結果、アプリケーションは前のフレームをコピーしながら次のフレームをレンダリングできるようになります。アプリケーションがレンダリングするスワップ チェーン サーフェスは、不透明なオフスクリーン バッファーです。アプリケーションがスワップ チェーンを表示する際、DirectX はスワップ チェーンの基になるバッファーのコンテンツをウィンドウのリダイレクト サーフェスにコピーします。前述のように、合成エンジンは最終的にすべてのリダイレクト サーフェスを合成し、デスクトップ全体を作成します。
今回の場合、ウィンドウにはリダイレクト サーフェスがないため、DXGI ファクトリの CreateSwapChainForHwnd メソッドは使用できません。ただし、Direct3D と Direct2D のレンダリングをサポートするためには、依然としてスワップ チェーンが必要です。DXGI ファクトリに CreateSwapChainForComposition メソッドがあるのはこのためです。このメソッドを使って、バッファーと共にウィンドウのないスワップ チェーンを作成できます。ただし、このスワップ チェーンを表示してもビットを (存在しない) リダイレクト サーフェスにコピーすることはなく、直接合成エンジンが利用できるようにします。これで、合成エンジンはこのサーフェスを受け取って、ウィンドウのリダイレクト サーフェスの代わりに直接使用できるようになります。このサーフェスは不透明ではなく、ピクセル単位のプリマルチプライ済みアルファ値を完全にサポートするピクセル形式なので、デスクトップ上でピクセルにとって完璧なアルファ ブレンドが行われます。また、GPU 内で不要なコピーを行うことがなく、バス経由でシステム メモリにコピーすることもないため、著しく高速になります。
以上が理論です。では、実際にやってみましょう。DirectX には COM が必須なので、インターフェイスのポインターを管理するため、Windows ランタイム C++ テンプレート ライブラリ ComPtr クラス テンプレートを使用します。DXGI、Direct3D、Direct2D、および DirectComposition の各 API をインクルードしてリンクすることも必要です。以下のコードは、これを行う方法を示します。
#include <wrl.h>
using namespace Microsoft::WRL;
#include <dxgi1_3.h>
#include <d3d11_2.h>
#include <d2d1_2.h>
#include <d2d1_2helper.h>
#include <dcomp.h>
#pragma comment(lib, "dxgi")
#pragma comment(lib, "d3d11")
#pragma comment(lib, "d2d1")
#pragma comment(lib, "dcomp")
通常これらはプリコンパイル済みヘッダーでインクルードします。今回の場合、ディレクティブの使用を省略し、アプリケーションのソース ファイルでインクルードするだけにします。
エラー処理が多くなると趣旨がわかりにくくなるので、例外クラスとエラーをチェックする HR 関数を使ってエラー処理をまとめます。図 4 はこのシンプルな実装ですが、エラー処理のポリシーを独自に決めてかまいません。
図 4 HRESULT エラーの例外への変換
struct ComException
{
HRESULT result;
ComException(HRESULT const value) :
result(value)
{}
};
void HR(HRESULT const result)
{
if (S_OK != result)
{
throw ComException(result);
}
}
ここからは、レンダリング スタックの組み立てを開始します。これに着手するには Direct3D デバイスが適しています。DirectX インフラストラクチャについては、2013 年 3 月号のコラム「Direct2D 1.1 の概要」(msdn.microsoft.com/magazine/dn198239、英語) で詳しく説明したため、ここでは簡単に触れるだけにとどめます。以下はDirect3D 11 インターフェイス ポインターです。
ComPtr<ID3D11Device> direct3dDevice;
これは Direct3D デバイス用のインターフェイス ポインターで、Direct3D デバイスの作成には D3D11CreateDevice 関数を使用します。
HR(D3D11CreateDevice(nullptr, // Adapter
D3D_DRIVER_TYPE_HARDWARE,
nullptr, // Module
D3D11_CREATE_DEVICE_BGRA_SUPPORT,
nullptr, 0, // Highest available feature level
D3D11_SDK_VERSION,
&direct3dDevice,
nullptr, // Actual feature level
nullptr)); // Device context
特に目をひくものはありません。GPU を利用する Direct3D デバイスを作成します。D3D11_CREATE_DEVICE_BGRA_SUPPORT フラグによって、Direct2D との相互運用性を確保します。DirectX シリーズは、さまざまな DirectX API に共通の GPU リソース管理機能を提供する DXGI によって結び付けられています。そのため、Direct3D デバイスに DXGI インターフェイスを照会する必要があります。
ComPtr<IDXGIDevice> dxgiDevice;
HR(direct3dDevice.As(&dxgiDevice));
ComPtr As メソッドは、QueryInterface メソッドのラッパーにすぎません。Direct3D デバイスを作成したので、次に合成に使用するスワップ チェーンを作成します。これを行うため、まず DXGI ファクトリを理解する必要があります。
ComPtr<IDXGIFactory2> dxFactory;
HR(CreateDXGIFactory2(
DXGI_CREATE_FACTORY_DEBUG,
__uuidof(dxFactory),
reinterpret_cast<void **>(dxFactory.GetAddressOf())));
ここで、開発中に貴重な助けとなる追加のデバッグ情報を選択します。スワップ チェーンの作成で最も難しいところは、必要なスワップ チェーンを DXGI ファクトリに対して記述する方法を考えることです。このデバッグ情報は、必要な DXGI_SWAP_CHAIN_DESC1 構造体を微調整するのに大いに役立ちます。
DXGI_SWAP_CHAIN_DESC1 description = {};
これは、構造体をすべてゼロに初期化します。次に、必要なプロパティをすべて設定します。
description.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
description.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
description.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
description.BufferCount = 2;
description.SampleDesc.Count = 1;
description.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED;
ここでは、プリマルチプライ済みの 8 ビットのアルファ コンポーネントを伴い、各カラー チャネルに 8 ビットずつ割り当てられた 32 ビットのピクセル形式を使用します。他の形式を利用できないわけではありませんが、デバイスと API とは無関係に最高のパフォーマンスと互換性を得るにはこれが一番です。
レンダリング ターゲットが出力先にできるように、スワップ チェーンのバッファーの用途を設定する必要があります。これは、Direct2D デバイス コンテキストがビットマップを作成して描画コマンドで DXGI サーフェスをターゲットにできるようにするために必須です。Direct2D ビットマップ自体は、スワップ チェーンで利用される抽象化にすぎません。
合成スワップ チェーンは、フリップシーケンシャルなスワップ効果にのみ対応します。このようにして、スワップ チェーンをリダイレクト サーフェスではなく合成エンジンに関連付けます。フリップ モデルでは、すべてのバッファーが合成エンジンと直接共有されます。これにより、合成エンジンは余分なコピーを行わずに、スワップ チェーンのバック バッファーから直接デスクトップを合成できます。通常、これが最も効率的な方法です。合成にも必要なので、今回もこの方法を使用します。フリップ モデルには少なくとも 2 つのバッファーが必要になりますが、マルチサンプリングには対応しないので、BufferCount を 2 に、SampleDesc.Count を 1 に設定します。この数は、 1 ピクセルあたりのマルチサンプル数です。これを 1 に設定すると、マルチサンプリングが無効になります。
最後に重要なのが、アルファ モードです。これは通常、不透明なスワップ チェーンでは無視されますが、今回は透明にする処理を含めます。プリマルチプライ済みアルファ値が通常最高のパフォーマンスを提供するため、フリップ モデルではこれ以外の方法はサポートされません。
スワップ チェーンを作成できるようにする最後の要素は、バッファーに必要なサイズを判断することです。通常、CreateSwapChainForHwnd メソッドを呼び出す際サイズを無視することができ、そうすると DXGI ファクトリがウィンドウにクライアント領域のサイズを問い合わせます。今回の場合、DXGI はスワップ チェーンを使って行うことを把握していないため、必要なサイズを具体的に指示する必要があります。ウィンドウを作成したので、ウィンドウのクライアント領域を問い合わせ、それに合わせてスワップ チェーンの記述を更新するのは簡単です。
RECT rect = {};
GetClientRect(window, &rect);
description.Width = rect.right - rect.left;
description.Height = rect.bottom - rect.top;
これで、この記述を含む合成スワップ チェーンと、Direct3D デバイスへのポインターを作成できます。Direct3D または DXGI のいずれかのインターフェイス ポインターを使用できます。
ComPtr<IDXGISwapChain1> swapChain;
HR(dxFactory->CreateSwapChainForComposition(dxgiDevice.Get(),
&description,
nullptr, // Don’t restrict
swapChain.GetAddressOf()));
これでスワップ チェーンが作成され、任意の Direct3D か Direct2D のグラフィックス レンダリング コードを使ってアプリケーションを描画し、必要に応じてアルファ値を使って必要な透明性を作成できます。目新しいことは何もありません。Direct2D を使ったスワップ チェーンのレンダリングの詳細についても 2013 年 3 月号のコラムを参照してください。図 5 に、ここまでの理解を確認するためのシンプルな例を示します。2014 年 2 月号のコラム「Windows 8.1 用の高 DPI アプリケーションを作成する」(msdn.microsoft.com/magazine/dn574798) で説明したモニターごとの DPI 対応を忘れないでください。
図 5 Direct2D を使ったスワップ チェーンの描画
// Create a single-threaded Direct2D factory with debugging information
ComPtr<ID2D1Factory2> d2Factory;
D2D1_FACTORY_OPTIONS const options = { D2D1_DEBUG_LEVEL_INFORMATION };
HR(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED,
options,
d2Factory.GetAddressOf()));
// Create the Direct2D device that links back to the Direct3D device
ComPtr<ID2D1Device1> d2Device;
HR(d2Factory->CreateDevice(dxgiDevice.Get(),
d2Device.GetAddressOf()));
// Create the Direct2D device context that is the actual render target
// and exposes drawing commands
ComPtr<ID2D1DeviceContext> dc;
HR(d2Device->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
dc.GetAddressOf()));
// Retrieve the swap chain's back buffer
ComPtr<IDXGISurface2> surface;
HR(swapChain->GetBuffer(
0, // index
__uuidof(surface),
reinterpret_cast<void **>(surface.GetAddressOf())));
// Create a Direct2D bitmap that points to the swap chain surface
D2D1_BITMAP_PROPERTIES1 properties = {};
properties.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED;
properties.pixelFormat.format = DXGI_FORMAT_B8G8R8A8_UNORM;
properties.bitmapOptions = D2D1_BITMAP_OPTIONS_TARGET |
D2D1_BITMAP_OPTIONS_CANNOT_DRAW;
ComPtr<ID2D1Bitmap1> bitmap;
HR(dc->CreateBitmapFromDxgiSurface(surface.Get(),
properties,
bitmap.GetAddressOf()));
// Point the device context to the bitmap for rendering
dc->SetTarget(bitmap.Get());
// Draw something
dc->BeginDraw();
dc->Clear();
ComPtr<ID2D1SolidColorBrush> brush;
D2D1_COLOR_F const brushColor = D2D1::ColorF(0.18f, // red
0.55f, // green
0.34f, // blue
0.75f); // alpha
HR(dc->CreateSolidColorBrush(brushColor,
brush.GetAddressOf()));
D2D1_POINT_2F const ellipseCenter = D2D1::Point2F(150.0f, // x
150.0f); // y
D2D1_ELLIPSE const ellipse = D2D1::Ellipse(ellipseCenter,
100.0f, // x radius
100.0f); // y radius
dc->FillEllipse(ellipse,
brush.Get());
HR(dc->EndDraw());
// Make the swap chain available to the composition engine
HR(swapChain->Present(1, // sync
0)); // flags
これでようやく、DirectComposition API を使ってすべてを合成することができます。Windows 合成エンジンはデスクトップ全体のレンダリングと合成を行いますが、DirectComposition API によってこの同じテクノロジでアプリケーションの表示を独自に合成できます。アプリケーションはビジュアルと呼ばれるさまざまな要素を合成し、アプリケーション ウィンドウ自体の外観を生成します。これらのビジュアルをさまざまな方法でアニメーションで動かし、変形させるようにして、見栄えが良く、動作がなめらかな UI を作成できます。また、ビジュアルの合成処理はそれ自体、デスクトップ全体の合成と共に実行されるため、より多くのアプリケーションの表示がアプリケーション スレッドから切り離され応答性が向上します。
DirectComposition の主な目的は、異なるビットマップを合成することです。Direct2D と同様、ここでのビットマップとは、異なるレンダリング スタックが協調動作し、スムーズで快適なアプリケーションの使い心地を生み出せるようにする抽象化にほかなりません。
Direct3D や Direct2D と同様、DirectComposition は GPU を利用する DirectX API です。DirectComposition デバイスは、Direct3D デバイスをポイントすることで作成され、これは Direct2D デバイスが基盤となる Direct3D デバイスをポイントするのとほぼ同じ方法で行われます。DirectComposition デバイスの作成には、以前スワップ チェーンと Direct2D レンダリング ターゲットを作成するために使用したのと同じ Direct3D デバイスを使います。
ComPtr<IDCompositionDevice> dcompDevice;
HR(DCompositionCreateDevice(
dxgiDevice.Get(),
__uuidof(dcompDevice),
reinterpret_cast<void **>(dcompDevice.GetAddressOf())));
DCompositionCreateDevice 関数は、Direct3D デバイスの DXGI インターフェイスを想定し、IDCompositionDevice インターフェイス ポインターを新しく作成する DirectComposition デバイスに返します。DirectComposition デバイスは、他の DirectComposition オブジェクトのファクトリとして機能し、最終的な合成と表示のためにレンダリング コマンドのバッチを合成エンジンにコミットするきわめて重要な Commit メソッドを提供します。
次に、表示先となるアプリケーション ウィンドウと合成されるビジュアルに関連付けるため、合成ターゲットを作成する必要があります。
ComPtr<IDCompositionTarget> target;
HR(dcompDevice->CreateTargetForHwnd(window,
true, // Top most
target.GetAddressOf()));
CreateTargetForHwnd メソッドの最初のパラメーターは、CreateWindowEx 関数から返されるウィンドウ ハンドルです。2 つ目のパラメーターは、ビジュアルを他のすべてのウィンドウ要素と組み合わせる方法を指示します。結果は、SetRoot という唯一のメソッドを持つ IDCompositionTarget インターフェイス ポインターになります。これにより、合成するビジュアルの想定されるツリーにルート ビジュアルを設定できます。ビジュアル ツリー全体は必要ありませんが、少なくとも 1 つのビジュアル オブジェクトが必要で、そのためもう一度 DirectComposition デバイスを使います。
ComPtr<IDCompositionVisual> visual;
HR(dcompDevice->CreateVisual(visual.GetAddressOf()));
ビジュアルはビットマップへの参照を保持し、一連のプロパティを提供します。このプロパティは、ツリー内の他のビジュアルやターゲット自体と関連してビジュアルのレンダリングや合成を行う方法に影響します。このビジュアルが合成エンジンに提供するコンテンツは、既に用意できてます。以前作成したスワップ チェーンです。
HR(visual->SetContent(swapChain.Get()));
ビジュアルの準備が整ったので、単純に合成ターゲットのルートとして設定するだけです。
HR(target->SetRoot(visual.Get()));
最後に、ビジュアル ツリーの形状を確立したら、DirectComposition デバイスの Commit メソッドを呼び出して合成エンジンに完了を通知します。
HR(dcompDevice->Commit());
このアプリケーションではビジュアル ツリーが変化しないため、Commit メソッドはアプリケーションの開始時に一度呼び出す必要があるだけです。当初は、スワップ チェーンを表示した後に Commit メソッドを呼び出す必要があると考えていましたが、スワップ チェーンの表示はビジュアル ツリーの変化と同期しないためこれは当てはまりませんでした。
図 6 は、Direct2D がスワップ チェーンにレンダリングされ、DirectComposition が合成エンジンに部分的に透明なスワップ チェーンを提供するようになったアプリケーション ウィンドウの外観を示します。
図 6 DirectComposition サーフェスでの Direct2D の描画
デスクトップの残りの部分とアルファ ブレンドを行った高パフォーマンスのウィンドウを生成するというかねてからの課題に、ついに解決策が得られました。DirectComposition API によってどのような可能性が開かれ、アプリケーションの UX デザインとネイティブ コード開発の未来にこれが何を意味するのかを考えるとわくわくします。
独自のウィンドウ クロムを描画する場合でも、問題ありません。ウィンドウの作成時に、WS_OVERLAPPEDWINDOW ウィンドウ スタイルを WS_POPUP ウィンドウ スタイルに置き換えるだけで対応できます。それでは、コーディングを楽しんでください。
Kenny Kerr は、カナダを拠点とするコンピューター プログラマであり、Pluralsight の執筆者です。また、Microsoft MVP でもあります。彼のブログは kennykerr.ca (英語) で、Twitter は twitter.com/kennykerr (英語) でフォローできます。
この記事のレビューに協力してくれた技術スタッフの Leonardo Blanco (Microsoft) に心より感謝いたします。