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

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

あの、キョーマさん……?


【DimensionW】「あの、キョーマさん……?」イラスト/sygh[sai] [pixiv]

2016年に入ってもう2か月ほど経ちますが、ようやく今年初めての更新になります。「DimensionW」から、百合崎ミラちゃん。このくらいだったらR18タグ付ける必要はないかな……と。DimensionW面白いですね。OPはなんだコレ!?って感じなんですがEDと対になっていて、双方ともめちゃくちゃよく動きます。

今期は面白いアニメが多くて取捨選択に苦労しました。SFは基本観る人ですが、ゲームやライトノベルからアニメ化される作品よりもコミックからアニメ化される作品のほうが好きかもしれません。DimensionWは攻殻機動隊に通じるものがあり、情報化社会に一石を投じるテーマが主軸になっています。アニメ第4話と第5話は難解で、たぶん1回観ただけでは伏線を見逃してしまうと思います。そもそもお風呂シーン→異空間転送→タオル1枚で逃走→鎖で拘束、の流れがインパクトありすぎて話が頭に入ってこないのも一因なんですが……

ちなみにDimensionWではおそらくもうひとつの空間軸を百合崎博士が発見・Wと定義し、コイルによって空間をねじ曲げることで「可能性」という名のエネルギーを取り出すという設定なんだと思いますが、実は我々の世界はすでにX, Y, Zに時間軸tを含めた4次元空間であり、理系人間的には「第4の次元」というのは普通時間軸tを指します。実際「可能性」というのは時間にも深い関わりがあるので、SF的にもかなり興味深い設定だと思います。

自作MetasequoiaプラグインをGitHubで公開しました

かつて2007年~2008年頃、こっそり自分のためだけに開発したMetasequoiaプラグインソースコードがHDDの肥やしになっていたので、Gitの練習も兼ねてGitHubに移管・オープンソース化しました。ライセンスはMITです。

今回ソースコードを公開したのは下記のプラグインです。すべてUV編集関係のミニツールになります。

プラグイン 機能概要
SyghMQUV2SVG.dll UV展開図をSVGファイルとして出力
SyghMQUVBBox.dll UVを境界ボックスベースで数値入力変形
SyghMQUVImplant.dll UVをオブジェクト間で移植
SyghMQUVMatrix.dll UVをSRT行列ベースで数値入力変形
SyghMQUVSelectUtil.dll UV選択操作のユーティリティ

Metasequoia 3/4用のビルド済みバイナリ(32bit/64bit)は下記からダウンロードできます。ヘルプ・取説としてテキストファイルも同梱しています。
github.com

とはいえ、オリジナルはC++を使い始めてまだ2年かそこらのときに書いたコードであり、今改めて見直すとそれはもうひどい有様でとても人様に見せられるレベルではなかったので、Visual C++ 2013とMFCを使って最大限書き直しました。VC2015はCRTに致命的なバグを抱えているため*1、今回は見送っています*2。公開したソースコードはいずれも規模がとても小さいので、MFC標準DLLやC++11規格の入門としては良い教材になるかもしれません。MFC自体すでに枯れた技術で、真新しさや面白みはほとんどありませんが……

なお、今回の公開はあくまでソースコードの共有とメンテナンスが目的です。リポジトリのreadmeやWikiにも記載しているように、新機能の開発に関しては今後行なう予定はありません。理由は察してください。

Metasequoiaの今後

Metasequoiaに関しては3DCGを始めた学生の頃からお世話になっていました。直感的なモデリングに特化したツールで、比較的安価であるにもかかわらずプラグインによる拡張性が高く、果てはボーンやモーフによるアニメーションまでMetasequoia上で実現する強烈なプラグインKeynote」が出現したことにより、当時のホビーCG界隈は最高潮に達していたのではないでしょうか。現役ゲームデザイナーによるHow-to本も多数出版されていたと記憶しています*3。ただ、Metasequoia 4の開発ロードマップをめぐってその後Keynoteの開発者mqdl氏との軋轢が深まるなど、今は全体的に活気がなくなっているような印象を受けます。これまでユーザー開発者から提供・公開されていたおなじみのプラグインも、32bit版Metasequoiaでしか使えず、64bit版Metasequoiaに対応するためにはプラグインの64bit化が必要となるにもかかわらず、一向に64bit対応が進んでいないどころかMetasequoiaプラグイン開発自体が縮小傾向にあるように思えてなりません。かつてプラグインを公開していたサイトが閉鎖されてしまったケースも多々あります。
また、プラグインSDK配布ページに記載されている下記の1文が、さらにプラグイン開発者離れを加速させてしまっているように思います。

「作成したプラグインは自由に配布することができますが、Metasequoia本体 (Standard/EXなど全エディションを含む) に搭載された機能と競合することを目的とした等価なプラグインを作成して配布することを禁止します。」

ほかの3DCGソフトウェアと比べるのはどうかとも思うのですが、このように(たとえ機能が競合したとしても)プラグイン開発に制限を課したり禁止したりするなど聞いたことがありません。プラグインによるクラッキングなどの違法行為や特許侵害は論外ですが、正当な手段と目的で開発されたプラグインを、機能が競合するからといって排除しようとするなど愚の骨頂です。せめて適用対象をもっと明確にしていただきたいところですが、議論は一向に進んでいないようです。何が怖いかというと、せっかくオープンソース化したソフトウェアが鶴の一声で公開停止を余儀なくされるというのは個人的にも痛いし、またそれ以上にそういった理不尽であいまいな規制はソフトウェアの健全な発展に悪影響を及ぼすことが予想されるからです*4

