2012年10月25日木曜日

C++のご案内

このサイトではC++の解説を網羅していません。入門書的な役割すら果たしていません。いくらか、私が気の向いたトピックを書いただけであり、他にも重要な話題が沢山あります。
Webや書店を探せば入門的な内容は沢山あるので、探してみて下さい。
このサイトにもいくらかリンクを貼っておきます。

C++は、C言語をベースに構築されたプログラミング言語です。そのためC++の解説と言ってもC言語の理解を前提としている場合と、C言語すら知らない人のために書かれている場合のどちらもあり得ます。C++の解説だけ見ても意味が判らんわという人はC言語入門的な解説で基礎を固めるのが良いかも知れません。
また、C入門や、C++入門的な解説の中にも、プログラミング自体が初心者だという人に向けた解説と、プログラミング自体は歴戦の猛者だけどCやC++は初めてという人に向けた解説というのもあり得ますので、そこはご注意を。

2012年10月23日火曜日

Direct3Dのバージョンと動作環境

Direct3Dは登場以来、年々バージョンアップを重ね、現在は11に至ってようやく停滞気味になっています。基本的にはバージョンが高いほど機能性が高く、出来る事も豊富になりますが、動作環境は制限されます。ビデオカードがDirect3Dのどのバージョンまで対応しているか、使っているOSは何か?等が動作環境に含まれます。
ビデオカードは、時代が新しくなれば例え安物でも新しいDirect3Dのバージョンに対応するのが普通です。
OSに関してはWinodwsXPや、それより昔のOSではDirectX9までしか動きません。(10や11は非対応です。)

Direct3D10、10.1、11で作られたプログラムは Windows Vista(サービスパック更新プログラムを適用済み) かWindows 7で動きます。8やそれ以降のOSが出ても当分は動くでしょう。

アプリケーションを制作する時にプログラマーは始めに機能レベルというのもを選択出来ます。機能レベルをDirectX9レベルのものまで下げてプログラムを作れば、そのプログラムの中でその機能レベル相当の機能しか使えませんが、DirectX9対応のビデオカード等でも動くようです。(どうせVista以降のOSはDirectX10以降の性能を備えたチップを搭載しているんじゃないかという気もするんですが。)

今になって見ると、Direct3D10と10.1と11は、使えるOSは同じで、使えるビデオカードが違うだけです。今時のビデオカードならDirect3D11以降に対応しているものが発売されているでしょう。


このブログでは主にDirect3D10の解説をする予定です。

参考
Windows のグラフィック API
Direct3Dから10への以降
Direct3D10.1の機能
Direct3D 11 の機能

2012年10月22日月曜日

Direct3D11からDirect3D10.1へ移行を検討中

移行を検討中って言ったってまだ何も作ってないんで何のダメージもない訳でして・・・。

このサイトではDirect3D11の解説を中心に書くつもりだったのだが、Direct3D10のサンプルやドキュメントの方が遥かに充実しており、初心者はDirect3D10から入った方が素直である。もしもDirect3D11でしか実現できない仕組みが見えてきたならその時点でそっちへ首を突っ込めば良い。D3D11はD3D10にちょこちょこと機能を追加したバージョンに過ぎないのでDirect3D10の知識は無駄にはならない。基礎的な内容において、既述の仕方はほとんど同じだ。D3D11の機能を使わなければ動作可能環境も広がるのだ。学習も捗る。D3D10.1ならDirect2D,Writeともすんなり連携できる。メリットだらけでね?
むしろなんで今までDirect3D11を?

2012年10月21日日曜日

COMインターフェースの基礎知識

COMの概要に関するMSの解説ページはここが分かり易そうです。

Direct3Dに限らず、Windowsの多くのライブラリがCOM(Component Object Model)というソフトウェア規格の元に実装されています。

例えばDirect3D11のチュートリアル中に登場する変数の型名で・・・

ID3D11Device
ID3D11DeviceContext
IDXGISwapChain
ID3D11RenderTargetView

の様に頭に「I」が付く型は、COMのインターフェースを表しています。インターフェースは普通のクラス型ではなく、COMという規格に基づいた型なのです。
(C++やJavaなどのオブジェクト指向言語では、抽象クラスに相当する機構をインターフェースと呼んだりしますが、ここではそっちのインターフェースの話はしないのでインターフェース=COMインターフェースと思って下さい。)

ただし、インターフェースと言ってもほとんど普通のクラスの様に扱えます。注意すべき点は、初期化時にnewやコンストラクタを使わない事です。
Direct3Dでは大抵、そのインターフェース専用の初期化用の関数が用意されているので、その関数にインターフェースのポインタを渡します。
メソッド(メンバ関数)へのアクセスは->を使います。

