syghの新フラグメント置き場

プログラミングTipsやコード断片の保管場所です。お絵描きもときどき載せます。

MFC-CLI 相互運用時の注意点‏

(これは2011-05-09に書いた故OCNブログの記事を移植したものです)

それなりに規模の大きい MFC プロジェクトなどで、共通言語ランタイムのサポート /clr を追加して .NET ハイブリッド アプリケーションを作るときに、起動時に EETypeLoadException 例外が発生して実行できない場合、コンパイル オプションに /GF(同一文字列の削除)を追加します。

VS 2008 IDE から設定する場合は、「構成プロパティ」→「C/C++」→「コード生成」→「文字列プール」を「はい」にします。
特にデバッグ ビルドで最適化を行なわない場合はこのオプションが OFF になっていることが多いので注意。ただし特定の最適化オプションを有効にすると自動で ON になることもあるらしいです。

今、仕事で関わっている案件では、これまでまともなビルドマスターやモジュール構成管理者がいなかったらしく、ひとつのEXEのプロジェクト ファイルですさまじい数のソースファイルがコンパイル&リンクされます。どうやらMFC拡張DLLの存在を知らなかったらしい(MFC拡張DLLを使えば簡単かつ効率的にMFCアプリを分割ビルドできるのに……)。

この状態で、すべてのソースを/clrコンパイルすると、ただでさえネイティブのみの場合でも長かったリンク時間がさらに長くなります。

多分、「x86 + MSIL」や「x64 + MSIL」の混在コードをリンクする処理に時間がかかっているみたいです。なので、すべて /clr を使ってコンパイルするのではなく、ネイティブのみでコンパイルできる部分は明示的に /clr なしで通常どおりコンパイルするようにしないと厳しいかもしれません。

なお、/clr ありと /clr なしとでは、プリコンパイル済みヘッダー(pch)を共有できないので、pchを作成するヘッダーとソース(通例stdafx.hとstdafx.cpp)を明示的に分ける必要があります。

例えば、

  • ネイティブ用に stdafx.h, stdafx.cpp, $(TargetName).pch
  • /clr 用に stdclr.h, stdclr.cpp, $(TargetName)_clr.pch

とか。

はっきり言ってめんどいことこの上なくて死にそうなので、もういっそネイティブのみを使うコードと/clrを使うコードを別々のプロジェクト(ネイティブEXEと混合DLL、もしくはネイティブDLLと混合EXE)に分離してしまったほうが良いです。

方法 : /clr に移行する

あと、C#はともかくC++/CLIをまともに使える開発者というのはかなり少ないので、MFCしか使えない人達には、

・/clrを使うホストEXE側のコードはいじらないで、ネイティブのMFC拡張DLLのコードだけいじるようにしてね(要するに筐体はいじらないで部品だけいじるようにしてね)

もしくは

・/clrを使うMFC拡張DLLにWPFラッパーのネイティブMFCインターフェイスのみを公開してあげるから、ホストとなるネイティブEXE側のコードだけいじるようにしてね(要するに部品はブラックボックスとして使ってね)

というルール作りも必要かと。そうでないと/clrオプションのもとにとんでもないコードを書き始める危険性があります。C#の素人にはunsafeを許可させないで制約を課したほうがいいのと似たようなものです(状況は逆だけど)。

ネイティブ・マネージ混在デバッグの注意点

VS 2008でx64のネイティブ・マネージ混在コードをデバッグするとき、EXE側の「構成プロパティ→デバッグ→デバッガのタイプ」を「混合」にしておくと、デバッグ実行時にブレークポイントが機能しなかったりステップインできなかったり、さらにASSERT(ATLASSERT)などによるアサートに引っかかったときプロセスが落ちる(アサート ダイアログで「再試行」とか「無視」を選択すると、本来続行可能な場面でも落ちる)現象が発生することがあるので、もしx64ネイティブ コードのほうをデバッグしたかったら「ネイティブ」に設定しておく必要があります。ただし「ネイティブ」に設定すると、今度はマネージ コードに対してブレークポイントが効かなくなったり、.NET側でIDE出力ウィンドウへのUnicode文字の出力ができなくなったりするので注意。これは結構面倒。x86のネイティブ・マネージ混在コードをデバッグ実行するときは「混合」でもアサートで落ちることはないのに……なので、64bit対応のMFC + .NETハイブリッド アプリケーションを作る場合は、まず32bit版で十分デバッグしてバグを取り除いておいたほうがいいです。64bit版でしかテスト・デバッグできないコードに関しては、面倒だがあきらめてデバッグ設定で回避するしかなさそう。なお、「デバッガのタイプ」のデフォルト設定「自動」は、EXEのプロジェクトがネイティブかそれとも/clrありの混合か、に左右されます。

ちなみにC#のコードからWin32のネイティブDLL関数をP/Invoke呼び出ししている場合に、デバッガでネイティブ関数内にステップインする場合、C#プロジェクト設定の「デバッグ」タブにある「アンマネージ コード デバッグを有効にする」にチェックを入れておきます。VC++もVC#も、こういったデバッグ時の設定に関しては、.vcprojとか.csprojといったプロジェクト ファイルではなく、.vcproj.<Windows Log-in User Name>.userとか.csproj.userとかのユーザー設定ファイルに書き込まれるので、開発者ごと(端末ごと)に逐一設定してやる必要があることに注意しましょう。

C++/CLI のインテリセンス

VC 2010ではC++/CLI言語のインテリセンスが動作しません(バグではなく仕様)。VC 2012ではちゃんと復活しています。なんで2010で一度死んだのかは不明ですが、C++0x対応やコンパイル前文法チェック機構の強化などを優先した関係上、C++のインテリセンスを再設計する必要があって、その余波でC++/CLI対応が後回しになったのかもしれません。ちなみにVC 2012では Windows ストア アプリ(WinRT)用に新たに C++/CX 拡張モードが追加されています。なお、C++/CX と C++/CLI は混在できません。

