Showing posts with label Vulkan. Show all posts
Showing posts with label Vulkan. Show all posts

Sunday, November 5, 2017

[Vulkan] vkCmdPipelineBarrierで指定するVkPipelineStageFlagBitsとは? [SDC 2017]

Samsung Developer Conference 2017で行われた、VulkanのvkCmdPipelineBarrierの使い方の技術セッションがYouTubeで公開されています。

vkCmdPipelineBarrierに指定するsrcStageMaskとdstStageMaskの指定方法に関して説明されており、Khronosの文書が関数リファレンスのレベルしか言及していない現状で、動作原理を解説する貴重なセッションとなっています。

連続する2つのパスはGPUで並行に実行される可能性がありますが、前のパスの結果に後のパスが依存する場合、vkCmdPipelineBarrierを置くことで後のパスを待たせる必要があります。ただし、GPUコア間の並行性が失われて待機時間が生じます。これがPipeline Bubbleと呼ばれます。

srcStageMaskは前のパスのパイプラインステージ(例:vertex shader等)、dstStageMaskは後のパスのパイプラインステージを表します。前のパスのsrcStageMaskで指定のステージが終わるまで、後のパスのdstStageMaskで指定のステージは始めてはいけないという意味になります。

VK_PIPELINE_STAGE_TOP_OF_PIPE_BITとVK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BITは存在しない一種の仮想のパイプラインステージで、それぞれ全てのパイプラインステージの前と後を意味しています。

保守的には、srcStageMask=VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT、dstStageMask=VK_PIPELINE_STAGE_TOP_OF_PIPE_BITとすることで並列動作を出来なくします。

逆に、srcStageMask=VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT、dstStageMask=VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BITとすることで、2つのパスは並列に動き出すそうです。

残念ながら、どのように依存関係を解決しながら平行動作させるかには言及されていませんでしたが、2つのパスの依存関係の無い場合には並列動作させることでpipeline bubbleを取り除こうという趣旨と思われます。

また、AMDのサイトでもVulkan barriers explained と題してvkCmdPipelineBarrierを解説しています。

おそらく最も使用頻度が多いのが、前のパスで作った絵を次のパスのfragment shaderで使うというシナリオだと思いますが、srcStageMask=VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT、dstStageMask=VK_PIPELINE_STAGE_FRAGMENT_SHADER_BITとすればよいそうです。これなら前のパスが絵を描き終わる前に次のパスの頂点処理を平行させることが出来そうです。

DX12では、ID3D12GraphicsCommandList::ResourceBarrierでリソースのステートを遷移させるには前後のD3D12_RESOURCE_STATESを指定すればタイミングはドライバが決めてくれました。Vulkanでは前後のVkImageLayoutに加えてVkPipelineStageFlagBitsでタイミングまでプログラマが把握して指定しなければいけません。

Sunday, October 22, 2017

[Vulkan] GeForceでスワップチェーンはRGBAでは無くBGRA

グラフィックスAPIにおいて紛らわしかったカラーチャンネルの並びの問題ですが、DirectX11やDirectX12ではRGBAだけ使えば良く、BGRAは忘れても良い存在になっていました。ところが、GeForceやIntelの統合型GPUのVulkanではスワップチェーンがBGRAでなければいけないようです。

vkGetPhysicalDeviceSurfaceFormatsKHR関数で列挙したと思われるリストが、gpuinfo.orgにあります。(Surfaceタブ→Surface formatsタブ)
https://vulkan.gpuinfo.org/displayreport.php?id=1895#surface - GTX 1050
https://vulkan.gpuinfo.org/displayreport.php?id=1922#surface - Mali-G71
https://vulkan.gpuinfo.org/displayreport.php?id=1945#surface - Radeon RX 560
https://vulkan.gpuinfo.org/displayreport.php?id=1973#surface - Adreno 540

AdrenoやMaliは逆にRGBAのみのサポートで、RadeonはRGBAとBGRAの両方をサポートしていることがわかります。

レンダーターゲットのフォーマットはRenderPassに含まれ、RenderPassはPSOが参照します。スワップチェーンに直接描画するPSOはデバイスに合わせてRGBAかBGRAかどちらかで生成されたRenderPassを指定しなければいけないことになります。