基本的に、派生されたインターフェースが派生元のインターフェースのメソッドを利用できる点もクラスと同じです。

例えばDirect3Dを学ぶ時におそらく真っ先に知る
ID3D11Device
というインターフェースの説明のページに

IUnknown
   ID3D11Device
という記述がありますが、
これはID3D11DeviceがIUnknownから派生している事を示しています。従ってID3D11Deviceというインターフェースを通してIUnknownが持つメソッドも使えます。

実はID3D11Deviceだけでなく、COMのあらゆるインターフェースは、最初の継承元としてIUnknownを持ちます。

では、IUnknownという哲学的な(?)名前のインターフェースにはどの様な役割があるのでしょうか?
Direct3Dを使うに当たって一番重要なのはIUnknownが持つRelease()というメソッドです。これは

インターフェースの解放処理

を行います。つまり、そのインターフェースに使われたオブジェクトやメモリを開放します。通常のクラス型をポインタにnewして構築した場合はdeleteによってオブジェクトのメモリを解放するのですが、COMインターフェースの場合はdeleteせず、替わりにRelease()を呼ぶという分けです。
既に解放済みのインターフェースを再度Release()してはいけないのはdeleteと同じです。


インターフェースの宣言
例としてTutorial01のデバイスの生涯を追ってみましょう。
デバイスは システム中のグラフィック装置を代表するDirect3Dにおいて最も基本的なインターフェースであり、名前はID3D11Deviceです。Tutorial01.cppのソースコードの初めの方に
//------------------------
// Global Variables
//------------------------
ID3D11Device* g_pd3dDevice = NULL;

という風にグローバル変数として宣言されています。
(デバイス以外の変数は省略しました)

グローバル変数として、(つまりあらゆる{}の外で)宣言されているので、このTutorial01.cpp中の、あらゆる場所からこの変数にアクセスする事が出来ます。
まずこれに g_pd3dDevice = NULL;
という風にNULLを入れています。
(NULLは、そこにマウスカーソルを当てれば分かるように
 #define NULL 0
です。)
つまりこの時点では ID3D11Device*型の変数 g_pd3dDevice に0が代入されています。
ご存知だと思いますが、ポインタに0を入れておくのは、それがまだ初期化前の状態だという事を明らかにするためです。C++ではグローバル変数は元々0で初期化されるのですが、=NULLと書いておくとより安心感があるかも知れません。

インターフェースの初期化
デバイスを初期化してインターフェースを利用可能状態にしているのは、ソースコード中腹にあるInitDevice()関数の中です。

InitDevice() の中の真ん中辺りでD3D11CreateDeviceAndSwapChain()という関数を呼んでいます。この関数の引数にg_pd3dDeivceのアドレスを入れる事でデバイスの作成が完了します。(その他にも様々な引数を入れていますが、その辺の解説は別のページでする予定です。)

さて、このデバイスにはDirect3D11関係の他のインターフェースを作成する機能があります。 InitDevice() のさらに下の方に
hr = g_pd3dDevice->CreateRenderTargetView( pBackBuffer, NULL, &g_pRenderTargetView );
という箇所があります。これはデバイスを使ってレンダーターゲットビューというインターフェースを作成してる部分です。ただしここで覚えて欲しいのは、デバイスが持つ具体的な関数の内容ではなく、初期化を終えたインターフェースは->(アロー演算子)を通してメソッドにアクセス出来るという事です。

インターフェースの解体
さて、こうして作成、利用したインターフェースも最後は開放してやらないメモリリークになってしまいます。しかも自分で作ったクラスのnewとdeleteと同じような仕組みではメモリリークを検知できません。
チュートリアルではインターフェースの開放処理はCleanupDevice()という関数の中で指示されています。この関数の中でデバイスは
if( g_pd3dDevice ) g_pd3dDevice->Release();
という風に解放処理が書かれていると思います。
まずif(g_pd3dDevice)によってg_pd3dDeviceに値が入っている (つまり0(NULL)ではない) かどうかをチェックします。宣言時にはNULLを入れていましたが、 D3D11CreateDeviceAndSwapChain()によってうまくインターフェースが作成出来たなら、この時点でNULL以外が入っているでしょうから、ここでRelease()を呼ばれ、インターフェースが解放される事になります。また、解放済みのインターフェースを再度開放しないように、解放後にNULLを入れておくのが良い場合があります。

g_pd3dDevice = NULL;