Metasequoiaはその後もバージョンアップを重ね、レイトレーシングやボーン/モーフまでをも独自に実装するなど、これまでプラグインによって実現されてきた機能を着実にネイティブ実装しつつあるようですが、このまま行くとコミュニティ総体としては先細りになってしまうのではないかと危惧しています。すでに現在のMetasequoiaは、2010年頃までのメインユーザー層が想定していた方向性とは違う向きに舵を切りつつあるように思います。ユーザーが欲しがっている(ように見える)ものをただ与えることがクリエイターやエンジニアの仕事なわけではないことは確かですし、個性全開でがむしゃらに突っ走った結果が最終的にユーザーのニーズにマッチして成功するという例は往々にして存在しますが、あえて余計な機能の追加をすることなくシンプルであり続ける、そして足らないものはコミュニティに任せる、というのも一つの選択肢なのではないか、と自分は思います。

閑話休題。とりあえず自分は今後どうするのかというと、LightWaveを持っているんだからさっさと使いこなせるようにがんばります。Metasequoia向けに実装したこのプラグインも、せっかくなのでLightWaveプラグインとして実装し直してみようかと考えています。

*1:Visual Studio 2015 / 2017 で発生する可能性がある _snscanf_s 関数の問題について | Microsoft Docs

*2:VC2015で導入されたUniversal CRTにおけるバグが原因で、MFCDDXが正常に機能しません。Windows SDK 8.1を利用し、CRTをスタティックリンクするという条件の両方に該当する場合、VC2015以降は使わないほうがよいです。

*3:Keynoteでのアニメーションは、まるで日曜大工の道具で人が住む家を建てるようなものです。無償のプラグインに依存しすぎるということは、プラグイン開発者の気分次第で開発が停滞・終了したときに技術が廃れたり陳腐化したりする危険性もあり、できれば避けるべきバッドノウハウなのですが、使い慣れたツール上で制約を乗り越えながら別次元の高度な機能を実現すること自体は素晴らしいチャレンジだと思います。

*4:同様に、ソフトウェア特許というものに対しても自分は懐疑的というよりむしろ反対派です。かつて松下電器産業がアイコン特許(笑)を傘にジャストシステムワープロソフト「一太郎」を訴えた問題がありましたが、当時所属していた研究室の助教授は「松下はナンセンス極まりないね」と強く批判していました。

bashでGitのファイル名を一括置換

Git for Windowsインタラクティブシェル(コマンドラインインターフェイス)として付属するbashで、文字列置換を使ってgit mvを一括実行してみよう、という話です。

例えば

./Projects/MyClass1.cpp
./Projects/MyClass1.h
./Projects/MyClass2.cpp
./Projects/MyClass2.h
……

というように命名されたファイルがGit管理下に多数存在したとして、これらを

./Projects/MyInternalClass1.cpp
./Projects/MyInternalClass1.h
./Projects/MyInternalClass2.cpp
./Projects/MyInternalClass2.h
……

というように一括でリネームしたいとき、1つ1つgit mvしたり、TortoiseGitなどのGUIフロントエンドからいちいちファイル名を置換したりするのは気が遠くなるくらい退屈な作業です。

そういうときはbashでループを書きます。

for i in $(find . -iname "MyClass*"); do git mv "$i" "${i/MyClass/MyInternalClass}"; done

実行前に確認してみたいときはechoを入れてみます。

for i in $(find . -iname "MyClass*"); do echo git mv "$i" "${i/MyClass/MyInternalClass}"; done

さらに複雑な置換をするには正規表現を使うとよいでしょう。

Visual Studio 2015とD3DCompiler

Visual Studio 2015でビルドした、DirectX 11.1を使ったMFC/WPF混合アプリ (.NET 4.5.2) をWindows 7エクスプローラーから起動すると、起動直後に勝手に終了する現象が出ました*1
しかし、終了時にWindowsからクラッシュエラーのタスクダイアログも表示されないし、イベントログにも何も記録が残りません。
Windows 8.1では普通に起動します。また、Windows 7でも、Visual Studio 2015 IDEからデバッグ実行した場合は普通に起動します。

調査

まずPowerShell経由で起動して、終了コードを取得してみました。

$result = Start-Process -FilePath "<DX11AppName>.exe" -PassThru -Wait
Write-Host $result.ExitCode.ToString("X8")

結果は「C0000135」となりました。
調べてみると、どうもこのコードはWindows OSの場合「Unknown Hard Error」で、アプリケーションの場合「Unable To Locate Component」らしいです。

DLL依存関係が怪しいと感じたのでDependency Walkerで調べてみたところ、D3DCompiler_47.dll勝手にリンクされていました。

Windows 7の場合、Visual Studio 2012や2013/2015をインストールしても、System32やSysWOW64にはD3DCompiler_46.dllやD3DCompiler_47.dllはインストールされません。これらは「DirectXエンドユーザーランタイム」にも含まれていません。
Windows SDKDirectX SDKが統合されたWindows SDK 8.0以降では、D3DCompilerのランタイムは

%ProgramFiles(x86)%\Windows Kits\8.0\bin\x86
%ProgramFiles(x86)%\Windows Kits\8.0\bin\x64
%ProgramFiles(x86)%\Windows Kits\8.1\bin\x86
%ProgramFiles(x86)%\Windows Kits\8.1\bin\x64
%ProgramFiles(x86)%\Windows Kits\10\bin\x86
%ProgramFiles(x86)%\Windows Kits\10\bin\x64