従来のWin32ネイティブ業務アプリをWPFに移行したいと考えたとき、全部をWPFに置き換えるのは現実的にいって不可能なので(WPF+C#自体は生産性は高いけどC#C++ほどきめ細かい制御ができないし、逆にネイティブC/C++GUIコード資産をWPFから使うにはあまりに制約が多い上に労多くして実りが少ない)、普通はHwndSourceとか使ってMFCアプリ内にWPFコントロールを埋め込む方法をとって徐々にUI部品などから.NETへ移行していく、またパフォーマンスが要求される部分だけC++実装を残しておいてC++/CLIでマネージインターフェイスを書く、という方法をとると思うんですが、肝心のC++/CLI言語に対するIDEサポートがおざなりだと致命的になります。C++/CLIは安易に使うと(ネイティブC++以上に)地獄を見るという、完全にマニア向けの言語ですが、MSにはもっとC++/CLIを使った相互運用のメリット・デメリットや具体的シナリオを説明するページを作成して欲しいです。

MFCのCFileDialog::SetDefExt()の引数の型について

(これは2010-06-05に書いた故OCNブログの記事を移植したものです)

バグの発見、MS Connect への報告と顛末

Visual Studio 2008/2010 の MFC 9.0/10.0 では、CFileDialog::SetDefExt() の引数のが間違っていて、本来 LPCTSTR (LPCWSTR for UNICODE / LPCSTR for MBCS) とすべきところを LPCSTR (MBCS) としてしまっています。
OS が Vista 以降でなおかつ Vista スタイルのファイル ダイアログの場合は、関数内部で LPCSTR 引数を CStringW 変数で受け取って変換しているんですが、OS が XP 以前もしくは非 Vista スタイルのレガシーダイアログの場合は、受け取った LPCSTR ポインタをそのまま SendMessage() に渡しています。
したがって、プロジェクト設定で Unicode 文字セットを使用するようにしている場合、SendMessage() は SendMessageA() ではなく SendMessageW() に置き換わり、非 Vista スタイルで素直に LPCSTR を渡してしまうと文字化けが発生します。
逆に Vista スタイルの場合、LPCWSTR を LPCSTR に強制キャストして渡してしまうと、CStringW の LPCSTR を受け取るコンストラクタが誤認するので、文字化けが発生します。
実際には、単に afxdlgs.h および dlgfile.cpp の関数インターフェイスが間違っているだけなので、非 Vista スタイルであれば LPCTSTR を無理やり LPCSTR に再解釈キャストしてから渡してしまえば(つまりコンパイラをだましてしまえば)一応正常に動作するんですが、Vista スタイルを使う場合は絶対に SetDefExt() に LPCWSTR を使うことはできません。

昔(2010年頃)、下記の URL で MS Connect 宛てに問題を報告したんですが、Connect の日本語版が米本国のサイトに統合されたときに消されてしまったようです(バグトラッキングを消去するなんて最低の行為ですね……)。確かそのときのタイトルは「MFCのCFileDialog::SetDefExt()の引数の型が不適切」というものでした。

当時 VS 2010 がまだベータの頃、VS 2008 で問題を発見して報告したんですが、MS からの回答は下記のとおりでした。

"To clarify, most likely this issue will be fixed in the next major release, e.g. Visual Studio 2012."

なん……だと

要するに、「例えば」VS 2012で「たぶん」修正されるというだけの回答でした。

その後のバグ修正状況と回避策

Visual Studio 2012 の MFC 11.0 で、確かに件の問題点は修正されていることを確認しました。MSDNにも記載があります。

ただしプロジェクト ラウンドトリップ機能を使うなどして MFC 9.0/10.0 と 11.0 以降をいったりきたりする可能性がある場合、SetDefExt() を呼び出す箇所では下記のような対処をしておく必要がありそうです。MFC 8.0 以前はどうなっていたのか調べていないので未対応です*1

#if (0x0900 <= _MFC_VER) && (_MFC_VER < 0x0B00)
#pragma message("MFC 9.0 / 10.0 CFileDialog::SetDefExt()")
if (m_bVistaStyle)
{
    this->SetDefExt(CStringA(strExt));
}
else
{
    this->SetDefExt(reinterpret_cast<LPCSTR>(static_cast<LPCTSTR>(strExt)));
}
#else
this->SetDefExt(strExt);
#endif

サンプル

かつて MS Connect にアップロードしたサンプルコードを、とりあえず VS 2008/2010/2012/2013 でビルド・比較できるようにしたものを下記にさらしておきます。
「ファイルの種類を変更すると、入力したファイル名の拡張子部分を自動的に更新するファイル ダイアログ」とか作るとき、とりあえずの回避方法として使ってやってください。

ちなみに、CFileDialog::SetControlText()は非Vistaスタイルのレガシーダイアログ専用機能であり、Vistaスタイルのダイアログではうまく動作しない模様です。ただしファイル名入力欄のテキストボックス文字列を設定する場合に関しては、代わりにIFileDialog::SetFileName()を使えばいいらしいです。実はVistaダイアログはCOMコンポーネントになっていて、VS 2008以降のCFileDialogはコンストラクタの引数フラグによってWin32 APIベースの旧ダイアログとCOMベースの新ダイアログを切り替えるラッパーになっています。だったらVistaスタイルのダイアログでもSetControlText()が動作するようにうまくラップしてくれよと言いたいところですが……

バグを見つけたら報告しましょう

MS Connect はいろいろ問題点もありますが、真面目に報告すればちゃんとリプライが来ますし、MSが修正するに足るバグだと判断すれば次期バージョンで修正してくれます。Visual Studio .NET Framework をより良くしたいという志があるのであれば、「いつか誰かが報告/修正してくれるだろう」という楽観的な態度でただ静観しているのではなく、積極的にバグ報告して改善に貢献したほうがよいです。ただし Connect では基本的に英語でコミュニケーションをとることになるので覚悟しましょう。日本のオペレーターが、こちらと米本国チームとのやりとりを翻訳して仲介してくれることもありますが、テクニカルライティングを知らないせいか翻訳がグダグダなことが多いので、英語で直接やりとりしたほうが手っ取り早いです*2

例えば下記の問題点は Visual C++ 2012 Update 4 で見つけたものですが、VC 2013 Update 2 ではすでに修正されていました。

なおVisual Studio 2013ではCommunityエディションが追加され、個人開発者や教育機関などであればATL/MFCが無償で利用できるようになりました。すでにレガシーAPIとなっているATL/MFCによる開発案件というのは今後積極的に増えることはないと思いますが、無償化によってATL/MFCのバグ報告が活発になり、品質が向上することを期待したいものです。

2015-11-06追記:
Windows 10には、非Vistaスタイルのレガシーダイアログがまともにリサイズできないデグレードがあるようです。レガシーダイアログを使っているアプリはまだ存在するし、古くてメンテナンスされていないクローズドソースのアプリだとお手上げなので、MSはデグレードを認めてさっさと直すべきでしょう。Windows 10はデグレードのひどい極めてお粗末なOSです。

*1:昔学生の頃に買った VS 2003、VS 2005 のインストール ディスクは一応持っているので調べようと思えば調べられるんですが、今更調べても需要がないのでやりません。

*2:そもそも英語のリーディング・ライティングスキルがないのに、まともなプログラミングなんてできるはずがありませんが……

Effects10, Effects11の実行時コンパイル

(これは2013-05-11に書いた故OCNブログの記事を移植したものです)

以前書いた、「Effects11 for Windowsストア アプリ」の記事とは趣旨が逆行する話ですが、一応参考程度に書き残しておきます。デスクトップ アプリでは実行時コンパイルを使うこともできますが、いろいろ問題点(特にランタイム再配布に関するもの)があるので、極力実行時コンパイルは避けて、エフェクトやシェーダーのコンパイルには fxc.exe を使ってオフラインで行なうようにしておいたほうがいいです。

今回のサンプルは単純なコンソール アプリです。下記の環境で検証しています。
D3DEffectLoadTest.zip

昔(2010年頃)、Effects10を使っていたコードをEffects11に移行したときにハマったことがあったのですが、D3D10&D3DX10&Effects10と、D3D11&D3DX11&Effects11には細かい違いが結構あります。単純な置き換えでは済まない部分もあるし、紛らわしい部分もかなりあります。

Effects10 Effects11
対応するDirect3D API Direct3D 10.x Direct3D 11.x
使用可能なプロファイル名 fx_4_0, fx_4_1 fx_5_0
使用可能なテクニック technique10 technique10, technique11
外部ソースファイルからの直接生成 D3DX10CreateEffectFromFile() N/A
メモリ上ソースファイルからの直接生成 D3DX10CreateEffectFromMemory() N/A
ソースファイルのコンパイル D3DX10CompileFromFile() D3DX11CompileFromFile()
コンパイル済みバイトコードからの生成 D3D10CreateEffectFromMemory() D3DX11CreateEffectFromMemory()

特にまぎらわしいのが、下記2つです。

  • D3DX10CreateEffectFromMemory()
  • D3DX11CreateEffectFromMemory()

名前は似てますが、前者はメモリ上ソースファイルからのエフェクト生成、後者はメモリ上バイトコードからの生成であり、機能が全然違うので注意が必要です。

前者はFromSourceFileOnMemory()、後者はFromBytecodeOnMemory()にして欲しかった……

エフェクトのプロファイル名というのは、エフェクト ファイルをコンパイルするときに必要となるバージョンのようなもので、各シェーダーステージのプロファイル(vs_4_0やgs_5_0など)とは意味が異なります。fx_5_0を使っていても、vs_4_0_level_9_1などのダウンレベル プロファイルもきちんと使えます。

なお、D3DXはWindows SDK 8.0以降では廃止されていて、D3DCompilerランタイムもろともWindowsストア アプリ開発で使用できない(DLLをリンクするとストア アプリが起動できなくなる*1)ので、今後はできるかぎり使用を避けたほうが良いのですが、ディレクトリの優先順位を

Windows SDK 8.x>旧DirectX SDK June 2010

のように設定することで、Visual C++ 2012/2013コンパイラでもD3DXライブラリを補助的に使うことはできます。要するに、D3D9/D3D10.x/D3D11.xコア ライブラリはWindows SDK 8.xのものを使用して、D3DX9/D3DX10/D3DX11ライブラリは旧DXSDKのものを使うというわけです。これにより、デスクトップ アプリではD3D 11.1&D2D 1.1とD3DX11、もしくはD3D 11.2&D2D 1.2とD3DX11を共存させることも一応できます。

D3DX MathからDirectXMathへの移行は(演算子オーバーロードがなくなっていることを除いて)比較的簡単だと思います。DirectXTKのSimpleMathライブラリを使うとさらに簡単になるでしょう。D3DX TextureからDirectXTexへの移行も、互換機能がだいたい用意されているようなので、注意深く実装すれば大丈夫でしょう。しかし、D3DX Mesh/Sprite/EffectsなどからDirectXTKへの移行はかなり大変な作業になることが予想されます。代替機能がないものも多数あります。また、DirectXTKはD3D11専用で、D3D9/D3D10からは使用できません。実際にDirectXTex/DirectXTKへ移行するか、それともそれらに頼らずD3DXを自前のライブラリで置き換えるか、それを決めるまでの暫定的対処としてしばらくD3DXライブラリを使い続けるという手もあります。

ちなみにSMAAのソースに付属するDirect3D 10サンプルコードではEffects10を使っているのですが、FXLVM(Effects Language Virtual Machine?)が1か所だけ使用されています。Effects11はFXLVMをサポートしないので、移植しようとするとき変更が必要になります。

*1:D3DCompilerランタイムに関してはWindows 8.1ストア アプリにおいて制限付きながら利用できるようになりました。

Effects11 for Windowsストア アプリ

(これは2013-04-13に書いた故OCNブログの記事を加筆修正したものです)

今回は旧D3DXライブラリの歴史にちょっとだけ触れるので、前置きが長いです。

Windowsストア アプリを開発できるVisual Studio 2012用のDirectX SDKWindows SDK 8.0)では、これまでDirect3D開発者にとっておなじみだったD3DXライブラリがほぼ完全に廃止されています。D3DXのピークはDirect3D 9用のD3DX9で、数学関数などのルーチン系だけでなくフォントやスプライト、エフェクト フレームワーク(Effects9)、果てはスキンメッシュやアニメーション コントローラー、GI(PRT)用のSH計算関数までデフォルトで用意されている充実っぷりでした。時は流れ、プログラマブル シェーダーが必須となったDirect3D 10では、D3DX10が大幅に弱体化し、スキンメッシュやアニメーション コントローラーがなくなった代わりに、エフェクト フレームワーク(Effects10)がコアAPIに組み込まれました。しかし続くDirect3D 11ではD3DX11がさらに弱体化し、ついにエフェクトもビルド済みライブラリの形では提供されなくなり、スタティックライブラリ用のソースコード(Effects11)のみがDirectX SDKのサンプルとして付属するだけになりました。