これらをまとめて行うマクロを書くと、次の様になります。
マクロの定義例
#define SAFE_REL(COMI) if(COMI){COMI->Release(); COMI = NULL;}
マクロの利用例
SAFE_REL(g_pd3dDevice)

マクロを利用すると書くのが楽になるし「三回同じ名前のインターフェース名を書いたつもりが、一部に他のインターフェースの名前が混じり込んでいた」という様なミスを撲滅出来ます。

話がそれますが 、インターフェースではなく普通のポインタ解放用のマクロも用意しておくと便利だと思います。

#define SAFE_DEL(x) if(x){ delete x; x=NULL;}

あと、ベクター配列に確保したインターフェース解放マクロもあると便利かも知れません。
#define SAFE_REL_VEC(COMIVec) for(unsigned int i = 0; i < COMIVec.size(); ++i)\
{ SAFE_REL(COMIVec[i]) COMIVec[i] = NULL; };

 コピーする場合

インターフェースは、ポインタを使って扱う訳ですが、これをコピーして使う場合、どういうことになるのでしょうか?
例えば上の例ではデバイスを作りました。

ID3D11Device* g_pd3dDevice;

そしてこれを D3D11CreateDeviceAndSwapChain()によって初期化しました。
さてここで、このデバイスを他のスコープから利用できるようにしたい場合があるかも知れません。その場合は、もう一つポインタを用意する事になるでしょう。例として新しいデバイスのポインタの名前をDevice2にするとしましょう。

 ID3D11Device* Device2;

この時、元のインターフェースのポインタをDevice2にコピーしてやればDevice2を使っても、同じデバイスインターフェースにアクセスできる様になります。

 Device2 =   g_pd3dDevice;

この様に、同じインターフェースのインスタンスを共同で使いたい場合は、単にポインタをコピーしてアクセス方法を増やすことが出来ます。

ただし、
コピーしたインターフェースを解放する
場合はどうするのでしょうか?

これは、 コピー先かコピー元の一つだけのインターフェースポインタをRelease()します。
普通のオブジェクトのポインタに対するnewとdeleteと話の本質は同じです。上の例ではインターフェース自体は一つしか作らず、ポインタをコピーしてアクセス方法を増やしただけなので、何度もリリースすれば多重解放を引き起こします。

AddRef()の利用
COMのMS公式の解説では、コピーする度にインターフェースをAddRef()する事を推奨しています。その場合はコピーしたインターフェース毎にRelease()をする必要があります。(詳しくはこれから参照カウントの項で説明します。)
AddRef()というのもIUnknownのメソッドです。AddRef()を呼び出す度に内部の参照カウントがカウントアップされます。

参照カウントについて
COMインターフェースは参照カウント用の変数を保持しています。「参照カウント」というのは、そのインターフェースをどれだけのポインタから参照しているかを表す数字です。

例えばデバイスを例にするなら
ID3D11Device* g_pd3dDevice; 
と書いた時点では、まだインターフェースのポインタを宣言しただけなので、参照カウントは0です。
 D3D11CreateDeviceAndSwapChain()を呼んでデバイスインターフェースを初期化した時点で参照カウントが1になります。
そしてもし、
ID3D11Device* Device2 =   g_pd3dDevice;
と書いたら事実上、デバイスへの参照は2つになる訳です。
しかしプログラマーが手動で
Device2->AddRef();
みたいに書いてやらないと、デバイス内の参照カウントは2になりません。(1のままです。)

Release()のメカニズム
実はRelease()を呼び出した時、まずは参照カウントがマイナス1されます。そしてその際、参照カウントが0に至れば実際にインターフェースが解体されます。つまり参照カウントが0にならなければ、他にもそのインターフェースを参照しているポインタがあるはずなので、解体しないというポリシーなのです。

MSが推奨しているのは、コピーする度にAddRef()し、そのコピーが不要になったらRelease()する事で、インターフェースの寿命を管理する事です。
事実上は、コピー時にAddRef()を全くしないで一度だけRelease()を呼んでも同じ事です。
ライブラリに用意されている関数を使って初期化した時だけはAddRef()を呼ばなくても自動的に参照カウントがプラス1されるので、このあと
プログラマがそのインターフェースのポインタをコピーしたとしても、あるいは、しなかったとしてもAddRef()さえしなければ、Release()も一回のままで済みます。

解放忘れのチェック

さて、何やかんや言って、実際に自分がコーディングした時にインターフェース(COMオブジェクト)の解放がちゃんと行えているかどうかは気になると思います。
Direct3D11なら
解放忘れのチェックを行う場合、デバイス作成時に
D3D11CreateDeviceAndSwapChain()の引数の
UINT Flagsの部分でD3D11_CREATE_DEVICE_DEBUGを指定していれば、アプリケーション終了時にインターフェースの解放漏れがあった場合、出力ウインドウに表示されます。
Tutorial01ではDebugビルド時には、この設定になるようにコーディングされています。

 UINT createDeviceFlags = 0;