といった場所にインストールされます。スタンドアロンのHLSLシェーダーコンパイラーであるfxc.exeと同じ場所です。ただし、ARM版やARM64版のDLLは存在しないようです。
Visual Studio IDEからの起動時には、この場所にパスを通すのでDLL解決できるんですが、エクスプローラーからの起動時にはDLL解決できなくて強制終了させられる模様。

Windows 8の場合、D3DCompiler_46.dllは標準コンポーネントとしてシステムフォルダーにインストールされていません。また、Windows 8のストアアプリでは、D3DCompilerの使用が認められていませんでした。
Windows 8.1の場合、Windows 8.1版のD3DCompiler_47.dllが標準インストールされています。
Windows 10の場合、Windows 10版のD3DCompiler_47.dllが標準インストールされています。
また、Windows 8.1ストアアプリおよびWindows 10ユニバーサルWindowsプラットフォーム (UWP) アプリでは、D3DCompilerの使用が認められるようになりました。
このため、Windows 8.1あるいはWindows 10でD3DCompiler_47.dllを利用する場合、デスクトップアプリでもストアアプリでも、同DLLをアプリに同梱する必要はありません。

ちなみに、Windows SDK 8.1に含まれるD3DCompiler_47.dllのバージョンは「6.3.9600.16384」です。
一方、Windows SDK 10 (10.0.10240.0) に含まれるD3DCompiler_47.dllのバージョンは「10.0.10240.16384」です。
ファイルサイズも異なるし、SDK 10版のほうはDirectX 12/11.3 API対応(シェーダーモデル5.1対応)が追加されているので、前方互換性はありません。さらに、システムフォルダーにインストールされているD3DCompiler_47.dllは、Windows Updateか何かで更新されているらしく、SDK同梱版とは末尾のパッチ番号(リビジョン番号)が微妙に異なるようです。ていうかこれまで通り新しいWindows 10バージョンではファイル名のナンバリングを更新しろよ……

結論

DirectX 11を使ったデスクトップアプリケーションをビルドし、Windows 7/Windows 8.x向けにも配布する場合、DLL依存関係には十分注意したほうがよさそうです。
D3DCompiler_47.dllをリンクした場合、Windows SDK 8.1/10に含まれる同DLLをアプリケーションに同梱する必要があります。D3DCompilerランタイムはレジストリ登録を必要とするCOMライブラリではなく、ただのC言語形式関数がエクスポートされているだけのDLLみたいです。つまりシステムに影響を与えることなく、アプリごとのプライベートアセンブリとして同梱できます。
もちろんDirectX 12/11.3はWindows 7/Windows 8.1にはバックポートされないので、当然それらの機能を使った場合はアウトですが、DirectX 11.1までの機能しか使わず、またD3DCompilerを明示的にリンクしない場合でも、VS2015ではD3DCompilerが勝手にリンクされることがある模様。かなり迷惑な仕様変更です。ちなみにx86/x64版のD3DCompiler (lib/dll) はSDKに同梱されていますが、ARM/ARM64版に関しては*.libのみで、*.dllは同梱されていません。

通例、アーリーバインドしたDLLの解決ができない場合は、アプリ起動時に「DLLが見つからない」という旨のエラーメッセージが出るはずなんですが、今回はメッセージが表示されることなく勝手に終了してしまい、イベントも記録されなかったこと、そしてWindows 8.1には同名のD3DCompiler_47.dllが存在し、エクスプローラーから実行してもDLL解決だけはできるようになっていたことが、エラー原因の発見の遅れにつながりました。

2018-02-07追記:
GitHubに登録する前の過去のソースコード履歴を調べてみたところ、開発環境をちょうどVS2013からVS2015に更新する少し前に、D3DCompilerを使ってシェーダーリフレクション機能をテストするコードを書いていたんですが、その際にd3dcompiler.libを明示的にリンクしており、テストが終わってもそのまま無効化するのを忘れていたことが原因だったようです。少なくともVS2015の仕様変更ではなかった模様。お騒がせしました。普段からD3DXやD3DCompilerを使わないように注意していたんですが……
なお、.NET 4.7ではWPFがD3DCompiler_47.dllに依存するようになったらしく、Windows 7の場合は.NET 4.7本体をインストールする前に、KB4019990をインストールしておくか、最新のWindows Updateを適用しておく必要があるようです。依存コンポーネントがあるならば.NETのインストーラーが自動でインストールするべきだと思うんですが。また、.NET 4.7のWPFには潜在的な不具合が多々あるようです。.NET 4.7.1で問題が解消されているかどうかは不明です。

*1:Windows 7 SP1はIE10/IE11などと同時にインストールされるPlatform UpdateプログラムKB2670838を適用することで、DirectX 11.1 APIを使用できるようになります。

XAudio2とMedia FoundationでMP3/WMA再生

経緯

以前、DirectXの学習を兼ねて開発していたゲームエンジンC++で様々なDirectX APIを直接叩いていました)では、効果音 (SE) の再生にXACT3を、BGMの再生にDirectShowを使っていました。本当はBGMにもXACT3を使ってもよかったのですが、MP3/WMAフォーマットの音楽ファイルを直接ストリーム再生したかったのでDirectShowを使っていました。ちなみにMIDIを扱っていた頃は、SE/BGMともに知る人ぞ知る太古のAPI、DirectMusicを使っていました*1

ですが、Windows 8で導入されたWindowsストアアプリ(のちのWindows 10におけるユニバーサルWindowsプラットフォームアプリ)ではXACT3もDirectShowも使えなくなりました。デスクトップアプリではともに一応使えますが、Windows SDK 8.0以降ではXACT3は廃止されてしまっています。