デスクトップ アプリの開発では、Visual Studio 2012/2013において、Visual C++ 2010コンパイラWindows SDK 7.x+DirectX SDK June 2010などの組み合わせを引き続き使用するように設定することもできます(ただしWindows SDK 7.xを使用する場合、Windows SDK 8.0のDirectX 11.1やDirect2D 1.1は使えません。ツールセット設定やディレクトリ優先順位を調整して、D3DコアライブラリはWindows SDK 8.0/8.1を使い、DirectX SDK June 2010のD3DXのみ補助的に使う、ということはできます)。しかし、Windows 8/RT向けストア アプリの開発ではVS 2012とWindows SDK 8.0の組み合わせが必須となっており、またD3DXランタイムおよびD3DCompilerランタイムが一切使用できません(これらのランタイムをリンクしてしまうとアプリが起動不能になる)。HLSLの実行時コンパイルをストア アプリでサポートしない理由などは下記で言及されてます。

ちなみにNVIDIA C for Graphics(Cg)のランタイムは、実はToolkitバージョン3.1時点でD3DXとD3DCompilerのランタイムに依存しているため、Cgランタイムを使ったストア アプリは現時点では作れません。

代わりにストア アプリでのDirectX 11補助ライブラリとして、CodePlexDirectX Tool Kit(DirectXTK, DXTK)というのが公開されているのですが、こいつは(少なくとも自分にとっては)なんとも使いにくいライブラリになってしまっています。従来のエフェクト フレームワークでは、エフェクトファイル(.fxファイル)に複数のシェーダー関数やステートを記述して、組み合わせをpassとtechniqueの形で階層化しパイプラインを構築するということが簡単にできていたのですが、DXTKのEffectはC++コードでパイプラインをガリガリ組み立てていくという、なんとも原始的なことをやってます。最初から用意されている出来合いの基本シェーダーだけを使うのであればそれでもいいかもしれませんが、試行錯誤しながらパイプラインをガンガン書き直す場合は辛いです。なにより今までのエフェクトファイルの資産がほとんど使えず、エフェクトのサブセットフォーマットすらサポートされないので、パイプラインおよびシェーダーステージごとに分割していかなければならないのはかなりの苦痛。