中間バッファにBGRAを使うことは無いはずなので、結局Vulkanでは同じシェーダでも「描画対象がスワップチェーンか中間バッファか」によって出来上がるPSOが別物になると考えなければいけません。

PSO生成の際のパラメータが増えて管理が煩雑になりそうですが、実際の所スワップチェーンに直接描画するものといえばUIかポストプロセスくらいなので、場合分けは難しくないと思います。

Sunday, October 15, 2017

[Vulkan] RadeonでVK_FORMAT_D24_UNORM_S8_UINTのdepth stencilテクスチャが作れない

近年多く使われていたD24_UNORM_S8_UINTのdepth stencilテクスチャはPCのVulkanで生成できないケースがあります。例えば、Radeon 560 RXでは、D24_UNORM_S8_UINTが未サポートです。代わりにD32_SFLOAT_S8_UINTを使わなければいけません。
https://vulkan.gpuinfo.org/displayreport.php?id=1945#formats

GTX 1050の場合はD24_UNORM_S8_UINTがサポートされています。
https://vulkan.gpuinfo.org/displayreport.php?id=1895#formats

DirectX12ではRadeon 560 RXでもDXGI_FORMAT_D24_UNORM_S8_UINTが作れましたが、内部でエミュレートしていると思われます。

Vulkanといえば、モバイルデバイスも気になるところですが、Adreno 540、Mali-G71共に、D24_UNORM_S8_UINTしか対応していないようです。Radeonとは逆ですね。
https://vulkan.gpuinfo.org/displayreport.php?id=1925#formats
https://vulkan.gpuinfo.org/displayreport.php?id=1922#formats
いずれのプラットフォームもD32_SFLOATは存在しているので、stencilを使わないならD32_SFLOAT、stencilを使いたいならプラットフォーム毎に分岐しなければいけなさそうです。

Sunday, January 8, 2017

[Vulkan] VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMICが便利

VulkanのUniform Bufferには、VK_DESCRIPTOR_TYPE_UNIFORM_BUFFERとVK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMICの二種類がありますが、"DYNAMIC"はオフセットをコマンドバッファに一緒に積めるので便利です。

DirectX12やVulkanのような低レベルAPIでは、Constant buffer/Uniform buffer用に大きなGPU側バッファをまず確保しておいて、自前のアロケータで割り当てながら使うことになると思います。どのDraw Callがどのメモリ領域をConstant buffer/Uniform bufferとして使うかは毎フレーム変化するしかないので、Draw Call毎にGPUアドレスを渡すことになります。

それは、VK_DESCRIPTOR_TYPE_UNIFORM_BUFFERを使っても可能なのですが、GPUアドレスはdescriptor setに書き込んで渡す必要があるので、uniform bufferを使う回数分vkAllocateDescriptorSetsとvkUpdateDescriptorSetsでdescritor setも作ってあげる必要があります。

VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMICを使うと、descritor setは1個だけ、大きなGPUバッファの先頭アドレスを書き込んだものを用意するだけで済みます。その代わりdraw call毎にvkCmdBindDescriptorSetsでUniform bufferをバインドするときにオフセットを指定します。複数のdescriptor setを管理する手間が省けるので便利です。

NVIDIAのドキュメントによると、NVIDIAのハードウェアで"Uniform Buffer Dynamic Binding"は速いのだそうです。
https://developer.nvidia.com/vulkan-shader-resource-binding

推測に過ぎませんが、NVIDIAのハードウェアではDirectX12の"Root Constant"が"Uniform Buffer Dynamic Binding"に相当するのかもしれません。
DX12 Do's And Don'tsのRoot Signaturesの項で"sit in the root"でバインドしたコンスタント/CBVが特にPixel Shaderで速いとされています。
https://developer.nvidia.com/dx12-dos-and-donts

SetGraphicsRootConstantBufferViewもDescriptor Heapを介さずにコマンドリストに直接GPUアドレスを乗せるという部分も、descriptor set1個を使いまわせるVK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMICに似ています。

Wednesday, November 2, 2016

Vertex Array Object(VAO)を使わない理由

OpenGL ES 3.xではVertex Array Object(VAO)が導入され、APIコールが減って効率的ということになっています。ところが、コーディングの特殊さによるVAOの弊害が多く、多分実行速度も変わらない気がしたので、VAOを使わなくてもいい理由を書いてみようと思いました。(ちなみに、Unreal Engine 4もVAOを使っていないようです)プラットフォームは主にAndroidを想定しています。