Microsoftが推奨している新しいオーディオ系の代替APIはいくつかあります。これらはデスクトップアプリでもWindowsストアアプリでもほぼ同様に使えます。一部はXboxとも共通のAPIとなっています。

  • Windows Core Audio (Windows Vista以降における新しいオーディオ基盤)
  • XAudio2 (DirectSoundの後継)
  • Media Foundation (DirectShowの後継)

Core AudioにはWASAPIと呼ばれる、高音質な排他モードをサポートするライブラリも含まれています。しかしゲームエンジンに使うのであれば、同時発音の処理は必須であり、たとえ音質を犠牲にしてもミキシング機能をとる必要があるため、結局XAudio2を使わざるを得ないようです。

最初に候補として検討したのは「DirectX Tool Kit (DirectXTK, DXTK) for Audio」です。DXTK AudioはXAudio2を使ったXNAライクの簡易音声再生補助ライブラリなのですが、どうもこのライブラリはWAVフォーマット (RIFF) にしか対応していないようです。DXTK AudioはまたWaveBank (xwb) にも対応しているのですが、XACT3やXNAで使用可能だったXMA/xWMA圧縮フォーマットには対応していません。つまりXACT3/XNA時代の資産はほとんど使えないといっても過言ではないでしょう。尺が比較的短いSEの再生には使えますが、BGMの再生となるとやや微妙です。また、ポーリングベースではなくイベントベースで実装されていることにも注意が必要です。

XAudio2は、実はWMA (xWMA) 圧縮フォーマットのオーディオデータをデコードする機能を標準で備えているようです。旧DirectX SDK June 2010に付属するxWMAEncode.exeなどを使えば、ゲーム用途におけるBGMファイルのサイズ問題は一応クリアできるでしょう。ただし、Windows 8.xに標準搭載されているXAudio2 v2.8では、xWMAはサポートされていません。Windows 10に標準搭載されているXAudio2 v2.9では、xWMAのサポートが復活しているようですが、xWMAを使い続けるのはどうも微妙な感じがします。

Media Foundation

かつてのDirectShowのように、MP3/WMAフォーマットのメディアファイルを直接ストリーム再生できるようにするには、それらのフォーマットに対応したデコーダーが必要となります。そこで出番となるのが、Media Foundation。やはりDirectShowの後継だけあって、MFが出てくるのは必然ともいえます。

MSDN Magazineに、WindowsストアアプリでXAudio2とMedia Foundationを使ってストリーム再生をするサンプル(C++/CX)が掲載されていました。

ただしこのMSDNサンプルは、オーディオプレイヤーの3大必須機能(Play/Pause/Stop)を実装していないうえにメモリやリソースの寿命管理がgdgdで、とても流用できるような類のものではありません。
まずデスクトップアプリ向けに書き直し、その後で改めてストアアプリ向けに再移植してみました。

サンプルコードは下記2つのソリューション&プロジェクトを含んでいます。DXTK (2015-08-18版) も比較を兼ねてお試しで使っているので、それぞれの環境に合わせたビルドが必要になります。

  1. MFCベースのデスクトップアプリ。ビルドにはVisual Studio 2015 Pro/Community以上が必要。
  2. Windows 8.1用のストアアプリ。ビルドにはVisual Studio 2013 Pro/Community以上が必要。

https://sygh-jp.github.io/content_hosting/my_program_ss/MyMfcDXTKAudioTest1_ss_2015_09_23a.png
https://sygh-jp.github.io/content_hosting/my_program_ss/MyWin81DXTKAudioTest1_ss_2015_09_23a.png

Windows 8.1ストアアプリではなく、Windows 10向けのUWPアプリにしてもよかったんですが、まだまともなWindows 10開発環境を準備できていないので今回はあきらめました。フルスクリーンだと上下左右の余白が寂しい感じです。
デスクトップ版はWindows SDK 10を使っていますが、ターゲット環境をWindows 10に設定していないため、XAudio2 v2.9 (XAudio2_9.dll) ではなくv2.8 (XAudio2_8.dll) がリンクされます。Windows 7あるいはそれ以前のWindowsで動かすためには、旧DirectX SDK June 2010を使ってビルドし、XAudio2 v2.7 (XAudio2_7.dll) をリンクする必要があります。
※2016-03-17追記:
SharpDX 3.0.2を使ったWPFサンプルも追加しました。XAudio2 v2.7が使われるため、DirectX SDK June 2010もしくはエンドユーザーランタイムを導入すればWindows 7でも実行できます。今回はC++版のクラス設計をほぼそのまま移植しているため、一部C#らしくないコードになっていますが意図的なものです。
※2018-02-26追記:
Windows 10向けUWPアプリのサンプル (VS2015) も追加しました。Windows 8.1ストアアプリとほぼ同じです。

なお音声サンプルとして、「魔王魂」さんからダウンロードさせていただいたフリー素材を使用しています。音声ファイルの利用は下記サイトの利用規約に従ってください。

コードの二次利用は自由ですが、自己責任でお願いします。仮に損害が発生したとしても、当方は一切の責任を負いません。
なお、とるに足らないテスト用アプリではありますが、コレをこのままストアに提出することは絶対にやめてください。コレはあくまでサンプルコードです。そのまま提出してもたぶん審査で落ちるだけだと思いますが、もしコレをベースにして似たようなアプリを作成する場合、必ず何らかの独自機能をきちんと付加した上で、Package.appxmanifestのPublisherとPublisherDisplayNameをきちんと更新してください。この約束が守られていないアプリを発見した場合、しかるべきところに通報させていただきます。

C++/CX