そこで自分が考えたのは、

「Effects11はソース提供されてるんだから、ストア アプリで使えるように修正したらいいじゃない!!」

結論から言うと、わりと簡単にできます。ただしストア アプリ対応のためにD3DCompilerランタイムへの依存を断つといくつかの制限が発生するため、エフェクト フレームワークのリフレクション機能をフル活用しているようなプログラムに対してはコード修正が必要です。が、エフェクトファイルによるパイプラインの構築という最大のメリットがそのまま活かせるのは大きいでしょう。エフェクトファイルではcs_5_0のようなDirectX 11対応H/W用プロファイルだけでなく、vs_4_0_level_9_1やgs_4_0のようなダウンレベルH/W用のプロファイルも使えるので、Surface RT向けアプリ(ARM用アプリ)の開発にも使えるはずです。またD3DCompilerへの依存を断つことで、DirectXエンドユーザーランタイムをインストールする必要がなくなるため、従来のWindows 7向けデスクトップ アプリでこの制限付きEffects11を心置きなく使えるというメリットもあります。

%ProgramFiles(x86)%\Microsoft DirectX SDK (June 2010)\Documentation\License Agreements\DirectX SDK EULA.txt

を読むかぎり、Effects11に関しては改変・再頒布して良いらしいので、ストア アプリでも使えるように修正したCompact Effects11と、そのCompact Effects11を使ったストア アプリのサンプルをアップしておきます。利用は自己責任で。サンプルのほうはぶっちゃけVisual Studio 2012のDirect3DアプリのテンプレートをEffects11使って手直ししただけですが、ID3DX11EffectTechniqueは普通に使えることが実証されました。

なお、サンプルを見れば分かりますが、特に大きな制限として、

(1) D3DCompilerランタイムを使用した、エフェクトの実行時コンパイルができない
(2) 頂点シェーダーBLOBのリフレクションが効かない
(3) 名前によるエフェクト変数(シェーダー定数)の参照と更新はできない
(4) ステート構文によるSamplerStateの設定はできない
(5) 動的シェーダーリンク(Dynamic Shader Linkage)はローレベルAPIで対処

などがあります。