#ifdef _DEBUG
createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

hr = D3D11CreateDeviceAndSwapChain( NULL, g_driverType, NULL, createDeviceFlags, featureLevels, numFeatureLevels,
D3D11_SDK_VERSION, &sd, &g_pSwapChain, &g_pd3dDevice, &g_featureLevel, &g_pImmediateContext );


これでデバッグビルド実行時にインターフェースの解放漏れがあると、上のようにダダダッとメッセージが出力されるでしょう。

Direct3D10の場合は…無理臭いな。
ググっても自分のサイトばっか引っかかってしまう…。

関数や目的の編集箇所の探し方

関数の探し方

 VC++で特定の関数を探す時はコード編集エリア上のプルダウンボックスみたいな奴から指定すると速いです。あと、クラスビューやオブジェクトブラザーを使っても良いでしょう。クラスビューやオブジェクトブラウザは上級者用の設定ならメニューの[表示]から出せます。
(上級者用の設定はメニューの[ツール]→[設定]から出来ます。)
あるいは、メニュー下のボタン類から出してもいいでしょう。


上の画像はInitDevice()という関数をプルダウンメニューから指定する例です。左右にあるのはクラスビューとオブジェクトブラウザであり、これらの関数名をダブルクリックしても、その箇所に飛べます。同じ関数名が二つ並んでいるのは関数の宣言と定義の2箇所にその関数名が使われているからです。
クラスビューやオブジェクトブラウザーで目的の関数を探す時は、その関数がグローバル関数の場合は「グローバル関数と変数」という箇所を一旦クリックしてから関数を探して下さい。何か特定のクラスに属する関数を探す場合は、そのクラス名をクリックしてからメンバ関数を探して下さい(まぁ分かると思います)。

呼び出し階層の表示

何かの関数名を右クリックして右クリックメニューの中から「呼び出し階層の表示」を選択すると、
・その関数内で呼び出している関数

・その関数を呼び出している関数
を検索して羅列表示する事が出来ます。検索結果はそれぞれ呼び出し階層ウインドウの
「○○からの呼び出し」と
「○○への呼び出し」言う名前のフォルダに収まっているので開いてから関数名をダブルクリックすると、その箇所へ飛べます。