簡単のため、ストアアプリ版のコードではMVVMは使っていません。MFC同様にコードビハインドを書いているだけなので、対比は楽だと思います。そのほか、ストアアプリ版はバックグラウンド再生にも対応させていないので、アプリが非アクティブになると再生が一時停止します。
しかし久しぶりにC++/CXを使ったんですが、PPL(特に例外処理)がめんどくさすぎて途中死にそうになりました。C++/CX版PPL (WinRT版PPL) は通常のC++版PPL (デスクトップ版PPL) と違って、C#のasync/awaitのようにタスク完了時に呼び出し元スレッドへのコンテキストの復帰を行なってくれるんですが、例外処理はどのみち煩雑すぎです。タスクチェーンの仕組みもよく考えられているとは思うんですが、やはり記述性や可読性の向上のためにresumable/awaitが欲しいところです。Visual C++ November 2013 CTPやVisual Studio 2015では実験的にresumable/awaitが実装されていますが、/awaitコンパイルオプション(/sdlや/RTCと共存できない*2*3)を明示的に指定する必要があったりして、まだ完成にはほど遠いようです。また、C#のasync/awaitとは違い、GUIアプリケーションのイベントハンドラーを気軽に(同期的に)書けるようになるという類のものではありません。やはりGUIフロントエンドはC#で記述するのが今のところベストだと思います。

ちなみにSharpDXにもXAudio2とMedia Foundationのローレベルなラッパーがあるのですが、リソース寿命管理や例外処理のことを考えると、世代別GCベースのC#から参照カウントベースのCOMコンポーネントを使うのはかなりしんどいです。Javaと違ってC#はある程度ローレベルな記述も可能な言語ではありますが、COMに関してはやはりC++のほうが親和性が高いです。DirectX全般にも該当しますが、もしC#でCOMコンポーネントを扱いたい場合、C++/CLIC++/CXでラップして、独自の上位レベルI/Fを公開するライブラリを作成したほうがかえって効率的だと思います。

*1:単純に音楽ファイルを再生するだけであれば、WinMMやMCI (Media Control Interface) 関数、WMP (Windows Media Player) コンポーネント、あるいはWPF/WinRTのような上位レベルAPIを利用するのが簡単なのですが、ゲーム開発で利用するとなるとパフォーマンス上の理由からDirectXファミリー一択になります。

*2:/sdlに関してはVisual Studio 2015 Update 2で改善されたようです。Visual Studio 2015 Update 2 | Microsoft Docs

*3:/RTCに関してはVisual Studio 2017で改善されたようです。Visual Studio 2017 15.0 リリース ノート | Microsoft Docs

Direct3D 12 (DirectX 12) の簡単なサンプル

(本記事の内容は執筆時点の暫定仕様に基づくものです。実際の最終製品版とは一部異なる部分があります)

GDCの開催された2015年3月にはすでにDirect3D 12 (DirectX 12) の暫定ドキュメントとVisual Studio 2015 CTPが公開されていたのですが、肝心の実行環境であるWindows 10評価版用のWDDM 2.0対応ドライバーがWindows Update経由でしか取得できず、また新しいWUはプロキシ経由で正常に動作しない現象が出ていたため、Direct3D 12の評価はしばらく見送っていました。
その後、4月末にWindows 10 Insider Preview Build 10074、Visual Studio 2015 RC、そしてWindows 10向けのNVIDIA GeForceベータ ドライバーのスタンドアロン版が相次いでリリースされたため、Direct3D 12の検証を再開することにしました。

日本語版Windows 10 Build 10074 (64bit) 上でdxdiagを実行してみたところ、確かにWDDM 2.0対応になっているようです*1。検証に使ったグラフィックスカードはNVIDIA GeForce GTX 770 4GB版です*2
https://sygh-jp.github.io/content_hosting/software_ss/windows10ip_build10074_dxdiag_ss01.png

Visual Studio 2015 RCでDirectX 12プログラミングを行なう場合、インストール時に[ユニバーサル Windows アプリ開発 ツール] - [ツールおよび Windows SDK 10.0.10069]にチェックを入れておく必要があります。デフォルトの設定ではインストールされないので、初回インストール時にチェックし忘れていた場合は、[プログラムと機能]からVisual Studioの更新インストール機能を使って追加します。今回はデスクトップ アプリ上でさくっとテストしたかったので、フレームワークにはおなじみのMFCを使っていますが、MFCもデフォルト設定ではインストールされないので、[Microsoft Foundation Classes for C++]にもチェックを入れておきます。
https://sygh-jp.github.io/content_hosting/software_ss/vs2015rc_setup_mfc_sdk10_ss.png

サンプルプログラムは下記です。MSDNの暫定ドキュメント「Creating a basic Direct3D 12 component」に書かれてある断片コードを参考にしていますが、現時点では暫定だけあって実際のSDK実装と微妙に異なっていたりするので苦労しました。また、Windows SDK 8.0で廃止されたと思われていたD3DXが、今度はC++ヘッダーベースのヘルパーとして復活するようですが、Visual Studio 2015 RCにはまだ <d3dx12.h> は含まれていないようでした*3

https://sygh-jp.github.io/content_hosting/my_program_ss/MyMfcSimpleD3D12Test1_ss_2015_05_14a.png

※2017-01-09追記:
サンプルのプロジェクトファイルをVisual Studio 2015 Update 3にてビルドできるように修正しました。Windows 10 November 2015 Update (version 1511, build 10586) とNVIDIA GeForceドライバー358.91にて動作確認しています。

今回はとりあえずD3D12コアAPIの雰囲気をつかむため、トライアングルを描画するだけの簡単なコードにしました。ただしPipeline State Object (PSO) の切り替え方法(ID3D12GraphicsCommandList::SetPipelineState()メソッド)をテストするために、下記2つのGraphics PSOを作成して、複数描画しています。

  • 図形の回転なし、ブレンディングなし
  • 図形の回転あり、ブレンディングあり

