社会人になってもう半年以上が経ちましたが、会社と家の往復生活でこれと言って面白いことは一つも無く、今の仕事(ネットワークインフラ運用・保守)にやりがいも感じず、「転職しようかな~」などとぼんやり考える日々を送っています。もういっそ田舎に帰って土でもいじろうか……。
社会人から学生の皆さんにアドバイスがあります。学生の間に遊んでください!死ぬほど遊んでください!人生最初で最後の夏休みを謳歌しなければ私のように後悔します!
……と愚痴はこの辺にして、最近ゲームエンジンを自作して、どうもレンダリングエンジンの流れが変わって来たのでそれについて書きたいと思います。
#DirectX12について
なんかMicrosoftさん「DirectXやめるよ~やめるよ~めっちゃやめるよ~」とか言ってた割に「パフォーマンスに優れた次世代のDirectXです。」みたいな感じでしれっと出してきましたね。
DirectXはwindowsでしか動かず、OpenGLの方がユーザも多いのに3Dの最先端を行くのはDirectXですし、アクセラレータのドライバはほぼDirectXに最適化されているのでOpenGLよりもDirectXの方がパフォーマンス出やすいという難物です。
とりあえずMicrosoftはアホみたいに独自仕様ばかり盛り込んでくるのでさっさと潰れてくれ。
#今までとどう変わったのか
それでは問題のDirectX12についてですが、その前に今まではどのように処理が行われていたかを軽く説明します。
##今までのプログラミングだと
そもそもゲームを個人で作った場合、処理にかかる負荷の内ほとんどが描画によるものです。なのでよほどのことがなければ計算部分をいくら頑張ってチューニングしたところで処理の負荷はほとんど軽減できないという悲しさがあります。
大体ゲーム作るときの処理の流れは以下のようになります。
+----計算処理---->+------------描画処理------------>+-画面更新->+---...
|
1フレーム(16.67msくらい)
そのままですね。敵やプレイヤー、その他諸々の計算を行い、その結果に従い描画するという手順です。
ここで並列化しようとすると若干大変になってきます。
CPU +----計算処理---->+----計算処理---->===========================+----計算処理---->...
| | |
GPU +================+------------描画処理------------>+-画面更新->+------------描画処理------------>...
| |
1フレーム目 2フレーム目
このような感じで多くの場合、片方のスレッドで計算処理を行い、次のフレームで別スレッドが1フレーム前の計算結果を描画するという流れになります。(実際はもっと複雑だったり、別の方法を取っていることもあると思います。)
ここで問題なのは図で "=" で示されている部分です。ここは処理が行われておらず、プロセッサが空転している部分です。一般的に計算処理は描画処理よりも速いため、描画待ち部分ができてしまいます。これではリソースが無駄になってしまいます。
##DirectX12だと
それではいよいよDirectX12の処理のお話ですが、DirectX12の内部的な話をここでしているとそれだけで日が暮れそうなので詳しい話はここではしません。(詳しい話はそのうち別の記事で書こうと思っています。)
簡単に説明しますと、DirectX12(以下D3D12とします)では描画が遅延実行になりました。(本当はDirectX11.3でも~とかありますが、細かい話は置いときます←)
どういうことかといいますと、描画を行う関数を呼び出し(ドローコールし)てもその場では描画されず、関数を抜けてきます。しかしそのドローコールはバッファに蓄えられ、GPUが空き次第実行されます。つまり、描画関数を呼び出しても描画待ちが発生しなくなります。このコマンドを蓄えるバッファを幾つか作って、あるバッファが描画で使用されているときは別のバッファにドローコールを積み込むというようにすると、描画を待つこと無く次々と描画命令をGPUに送ることができます。
この機構の動作を説明しようとすると、コマンドリストやらコマンドキューやらコマンドアロケータやらのお話になるのでやっぱり割愛で。。。
図で説明すると以下のようになります。
CPU +----Buf1にドローコール--->+----Buf2にドローコール--->+----Buf1にドローコール--->+...
| | | |
Buf1 +--ドローコールが積まれる-->+---描画で使用される--->+==+--ドローコールが積まれる-->+...
| | | |
Buf2 +=========================+--ドローコールが積まれる-->+---描画で使用される--->===+...
| | | |
GPU +=========================+--Buf1を使用して描画-->+==+--Buf2を使用して描画-->===+...
CPUの待ち部分("=")が消えました。上の図は今までと違い、1フレーム内で行われています。このように、1フレーム内で描画を非常に細かく行うようになりました。バッファには使用されていない部分がありますが、単なるバッファなので処理に影響はありません。
しかし、まだGPUに若干の空きがあります。DirectX12ではなんとドローコールをCPUの複数スレッドで積むことができるので、理想的に描画ドローコールが投げられると、GPUの空き時間を完全になくすことができます。(まぁ実際はそううまくも行かないですがそれでもかなりのパフォーマンス向上が期待できます。)
CPU1 +----Buf1にドローコール--->+----Buf2にドローコール--->+----Buf1にドローコール--->+...
| | | |
Buf1 +--ドローコールが積まれる-->+---描画で使用される--->+==+--ドローコールが積まれる-->+...
| | | |
Buf2 +=========================+--ドローコールが積まれる-->+===================+---描画で使用される--->===+...
| | | |
| | | |
CPU2 +===----Buf3にドローコール--->+----Buf4にドローコール--->=================+----Buf3にドローコール--->+...
| | | |
Buf3 +===--ドローコールが積まれる-->+===================+---描画で使用される--->+--ドローコールが積まれる-->+...
| | | |
Buf4 +============================+--ドローコールが積まれる->========================================+---描画でry
| | | |
| | | |
GPU +=========================+--Buf1を使用して描画-->+--Buf3を使用して描画-->+--Buf2を使用して描画-->+--Buf4をry
図がかなり複雑になってしまいました^^;
このようにGPUの空きもなくすことができるのですが......見づらいので雰囲気伝わってくれればいいです←
CPU2に空きがありますが、これはバッファを増やせばなくなります。
まぁ何はともあれこのように最近のAPIはコマンドを積み込んでプロセッサができるものから実行するように変わってきたようです。とりあえずOpenGLに頑張って頂いて早いうちにWindowsとは決別したい限りです。(OpenGLはまだこのような処理は行っていません。)
#終わりに
パフォーマンスが向上した反面操作が複雑になってきてしまい、なかなか個人のゲーム制作に使うには敷居が高いかなという状況になってきてしまいました。やっぱりDirectX9時代のお手頃な感じが人気があるのもうなずけます。このパフォーマンスを個人制作でも使いたい!ということで現在DirectX12を使用したライブラリの制作を行っています。そのうち公開しようと思うので、興味のある方はぜひ。
ちょっとしたステマも入れたところでそろそろ終わりにしようと思います。最後まで読んで頂いた方、長々としたお話にお付き合いいただきありがとうございました。
それでは!