1. VAOが頂点バッファと結びつくのが困る


頂点バッファをシェーダに結びつけるいわゆる"Input Layout"はAPI毎に格納されるオブジェクトが違います。DX11ではID3D11InputLayout、DX12やVulkanではPSO(pipeline state object)、ES 3.xはVAOに格納されます。

そのうちVAOだけの厄介な点は特定の頂点バッファと結びつく仕様になっていることです。実務でもありそうな例として、モンスターA、B、Cを、Gバッファ生成シェーダー、シャドウマップ生成シェーダ、光学迷彩シェーダで描画するとします。頂点バッファから送るのはGバッファは全情報、シャドウマップは座標だけ送れば十分、光学迷彩はテクスチャマッピングを省くことにします。

VAOを使って必要な情報のみ必要なシェーダに送るためには、モンスター3種とシェーダー3種、3x3=9個のVAOが必要になります。さすがにこのような煩雑なことはしたくないので、実際にはモンスター毎に1つずつVAOを作って、それぞれ頂点の全情報を送るように設定し、あとはドライバレベルでの最適化に託す、という書き方になりそうです。

2. わかりにくいバグを作りやすい


経験上、描画が終わったらglBindVertexArray(0)でVAOのバインドを忘れず解除すべきです。なぜなら、バインドしっぱなしにすると他の描画モジュールがバインド中のVAOを書き換えることが出来てしまい、どこでどう地雷を踏んでいるかわからない難解なバグとなります。

描画の為のglBindBufferやglVertexAttribPointerのみならず、glBindBufferの周辺はVAOを書き換える可能性に気を使います。例えばインデックスバッファを生成したり書き換えたりする時にglBindBufferを呼びますが、これがよそのVAOを壊してしまうかもしれません。


3. VAOでAPIコールを減らしても(多分)速くならない


VAOを採用する動機は、なんとなく速そうという期待ではないでしょうか。しかし、本当に速くなるでしょうか。VAOが無いES2.0は、頂点バッファを切り替える度にglBindBufferとglVertexAttribPointerの複数回に及ぶコールで毎回”Input Layout"に該当する情報をドライバに伝えます。それが省ける点はVAOが有利に見えます。

ところが、DX12やVulkanを見るとVAOは少なくとも最近のハードウェアの実装からかけ離れている事が想像できます。バッファはコマンドリストに乗るGPUアドレスに過ぎず、Input LayoutはPSOの一部に過ぎません。この2つを取り出して1つにまとめる事そのものに最適化的な利点は期待できなさそうです。

OpenGLのドライバの実装を想像してみます。GLも内部でPSOのような物を持っているはずです。PSOの生成は重い処理なので、一回作ったらハッシュ値などで探せるようにして実体をキャッシュしておくでしょう。他のステートが決まらないとPSOが確定できないので、ハッシュ値の計算もPSOの生成もすぐには行われず、ドローコール(glDrawElements等)のタイミングで行われるはずです。

こう考えると、glVertexAttribPointerにしろ、glBindVertexArrayにしろ、ハッシュ値を計算するための元データの提供に過ぎず、バッファのバインドを除いてCPU内で完結しています。VAOを使ったほうがAPIの呼び出しは減るかもしれませんが、無理にVAOを使って得られるものがあるわけではなさそうです。

別の視点から見てみると、また、現状多くのゲームが互換性のためにES2.0で実装されています。ドライバ開発者の立場としても、VAOありきで最適化できないと思われます。

4. ES2.0がまだまだ現役、そしてVulkanの登場


従来はES2.0をベースとして上位機種はES3.x採用という戦略がありました。今後は上位機種はVulkanで互換性のためにES2.0、という組み合わせが増えると思います。対応機種を狭める上に将来的にVulkanに置き換えられる運命にある、ES3.xの必要性が薄れてきました。

現在のようにAPIが移行期にある中では特定APIに依存しないように抽象化を試みることも多いと思いますが、VAOはその特殊さゆえに抽象化がとても難しいです。ES2.0のglVertexAttribPointerも非常に変則的ですが、使うシェーダが決まるまで頂点レイアウトの決定を保留できる分、VAOよりは抽象化はしやすいと言えるかもしれません。