今後はコンピュート機能の非同期実行効率がどうなっているのか、またWindowsストアアプリ(XAML連携)やDirect2D/DirectWriteとの相互運用に関しても調査する予定です。どうもD3D12デバイスからはDXGIデバイスインターフェイスが取得できなくなっているため、D3D11デバイスとの相互運用機能(D3D11On12CreateDevice()関数など)を経由する必要があるようです。

Direct3D 12 APIを触ってみた感想

今回のサンプルの目的自体は単純とはいえ、Direct3D 12はMantleのようにハードウェアに近いローレベルAPI*4を標榜しているだけあって、ユーザーコードの記述量はDirect3D 11までと比べて格段に増えています。特に難しいと感じたのがDescriptor Heapまわりです。一方、Direct3D 11ではID3D11VertexShader, ID3D11PixelShaderや、ID3D11Buffer, ID3D11Texture2Dなどのようにシェーダーステージやリソースの種類ごとに細かくインターフェイスが分かれていたのですが、Direct3D 12ではID3D12PipelineStateやID3D12Resourceのように統合・統一され、結果としてCOMインターフェイスの数はD3D11よりも少なくなっています。これによってD3D12ではオブジェクトやリソースの初期化が統一的に行なえるようになっているのですが、ID3D12Device::CreateGraphicsPipelineState()やID3D12Device::CreateCommittedResource()といった生成メソッドはパラメータ(に使う構造体のメンバー)が非常に多くなっていて大変です。また、これまでAPIおよびドライバー層が面倒をみてくれていたCPUとGPUの同期も、ユーザーコードで明示的に記述する必要があります。

なおDirect3D 12 with Visual Studio 2015 RCのサンプルとして、いくつかの実践的なテストプログラムを下記GitHubにアップロードされている日本人の方がいます。こちらも参考になるでしょう。

Direct3D 10以降、固定機能シェーダーが廃止されてプログラマブルシェーダーが必須となり、Direct3D 9のようなお手軽感はなくなっていたものの、単に3Dグラフィックスを描画するだけであれば、アプリケーションのコーディングが死ぬほど大変というほどのものではありませんでした。ハードウェアの限界性能を引き出して最大限最適化しなければならないというような厳しい要求がなければ、Direct3D 11は十分に完成形と言えるAPIで、Direct3D 9ユーザーでも時間をかければ確実に習得して使いこなせる易しいAPIだと思います。しかし、Direct3D 12は実行効率の向上を至上目的とした、完全に玄人向けのAPIです。ゲームエンジン設計者ならばいざしらず、純粋にアプリケーションコードのほうに注力したいであろう標準的なグラフィックスプログラマーが手を出せるシロモノではありません。おそらくVulkanもDirectX 12同様の玄人志向APIとなることでしょう。UnityやUnreal EngineはすでにDirectX 12への対応を表明しているため、単に3Dグラフィックス プログラミングをやりたいだけであれば、そういったミドルウェアを利用するか、DirectX 11.xやOpenGL/OpenGL ESを使い続けるのが賢明だと思います。

Windows 10に思うこと

先日、Windows 10が最後のWindowsメジャーバージョンアップとなることがマイクロソフトによって正式に言及されましたが、それにしてはOSの完成度が異様に低いように感じます。まだ評価版の段階ではありますが、7月末リリースを目標としているらしい状況にもかかわらず、現時点でGUIの設計にかなり粗が目立ちます。自分はPC主体のWindowsユーザーですが、ぶっちゃけ全体的にWindows 8.1のほうが使いやすいですし、タイトルバーやフォント、アイコンのデザインも8.xのほうが良いと思います。現状、Windows 10のタイトルバーはアライメントや配色のバランスが悪くて安っぽく、また日本語版の新たなシステムフォントとして使われている細身のYu Gothic UIフォントはClearTypeの調整不足もあるせいか貧弱・低品質な印象を受けます。ていうかおためごかしにフォントを変えるよりも、まずフォントレンダリングエンジンの品質をAppleAdobe並に強化するべきでしょう。また特に今回はタスクバーまわりに頻繁に手を入れているらしく、最もユーザーが使う機能でありながら評価版ではバグの温床となっているように見受けられます。そのほか、Windows 7で機能的にも充実し、完成形と思われていた、おなじみのデスクトップ版の電卓アプリ(calc.exe)が廃止され、大味なストアアプリ版の電卓アプリに一本化されたのも地味に悲しいです。

DirectX 12はおそらくWindows 7/8.xにはバックポートされず、結局Windows 10以降専用の機能となるように思われるのですが、最終製品版ではもっともっとOS全体の完成度を上げてくれないと、DirectX 12のためにアップグレードする気には到底なれません。
それにしてもフィードバックは専用のInsider Programに参加させるのではなく、MS Connect経由でも提出できればいいのに……

*1:ちなみにWindows 10上でもWindows 7/8/8.1向けのWDDM 1.1/1.2/1.3ドライバーを使うことはできるようです。2014-07-29にリリースされたNVIDIAの安定ドライバー340.52をインストールしたWindows 8Windows 10 TPにアップグレードすると、WDDM 1.3になっていました。ただしWDDM 1.x環境ではD3D12を使用することはできず、D3D12CreateDevice()関数がDXGIエラー (DXGI_ERROR_UNSUPPORTED, 0x887A0004) で失敗します。

*2:Kepler世代はいずれも機能レベル11_0になります。

