私の頭の中では
・x64でビルドしてもすぐにそのアプリケーションが64ビット向けのプログラムになるのかと言うと、そうではなくて、他にもVC++の設定が必要なんでね?
・64ビット用プログラムは、ギガ単位のメモリを使う場合に有効だが、それ以外では実行速度が速くなる様なメリットはない。
・64ビット用プログラムは未だ存在する32ビット環境では動くかどうか分からんが、逆に32ビット用のプログラム(x86向け)なら、64ビットの環境でも動くだろう。
と言った所です。
2012年8月12日日曜日
時間の測定
アニメーションなどの定期的に実行する処理を作る時は勿論、自分が何か変な事をやって異常な時間コストを払うプログラムを書いていないか等をチェックする時にも時間の計測が有効です。
windowsで高精度な時間の測定が行える関数は
QueryPerformanceCounter() です(以後Counterと呼ぶことにする)。
ただしこの関数で得られる時間は実行環境によって精度が異なるため
QueryPerformanceFrequency()(以後Frequencyと呼ぶことにする)
という関数を一度は呼び出して、その環境における精度を取得しておく必要があります。この精度は周波数と呼ばれ、Counterが1秒間に何回カウントするのかを示します。
Counterで得られる値は、システム起動後から刻一刻と加算されていく数字なので、何かの時間を計るにはCounterを2回呼び出して前後の差を求めます。そしてその結果をFrequencyで割れば秒単位の時間が求まります。
なお、これらの関数の利用には
#include <windows.h>のインクルードが必要です。
例)-------------
//精度の取得
LARGE_INTEGER freq;
QueryPerformanceFrequency(&freq);
//現在の時間を取得
LARGE_INTEGER start;
QueryPerformanceCounter(&start);
/*ここに測定する処理の記述*/
//もう一度時間を取得
LARGE_INTEGER end;
QueryPerformanceCounter(&end);
double time = double (end.QuadPart - start.QuadPart) / double(freq. QuadPart);
---------------------
以上のように記述すると2回のCounter呼び出しの間に何秒間経過したかがdoubleのtimeという変数に入ります。
CounterやFrequencyが取る引数はLARGE_INTEGERという特殊な型であり、このままでは整数として扱えないので.QuadPartというメンバにアクセスして利用します。
以下は時間の測定結果をミリ秒単位で戻すクラスの定義例とその利用例です。
------------------------------
#include <windows.h>//QueryCounter、及びSleep()用
#include <sstream> //ods()用
//デバッグウインドウへ出力
template < typename T, typename T2 > void ods( T tep, T2 tep2, T tep3)
{
std::wstringstream ss;
std::wstring st;
ss << tep << tep2<< tep3 << L"\n";
st = ss.str();
OutputDebugString(st.c_str());
};
// ミリ秒単位でStart()からEnd()までの時間を取得するクラス
class QueryCounter
{
public:
QueryCounter(void)
{
LARGE_INTEGER freq;
QueryPerformanceFrequency(&freq);
dFreq_ = (double)freq.QuadPart / 1000.0;
}
void Start()
//時間計測の開始時に呼んで下さい
{
QueryPerformanceCounter(&start_);
}
double End()
//時間計測の終了時に呼んで下さい
{
LARGE_INTEGER end;
QueryPerformanceCounter(&end);
return (double (end.QuadPart - start_.QuadPart) / dFreq_ );
}
private:
LARGE_INTEGER start_;
double dFreq_;
};
int main()
{
QueryCounter qc;
qc.Start();
Sleep(10);
ods(L"Sleep(10): ", qc.End(),L" ㍉秒");
double d;
qc.Start();
Sleep(5);
d = qc.End();
ods(L"Sleep(5) : ", d ,L" ㍉秒");
getchar();
return 0;
}
-------実行結果-------------
Sleep(10): 9.99923 ㍉秒
Sleep(5) : 4.41183 ㍉秒
----------------------------
SleepはWindowsでミリ秒単位で休止時間を指定できる関数ですが、その精度はミリ秒とはいかない様です。(そもそも関数呼び出しのオーバーヘッドやCPUやコンパイラが行う最適化の都合などで極小の処理時間を計るのは難しい作業の様な・・・。)
ゲームのタイミングとマルチコア プロセッサ
というMSのページでこの関数についての説明があります。
DXUTや他のWindowsのライブラリでも内部でこの関数を使っているっぽいので、益々この関数の重要性がまして来ると考えられる昨今、この関数に対応しない環境は減少に向かうのではないでしょうか?
WindowsXPでこの関数が使えなかったので色々と調べた人のページ
が参考になるかも知れません。
我らはDirectX11以降=vista以降の環境を対象にプログラミングをするからXPの話は問題にならないのだ!
と考えるのも良いし(てゆうかXPでも大体使えるだろうし)、一応、使えなさそうな時は
timeGetTime()を使うには以下のヘッダとライブラリが必要です。
#include <mmsystem.h>
#pragma comment(lib, "winmm.lib")
timeGetTime() 関数を比較的精度よく使う時は、またちょっとした手法が必要だったりします。WindowsNT系のOSでは
timeBeginPeriod()という関数で精度を指定しないと分解能が悪かったりするとうい話です。(ビスタや7も含め、Windows2000以降ぐらいからNT系のOSだったりします。)
引数はミリ秒単位で、最小1ミリ秒まで指定できます。
そして使い終わったら
timeEndPeriod()を呼ぶ必要があります。
timeBeginPeriod(1);
を自作タイマークラスのコンストラクタとかで呼んでおいて
timeEndPeriod(1);
をデストラクタで呼ぶ等すればいいのではないでしょうか?
環境によってはtimeBeginPeriodでタイマーの精度を上げるとCPUの負荷が増すなどのちょっとした副作用があるっぽいです。
windowsで高精度な時間の測定が行える関数は
QueryPerformanceCounter() です(以後Counterと呼ぶことにする)。
ただしこの関数で得られる時間は実行環境によって精度が異なるため
QueryPerformanceFrequency()(以後Frequencyと呼ぶことにする)
という関数を一度は呼び出して、その環境における精度を取得しておく必要があります。この精度は周波数と呼ばれ、Counterが1秒間に何回カウントするのかを示します。
Counterで得られる値は、システム起動後から刻一刻と加算されていく数字なので、何かの時間を計るにはCounterを2回呼び出して前後の差を求めます。そしてその結果をFrequencyで割れば秒単位の時間が求まります。
なお、これらの関数の利用には
#include <windows.h>のインクルードが必要です。
例)-------------
//精度の取得
LARGE_INTEGER freq;
QueryPerformanceFrequency(&freq);
//現在の時間を取得
LARGE_INTEGER start;
QueryPerformanceCounter(&start);
/*ここに測定する処理の記述*/
//もう一度時間を取得
LARGE_INTEGER end;
QueryPerformanceCounter(&end);
double time = double (end.QuadPart - start.QuadPart) / double(freq. QuadPart);
---------------------
以上のように記述すると2回のCounter呼び出しの間に何秒間経過したかがdoubleのtimeという変数に入ります。
CounterやFrequencyが取る引数はLARGE_INTEGERという特殊な型であり、このままでは整数として扱えないので.QuadPartというメンバにアクセスして利用します。
以下は時間の測定結果をミリ秒単位で戻すクラスの定義例とその利用例です。
------------------------------
#include <windows.h>//QueryCounter、及びSleep()用
#include <sstream> //ods()用
//デバッグウインドウへ出力
template < typename T, typename T2 > void ods( T tep, T2 tep2, T tep3)
{
std::wstringstream ss;
std::wstring st;
ss << tep << tep2<< tep3 << L"\n";
st = ss.str();
OutputDebugString(st.c_str());
};
// ミリ秒単位でStart()からEnd()までの時間を取得するクラス
class QueryCounter
{
public:
QueryCounter(void)
{
LARGE_INTEGER freq;
QueryPerformanceFrequency(&freq);
dFreq_ = (double)freq.QuadPart / 1000.0;
}
void Start()
//時間計測の開始時に呼んで下さい
{
QueryPerformanceCounter(&start_);
}
double End()
//時間計測の終了時に呼んで下さい
{
LARGE_INTEGER end;
QueryPerformanceCounter(&end);
return (double (end.QuadPart - start_.QuadPart) / dFreq_ );
}
private:
LARGE_INTEGER start_;
double dFreq_;
};
int main()
{
QueryCounter qc;
qc.Start();
Sleep(10);
ods(L"Sleep(10): ", qc.End(),L" ㍉秒");
double d;
qc.Start();
Sleep(5);
d = qc.End();
ods(L"Sleep(5) : ", d ,L" ㍉秒");
getchar();
return 0;
}
-------実行結果-------------
Sleep(10): 9.99923 ㍉秒
Sleep(5) : 4.41183 ㍉秒
----------------------------
SleepはWindowsでミリ秒単位で休止時間を指定できる関数ですが、その精度はミリ秒とはいかない様です。(そもそも関数呼び出しのオーバーヘッドやCPUやコンパイラが行う最適化の都合などで極小の処理時間を計るのは難しい作業の様な・・・。)
QueryPerformanceCounterは万能か?
Windowsで経過時間を取得する方法は沢山あるのですが、その中でなぜこの関数が良いのでしょうか?ゲームのタイミングとマルチコア プロセッサ
というMSのページでこの関数についての説明があります。
DXUTや他のWindowsのライブラリでも内部でこの関数を使っているっぽいので、益々この関数の重要性がまして来ると考えられる昨今、この関数に対応しない環境は減少に向かうのではないでしょうか?
WindowsXPでこの関数が使えなかったので色々と調べた人のページ
が参考になるかも知れません。
我らはDirectX11以降=vista以降の環境を対象にプログラミングをするからXPの話は問題にならないのだ!
と考えるのも良いし(てゆうかXPでも大体使えるだろうし)、一応、使えなさそうな時は
timeGetTime
を使うように処理を切り替える様な安全弁を設けておいても良いでしょう。timeGetTime()を使うには以下のヘッダとライブラリが必要です。
#include <mmsystem.h>
#pragma comment(lib, "winmm.lib")
timeGetTime() 関数を比較的精度よく使う時は、またちょっとした手法が必要だったりします。WindowsNT系のOSでは
timeBeginPeriod()という関数で精度を指定しないと分解能が悪かったりするとうい話です。(ビスタや7も含め、Windows2000以降ぐらいからNT系のOSだったりします。)
引数はミリ秒単位で、最小1ミリ秒まで指定できます。
そして使い終わったら
timeEndPeriod()を呼ぶ必要があります。
timeBeginPeriod(1);
を自作タイマークラスのコンストラクタとかで呼んでおいて
timeEndPeriod(1);
をデストラクタで呼ぶ等すればいいのではないでしょうか?
環境によってはtimeBeginPeriodでタイマーの精度を上げるとCPUの負荷が増すなどのちょっとした副作用があるっぽいです。
2012年7月31日火曜日
単純な初期化の解説
前の節に続いてDirect2Dの解説をします。
以下はこの2つを初期化する例です。
-------------------
//ファクトリ
ID2D1Factory* pD2DFactory ;
//レンダーターゲット
ID2D1HwndRenderTarget* pHwndRenderTarget ;
//ファクトリの作成
D2D1CreateFactory(
D2D1_FACTORY_TYPE_SINGLE_THREADED,
&pD2DFactory);
//レンダーターゲットのサイズの準備
RECT rect;
::GetClientRect(hWnd, &rect);
D2D1_SIZE_U size =
D2D1::Size<UINT>(rect.right, rect.bottom);
//レンダーターゲットの作成
pD2DFactory->CreateHwndRenderTarget(
D2D1::RenderTargetProperties(),
D2D1::HwndRenderTargetProperties(hWnd, size),
&pHwndRenderTarget);
----------------------------
(完全なサンプルは前の節で示しています。)
ファクトリ( ID2D1Factory )というのはDirect2Dのリソース(描画用オブジェクト等)を作るための工場みたいなものです。Direct2Dを利用する時はまずはこれを作る必要があります。
ファクトリを作るにはD2D1CreateFactory()という関数を呼びます。
この時に引数で指定できるのはシングルスレッド用にするかマルチスレッド用にするかです。
ファクトリを使って次に説明するレンダーターゲットを作る事が出来ます。レンダーターゲットは重要なリソースです。
レンダーターゲット(ID2D1RenderTarget )は描画対象を管理するインターフェースです。この インターフェース を通して描画に必要な関数を呼び出せます。ブラシを作る時にも必要です。
この ID2D1RenderTarget にはいくつか派生の インターフェース があり、今回作ったのは普通のウインドウを描画対象にするための ID2D1HwndRenderTarge という インターフェース です。
(Direct3Dと同じ描画先に出力したい場合は別の インターフェース を使う必要があります。)
レンダーターゲットの作成は先ほど作成したファクトリのメソッド CreateHwndRenderTarget() を使って行います。
その際、いくつかの初期設定が行えるのですが、例で示したデフォルトの設定でハードウェアレンダリングによる高速な描画が期待できます。
例)
pHwndRenderTarget->BeginDraw();
//画面を一色で塗り潰す
pHwndRenderTarget->Clear(D2D1::ColorF(1.0F, 1.0F, 1.0F));
pHwndRenderTarget->EndDraw();
前節のサンプルでは描画テストをするRenderTest()という関数の中でブラシを作成していました。(リンク:ブラシの概要)
// ブラシの作成 --------
ID2D1SolidColorBrush* pGreenBrush = NULL;
pHwndRenderTarget->CreateSolidColorBrush(
D2D1::ColorF(0.0F, 1.0F, 0.0F), &pGreenBrush);
-----------------------
そしてBeginDraw()とEndDraw()の間で 四角形の描画等をしていました。
//四角形の描画-----------
D2D1_RECT_F rect1 =
D2D1::RectF(100.0F, 50.0F, 300.0F, 100.F);
pHwndRenderTarget->FillRectangle(&rect1, pGreenBrush);
----------------------------
そして開放していました。
pGreenBrush->Release();
簡単に言ってしまえばDirect2Dで使用する描画関連の情報です。
リソースにはビデオメモリに配置する物と、そうでない物があります。今回使用したリソースは
・ファクトリ( ID2D1Factory)
・レンダーターゲット( ID2D1HwndRenderTarge)
・ブラシ (ID2D1Brush)
の3つであり、
この内レンダーターゲットとブラシはビデオメモリと関連付けられます。
ビデオメモリと関連付けられるリソースはデバイス依存、そうでないリソースはデバイス非依存と呼ばれたりもします。ここで言うデバイスはグラフィック装置というニュアンスです。
リソースは単なる構造体のデータなどとは違い、Direct2DAPIによってメモリ管理がなされる、言わば「重めのデータ」あるいは「高貴なデータ」と言った感じがします。
今回のサンプルではデバイスの初期化に比重を置き、ブラシを使った図形の描画はRenderTest()内にまとめました。しかし短い時間に何度も描画する場合はリソースを頻繁に作成するのではなく、ファクトリやレンダーターゲットと同様にクラスメンバとして管理するのが良いでしょう。
ただし、今回利用したSolidColorBrush(単色のブラシ)に関しては毎回作ってもさほど重くもないとか。…また、アプリケーションによってはウインドウのリサイズ時にしか再描画の必要がなくてパフォーマンスが気にならないというケースもあるでしょうが。
以下はいずれもブラシを赤色に再設定する例です
pGreenBrush->SetColor(D2D1::ColorF(1.0f, 0.0f, 0.0f));
pGreenBrush->SetColor(D2D1::ColorF(0xFF0000));
pGreenBrush->SetColor(D2D1::ColorF(D2D1::ColorF::Red));
SetColor()の引数の中では
namespace D2D1の関数を使って色を作っています。
透明度の設定
は以下のメソッドで出来ます。
pGreenBrush->SetOpacity(0.5);//半透明
SetColorに4つめの引数を与えて色の設定と一緒に設定する事もできます。
pGreenBrush->SetColor(D2D1::ColorF(1.0f, 0.0f, 0.0f, 0.5f));
//ファクトリとレンダーターゲットの解放例
C++でnewしたオブジェクトを2回deleteしてはいけないのと同様、リソースも通常は2回以上
今回のサンプルのクラスをコピー(代入)禁止にしたのは、デストラクタでリソースを解放する様にしたからです(より正確に言うとデストラクタの中のD2D::DestroyDevice()というメソッドで開放処理をしています)。
Direct2Dの初期化
何を描画するかでどんなオブジェクトを初期化する必要があるかも変わってきますが、最低限必要なものとしてはファクトリとレンダーターゲットがあります。以下はこの2つを初期化する例です。
-------------------
//ファクトリ
ID2D1Factory* pD2DFactory ;
//レンダーターゲット
ID2D1HwndRenderTarget* pHwndRenderTarget ;
//ファクトリの作成
D2D1CreateFactory(
D2D1_FACTORY_TYPE_SINGLE_THREADED,
&pD2DFactory);
//レンダーターゲットのサイズの準備
RECT rect;
::GetClientRect(hWnd, &rect);
D2D1_SIZE_U size =
D2D1::Size<UINT>(rect.right, rect.bottom);
//レンダーターゲットの作成
pD2DFactory->CreateHwndRenderTarget(
D2D1::RenderTargetProperties(),
D2D1::HwndRenderTargetProperties(hWnd, size),
&pHwndRenderTarget);
----------------------------
(完全なサンプルは前の節で示しています。)
ファクトリ( ID2D1Factory )というのはDirect2Dのリソース(描画用オブジェクト等)を作るための工場みたいなものです。Direct2Dを利用する時はまずはこれを作る必要があります。
ファクトリを作るにはD2D1CreateFactory()という関数を呼びます。
この時に引数で指定できるのはシングルスレッド用にするかマルチスレッド用にするかです。
ファクトリを使って次に説明するレンダーターゲットを作る事が出来ます。レンダーターゲットは重要なリソースです。
レンダーターゲット(ID2D1RenderTarget )は描画対象を管理するインターフェースです。この インターフェース を通して描画に必要な関数を呼び出せます。ブラシを作る時にも必要です。
この ID2D1RenderTarget にはいくつか派生の インターフェース があり、今回作ったのは普通のウインドウを描画対象にするための ID2D1HwndRenderTarge という インターフェース です。
(Direct3Dと同じ描画先に出力したい場合は別の インターフェース を使う必要があります。)
レンダーターゲットの作成は先ほど作成したファクトリのメソッド CreateHwndRenderTarget() を使って行います。
その際、いくつかの初期設定が行えるのですが、例で示したデフォルトの設定でハードウェアレンダリングによる高速な描画が期待できます。
描画の実行
をする時は、今作ったレンダーターゲットのメソッドBeginDraw()とEndDraw()の間で行います。例)
pHwndRenderTarget->BeginDraw();
//画面を一色で塗り潰す
pHwndRenderTarget->Clear(D2D1::ColorF(1.0F, 1.0F, 1.0F));
pHwndRenderTarget->EndDraw();
前節のサンプルでは描画テストをするRenderTest()という関数の中でブラシを作成していました。(リンク:ブラシの概要)
// ブラシの作成 --------
ID2D1SolidColorBrush* pGreenBrush = NULL;
pHwndRenderTarget->CreateSolidColorBrush(
D2D1::ColorF(0.0F, 1.0F, 0.0F), &pGreenBrush);
-----------------------
そしてBeginDraw()とEndDraw()の間で 四角形の描画等をしていました。
//四角形の描画-----------
D2D1_RECT_F rect1 =
D2D1::RectF(100.0F, 50.0F, 300.0F, 100.F);
pHwndRenderTarget->FillRectangle(&rect1, pGreenBrush);
----------------------------
そして開放していました。
pGreenBrush->Release();
リソースについて
リソースというのは型通りの説明をするならばCOMのIUnknownというインターフェースを継承するID2D1Resource型の派生クラスです。簡単に言ってしまえばDirect2Dで使用する描画関連の情報です。
リソースにはビデオメモリに配置する物と、そうでない物があります。今回使用したリソースは
・ファクトリ( ID2D1Factory)
・レンダーターゲット( ID2D1HwndRenderTarge)
・ブラシ (ID2D1Brush)
の3つであり、
この内レンダーターゲットとブラシはビデオメモリと関連付けられます。
ビデオメモリと関連付けられるリソースはデバイス依存、そうでないリソースはデバイス非依存と呼ばれたりもします。ここで言うデバイスはグラフィック装置というニュアンスです。
リソースは単なる構造体のデータなどとは違い、Direct2DAPIによってメモリ管理がなされる、言わば「重めのデータ」あるいは「高貴なデータ」と言った感じがします。
今回のサンプルではデバイスの初期化に比重を置き、ブラシを使った図形の描画はRenderTest()内にまとめました。しかし短い時間に何度も描画する場合はリソースを頻繁に作成するのではなく、ファクトリやレンダーターゲットと同様にクラスメンバとして管理するのが良いでしょう。
ただし、今回利用したSolidColorBrush(単色のブラシ)に関しては毎回作ってもさほど重くもないとか。…また、アプリケーションによってはウインドウのリサイズ時にしか再描画の必要がなくてパフォーマンスが気にならないというケースもあるでしょうが。
ブラシの色の変更
今回作ったブラシの名前が pGreenBrushなので使う色の種類毎にブラシを用意しなければならない様に思えるかも知れませんが、そんな事はありません。 ブラシには色を変更するメソッドが用意されています。以下はいずれもブラシを赤色に再設定する例です
pGreenBrush->SetColor(D2D1::ColorF(1.0f, 0.0f, 0.0f));
pGreenBrush->SetColor(D2D1::ColorF(0xFF0000));
pGreenBrush->SetColor(D2D1::ColorF(D2D1::ColorF::Red));
SetColor()の引数の中では
namespace D2D1の関数を使って色を作っています。
透明度の設定
は以下のメソッドで出来ます。
pGreenBrush->SetOpacity(0.5);//半透明
SetColorに4つめの引数を与えて色の設定と一緒に設定する事もできます。
pGreenBrush->SetColor(D2D1::ColorF(1.0f, 0.0f, 0.0f, 0.5f));
リソースの解放
リソース型は IUnknownというインターフェースを継承すると上で書きました。リソースを解放するにはこのIUnKnownのRelease()というメソッドを使います。//ファクトリとレンダーターゲットの解放例
pHwndRenderTarget->Release();
pD2DFactory->Release(); C++でnewしたオブジェクトを2回deleteしてはいけないのと同様、リソースも通常は2回以上
Release()
してはいけません。今回のサンプルのクラスをコピー(代入)禁止にしたのは、デストラクタでリソースを解放する様にしたからです(より正確に言うとデストラクタの中のD2D::DestroyDevice()というメソッドで開放処理をしています)。
2012年7月30日月曜日
デバッグウインドウへ出力する
Win32APIのソフト開発なら
#include <windows.h>とインクルードしていると思いますが、この時
OutputDebugString(L"この文字列が出力される");
という関数でプログラムの実行中に下に表示される「出力」というウインドウに文字列を表示できます。(文字列の前につけているLはユニコード設定でのコンパイル用です。)
数字の出力
以下は、数字も出力できるようにテンプレート関数を定義して使う例です。(ブラウザによっては改行コードが文字化けしているかも知れません。また、意味もなく文字の色が変わっているかも知れません。)
x = 3 センチメートル
-------------------------
以上の例で定義したodsという名前の関数は内部でストリングストリームというクラスを使って数字を文字列に変換しているためsstreamというヘッダが必要に成ります。
//---出力結果----
あ~テステス 180度
円周率 = 3.14159265358979…
ーーーーーーーーー
こちらはIOストリームの整形機能が利用できるし、沢山の出力をしたい場合、速度面で有利です。
まぁ普通にストリングストリームを使うのとさほど違いはありません。
#include <windows.h>とインクルードしていると思いますが、この時
OutputDebugString(L"この文字列が出力される");
という関数でプログラムの実行中に下に表示される「出力」というウインドウに文字列を表示できます。(文字列の前につけているLはユニコード設定でのコンパイル用です。)
数字の出力
以下は、数字も出力できるようにテンプレート関数を定義して使う例です。(ブラウザによっては改行コードが文字化けしているかも知れません。また、意味もなく文字の色が変わっているかも知れません。)
#include <windows.h>
#include <sstream>
//引数一つ
template < typename T > void ods( T tep)
{
std::wstringstream ss;
std::wstring st;
ss << tep<< L"\n";
st = ss.str();
OutputDebugString(st.c_str());
};
//引数2つバージョン
template < typename T, typename T2 > void ods( T tep, T2 tep2)
{
std::wstringstream ss;
std::wstring st;
ss << tep << tep2<< L"\n";
st = ss.str();
OutputDebugString(st.c_str());
};
//引数3つバージョン(ただし型の種類は2つで交互に配置)
template < typename T, typename T2 > void ods( T tep, T2 tep2, T tep3)
{
std::wstringstream ss;
std::wstring st;
ss << tep << tep2<< tep3 << L"\n";
st = ss.str();
OutputDebugString(st.c_str());
};
int main()
{
int x = 3;
ods(L"x = ", x, L" センチメートル");
getchar();
return 0;
}
--------出力結果----------
x = 3 センチメートル
-------------------------
以上の例で定義したodsという名前の関数は内部でストリングストリームというクラスを使って数字を文字列に変換しているためsstreamというヘッダが必要に成ります。
ストリングストリームを継承してみた
#include <windows.h>
#include <sstream>
// デバッグウインドウへ文字列を出力する関数OutputDebug()があることと、その関数がデストラクタから呼ばれる事以外は
//wostringstreamと変わらないクラス
class DebugStringStream : public std::basic_ostringstream <wchar_t, std::char_traits<wchar_t> >
{
public:
//デストラクタによる自動出力
~DebugStringStream ()
{
OutputDebug();
}
//引数で改行量を指定して出力
void OutputDebug(int kaigyou = 1)
{
for(int i = 0; i < kaigyou; ++i)
{
*this << L"\n";
}
//大域空間のOutputDebugStringを呼ぶ
::OutputDebugString(this->str().c_str());
//一回呼ぶと内容はクリアされる
this->str(L"");
}
};
int main()
{
DebugStringStream dss;
dss << L"あ~テステス " << 180 <<L"度";
dss.OutputDebug(2);//これで出力
double pai = 3.14159265358979;
//doubleやfloatの精度を設定
dss.precision(15);
dss << L"円周率 = " << pai <<L"…";
//デストラクタが呼ばれても出力される
return 0;
}
//---出力結果----
あ~テステス 180度
円周率 = 3.14159265358979…
ーーーーーーーーー
こちらはIOストリームの整形機能が利用できるし、沢山の出力をしたい場合、速度面で有利です。
まぁ普通にストリングストリームを使うのとさほど違いはありません。
分割コンパイルの仕組みとか
他のファイルに書いた機能を使いたい時は
#include "ファイル名.拡張子"
という風に書いてファイルをインクルードします。
そうするとビルド時、前処理としてその場所にファイルが読み込まれ、それからコンパイルが行われます。
インクルードの仕組みは単純で、つまりこの時、単にソースコードが複製されているのだと言えます。
コンパイルはファイル毎に行われるので、インクルードされたファイルはその場所でコンパイルされますが、さらに元々のファイルでもコンパイルされるのが普通です。
変数や関数などを使う時、宣言は複数あっても良いが、定義は1度だけというルールがあります。
だからもしインクルードで読み込んだファイルと読み込む側に同じ定義があればコンパイルエラーになります。
さらにインクルードで読み込んだファイルに何らかの定義があれば、例え読み込む側に同じ定義がなくてもリンクエラーになります。
インクルードで読み込んだファイルに定義があれば読み込む側と読み込まれる側の両方で定義が発生しますが、 コンパイルの段階ではファイル毎に別々に処理が行われるので問題がありません。
しかしリンクの段階で、インクルードによって複製されたコードが統合されるので、2重定義となってしまうのです。
だから通常は宣言だけを書いたヘッダファイルと定義を書いたファイルに分けて、ヘッダファイルを複数のファイルからインクルードするようにします。(VC++ではヘッダファイルの拡張子は.h、定義などの通常のソースコードの拡張子は.cppにします。本来、拡張子はファイルの内容に影響を与えませんが、開発環境がそれを区別するという事はあり得ます。)
例えばグローバルスコープに
int a;
と書けば、aは0で初期化されるのでメモリが確保され、宣言も定義も書いていることに成ります。
関数は
int function();
のように、関数の内容を書かなければ定義にはなりません。これを関数の プロトタイプ宣言と言います。
コンパイラは内容がなくてもプロトタイプ宣言だけ見れば
「きっとどこかでまともな関数が定義されているはずだ」と信用してくれます。
だからまだ中身を書いていない内から、あるいはそのコンパイル単位に中身が登場しなくたって、
int a = function();
という風にその関数を使ってもコンパイルが通ります。
ここでコンパイラが責任を持つのは「functionという名前の関数がint型を戻すという事と、aという名前の変数がint型であるので、この式は型の使い方に問題がない」という部分です。
コンパイルはファイル毎に行われので、全体の何処かに本当に定義があるのかどうかに関しては無知なのです。統括の責任を負うのはリンカの役割です。
テンプレート関数はコンパイラから見える範囲に定義を書かないとリンクエラーになります。
テンプレート関数は、それを使用するコードを書かない限りは、実際の関数が生成されなかったりします。実際に使われた型のバージョンの関数だけを生成して無駄を省くためです。
cppファイルなどにテンプレート関数の実体を定義し、ヘッダファイルにプロトタイプ宣言してそれをインクルードしてその関数を使った場合、コンパイラはそういう関数があると信じることはできるので、コンパイルは通ります。ここまでは普通の関数と同じです。
しかし普通の関数がcppファイルをコンパイルする際に関数がコンパイルされるのに対し、テンプレート関数はそのファイル内でその関数を利用しない限りは実体化が起こりません。それでリンクの時点でその関数が見つからずにエラーになるらしいです。
その代わりヘッダファイルに定義を書いても大丈夫なのでむしろ便利かも知れません。
どこにも定義していないクラスの名前を次のように書いてもエラーにはなりません。
class MyClass; //MyClassというクラスを使うという宣言
MyClass* myclass; //MyClassのポインタの宣言
class MyClass;
という記述は、どこかにMyClassというクラスを定義しているのだという事をコンパイラに信じさせます。
MyClass* myclass;
は、MyClass型のポインタを使うという宣言であり、メモリを確保しないので実際のMyClassの定義を必要とはしません。
つまり上に書いたclass MyClass;があれば、MyClass型のポインタの宣言が出来るのです。
しかし
MyClass myclass;
と言う風にmyclass変数の定義を書くとMyClassのコンストラクタを呼ぶ必要があるのでMyClass型の実際の定義情報が必要になります。
クラス宣言を使うと、あるクラスの中のメンバとして他のクラス型を持たせる場合、ヘッダファイルに他のクラスのヘッダファイルをインクルードしなくても済みます。
例)
class MyClass;
class tess
{
MyClass* myclass;
};
この場合、
#include "MyClass.h"は不要です。
#include "ファイル名.拡張子"
という風に書いてファイルをインクルードします。
そうするとビルド時、前処理としてその場所にファイルが読み込まれ、それからコンパイルが行われます。
インクルードの仕組みは単純で、つまりこの時、単にソースコードが複製されているのだと言えます。
コンパイルはファイル毎に行われるので、インクルードされたファイルはその場所でコンパイルされますが、さらに元々のファイルでもコンパイルされるのが普通です。
変数や関数などを使う時、宣言は複数あっても良いが、定義は1度だけというルールがあります。
だからもしインクルードで読み込んだファイルと読み込む側に同じ定義があればコンパイルエラーになります。
さらにインクルードで読み込んだファイルに何らかの定義があれば、例え読み込む側に同じ定義がなくてもリンクエラーになります。
インクルードで読み込んだファイルに定義があれば読み込む側と読み込まれる側の両方で定義が発生しますが、 コンパイルの段階ではファイル毎に別々に処理が行われるので問題がありません。
しかしリンクの段階で、インクルードによって複製されたコードが統合されるので、2重定義となってしまうのです。
だから通常は宣言だけを書いたヘッダファイルと定義を書いたファイルに分けて、ヘッダファイルを複数のファイルからインクルードするようにします。(VC++ではヘッダファイルの拡張子は.h、定義などの通常のソースコードの拡張子は.cppにします。本来、拡張子はファイルの内容に影響を与えませんが、開発環境がそれを区別するという事はあり得ます。)
コンパイラの役割
上でも書きましたが宣言は複数あっても良いが、定義は1度だけというルールがあります。宣言というのは変数で言うならメモリを確保しない名前だけの提示を言います。例えばグローバルスコープに
int a;
と書けば、aは0で初期化されるのでメモリが確保され、宣言も定義も書いていることに成ります。
関数は
int function();
のように、関数の内容を書かなければ定義にはなりません。これを関数の プロトタイプ宣言と言います。
コンパイラは内容がなくてもプロトタイプ宣言だけ見れば
「きっとどこかでまともな関数が定義されているはずだ」と信用してくれます。
だからまだ中身を書いていない内から、あるいはそのコンパイル単位に中身が登場しなくたって、
int a = function();
という風にその関数を使ってもコンパイルが通ります。
ここでコンパイラが責任を持つのは「functionという名前の関数がint型を戻すという事と、aという名前の変数がint型であるので、この式は型の使い方に問題がない」という部分です。
コンパイルはファイル毎に行われので、全体の何処かに本当に定義があるのかどうかに関しては無知なのです。統括の責任を負うのはリンカの役割です。
テンプレート関数のコンパイル
テンプレート関数はコンパイラから見える範囲に定義を書かないとリンクエラーになります。
テンプレート関数は、それを使用するコードを書かない限りは、実際の関数が生成されなかったりします。実際に使われた型のバージョンの関数だけを生成して無駄を省くためです。
cppファイルなどにテンプレート関数の実体を定義し、ヘッダファイルにプロトタイプ宣言してそれをインクルードしてその関数を使った場合、コンパイラはそういう関数があると信じることはできるので、コンパイルは通ります。ここまでは普通の関数と同じです。
しかし普通の関数がcppファイルをコンパイルする際に関数がコンパイルされるのに対し、テンプレート関数はそのファイル内でその関数を利用しない限りは実体化が起こりません。それでリンクの時点でその関数が見つからずにエラーになるらしいです。
その代わりヘッダファイルに定義を書いても大丈夫なのでむしろ便利かも知れません。
クラス宣言とポインタ
どこにも定義していないクラスの名前を次のように書いてもエラーにはなりません。
class MyClass; //MyClassというクラスを使うという宣言
MyClass* myclass; //MyClassのポインタの宣言
class MyClass;
という記述は、どこかにMyClassというクラスを定義しているのだという事をコンパイラに信じさせます。
MyClass* myclass;
は、MyClass型のポインタを使うという宣言であり、メモリを確保しないので実際のMyClassの定義を必要とはしません。
つまり上に書いたclass MyClass;があれば、MyClass型のポインタの宣言が出来るのです。
しかし
MyClass myclass;
と言う風にmyclass変数の定義を書くとMyClassのコンストラクタを呼ぶ必要があるのでMyClass型の実際の定義情報が必要になります。
クラス宣言を使うと、あるクラスの中のメンバとして他のクラス型を持たせる場合、ヘッダファイルに他のクラスのヘッダファイルをインクルードしなくても済みます。
例)
class MyClass;
class tess
{
MyClass* myclass;
};
この場合、
#include "MyClass.h"は不要です。
2012年7月29日日曜日
Direct2Dの初期化と単純な描画コード
Direct2Dの解説は
プログラミングガイド から色々たどれば基本的な事は分かるのではないかという気がします。
このページではDirect2Dの基本的な利用方法 を参考にデバイスの初期化をやってみます。
まず、win32API的な普通のウインドウを出せる事が前提です。
(これはVC++のファイル→新規作成→プロジェクト→Win32プロジェクトとかで簡単に作れるので問題ないかと思います。)
インポートライブラリの追加
ソリューションエクスプローラーのプロジェクトを右クリックするかメニューのプロジェクトからプロパティを開き、構成プロパティ→リンカ→入力を開きます。
Direct2Dの初期化部分は通常のウインドウの初期化部分と明確に分けて見通しを良くしたかったのでD2Dという名前のクラスを作ってそこに記述しました。そのクラスのCreateDevice()という関数で初期化の例を示しています。
はじめにこのクラスのヘッダファイル(D2D.h)と本体(D2D.cpp)を掲載します。
次にこのクラスの使い方を説明します。
最後にDirect2Dの解説をしようかと思いましたが、それは次節に譲ります。
なお、VC++の設定及びwin32プロジェクトの作り方によってはD2D.cppファイルの頭に #include "StdAfx.h" を加える必要があります。
このクラス(D2D)のおおまかな利用方法は次の通りです。
1・D2Dクラスのオブジェクトを作成
2・ウインドウのハンドルを渡してDirect2Dを初期化
3・ ウインドウサイズの変更に対応
4・ 描画処理を記述
これらを、順を追って説明します。
1・D2Dのオブジェクトを作成
C++の基礎的な話になりますが、まずオブジェクトを作ります。
例えばオブジェクトの名前(変数名)をd2dにしたければ
D2D d2d;
と言った感じです。
2・ウインドウのハンドルを渡して初期化
普通のwin32APIのプログラムならウインドウを作っている部分があると思います。例えば
hWnd = CreateWindow(・・・
if (!hWnd)
{//初期化失敗時は終了
return FALSE;
}
という様な部分です。
Direct2Dの初期化はこの後に D2DのCreateDevice(const HWND hWnd)を呼べばできます。その際、引数としてウインドウのハンドルを渡して下さい。
例えば次の通りです。
d2d.CreateDevice(hWnd)
CreateDevice(const HWND hWnd)は戻り値として初期化の成否を返すので、失敗した場合は次のようにしてアプリケーションを終わらせると良いかも知れません。
if (!d2d.CreateDevice(hWnd))
{
//失敗したのアプリケーションを終わらせる。
}
3・ ウインドウサイズの変更に対応
といった感じです。
4・描画処理の記述
RenderTest()で描画テストが行えます。
ウインドウプロシージャのWM_PAINTメッセージ受信時に呼ぶ例は
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
d2d.RenderTest();
EndPaint(hWnd, &ps);
break;
という感じです。
実行結果はこんな感じです。
解体処理について
このクラスで使用したDirect2Dリソース等のオブジェクト類はデストラクタ任せで解体されます。つまり普通に作ったオブジェクトは何もしなくてもアプリケーション終了時に解体されます。
D2D* d2d;
d2d = new D2D;
という風にnewで作った場合は
delete d2d;
という風にdeleteした時点で解体されます。
というメソッドの中がDirect2Dを初期化する部分だと言えます。
簡単な解説は次の節で行うつもりです。
プログラミングガイド から色々たどれば基本的な事は分かるのではないかという気がします。
このページではDirect2Dの基本的な利用方法 を参考にデバイスの初期化をやってみます。
まず、win32API的な普通のウインドウを出せる事が前提です。
(これはVC++のファイル→新規作成→プロジェクト→Win32プロジェクトとかで簡単に作れるので問題ないかと思います。)
インポートライブラリの追加
ソリューションエクスプローラーのプロジェクトを右クリックするかメニューのプロジェクトからプロパティを開き、構成プロパティ→リンカ→入力を開きます。
追加のファイルに「D2D1.lib」を指定します。
はじめにこのクラスのヘッダファイル(D2D.h)と本体(D2D.cpp)を掲載します。
次にこのクラスの使い方を説明します。
最後にDirect2Dの解説をしようかと思いましたが、それは次節に譲ります。
D2D.h
/* D2D.h
D2D:このクラスの説明
Direct2Dの初期化を行います。
ただしこのクラスではDirect3DやGDIとの相互運用はできません。
(ID2D1HwndRenderTargetの初期化を行うクラスです。)
ウインドウ作成した後、
CreateDevice(const HWND hWnd)
で作成済みのウインドウハンドルを渡して下さい。
初期化の内容は
ID2D1FactoryとID2D1HwndRenderTargetの作成です。
--------------------------------------------
作成したリソースの開放処理はデストラクタで行われます。
--------------------------------------------
ウインドウのサイズ変更時にはResizeWindow(HWND hWnd)にウインドウハンドルを
渡してください。
(ウインドウプロシージャでWM_SIZEメッセージを受け取った時に呼ぶ例)
case WM_SIZE:
d2d.ResizeWindow(hWnd);
break;
これをやらない場合は、ウインドウサイズに合わせて描画対象も変形します。
-------------------------------------------
RenderTest()で描画テストが行えます。
(ウインドウプロシージャのWM_PAINTメッセージ受信時に呼ぶ例)
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
d2d.RenderTest(hWnd);
EndPaint(hWnd, &ps);
break;
------------------------------------------
*/
#pragma once
#include <D2d1.h>
class D2D
{
public:
D2D();
~D2D(void)
{
DestroyDevice();
};
bool CreateDevice(const HWND hWnd);
//D2Dの機能を使う前の初期化を行います。
//引数には描画対象となる作成済みウインドウのハンドルを渡して下さい
void RenderTest();
//描画テストです
//この関数を呼ぶ場合はCreateDeviceで初期化を済ませる必要があります。
void ResizeWindow(HWND hWnd);
// ウインドウのサイズを更新します。
// ウインドウプロシージャでウインドウサイズの変更の
//通知を受け取った時等に呼んで下さい。
private:
//コピーや代入は禁止しています。
D2D(const D2D& t);
D2D& operator=(const D2D& t);
void DestroyDevice();
//CreateDeviceで初期化したD2Dのデバイスを解体します。
//デストラクタから呼ばれます。
ID2D1Factory* pD2DFactory ;//ファクトリ
ID2D1HwndRenderTarget* pHwndRenderTarget ;//レンダーターゲット
};
D2D.cpp
#include "D2D.h"
#include <crtdbg.h>
D2D::D2D(void)
{
pD2DFactory = NULL;
pHwndRenderTarget = NULL;
}
bool D2D::CreateDevice(HWND hWnd)
{
HRESULT hr; //成否メッセージ戻り値
// Direct2D ファクトリの作成
// レンダーターゲットを作るためにこれが必要です
if(pD2DFactory == NULL)
{
hr = ::D2D1CreateFactory(
D2D1_FACTORY_TYPE_SINGLE_THREADED,&pD2DFactory_);
if(!SUCCEEDED(hr))
return false;
}
//< レンダーターゲット(描画対象)の作成 >
// ウインドウとDirect2Dの描画装置を関連付けます。
// ウインドウと関連付けた描画装置は
//レンダーターゲットという名前で呼びます。
//ブラシなどの描画道具の作成もレンダーターゲットが行います。
if( pHwndRenderTarget == NULL)
{
RECT rect;
::GetClientRect(hWnd, &rect);
D2D1_SIZE_U size =
D2D1::Size<UINT>(rect.right, rect.bottom);
hr = pD2DFactory->CreateHwndRenderTarget(
D2D1::RenderTargetProperties(),
D2D1::HwndRenderTargetProperties(hWnd, size),
&pHwndRenderTarget);
if(!SUCCEEDED(hr))
return false;
}
return true;
}
void D2D::DestroyDevice()
{
if(pHwndRenderTarget != NULL)
{
pHwndRenderTarget->Release();
pHwndRenderTarget = NULL;
}
if(pD2DFactory != NULL)
{
pD2DFactory->Release();
pD2DFactory = NULL;
}
}
void D2D::RenderTest()
{
//RenderTestは描画テストをする関数です。
//この関数を呼ぶ場合はCreateDeviceで初期化を済ませる必要があります。
//初期化がうまくいっていないとアサートします。
_ASSERT(pHwndRenderTarget);
//ウィンドウが他のウィンドウに覆われていたら描画処理をしない
if(pHwndRenderTarget->CheckWindowState() & D2D1_WINDOW_STATE_OCCLUDED)
return;
HRESULT hr;
// ブラシの作成
ID2D1SolidColorBrush* pGreenBrush = NULL;
hr = pHwndRenderTarget->CreateSolidColorBrush(
D2D1::ColorF(0.0F, 1.0F, 0.0F), &pGreenBrush);
if(!SUCCEEDED(hr))
return;
//---------描画開始--------
pHwndRenderTarget->BeginDraw();
//背景クリア
pHwndRenderTarget->Clear(D2D1::ColorF(1.0F, 1.0F, 1.0F));
//円の描画
D2D1_ELLIPSE ellipse1 =
D2D1::Ellipse(D2D1::Point2F(120.0F, 120.0F), 100.0F, 100.0F);
pHwndRenderTarget->DrawEllipse(ellipse1, pGreenBrush, 10.0F);
//四角形の描画
D2D1_RECT_F rect1 =
D2D1::RectF(100.0F, 50.0F, 300.0F, 100.F);
pHwndRenderTarget->FillRectangle(&rect1, pGreenBrush);
pHwndRenderTarget->EndDraw();
//---------描画終了---------
//ブラシの解放
if(pGreenBrush != NULL)
{
pGreenBrush->Release();
pGreenBrush = NULL;
}
}
void D2D::ResizeWindow(HWND hWnd)
{
//レンダーターゲットの論理サイズの再設定
if(pHwndRenderTarget)
{
RECT rect;
::GetClientRect(hWnd, &rect);
pHwndRenderTarget->Resize( D2D1::SizeU(rect.right, rect.bottom) );
}
}
なお、VC++の設定及びwin32プロジェクトの作り方によってはD2D.cppファイルの頭に #include "StdAfx.h" を加える必要があります。
このクラスの利用方法
このクラス(D2D)のおおまかな利用方法は次の通りです。
1・D2Dクラスのオブジェクトを作成
2・ウインドウのハンドルを渡してDirect2Dを初期化
3・ ウインドウサイズの変更に対応
4・ 描画処理を記述
これらを、順を追って説明します。
1・D2Dのオブジェクトを作成
C++の基礎的な話になりますが、まずオブジェクトを作ります。
例えばオブジェクトの名前(変数名)をd2dにしたければ
D2D d2d;
と言った感じです。
2・ウインドウのハンドルを渡して初期化
普通のwin32APIのプログラムならウインドウを作っている部分があると思います。例えば
hWnd = CreateWindow(・・・
if (!hWnd)
{//初期化失敗時は終了
return FALSE;
}
という様な部分です。
Direct2Dの初期化はこの後に D2DのCreateDevice(const HWND hWnd)を呼べばできます。その際、引数としてウインドウのハンドルを渡して下さい。
例えば次の通りです。
d2d.CreateDevice(hWnd)
CreateDevice(const HWND hWnd)は戻り値として初期化の成否を返すので、失敗した場合は次のようにしてアプリケーションを終わらせると良いかも知れません。
if (!d2d.CreateDevice(hWnd))
{
//失敗したのアプリケーションを終わらせる。
}
3・ ウインドウサイズの変更に対応
ウインドウのサイズ変更時にはResizeWindow(HWND hWnd)にウインドウハンドルを
渡してください。
ウインドウプロシージャでWM_SIZEメッセージを受け取った時に呼ぶ例はcase WM_SIZE:
d2d.ResizeWindow(hWnd);
break;
といった感じです。
これをやらない場合は、ウインドウサイズに合わせて描画対象も変形します。
4・描画処理の記述
RenderTest()で描画テストが行えます。
ウインドウプロシージャのWM_PAINTメッセージ受信時に呼ぶ例は
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
d2d.RenderTest();
EndPaint(hWnd, &ps);
break;
という感じです。
実行結果はこんな感じです。
解体処理について
このクラスで使用したDirect2Dリソース等のオブジェクト類はデストラクタ任せで解体されます。つまり普通に作ったオブジェクトは何もしなくてもアプリケーション終了時に解体されます。
D2D* d2d;
d2d = new D2D;
という風にnewで作った場合は
delete d2d;
という風にdeleteした時点で解体されます。
Direct2Dの初期化
・・・さて、とりあえず上に書いたクラスのCreateDevice(HWND hWnd)
というメソッドの中がDirect2Dを初期化する部分だと言えます。
簡単な解説は次の節で行うつもりです。
登録:
投稿 (Atom)