ヽ(=´ω`=)/ こいつは便利だぜ~

また、関数名の頭に付いている三角矢印をクリックすると、さらにその関数に対する呼び出し類を検索できます

あとまぁ

ブックマーク

とか

検索

とか抑えておくと良いかと

2012年10月20日土曜日

ライブラリのリンクをソースファイル内に記述する

VC++でリンクするライブラリを指定する時、上の画像みたいに、いちいちプロパティ→リンカー→入力に書き加えるのも面倒だったりします。

ソースコードによって、どんなライブラリが必要かは予め自明なので、インクルードと同じ感覚でソースコード中で指定できればそのソースコードを再利用する時に便利だったりします。

やり方は
ソースファイル中に
#pragma comment(lib,"ライブラリファイル名.lib")
というふうに書き込みます。

例えばDirect2DとDirectWriteを使いたいなら
d2d1.libとdwrite.libをリンクする必要があるので、
ソースコード中に

#pragma comment(lib, "d2d1.lib")
#pragma comment(lib, "dwrite.lib")
と書いておけばOKです。

Direct2Dのリソースの解放忘れをチェックする

Direct2Dでは標準ではリソースの解放忘れをチェックして警告してくれるようなデバッグモードは実装されていないらしいです。それはCOMっちゃうな~;と思った人はここからDirect2D用のデバッグレイヤーをダウンロードして実行しましょう。
この時 d2d1debug.dllがインストールされるらしいのですが ライブラリファイルの追加等のVC++の設定作業は不要です。Direct2Dが内部からd2d1debug.dllを呼べるみたいです。

後は、我々はただ単にD2D1CreateFactory()を呼ぶ時にデバッグオプションを指定すればいいだけです。
詳しくは以下をどうぞ。
Direct2Dデバッグレイヤーの概要

リソースの解放をチェックする場合の注意点としては、
1・あらゆるリソースの中でファクトリ(ID2D1Factory)を最後にRelease()する。
2・ファクトリの解放自体はチェックされない。

という事です。
ファクトリによってその他のリソースをチェックしている事が伺えます。

ファクトリをリリースした時点で未解放のリソースをがあればデバッグウインドウにメッセージが表示されます。

2012年10月8日月曜日

DrawTextLayout()を使った描画

DrawTextLayout()を使ってテキストを描画するには

IDWriteFactoryの作成
IDWriteTextFormat(テキストフォーマット)の作成
IDWriteTextLayout(テキストレイアウトの作成
DrawTextLayout()を呼ぶ

というステップが必要です。
前回 の手順IDWriteFactoryIDWriteTextFormat
の作り方を述べたので、今回はその時作った変数を使いまわします。

なので

1)CreateTextLayout()テキストレイアウトを作成
2) DrawTextLayout()を呼ぶ。


という2ステップで文字が描画できます。

1) テキストレイアウトを作成

以下はpTextLayoutという名前でテキストレイアウトを作成する例です。

std::wstring wst(L"abcd文字列");

IDWriteTextLayout* pTextLayout = NULL;

pWriteFactory_->CreateTextLayout(
wst.c_str(),
wst.size(),
pFom,
 1000, //レイアウト幅
 30,//レイアウト高さ
&pTextLayout
);

上から順に引数を解説します。
[in] const WCHAR *
描画する文字列です。DrawText()の時と違い、今回呼ぶDrawTextLayout()は描画する文字列を直接指定しません。代わりに今ここで、テキストレイアウト内の文字列として描画した文字列を指定して置きます。

UINT32
描画する文字数です。

IDWriteTextFormat * 
(前回作成した)テキストフォーマットです。

FLOAT  
レイアウトボックスの幅です。描画する文字列がどのくらいの幅になったら改行するのかを指定します。

FLOAT
レイアウトボックスの高さです。 DrawTextLayout()の第4引数でD2D1_DRAW_TEXT_OPTIONS_CLIPを指定した場合、この高さからはみ出す部分がトリミングされます。

[out] IDWriteTextLayout**
出力されるテキストレイアウトです。

2) DrawTextLayout()を呼ぶ 


D2D1_POINT_2F points;
points.x = 50.0f;
points.y = 50.0f;

pHwndRenderTarget->DrawTextLayout (points, pTextLayout , pBrush_);

引数は以下の通りです。

1:D2D1_POINT_2F
描画するテキストの左上隅座標です。

2:IDWriteTextLayout
ステップ1)で作成したテキストレイアウトです。

3:ID2D1Brush
描画用のブラシを表すDirect2Dのオブジェクトです。

4(オプション):D2D1_DRAW_TEXT_OPTIONS

オプション的に指定可能なD2D1_DRAW_TEXT_OPTIONS 列挙型です。文字列をCreateTextLayout()の引数で指定したレイアウトに合わせてトリミングしたい場合はD2D1_DRAW_TEXT_OPTIONS_CLIPを指定します。他の値は「ピクセル境界に対してスナップするかどうか」という表示における微妙な問題を指定できるっぽいです。

以上で、DrawTextLayout()を使った描画の完成です。 文字列の描画が不要になったら、最後は作ったインターフェースは Release() しましょう。

実はDrawText()の時の画像の使い回し

DrawText()による描画を経験した後だと新しい概念はさほど必要無かったと思います。
しかしDrawTextLayout()を使った描画では IDWriteTextLayoutメソッドによって描画したい文字列の設定を後から変更したり下線を付けたりも出来ます。

 下線取り消し線を付ける

下線の設定はテキストレイアウトメンバのSetUnderline()で出来ます。
上の表示は下のコード結果です。

//まず、構造体で対象文字列の設定をします
DWRITE_TEXT_RANGE dtr;
dtr.startPosition = 1;//配列何番目から
dtr.length = 6;//何文字分
pTextLayout->SetUnderline(TRUE, dtr);//TRUEで下線を引く
//描画
pHwndRenderTarget->DrawTextLayout (points, pTextLayout , pBrush_);

dtr.length =3;//今度は3文字だけ
pTextLayout->SetUnderline(FALSE, dtr);//FALSEで下線を取り消す
//さっきの文字列の50下に描画
points.y +=50;
pHwndRenderTarget->DrawTextLayout (points,pTextLayout , pBrush_);

dtr.startPosition = 11; //今度は別の場所に下線を加える
pTextLayout->SetUnderline(TRUE, dtr);
//さっきの文字列の50下に描画
points.y +=50;
pHwndRenderTarget->DrawTextLayout (points,pTextLayout , pBrush_);

取り消し線も同様の設定でSetStrikethrough()を呼べば付けられます。
例えば上の例でSetUnderlineと書いてある場所を
SetStrikethroughに置き換えれば、下線が付いている場所が取り消し線に替ります。


その他はIDWriteTextLayout インターフェイスをどうぞ

2012年10月7日日曜日

DrawText()を使ったテキストの描画

Direct2Dの基本的な文字描画関数にはDrawText()DrawTextLayout()がある事と、その際にDirectWriteのインターフェースもいくつか作成する必要がある事を前回の大まかな概念で説明しました。
文字列の描画に関わる基本要素

今回はとりあえず DrawText() で実際に文字を出す例を示そうと思います。 DrawTextLayout() で描画する時もここで解説する事の多くが役立つと思います。

なお、 DrawText()DrawTextLayout() もDirect2Dのレンダーターゲットインターフェースのメソッドですから、レンダーターゲットが作られている事が前提となります。レンダーターゲットの作り方は既に説明済みなのでここでは触れません。

おおまかな流れ

 下の図の番号は DrawText() を行うまでの基本的な手順です。

DrawText()までの手順

1)IDWriteFactoryの作成
2)CreateTextFormat()を利用してテキストフォーマットIDWriteTextFormat)を作成
3) DrawText() を呼ぶ
という3ステップに分けました。

我々は DrawText() を呼びたいだけなんですが、その引数で

どの様な書式の文字列を作成するか?

という情報をテキストフォーマットで指定する必要があります。
テキストフォーマットIDWriteTextFormat というDirectWriteのインターフェースなので先に示したの手順が必要になります。

…その前にDirectWriteを使えるようにVC++にライブラリ(DWrite.lib)を登録しましょう。
プロパティ→リンカ→入力→追加の依存ファイルにDWrite.libを加えればOKです。
また、ソースコード中にヘッダファイルのインクルードが必要です。
#include <D2d1.h>
#include <Dwrite.h>

1)IDWriteFactoryの作成

以下はpWriteFactory_という変数名でIDWriteFactoryを作成する例です。

IDWriteFactory* pWriteFactory_;
HRESULT hr;

hr = DWriteCreateFactory(
DWRITE_FACTORY_TYPE_SHARED,
__uuidof(IDWriteFactory),
reinterpret_cast<IUnknown**>(&pWriteFactory_)
);
//失敗時はメッセージBOXを表示
if (!SUCCEEDED(hr))
{
MessageBox(NULL,L"DWriteCreateFactory()に失敗",NULL,NULL);
return false;
 }

DWriteCreateFactory()という関数にIDWriteFactoryのポインタを入れて上げればOKです。ただしIUnknown型の引数を渡す必要があるのでキャストしています。

2)CreateTextFormat()テキストフォーマットを作成

下の例は pFom という変数名のテキストフォーマット(IDWriteTextFormat)を作成しています。
なお、成否の調査    if (SUCCEEDED(hr))は省略しました。
_________
IDWriteTextFormat* pFom = NULL;

pWriteFactory_->CreateTextFormat(
 L"メイリオ", // フォント(MS P明朝/MS Pゴシックとか)
 NULL, // フォントコレクション(NULL=システムフォント)
 DWRITE_FONT_WEIGHT_NORMAL, //太さ
 DWRITE_FONT_STYLE_NORMAL, //スタイル(傾き)
 DWRITE_FONT_STRETCH_NORMAL,//幅
 100.0f,//文字の大きさ
L"ja-jp",//ロケール(地域)/ja-jp/en-us
& pFom
);
________

CreateTextFormat()の引数でどのような書体=テキストフォーマットを作成するかを指定できます。DrawText()を呼ぶ時に、ここで作ったテキストフォーマットが利用される分けです。ただし、全てのフォントがこれらのパラメーターの全てをサポートしている分けではなかった様な気がします。

第1引数
フォントの種類です。 例えばメイリオだとかMS P明朝だとかMS Pゴシックなどです。(私の環境では)ここで間違った名前を入れちゃうとメイリオになるっぽいです。(メイリオっていうフォントは現状、一番綺麗にCleary Typeで描画される様な気がします。)
参考:フォントを列挙する方法

第2引数
フォントコレクションを指定します。フォントというのは誰がどれだけ作っているか分からないのでこの様な概念が必要になります。一つのフォントコレクションの中に、第一引数で選べた様な複数のフォントが格納されていたりします。NULLを指定すれば、システムフォントコレクションが利用されます。システムフォントコレクションというのはOSに最初から備わっているフォントコレクションの事です(多分)。

第3引数
 DWRITE_FONT_WEIGHT 列挙型で文字の太さを指定できます。

第4引数
DWRITE_FONT_STYLE 列挙型でイタリックの様な傾斜のあるスタイルを指定できます。

第5引数
DWRITE_FONT_STRETCH 列挙型で文字の幅を指定でき・・・るはずなのですが私はうまく行きませんでした。

第6引数
FLOATでフォントの大きさを指定します。

第7引数
ロケール名です。言語圏を指定します。

第8引数
ここに IDWriteTextFormatのポインタ変数のアドレスを入れます。関数呼び出し後にこの引数で指定した変数がテキストフォーマットとして完成する分けです。


3) DrawText() を呼ぶ

さて、ようやく最後のステップ、本命のDrawText()を呼び出して文字列の描画を行うことが出来ます。
たかが文字を描画するだけでどんだけ大変なんだアホ、バカ、ボケ!
ヽ(`Д´)ノ・・・と思っていた人も溜飲が下がる瞬間だと言えます。
(*´ー`*)ーЭ ふぅ・・・

大きさ100でテキストフォーマットを作った例

下の例では一旦std::wstringに描画したい文字列を格納しているので
#include <string>
だか何かのヘッダファイルも必要です。

//描画する文字列
std::wstring wst(L"abcd文字列");

//レイアウト
float width = 1300.0f;
float height = 100.0f;

//描画位置
D2D1_RECT_F rect;
rect.left = 50.0f;
rect.top = 50.0f;
rect.right = rect.left + width;
rect.bottom = rect.top + height;

//DrawText
 pHwndRenderTarget->DrawText(wst.c_str(), wst.size(), pFom, rect, pBrush_);


レンダーターゲットは pHwndRenderTargetという名前の変数を事前に用意してあります。レンダーターゲットの作り方はここで説明しました。
DrawText()の引数を解説します。

第1引数: WCHAR *
描画する文字列を表します。

第2数:UINT
描画する文字の数です。

第3引数:IDWriteTextFormat
テキストフォーマットです。

第4引数: D2D1_RECT_F
テキストの配置を表す矩形です。D2D1_RECT_Fという構造体メンバleftがテキスト表示範囲の左端の座標を、topが上端の座標を表します。
矩形の幅は文字列の行の長さを表します。
例としてこれを100に変えてみましょう。(これは上の例におけるレイアウトというコメントの部分を次の様に変えれば出来ます。)

//レイアウト
float width = 220.0f;
float height = 100.0f;

すると実行結果は次の様に改行されます。

長方形はレイアウト幅を示します

ちなみに文字列に改行コードを入れても改行出来ます。
改行コード

第5引数: ID2D1Brush
テキストを描画するブラシです。これはDirect2Dの基本的なインターフェースであり、描画色等を指定できます。
-----------------------
これ以降の引数はオプションです。上の例では指定していません。
------------------------
第6引数:D2D1_DRAW_TEXT_OPTIONS
描画オプションです。指定しないとデフォルト引数がD2D1_DRAW_TEXT_OPTIONS_NONEとなります。
ここで
D2D1_DRAW_TEXT_OPTIONS_CLIP
を指定するとどうなるでしょうか?
結果は下の様になります。

 第4引数で指定した矩形の中に収まらない部分はトリミングされます。

このレイアウトを表す矩形の概念は次回 IDWriteTextLayout(テキストレイアウト)を作る時にも登場します。

そう言えば、使い終わった後はお約束のインターフェースの解放処理もやった方が良いのだと思います。

例)
if(pWriteFactory_!= NULL) pWriteFactory_->Release();

pFomやDirect2D系のリソースも言わずもがな。

2012年10月6日土曜日

大まかな概念:DirectWriteを使った文字列描画

Direct2Dで文字を表示するにはDirectWriteというAPIの力を借ります。
DirectWriteはフォントデータの管理が主な仕事であって、それを描画するにはDirect2Dの様な別のAPIが必要みたいです。つまり2つのAPIが協力して文字を描画します。

下の図はDirect2DとDirectWriteを使って文字を表示する時に使う、関数、及びインタフェースをまとめてあります。

図の左半分がDirect2Dのインタフェースと関数です。
図の右半分がDirectWriteのインタフェースと関数です。

全体的な構造

Direct2D側にはID2D1RenderTarget(レンダーターゲット)というDirect2Dにおける基礎的なインタフェースとそれが持つ基本的な文字列描画関数であるDrawText( 図中左上 )DrawTextLayout(図中左下)を図示しました。この2つの関数のどちらを使っても文字列が描画出来ます。使い分けについては後で説明します。

DirectWrite側にはIDWriteFactoryというDirectWriteにおけるお母さんみたいな?インタフェースと、それが持つ関数CreateTextFromat(図中右上)CreateTextLayout(図中右下)を示しました。
この二つの関数はそれぞれIDWriteTextFormat(以下テキストフォーマット)と、IDWriteTextLayout(以下、 テキストレイアウト)というインターフェースを生成する役割があります。

このページでこれらの用語の色分けは図と一致させています。

DirectWriteを使って文字を描画するには今挙げた2つのインターフェースが重要な働きをします。簡単に説明すると

テキストフォーマット(IDWriteTextFormat)はフォントやフォントのスタイル等の書体情報を表します。

このインタフェースはCreateTextFormat()の引数としてポインタのポインタを渡す事で得られます。そして図中でそこから伸びる線を追うとDrawText()の引数、及びCreateTextLayout()の引数として使用されている様が確認できると思います。

・・・そしてもう一方の

テキストレイアウト(IDWriteTextLayout)テキストフォーマットを継承するインターフェースです。 テキストフォーマット が持つ書体情報に加え、描画する文字列自身の内容と、レイアウト(どんぐらいで改行するかとか)や、その他多くの情報も含みます。このインターフェースを作るには CreateTextLayout()を呼ぶ必要があるのですが、その際の引数として結局テキストフォーマットも必要になります。

さて、既にここまでで沢山のインターフェースや関数が登場した様に見えますが、ここで示した中で実際に文字を描画するメソッドはDrawText()DrawTextLayout()の2つしかありません。しかもDrawText()で文字を描画したい場合は図中の上半分を、DrawTextLayout()で文字を描画したい場合は図中の下半分に注目すれば概ねOKです。

細かいことはこれから説明しますがここでは
描画メソッドはDirect2DのDrawText()、もしくは DrawTextLayout() を使うという事と、そのメソッドが取る引数はDirectWriteのインタフェースであるテキストフォーマット、または テキストレイアウトを取るという事は覚えておいて下さい。


ここまでのまとめ
DrawText()  引数にテキストフォーマット (IDWriteTextFormat) が必要(だから事前に CreateTextFormat() も呼ぶ必要あり)
DrawTextLayout()  引数に テキストレイアウト(
IDWriteTextLayout) が必要(だから事前に CreateTextLayout()も呼ぶ必要があり、その前に結局CreateTextFormat()も呼ぶ必要あり。(´・ω・`))。