*3:Direct3D 12ドキュメントの正式版「Helper Structures and Functions for D3D12」によると、d3dx12.hはGitHubにて補助ライブラリとして分割管理されているようです。また、正式版のVisual Studio 2015に含まれているDirectX 12のUWPアプリケーションプロジェクトテンプレートにも、d3dx12.hが含まれているようです。

*4:low-levelは日本語で「低レベル」「低水準」と訳されることが多いのですが、日本語で「低レベル」「低水準」というと、「低俗」「下等」といったイメージを与えかねないので、自分は「ローレベル」あるいは「下位レベル」と表記することが多いです。

GPGPU戦争の歴史を紐解く ―勃発から現在に至るまで―

GDC 2015でついにOpenCL 2.1が発表されました。SPIR-V中間表現(中間言語バイトコード)でプログラミング基盤をVulkan API (OpenGL Next Generation, glNext) と共有することができるそうです。これでカーネルをオフラインコンパイルできるようになります。OpenCL 1.2/2.0にはもともとSPIR 1.2/2.0という中間言語がすでに存在していたのですが、コア機能ではなく拡張機能扱いで、ベンダーごとに対応がまちまちでした。SPIR-Vは晴れてOpenCLのコア機能となります。個人的にはカーネル記述言語にC++14ベースが導入されることになったのが嬉しいですね*1。テンプレートやラムダ式が使えます(特にテンプレートは渇望していた機能)。というかこれまでのC99/C11ベースのOpenCL-Cはあまりにプログラマビリティが低すぎました。CUDAはもちろんHLSLにも後れを取る始末で、正直GLSLと大差ありません。OpenCLカーネルにおけるC++採用は、C言語がすでにアプリケーション記述の主力言語としては時代遅れとなっていることを象徴しているのではないでしょうか。

そのついでといってはなんですが、今日はGPGPUの発展・普及の歴史の中でも最大の功労者達と思われるAMD (ATI) とNVIDIAの戦いのうち、まずはDirectX 10出現の前後からDirectX 11 (DirectCompute) 出現までの時間軸におけるデファクトスタンダードの存亡をかけた攻防戦を見ていくことにします。

DirectX 10世代から11世代に至るまでのお話

2015年現在、GPGPU基盤技術として知名度が高いのは圧倒的にNVIDIA CUDAのほうで、AMD StreamとかAMD APP (AMD Accelerated Parallel Processing) とか何それ食えるの?という有様です。後から出てきたDirectComputeにすら負けています。

しかし実はATI (AMD) のほうがNVIDIAよりもGPGPU対応製品の投入時期が先だったとか倍精度対応が先だったとかいう情報があります。
前々から真偽のほどが気になっていたので、その裏付けをやってみることにします*2

GPGPU対応製品の投入

ATIRadeon X1900(Direct3D 9世代、統合型シェーダーアーキテクチャではない)上でのGPGPU基盤技術として2006年10月に「Stream Computing」を実現・実用化しているので、確かにNVIDIA CUDA(2006年11月発表、2007年7月リリース、統合型シェーダーアーキテクチャ必須)よりも先になる。
ATI Stream Computingの発表は、奇しくも統合型シェーダーアーキテクチャを初めて採用したXbox 360Xenos供給開始後。

ちなみにAMDによるATI買収は2006年10月に完了。買収騒ぎでドタバタしている中、技術者達は普段通り頑張っていたらしい。

しかしAMD FireStream 9170よりも前のストリームプロセッシング(GPGPU)向け製品(第1世代)が見つからない。英語版Wikipediaの情報が確かならば、ATIの初代GPGPU向け製品はRadeon X1900ベースのFireStream 2U (R580) とかいうヤツらしい?
(ちなみに日本語版のWikipediaはほぼ英語版のコピーで、たいした情報はない)

しかし「FireStream 2U」で検索しても、いつリリースされたのかまったく不明。オークションや中古品の取引情報以外はまともな情報がまったく引っかからない。

実は初代FireStreamは「AMD Stream Processor」という名称だったらしい。2006年11月発表。この子を誰か覚えていますか……

一方、G80ベースの初代NVIDIA Tesla C870は2007年6月発表。確かにGPGPU向けデバイスを発表したのはAMDのほうが先(Direct3D 10とCUDAに初めて対応したGeForce 8800は2006年11月発表だが、GPGPU向け製品ではない)。

倍精度対応

ゲームのようなリアルタイム3Dグラフィックスでは倍精度浮動小数点数が必要な場面はほとんどなく、特にGPUが担当する画面表示に関しては単精度浮動小数点ですべてまかなえる(CADに関しては話は別だが、内部演算精度と画面表示精度はまた別物)。
一方、科学技術計算においては、倍精度対応は必須であり、倍精度対応の出来が汎用計算に使えるかどうかの分かれ目になる。

調べてみたところ、倍精度対応も確かにNVIDIA Tesla C1060(2008年7月発表)よりAMD FireStream 9170(2007年11月発表)が先らしい。

しかもTesla C1060は単精度に比べて倍精度演算がすさまじく遅かった。それもそのはず、倍精度演算ユニットはSM (streaming multiprocessor: 複数のCUDAコアを束ねるユニット) に対して1個だけであり、まともに積んでいない。
NVIDIAが本気で倍精度対応するのはFermi世代から*3。ただしハーフレートの倍精度を実装したのは後にも先にもFermiのみで、その後にリリースされたKepler、Maxwellでは倍精度のハードウェアサポートが削られている。

余談:その名はCUDA ―APIの使いやすさと普及性―

