この記事はC++ Advent Calendar 2015の20日目の記事です.
昨日はろっさむ(4_mio_11)さんの「つまづいたっていいじゃないか、C++だもの」でした.
I(@wx257osn2)です.また20日です1.
ところで今年はやたらとGPUを燃やそうとする人が多いんですが一体どうしたんですか?2
ネタが被るんじゃないかとびくびくしながら1ヶ月過ごすのは中々つらかったです.
おことわり
当記事ではVS2015を使ってC++ AMPで遊びます.当記事でC++ AMPと言ったらMSVC上でのそれです.
というわけでWindows73以降とVisual Studio 20154,及びDirectX11.0以降に対応したGPUが必要です.
持ってない人は諦めてください.
ちなみに私の環境はWin8とVS2015,GTX780です.
C++ AMPについて
3日目にりやさん(Riyaaaaa)さんが入門記事を書いてらっしゃるのでそちらを読んでください(丸投げ)
気になる記事が
C++ AMPを学ぶ際,やはり1次資料的な位置付けとなるのがMSDNとかMSDN Blogsですが,そんなMSDNの中に面白い記事が.
グラフィックス (C++ AMP)
https://msdn.microsoft.com/ja-jp/library/hh913015.aspx
要約すると,
- 計算バッファとして空間局所性の高い「テクスチャ」を利用できる
-
norm
/unorm
5,short vectorクラス6が提供されている get_texture
とmake_texture
を使うことでD3Dのtexture1d
/texture2d
/texture3d
と相互運用できる7
の3つです.
つまり,GPGPUで2次元テクスチャの各ピクセルに対して何らかの処理をして値を書き込むみたいなことも出来るというわけ.
…あれ?それなんてピクセルシェーダー8?9
というわけで,C++ AMPによるGeneral-Purpose computing on Graphics Processing UnitsをGraphical-purpose computing10に使ってみましょう.
C++ AMPグラフィックスの概要 : short vectorクラスとテクスチャクラス
というわけでまずは件の記事の内容をざざっと.
先におことわりですが,今回はシェーダープログラミングに必要な最低限の内容のみを取り扱っていきます.
簡単のために,多数のオーバーロードやオプションの引数,使わなかった要素については大胆に解説を削っていきますのでご了承ください.
まず,グラフィックス関連の諸々はみなamp_graphics.h
内でconcurrency::graphics
名前空間に入ってます11.
concurrency::graphics::norm
とconcurrency::graphics::unorm
は「5」の補足の通りです.それ以上の詳細はMSDN行ってください.
##short vectorクラス
short vectorクラスはC++ AMPで使える「6」です.
1次元についてはそれぞれの型そのものであり,実際に所謂ベクトルクラスっぽくなるのは2次元以上です.
命名は,T_N
の形になります.int
型2次元ならint_2
,unorm
型4次元ならunorm_4
…という感じです.
これらがconcurrency::graphics
名前空間に属するので,using
等をしない場合にはconcurrency::graphics::int_2
等といった形になります12.
基本的にそこらのベクトルクラスみたいなものですが,特徴的な機能としてスウィズリング式(swizzling expression)があります.
例えば,norm_4
の第0要素と第2要素を取り出したnorm_2
が欲しい…と言ったとき,以下のように記述することが出来ます.
const graphics::concurrency::norm_4 vector = {0.f, .1f, .2f, .3f};
auto vec = vector.xz; //decltype(vec) : graphics::concurrency::norm_2
vector.xz = graphics::concurrency::norm_2{.4f, .5f};
// vector == {.4f, .1f, .5f, .3f}
このように,特定の識別子をメンバのごとく連ねることでアクセスが簡単にできるようになっています.
これら識別子は以下のように対応しています.
アクセス先 | 識別子1 | 識別子2 |
---|---|---|
0 | x |
r |
1 | y |
g |
2 | z |
b |
3 | w |
a |
識別子はxyzw
とrgba
でグループとなっており,これらを混ぜて使うことは出来ません.
vec.xy //OK
vec.xg //NG
また,vec.xx
等のように同じ識別子を連ねることも出来ません13.
そして,concurrency::graphics::short_vector_traits<T>
はshort vectorクラスT_N
に対して(擬似コードですが)大体以下のような定義がなされています.
struct short_vector_traits<T_N>{
using type = T;
static cosnt int value = N;
};
これを用いることでtemplate
を用いつつ処理を分岐したりまとめたり,といったことが可能です.
##concurrency::graphics::texture<T, N>
とconcurrency::graphics::texture_view<T, N>
続いてテクスチャクラスについてです.
texture<T, N>
のT
にはdouble_4
を除く1・2・4次元のshort vectorクラスが,N
には1~3が入り,これによってテクセル形式T
,次元N
のテクスチャが生成されます.
まぁ大体concurrency::array<T, N>
と大差無いです.しかし,唯一大幅に違うのは,texture
クラスには実質的に14writeアクセスが出来ないということです.
texture
のメンバは,operator[]
もoperator()
もget()
も全て値を返します.参照は返してくれません.
あとstd::vector
への変換とかは出来ません.基本的には,テクスチャはアクセラレーター固有のバッファとして扱うことになります.
さて,書き込めないtexture
をどう使えばいいのかというと,concurrency::array_view<T, N>
の如く,concurrency::graphics::texture_view<T, N>
というクラスが存在し,こちらからset()
メンバ関数を通して書き込むことが出来ます15.
texture_view
も概ねarray_view
みたいなものですが,CPUや異なるアクセラレーターへのデータの自動的な移動などは行ってくれません.
テクスチャはあくまでアクセラレーター固有の(ry
##D3Dとの相互運用
さて,C++ AMP単体だと完全に「アクセラレーターに強く紐付けられた速い一時バッファ」でしかないテクスチャですが,D3Dとの相互運用によってただのバッファからGUIやらゲームやらで使える「テクスチャ」に変貌します.
concurrency::direct3d
名前空間には一般的なD3Dとの相互運用性のためのユーティリティ群が,concurrency::graphics::direct3d
にはグラフィックス関連のD3Dとの相互運用性のためのユーティリティ群が入っています.
concurrency::direct3d::create_accelerator_view
関数16にD3DのDeviceインターフェースを投げるとconcurrency::accelerator_view
が返ってきます.
このacclerator_view
を用いて以後のC++ AMPプログラミングを進めることが可能です17.
そして,D3D側で作成したテクスチャと上述のcreate_accelerator_view
にて作成したaccelerator_view
オブジェクトをconcurrency::graphics::direct3d::make_texture
関数16に渡すことで,C++ AMPのconcurrency::graphics::texture
を得ることが出来ます.
これを使えば,C++ AMPでの画像処理に入力画像を渡すことも容易です.
また,C++ AMPで処理したconcurrency::graphics::texture
を渡すことで元になるD3Dのテクスチャインターフェースを返す関数がconcurrency::graphics::direct3d::get_texture
16です.
一応補足しておくと,create_accelerator_view
でD3D Deviceから生成したaccelerator_view
を用いて作成したconcurrency::graphics::texture
であれば,make_texture
を使っていなくともget_texture
可能です.
#C++でシェーダーを書こう
それではようやく本題です.C++のみでシェーダーアートを楽しんでみましょう.
大量の関門
と言いたいところですが,実はそのままシェーダーを書くには問題がメチャクチャたくさんあります.
- short vector用の関数が無い
無いです.float
用の数学関数などはamp_math.h
のconcurrency::fast_math
名前空間に用意されているので,諦めて自分で各次元用に自作しましょう.
ちなみに,min
やmax
,floor
やabs
なども当然無いですが,そもそもvectorとして大変重要なdot
とcross
も存在しません.自作しましょう. -
modが負値を返しうる
メチャクチャ落とし穴ですが,C++の剰余は負値を返すことがあります.
いや,それ自体は別に落とし穴でもなんでもないんですが,問題なのはGLSLのmod
は負値を返しません.
というわけで,実はconcurrency::fast_math::fmod
を直接使うと,剰余として常に正の値が返ってくることを想定したプログラムが上手く動作しないことがあります. - C++ AMPはインデックスの順番が逆
縦横のサイズを指定するconcurrency::graphics::texture
のコンストラクタをよく見てみると,コメントに引数の順番がheight
,width
の順であるとの記述があります.
同様に,concurrency::graphics::texture<int, 2> texture
に対するtexture.extent
によって得られるconcurrency::extent<2>
から取り出したconcurrency::index<2>
は[0]
がy
,[1]
がx
の順に並びます.
よって,x = idx[0], y = idx[1]
のようなコードをうっかり書いてしまうと,解像度が縦横同一でない限りほぼ確実にうまく動かなくなります. - short vectorのコンストラクタが貧弱
特にGLSLのコードを移植する際に弊害となりえます.1次元,2次元,1次元の3つのshort vectorをコンストラクタに渡して4次元のshort vectorを作る…といったことがそのままでは出来ません. - 行列型が存在しない
やる気あんのかって感じですが,行列型もありません. - テクセル形式
本来は問題では無いのですが,D2Dと組み合わせようとするとここで問題が発生します.
C++ AMPのconcurrency::graphics::texture
から最終的にD2DのID2D1Bitmap1
インターフェースに落としこむことを考えた時,D2D側のピクセル形式は実質的にDXGI_FORMAT_B8G8R8A8_UNORM
一択となります.
一方,get_texture
で得られるD3D Textureインターフェースのテクセル形式はDXGI_FORMAT_R8G8B8A8_UNORM
(Creating textures with specific DXGI_FORMAT参照)です.
つまり,一度何らかの方法でD3D Textureのテクセル形式をRGBAからBGRAに変更する必要があります.
と.このように問題が山積しています.
##めんどうくさいので
テンプレ作りました.
git submoduleを使っているのでよしなにしてください.
shader.hpp
内にいい感じにシェーダーを書くとなんかそれっぽく動きます.
サンプルとしてネット上に公開されているGLSLシェーダーを幾つか(一部除き作者の皆様には無断で)C++コードに移植して入れておいたので参考にしてください.
そこそこ素直に移植できます.さらに,C++なのでautoとか使い放題です.
###TODO
0. 関数が足りない
サンプルで必要な分は最低限作りましたが,その程度です.足りません.
0. 行列型がない
サンプルの一つに行列を使ったものがありましたが,定数だった上に面倒だったので関数として直書きしてしまいました.そのうち行列型も作ります.
0. スウィズリング式がアレ
上述の通り,vec.xx
などのように同じ識別子を連ねることが出来ないため問題となる部分がいくつか出ています.そのうち実装します.
#まとめ
0. C++ AMPにはグラフィックスに特化した機能がある
0. C++ AMPでシェーダーアートを楽しむことが出来る
0. 標準のみで書きやすいとは言っていない
#謝罪
がむさん,tomohiroさん,i-saintさん,勝手にシェーダ使ってごめんなさい…
なにか問題がございましたらおっしゃってください,リポジトリごと作りなおして消します…
というわけで,Advent Calendarも残すところあと5日間となりました.
明日は21日目,みやたけ ゆきさんです.
-
3年連続20日目担当 ↩
-
私は自室が北側で寒いので燃やしてます. ↩
-
Win7はPlatform Update適用済みの場合のみ可…このご時世に未適用の環境なんてそんなに残ってるとは思いませんが. ↩
-
「VC++はC++ではない」的な話をしたい方,最近のMSさんは以前に比べればメチャクチャ頑張ってるので,そろそろVC++のことも許してあげてください.まだTwo Phase Lookup実装されてませんが,実装するつもりではいるらしいです. ↩
-
int
,uint
,float
,double
,norm
,unorm
の1~4次元のベクトルクラス,HLSLやったことある人にはお馴染みのアレ(機能は制限されてるとかなんとか,私HLSL知らないので…) ↩ ↩2 -
流石はMS製という感じ.実際MSVCにおけるC++ AMPはDirectComputeをバックエンドとして実装されているので,こういった芸当が可能なのだと思われます. ↩
-
フラグメントシェーダーとも言う.ぶっちゃけ何処理すんだかちっともわからない名前なので「ピクセルシェーダー」の方が好きです. ↩
-
先述の通りバックエンドはDirectComputeなので,C++ AMPはコンピュートシェーダー相当のことが出来ます.テクスチャに対してピクセル単位で処理を行えば,ある程度ピクセルシェーダーのように振る舞うことも可能ということです. ↩
-
「C++ AMPでGraphical-Purpose computing on Graphics Processing Units!」 ↩
-
厳密にはshort vectorクラスは
amp_short_vectors.h
に入ってますがamp_graphics.h
でincludeされているのでとりあえずそっちincludeしとけばいいと思います ↩ -
一応typedefとして
concurrency::graphics::direct3d::int2
という形もあるのですが,今回は扱いません. ↩ -
正直これ欠陥だと思う…ちなみに内部実装は
__declspec(property(get=getter, put=setter))
って感じ.こう見てみると,いざsetter定義しようとするとどうすんだこれってなる(std::tie(x, x) = std::make_pair(1, 2)
みたいなもんなので)のも事実なので難しいところではあります(まぁsetter定義しなきゃいいんじゃ…とも思うんですが) ↩ -
実際には
T
がint
とかfloat
とかだったら書き込めるらしいですが,そんな状況私が考えうる限りZバッファぐらいしか無い気がしますし,後述の方法でいくらでも出来るので… ↩ -
決して書き込み専用のクラスというわけではなく,特に
texture_view<const T, N>
とすることで読み取り専用のtexture_view
が作れたりするのですが,今回は使ってないのでパスします ↩ -
恐らくD3Dのバージョンが上がった際にも対応できるように,或いは次元ごとに関数を分けたくないという考えからか,D3DのインターフェースはIUnknown*になっています. ↩ ↩2 ↩3
-
同時にD3D DeviceとC++ AMP Accelerator Viewを操作する場合には
concurrency::direct3d::d3d_access_lock
などを用いてロックを取る必要があるようですが,今回は使ってないのでパス ↩