(1) はD3DX11CompileFromFile()が使えないということですが、コンパイルVisual Studioのビルドプロセスで行なって、実行時にコンパイル済みバイトコードをD3DX11CreateEffectFromMemory()に食わせてやることで対処します。VS 2012/2013であれば.fxファイルや.hlslファイルの単純なコンパイル設定はVisual Studio IDEに専用のツールが組み込まれているので簡単ですが(VC++のプロパティ設定UIが使える)、以前のバージョンのVisual Studioでデスクトップ アプリを開発する場合や、1つのファイルに対してオプションの異なる複数のコンパイル処理を走らせたりする場合はコマンドラインでfxc.exeを使って別途エフェクトファイルをコンパイルするか、カスタムビルドツール設定でのコマンドライン指定が要ります(※後述)。

(2) はID3DX11EffectPass::GetDesc()によるバイトコードの取得が使えないということですが、頂点シェーダーバイトコードのバイナリデータがないとID3D11Device::CreateInputLayout()による頂点入力レイアウトの作成ができません。エフェクトファイル本体のコンパイルとは別に頂点シェーダーだけエントリーポイント関数名を指定してコンパイルし、バイトコードを事前作成することで対処します。これも(1)同様です。

(3) はID3DX11EffectVectorVariable::SetFloatVector()などが一律使えないということですが、代わりにcbufferを使って、C++による明示的な定数バッファの作成と更新を行なうことで対処します。サンプラー変数やテクスチャなどのリソース変数(ID3DX11EffectSamplerVariable, ID3DX11EffectShaderResourceVariable)に関しても同様です。まぁヘタにエフェクト変数に頼りすぎるとDirect3Dデバッグレイヤーによるエラーレポートの解読が困難になるなどの問題をいろいろ引き起こすことがあるので、この際あきらめましょう。uniform変数ごとの名前アクセスが主流であるGLSLから移植したりする際はこのあたりに注意が必要です(OpenGL 3.1やES 3.0では逆に定数バッファ相当機能のUBOが追加されているので、cbufferとUBOを使えばDirect3D/OpenGL間でデータ構造の共有もしやすくなります)。

(4) はエフェクトファイルでなくC++でステートオブジェクトを作成・設定することで対処します。なお、サンプラーステートはC++で明示的作成する必要がありますが、レンダリングステートは従来通りエフェクトファイル側で書けます。

(5) はパフォーマンス損失を抑えつつシェーダーの組み合わせ爆発を防ぐために導入されたDirectX 11(シェーダーモデル5.0)の新機能で、シェーダープログラムでinterface(仮想メソッド)が使えるというとんでもない代物なのですが、(3)同様にID3DX11EffectInterfaceVariable::SetClassInstance()などはD3DCompilerのリフレクション(D3DReflect()関数)に依存しているため使えません。そのため、インスタンス テーブル用の定数バッファを明示的に宣言したり、ID3D11DeviceContext::XXSetShader()メソッドに対して明示的にID3D11ClassInstance配列を渡したり、といった工夫が必要になります(ちなみに動的シェーダーリンク目的以外でも、HLSLでclassキーワードを使って単純なクラスを定義することはできます)。ID3DX11Effect::GetClassLinkage()やID3D11ClassLinkage::GetClassInstance()に関しては普通に使えるようなので、Effects11を一切使わない場合と比べて多少は楽になる部分もありますが、ID3DX11EffectPass::Apply()はID3D11ClassInstanceを受け取れないのでやはりトリックが必要になります。実は、

// FX 側:
PixelShader g_psHyperShader = CompileShader(ps_5_0, psHyperShader());
// C++ 側:
m_pEffect->GetVariableByName("g_psHyperShader")->AsShader()->GetPixelShader(0, &pPixelShader);

という感じで特定のシェーダーステージのインターフェイスを取得する機能は動作するので、いったん動的シェーダーリンクを使わないダミーシェーダーを含んだエフェクトのパスを適用した後、特定のシェーダーステージだけ後から設定し直すということはできるんですが、複数のインターフェイスを使う場合は結局ID3D11ShaderReflection::GetNumInterfaceSlots()やID3D11ShaderReflectionVariable::GetInterfaceSlot()を使ってリフレクション経由でインターフェイス スロット情報を調べる必要があるため、動的シェーダーリンクを使うシェーダーだけはエフェクトとは別にコンパイルするようにしたほうが良いでしょう。

長々と書きましたが、D3DCompilerランタイムが使えないことによる制約はかなり大きいです。エフェクト フレームワークのほとんどの便利機能が使えなくなります。ただDXTKのEffectを使うよりは生産性が高くなることは間違いないし、これまでのエフェクトファイルの資産がある程度使えるのはありがたいです。

DirectX 12ではシェーダーステージの追加だけでなく、シェーダー自体の在り方もまたガラッと変わる可能性はあるので、Effects11を更新してEffects12に、ってわけにはいかないと思います。そもそも新しいD3DCompilerがリリースされても、DirectX 12対応のエフェクトファイル仕様(fx_6_0?, technique12など)はもう現れないでしょう。ただストア アプリの場合、DirectX 11.1/Direct2D 1.1や、DirectX 11.2/Direct2D 1.2テクノロジーにOSレベルでがっつり依存しているフレームワーク(&サンドボックス)内でしか動作できないため、DirectX 12がリリースされてもすぐに対応できるわけじゃないと思います(DirectX 12はXbox Oneにはプラットフォーム更新プログラムとして必ず供給されるでしょうが、Windows 8.xや7にバックポートされる可能性は低く、当然DirectX 12対応のストア アプリ/デスクトップ アプリはWindows 10でしか動作しないように思われます)。

ちなみにコンピュート シェーダー用のエクステンションであるD3DCSX拡張ライブラリは一応D3DXの仲間なのですが、例外的にWindows SDK 8.0/8.1にも同梱されています。ただし、これを使うとd3dcsx_46.dll/d3dcsx_47.dllに依存してしまうことに注意が必要。また、ストア アプリでは使えません。