下記は古い記事だが、医療分野でCUDAを利用したアプリケーション開発者へのインタビュー(特にPS3のCellやAMD/ATIのClose To Metalとのプログラマビリティ比較に関する所見)が興味深い。
結局のところ、最後はソフトウェアの組みやすい環境へとアプリケーション開発者は流れるもの。
いくら良いハードウェアを作っていても、ソフトウェア開発技術の発展と普及推進、およびそのための基盤整備を怠ったことがATI/AMDの敗因だったといえる。

AMDGPGPUテクノロジーAPI)は、Close To MetalやATI Stream/AMD Stream、CALやBrook+言語というわけのわからない(というか総じて馴染みの薄い)キーワードばかりが羅列され、ニューカマーには実態や構造がいまいちよく分からず、さらに現在はAMD APPとしてひとくくりに包括されてしまっている始末。
たぶんAMDの広報担当ですら、すべて正確に把握できている人間はいないのではないかと。
このあたりのブランド戦略の一貫性のなさも、GPGPU/HPCにおけるAMD劣勢の一因だと思われる。とにかくAMDGPGPUはふらふらしていて足元がおぼつかない。

NVIDIAがCUDAやTeslaという用語やブランドを継続して使用しているのとは対照的。CUDAのネームバリューはすさまじく、今はとりあえず「CUDA!!」と言っておけば大抵なんでも通じる。技術的知識がない人も「CUDA!!」でだませる。あと固有名詞なので、Web検索性が高く、狙った情報が手に入りやすい*4

そしてOpenCL

実際にはもっと熱くて濃ゆい、漢(オトコ)の戦いが繰り広げられていたと思うんですが、今回のツッコミというか検証はここまでにします。

現在のAMDはとりあえず独自API路線はやめてOpenCL推進に落ち着き、開発ツールの整備(CodeXL)や2.0規格のサポートも進めている模様(しかし肝心の対応SDKは2015年3月時点でいまだにベータ)。

今後のAMDOpenCLと命運をともにすることになると思われます。
上位レベルAPI(言語拡張)であるC++ AMPに関しても、Microsoftと協力してLinux移植を進めるなど、わりと意欲的です(OpenACCを開発したPGIを買収したNVIDIAに対する牽制の意味もあるのかもしれません)。
ちなみにJavaコンパイラをHSA環境向けの中間言語HSAILに対応させて、JavaGPGPUプログラミングできるようにする計画もあるそうです。C++ AMP相当の拡張をJavaに導入する感じになるんじゃないでしょうか。

あとFireStreamは2015年現在、FireProブランドに統一されていて、FirePro Sシリーズとなっています。名前変えすぎです。どおりでFireStreamで検索してもPCI-e 2.0世代の旧製品しか引っかからないわけだ……*5

結論としては、

  • ATI (AMD) が実はGPGPUの先駆者だったという伝説はデマではなく確からしい。
  • 現在のAMD環境向けGPGPUアプリケーション開発者はとりあえずOpenCL、DirectComputeもしくはC++ AMPを使っとけ(AMD独自技術はNG)。
  • ローレベル技術(ハードウェア層)には手を出すな。十分な開発情報が得られないだけでなく、すぐに廃れて過去の遺物になるだけ。もしどうしても使う気ならばそれを覚悟するべし。

なおローレベルAPIといえばMantleですが、Vulkan APIの要素技術として取り込まれることになったとはいえ、単独のMantleの今後はどうも怪しい感じです。

※2018-05-19追記:
AMD APP SDKは2015年8月にWindows版のv3.0が、2016年3月にLinux版のv3.0が正式リリースされたんですが、2018年5月現在、当のSDKを配布していたページが消失してしまっているようです。CodeXLのページも消失しています。確か3月頃まではアクセスできていたはずですが、4月に問題が報告されているにもかかわらず、ずっと放置されています。

ちなみにひっそりとGitHubにコードベースがあるようです。

オープンソース戦略(だけ)は評価しますが、リダイレクトも作らないで404 Not Foundのまま放置なんて、相変わらず仕事が雑なうえに呑気ですね。
AMDはその後ROCmとかHCCとかCUDAモドキのHIPとか始めちゃってますが、CUDAに追随するということはAMD自ら敗北を認めて軍門に下ったということに他なりません。また、残念ながらHIPはCUDAと完全互換ではなく、あくまで類似APIトランスレーターのレベルでしかありません。こんな煩雑でニッチな開発環境、いったい誰が使いたがるんでしょうか。OpenCLを直に叩いたほうがよっぽど楽で、移植性や将来性の点でもマシだと思います。

*1:その後、OpenCL 2.1の正式仕様が発表され、結局C++14の採用は先送りになりました。OpenCL 2.2では晴れてC++14が導入されましたが、2018年2月時点でベンダー実装は遅々として進んでいません。

*2:ちなみに自分がGPGPUに取り組み始めたのは、DirectX 11対応のNVIDIA製品すなわちFermiがリリースされた後だったりします。なので「GPUでもHPC向けだったら倍精度対応とか当たり前じゃん」って感じですね。むしろ倍精度に対応してない時代があったことのほうが信じられない。

*3:FermiのTeslaといえばGTC会場においてでっち上げられたモック(ハリボテ)が有名。

*4:AMDのほうは、BrookはまだしもClose To MetalとかStreamとか一般名詞や形容詞の組み合わせだったりするのでWeb検索性が異様に悪いです。AMDという社名自体もWeb検索性が悪く、Google検索では「AMD」で検索すると「and」もついでにハイライトされるのでノイズが増えます。

*5:グラフィックスカード製品はあっという間に時代遅れの骨董品になります。その速度たるやCPUのそれを凌ぐレベルで、それだけ技術の進歩・進化が激しい分野だと言えます。