描画用メソッド

描画用のメソッドにはDrawText()DrawTextLayout()の2つがあると述べましたが役割の違いは何なのでしょうか?

構造上の大きな違い
DirectWriteのインターフェースである テキストレイアウト  IDWriteTextLayoutを必要とするかどうかです。 テキストレイアウトが必要な関数が DrawTextLayout()、必要ないのが DrawText()です。(関数名にLayoutが含まれているかどうかで判別できます。)

テキストレイアウトが必要な DrawTextLayout()は、言い換えれば  テキストレイアウトの機能を利用できる分けです。例えば テキストレイアウト のメソッドを使って描画する文字列に下線を付けるなどの操作が可能です。

ところで テキストレイアウト作成時にテキストフォーマットも作成する必要があると言いましたが、これは  テキストレイアウトテキストフォーマット の派生クラスであり、 テキストフォーマット が持つ情報は全て テキストレイアウトに含まれるという設計上の構造を反映しています。


テキストフォーマット を作る時は CreateTextFormat() の引数で、描画する文字列をどの様なフォントや書体にするか指定出来ますが、そこで指定した情報はそのままでは変更できません。しかし  CreateTextLayout() よって テキストレイアウト を生成すると、 テキストレイアウト のメソッドを通じてテキストフォーマット内部にあった書体情報も変更が可能になります。

DrawText() が必要とするDirectWriteのインターフェースは テキストフォーマット (IDWriteTextFormat) だけなので、使えるメソッド も少ないです。本来 テキストフォーマット の管轄である書体情報ですら書き換え不能なのは先程述べた通りです。

ところで、 IDWriteTextLayoutIDWriteTextFormat の派生クラスなので、 DrawText()IDWriteTextLayout を入れて使う事も出来ます。(ただしその場合、  テキストレイアウト で設定した項目は無視されます。)



2つの関数は実行速度にも大きな違いがあります。
DrawText()は一回きりの描画ならば DrawTextLayout()よりも若干速い様です。しかし2回以上の描画から、 DrawTextLayout()の方がずっと速くなります。
尚、どちらの関数を使うにしろ、細切れな短文を何度も出力するよりも沢山の文章を一度に出力した方が、高速です。

DrawTextLayout()のデメリット
描画する文字列そのものを変更する時は、別の文字列で初期化したテキストレイアウトを用意する必要があります。



参考:テキストの書式設定とレイアウト(MSDN)