カスタム ビルド ツールによるシェーダーコンパイル

カスタム ビルド ツールを指定するとき、例えば.fxファイルに対して下記のような「コマンド ライン」を指定します。

@echo off
setlocal EnableDelayedExpansion
echo Now compiling effect file by strict mode...
set varDefs=/D DOWN_SAMPLED_TEX_SIZE=128 /D COMPUTING_TEMP_WORK_SIZE=256
"%DXSDK_DIR%Utilities\bin\x86\fxc.exe" /nologo /Ges MyHLSL\MyShader.fx /Fo "$(OutDir)MyShader.fxbin" /T fx_5_0 %varDefs%
if "!ERRORLEVEL!"  neq "0" ( exit !ERRORLEVEL! )
"%DXSDK_DIR%Utilities\bin\x86\fxc.exe" /nologo /Ges MyHLSL\VSInOutStruct.hlsli /Fo "$(OutDir)MyVertexXX.vsbin" /E vsDummyLayoutXX /T vs_4_0_level_9_1
if "!ERRORLEVEL!"  neq "0" ( exit !ERRORLEVEL! )
endlocal

各オプションの意味は、fxc.exeのヘルプに書いてあります。

VS 2012/2013の場合は起動するだけで「%ProgramFiles(x86)%\Windows Kits\8.0\bin\x86」もしくは「%ProgramFiles(x86)%\Windows Kits\8.1\bin\x86」へのパスが通っているので、上記の「%DXSDK_DIR%Utilities\bin\x86\...」というパス指定は不要となり、「fxc.exe /nologo ...」でOKです(以前のバージョンのDirectX SDKのインストールも不要です)。

注意点としては、fxc.exeを実行するたびにERRORLEVELをちゃんとチェックすること、入出力に相対パスを指定するときはVC++プロジェクト ファイル(.vcproj)からの相対パスを記述すること。また、今回の例でいうと、「出力ファイル」に「$(OutDir)MyShader.fxbin」を、「追加の依存ファイル」に「MyHLSL\VSInOutStruct.hlsli;」を指定しておくと、ソースファイル.fxやヘッダーファイル.hlsliの変更、プライマリー出力ファイル.fxbinの不在を自動検出することができるようになります。

なお、「構成プロパティ」→「全般」→「クリーン時に削除する拡張子」に、*.fxbinや*.vsbinを指定しておくとさらに確実になるかもしれません。上記例の.fxbinや.vsbinは自分が勝手に付けたシェーダーバイトコード用拡張子です。VS 2012/2013の組み込みビルド ツールでは、シェーダーバイトコードの拡張子は.cso(Compiled Shader Object?)がデフォルトのようですが、あえて区別を付けるために分けています。

ちなみにVS 2012/2013でHLSLのシンタックス ハイライト(色分け)が効くようになる拡張子は、.fx, .hlsl, .hlsliだけのようで、「ツール」→「オプション」→「テキスト エディター」→「ファイル拡張子」で追加することもできない模様。

なお、自分はCg(.cg, .cgfx)、OpenCL(.cl)、GLSL(.vert, .frag, .glsl)のソースファイルをVisual Studioで編集する際はVC++エディタに関連付けていますが、float4やvec4などが色分けされないとちょっと不便。IDEの設定ファイルをいじると、ユーザー定義型をキーワードとして色分けすることもできるようになるんですが、わざわざそこまでするのも面倒……どのみちインテリセンスは効かないので、愛用のテキストエディタであるMIFESで編集したほうがよかったりします。

Windows 8.1のD3DCompiler

なおWindows 8.1DirectX 11.2では分割コンパイルのサポートに伴って、どうやらD3DCompilerのAPIの一部がストア アプリでもエンドユーザー環境で再び使えるようになっているらしいです。

復活した詳しい経緯は調べてませんが、たぶんDirectXのパワーユーザーである海外のハイエンドゲーム開発者から実行時コンパイルに関する強い要望もあったんだと思います(現にCrysisとかはシェーダーの実行時コンパイルを使って実行環境や設定に合わせて最適化してるっぽい)。個人的にはDynamic Shader Linkage用のリフレクション機能は便利だとは思うけど、それ以外は特に無くても困らないと思うんですが。また、HLSL shader compiler sampleなるものも公開されています。DirectX 11.2を採用しているXbox Oneでも同様にD3DCompilerが使えるようになる模様。まだ試してはいませんが、つまりWindows 8.1Xbox OneではVisual Studio 2013/Windows SDK 8.1に対応する最新のD3DCompilerランタイムが標準インストールされていて、デスクトップ アプリ同様にDynamic Shader LinkageはもちろんEffects11の全機能が使えるってことでしょう。ただ、Win7Win8.0などの旧環境でも(D3DCompilerランタイムの追加インストールをすることなく)そのまま動作するようにしたければ、今回紹介したCompact Effects11を使う意義はありそうです。

オリジナルのEffects11に関して

今回ベースにしたEffects11は、DirectX SDK June 2010に付属していたソースコード+プロジェクトです。なお、Effects11自体は一応CodePlexに移管されているようです。ただしD3DCompilerに依存する従来のコードをそのまま使っているらしく、Win8.0ストアアプリには使えない模様(ストアに提出できない)。いずれにせよ、将来的にDirectX 12のシェーダーモデル6.0対応fxc.exeではEffects12やエフェクトファイルの新しいプロファイル(fx_6_0?)やテクニック(technique12)が提供されない可能性が高いので、それを覚悟して使う必要があります。

Photoshopでゴッドレイ

(これは2012-06-07に書いた故OCNブログの記事を移植したものです)

