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

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

Vulkanグラフィックスの簡単なレンダリングサンプル

このあいだ書いた記事「VulkanシェーダーでSub-group命令を使う」では、グラフィックスをすっ飛ばしていきなりコンピュートシェーダーで拡張命令を試すという変態的なことをしましたが、GPGPUに興味のない人には退屈な内容だったかもしれません。

以前、Direct3D 12の簡単な入門記事「Direct3D 12 (DirectX 12) の簡単なサンプル」を書いたときには、コマンドリストの中で複数のPipeline State Object (PSO) を切り替えて、異なるシェーダーを適用してトライアングルをレンダリングするためのテストコードを実装しました。
今回はVulkanを使ってまったく同じことをします。レンダリング結果はD3D12バージョンとまったく同じになるようにしました。あらかじめ断っておきますが、「簡単なレンダリングサンプル」と言ったな。あれは嘘だ。

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

例のごとくMFCと公式のVulkan C++ラッパーを使って省力化していますが、それでも初期化コードが鬼のように長くなりました。Queue Family IndexやMemory Type Indexなど、ハードウェアの構成や仕様の差異にプログラマーサイドが柔軟に対応できる能力がある反面、抽象化層が極端に薄く、やはりAPIとしてはDirect3D 12を遥かにしのぐ難易度です。なかでも最難関だと感じたのは、やはりD3D12でのDescriptor Heapに相当するDescriptor Setまわりでした。頂点バッファとUniform Buffer Object (UBO) を使っているだけなのに、すでにこの有様です。分かってはいましたが、これにインデックスバッファやテクスチャ、Shader Storage Buffer Object (SSBO) といったおなじみの要素が加わってくるとなると、もうあっという間に複雑度が爆発して個人レベルでは手に負えなくなるのは目に見えています。

ちなみに、前回D3D12で使ったHLSLシェーダーを、中間言語であるSPIR-Vにコンパイルして、Vulkanでもそのまま使うことができました。一見地味ですが、HLSLシェーダーを一切変更することなく、Vulkanからも利用できるというのは驚異的ですね。これこそが中間言語の威力です。Cg言語のようなソフトウェアトランスレーターとは訳が違います。Direct3DではHLSLが登場する前から、ベンダー非依存のシェーダーアセンブラ中間言語として導入されていたことで、ハードウェアやドライバーの差異を気にすることなく開発できていましたが、OpenGLはなぜもっと早く同様のシェーダー中間言語を導入してこなかったのか、本当に惜しまれます*1

相互運用について

OpenGLDirect3D 11には、VulkanやDirect3D 12との相互運用を可能にするレイヤーや拡張が用意されています。従来のGL/D3Dアプリにおいて、Vulkan/D3D12に完全移行することはできないが、大量のドローコールのせいでボトルネックになっている箇所だけどうしても高速化したいという場合には、そういった相互運用を検討してもよいかもしれません。ただ、個人的な経験(手痛い失敗経験)から言わせてもらうと、やはり安定したアプリケーション開発のためには、あちこちに手を広げてむやみに複雑な解決策を持ち込もうとせず、できるかぎり単一のAPIのみに絞ったほうがいいと思います。理由として、複数の言語やAPIおよびフレームワークを混在させたハイブリッド開発は、コーディングがメチャクチャ疲れるうえに不具合を混入させやすいからです。また、相互運用が標準機能でなく拡張機能の場合は、ベンダーやユーザープログラマーによって十分テストされていないことから潜在的に不具合を抱える確率が高くなります。単に相互運用の能力があるということと、実際にそれを製品コードに使う、ということには大きな隔たりがあります。
CUDA/OpenCLにもGL/D3Dとの相互運用機能が存在しますが、GPGPUによるシミュレーション結果を可視化したいときは、(CUDA/OpenCLを使ったほうが効率的に記述できる場合を除いて)できるかぎりGLSL/HLSLのコンピュートシェーダーを使うべきでしょう。

*1:OpenGL 4.6でGL_ARB_gl_spirvつまりSPIR-Vのサポートが標準化されましたが、2018年4月時点でフル対応ドライバーをリリースしているのはNVIDIAだけです。本家OpenGL中間言語が早期に実現されていれば、派生規格であるOpenGL ESでも採用されていたはずで、アプリケーション開発者がベンダードライバーごとのシェーダーコンパイラーの挙動の違いなんぞに苦労することもなかったと思います。