ゴッドレイっていうのはいわゆるエンジェルラダー(天使の梯子)とかのアレです。雲間などから光条が漏れているような効果のことです。英語では"God rays"とか"Crepuscular rays"と言うそうです。LightWave v9にはゴッドレイそのものの機能がない、というかボリュームライト*1を使っただけでは画面が白飛びするので、Photoshopでスクリーンスペースのポストエフェクトをかけようと思って真っ先に思いついたのが「ぼかし(放射状)」なんですが、いまいち思った通りの結果になりません。で、「Photoshop ゴッドレイ」でGoogle検索したんですが、これまたあんまり良いのが引っかかりません。代わりにVueっていう景観作成ソフト関連の記事が引っかかります。「Photoshop 差し込む光」で検索すると、よさげな解説サイトがありました。やっぱりそこでも「ぼかし(放射状)」を使っていましたが、キーとなるのは「種類」を「ズーム」にすること。デフォルトの「回転」だと思ったようなぼかし結果になりませんので注意。

・適用前:
https://sygh-jp.github.io/content_hosting/illust/godray_making1.png

画像右上の黄色の部分が別レイヤーになっているんですが、このレイヤーに放射ぼかしを適用し、加算合成設定にして、何回かコピーします。

・適用後:
https://sygh-jp.github.io/content_hosting/illust/godray_making2.png

今回のは放射が弱いので、ゴッドレイというよりはむしろブルームエフェクトです……
ぼかす前の黄色の部分の形状を工夫すれば、もっと光が差し込んでいるような感じにもできるんじゃないでしょうか。

余談

ちなみに2012年当時使っていたCPUはAMD Athlon 64 X2 5600+で、キッチン(3Dモデルデータ)のほうは反射レイトレ+ラジオシティレンダリングしたんですが、出力解像度を5,000ピクセル×3,750ピクセルアンチエイリアスを中程度(エンハンス)設定にして1時間半くらいかかりました。ポリエチレン製のまな板に表面下散乱(SSS)を適用したかったんですが、いまいち思った通りの設定にできなかったというかものすごいレンダリング時間がかかりそうだったのであきらめました。その後Core-i7 4770KとLightWave 11を入手し、もっと高速にレンダリングできる環境を構築したので、積極的に使い込んでいきたいところです。

*1:LightWave日本語版では「ヴォリュームライト」表記になっています。

HDDの移行作業

(これは2011-06-27に書いた故OCNブログの記事を移植したものです)

自作PCを最初(2007年)に組んだときの160GBと後から追加した400GBだけではそろそろ容量が足らなくなってきた上に、メインOS(Windows 7)のシステム ドライブが圧迫されてきたので、以前買っておいた1TBのHDDに移行しました*1。ちなみに個人的な好みにより、旧HDD、新HDDいずれも日立GST製です。

といっても、OSのクリーン インストールはさすがにきついので、市販のコピーソフト+フリーのパーティション編集ソフトを使用しました。

以下、作業手順。

(1) NeoSmart EasyBCDを使い、WinXP x86 + WinVista x86 + Win7 x64のマルチブート構成を解除して、Win7のシングルブートに一本化。

(2) LB コピー ワークス(CDブート)を使い、Windows PE上でブート パーティションとシステム パーティションを古いHDD(2台)から新しいHDD(1台)へまるごとコピー。

(3) MiniTool Partition Wizard Home Editionを使い、パーティションの空き領域を増やす。

ちなみに(3)はWindows 7標準の[ディスクの管理]スナップイン ツールでも一応できますが、細かいカスタマイズがGUIで直感的に行なえるPartition Wizardを選択しました。


それより手こずったのが(2)の作業。実は後からインスコしたWin7のインストール ドライブ(C:)のほうはシステムではなくブート パーティション扱いで、以前マルチブートしていたWinXPのインストール ドライブ(P:)のほうがシステム パーティション扱いになっていました。で、このシステム パーティションのある160GB HDDを取り外して400GB HDDだけの構成にすると「DISK BOOT FAILURE,INSERT SYSTEM DISK AND PRESS ENTER」とBIOSに言われてWindows 7が起動できません。160GB HDDはもう要らないんだよ……Windows 7のインストールDVDを使ってスタートアップ修復を実行、再起動するも効果なし。ログを見るとパーティション テーブルにシステム パーティションが見つからないことが原因らしい。そりゃそうですね。やはりツールでは修復のしようがないらしいです。

https://sygh-jp.github.io/content_hosting/software_ss/active_system_partition_ss_2011_06_26a.png

いろいろ調べてみたけど、システム パーティションを変更する方法はなさそうなので、仕方ないHDDの統合はあきらめてOSのクリーン インストールから始めるか……
と思ったんですが、実はLB コピー ワークスはパーティションのRawコピーができます。
古いHDDの(P:)をまず新しいHDDにコピー、さらに(C:)を新しいHDDにコピーしたのち、古いHDDを外した状態でLB コピー ワークスを使って以前と同じドライブ レターを割り当てることで、物理HDD×2を別のHDD×1に完全統合し、無事起動できるようになりました。やったね!

ファイルの読み書き速度もかなり速くなりました。ただ……新しいHDDはHDS721010CLAなんだけど、あんまり評判は良くないみたいです。自分はシーク音に関しては別にあまり気にしないんですが、耐久性に難があるのはちょっとね……やはりHDDは定期的にクローンを作ってまるごと交換するようにしたほうがいいかもしれません*2

ついでにデータ用ドライブ(X:)は論理ドライブ化しました。ちなみにVistaの入っていた(V:)とか古いデータ用ドライブ(E:)はもう要らないのでコピーしていません。

あと、プライマリ パーティション(or拡張パーティション)は1つの物理ディスク上に4つまでしか作成できないので、複数のHDDを統合するときは注意する必要があります。

https://sygh-jp.github.io/content_hosting/software_ss/active_system_partition_ss_2011_06_27a.png

その後のお話

2014年7月、今度はHDDの調子が悪くなり、今にも死にそうな状態に陥ったので、またHDDを交換しました。せっかくなのでM/BやCPU/GPU、OSを刷新する良い機会だと思い、今度はHDDはコピーせず、まっさらの新しいHDD(Western Digital Red 1TB)にWindows 8.1クリーンインストールして、ユーザーデータだけ古いHDDから抽出する形で移行しました*3。日立GSTは先行きが怪しかったので、日立に次いで信頼性の高いWDを選択しましたが、今回は日立のHDDが突然死せずに最後までギリギリ動作してくれたので助かりました。自分はPC自作派ですが、可能な限りバルク品は買わないなどの注意を払っているせいか致命的なハズレパーツ(初期不良/欠陥製品)に当たったことはほとんどなく、そういう意味では幸運なのかもしれません。

なお日立がHDD事業を売却した今、個人的におすすめのHDDメーカーはWDです。

ハードディスクはどこのメーカー製が一番壊れにくいのかが2万5000台の調査結果でついに明らかに - GIGAZINE

*1:昔のHDDは使わずに長期間放置しておくとメカ的な吸着やグリスの固化により故障してしまうという問題があったそうですが、比較的新しい製品に関しては冷暗所で保管すれば問題ないそうです

*2:もちろんユーザーデータに関してはUSBメモリ/外付けHDD(一時的)やDVD/BD(半恒久的)にバックアップを取るのは当たり前なんですが、時間をかければ再構築できるOS・アプリケーション領域に関してもバックアップをとっておくと、いざというときに短時間で復旧できます。

*3:Windows Vista以降では、M/B交換後にOSを再インストールすることなくHDDをそのまま利用するのが難しくなっています。

ライセンスゆるめの日本語無償フォント

(これは2010-10-16に書いた故OCNブログの記事を加筆修正したものです)

当方お勧めの無料で使える日本語フォントです。漫画やイラスト、Webサイト/ゲーム制作などで使えます。いずれも商用利用可ですが、不特定多数に公開する作品などで利用する際は事前・事後連絡が必要なもの、ライセンス表記/ライセンスファイルの添付が必要なもの、またフォントデータ自体の二次配布を禁止しているものもあるので、詳しいライセンス運用形態はそれぞれの公式フォント配布サイトで確認してください。

「梅フォント」はUbuntuのUIに使っている人もいるらしいです。なんでもJIS 2004対応だそうで。パッケージは.7zという見慣れない圧縮フォーマットですが、Windowsユーザーの方はExplzhなどでどうぞ(Explzhは非常に良くできたアーカイバで、拡張性や機能性が高く更新も頻繁です)。
なお、「SOフォント」や「DQフォント」は原作ゲームの著作権的にはどうもグレーな感じがします*1。そのあたりは自己責任でどうぞ。おそらく二次創作の範疇に入ると思われるため、一般流通ルートで販売される作品に原作ゲームの許可を得ず使用するのは危険な気がします。*2

Windows標準の日本語フォントである、「MS 明朝」、「MS ゴシック」、そして「メイリオ」と比較した、MS Office Word 2007上のスクリーンショットは下記(超適当)。*3

https://sygh-jp.github.io/content_hosting/software_ss/jp_font_test_01.png

Unicodeの対応度を調べるつもりで、ハートマーク(U+2665H)と版権マーク(U+00A9H)を表示させてみたところ、実際に対応しているらしいのは「梅フォント」だけでした。スクリーンショットでは「しねきゃぷしょん」や「みかちゃん」も表示できてるっぽく見えますが、Wordではマッピングできない文字があるとき、場合によってはドキュメントの既定のフォント(日本語版ではデフォルトで「MS 明朝」と「Century」)が使用されるので、実際にゲームなどのアプリケーションで表示させようとすると中黒(・)とか豆腐(□)になってしまうんではないかと。通常のGDIであればフォントリンク、WPFやDirectWriteであれば合成フォント機構があるので、表示できない文字はデフォルトでメイリオあたりが代わりに使われるはずです*4。いずれWPF/DirectWriteで表示させるサンプル プログラムでも作ろうかと思います。

あと「梅ゴシック」のASCIIバックスラッシュ(U+005CH)が円記号ではなくバックスラッシュになってるのは謎です……たぶん「梅明朝」とは違ってプログラミング用途のテキストエディターなどで使われることも想定して、円記号ではなくバックスラッシュのグリフが用意されているものと思われます。

各種日本語フォントの表示をJavaScriptでテストできる便利なサイトも見つけました。これはサーバーサイドにフォントがインストールされているようです。

http://fonthack.jp/

最後に

英数字のフォントはせいぜい100文字程度なのに対して、日本語のフォントはJIS第一水準でも3,000文字近くにのぼります。日本語のフォントが概して高価なのも納得できます。デジタルデータはコピーが簡単なので、時として忘れてしまいがちですが、イラストやフォントの制作には膨大な労力がかかっていることを忘れないようにしましょう。
なおダイナフォントなどのように、商用利用する際には追加の使用料が発生する製品もあります。

*1:フォントもロゴと同じく本来は意匠権で保護されるべきですが、日本という国の制度では2014年時点で対象になっていないそうです。

*2:「~風フォント」を使用するときに注意すべき事例として、今回あえて列挙しました。

*3:ちなみに「MS 明朝」、「MS ゴシック」、「メイリオ」はUnicodeに完全対応していません。カバーしているのはJIS 2004規格のみだそうです。「Arial Unicode MS」はUnicode 2.1規格に対応しているそうです。http://edn.embarcadero.com/jp/article/38786

*4:GDIのフォントリンクはWPF/DirectWriteの合成フォントよりも精度や柔軟性が低く、文字によっては適切なフォントにマッピングされず正しく表示できないということがときどきあります。今回の例でいうとあずきフォントおよびみかちゃんフォントの未対応文字がマッピングされず空白のままになっています。