Raytracing API統合 – DXRとVulkan Ray Tracingの差異まとめ
はじめまして。
ミドルウェア技術部に所属しているZhangです、毎日エンジン開発を進めています。
DirectX RaytracingとVulkan Raytracing extensionは同じ基礎(NVIDIAのRTX platform)から設計されたAPIなので、大体の仕組みは似ています。
一方で、両方のリソース管理パターンが著しく異なっているため、バッファに関わるAPIの振る舞いを統合してカプセル化したい場合は、かなりの工夫が必要です。下記の5つに関しての工夫が不可欠となります。
Acceleration Structure
従来のGraphicsパイプラインにシーンの構造が頂点バッファオブジェクト(VBO)としてアクセスされていますが、Raytracingパイプラインの場合、シーンの構造をまずAcceleration Structure(AS)の形へ変換することです。実行効率とジオメトリー再利用を考慮の上、Acceleration Structureは更に「Bottom-Level Acceleration Structure(BLAS)」と「Top-Level Acceleration Structure(TLAS)」に分けられています。
かなり複雑な構造ですが、幸いDirectXとVulkanのAcceleration StructureのビルドAPIはほぼ同じです。
DirectX | Vulkan | |
---|---|---|
ASビルド情報構造体 | D3D12_BUILD_RAYTRACING _ACCELERATION_STRUCTURE_DESC | VkAccelerationStructureBuildGeometryInfoKHR |
BLASのための オメトリー記述構造体 | D3D12_RAYTRACING_GEOMETRY_DESC | VkAccelerationStructureGeometryDataKHR:: VkAccelerationStructureGeometryTrianglesDataKHR + VkAccelerationStructureGeometryDataKHR:: VkAccelerationStructureGeometryAabbsDataKHR |
TLASのための インスタンス記述構造体 | D3D12_RAYTRACING_INSTANCE_DESC | VkAccelerationStructureGeometryDataKHR:: VkAccelerationStructureInstanceKHR |
Scratchバッファの用意 | 必要 | 必要 |
なお、DirectXとVulkanはどちらもジオメトリーレベル、インスタンスレベル、シェーダーレベルでジオメトリーの不透明さフラグを指定することができ、異なる粒度でAny Hit Shaderの実行頻度を最適化できます。各レベルの優先順位は下記の通りです。
シェーダーレベル > インスタンスレベル > ジオメトリーレベル
新しいShader Stages
DirectXとVulkan両方のRaytracingパイプラインも真新しいの5つのShader stagesで構成されます。
RayGeneration Shader | パイプラインの入口と最初のレイ発射の実行場 |
Miss Shader | レイがAcceleration Structureのどのプリミティブでも交差できない場合に実行する |
Intersection Shader | レイとプリミティブの交差判定アルゴリズムの実行場、ほとんどの場合はデフォルトのものを使う |
Any Hit Shader | レイが透明なジオメトリーと交差したら実行する |
Closest Hit Shader | レイが始点から最も近いプリミティブと交差する場合に実行する |
そのなかで、最後の3つのShaderは、HitGroupという対象にまとめられてパイプラインに渡すことです。
Shader Binding Table
Raytracingパイプラインには「どのレイ」が「どのジオメトリー」の「どのプリミティブ」と交差するかを事前に特定することができないので、すべてのジオメトリーに使われるシェーダーとリソースをまとめてパイプラインに渡さなければなりません。その対象をShader Binding Table(SBT)といいます。DirectXとVulkanは特殊なクラスや構造体が用意されず、SBTを普通のバッファと見なしています。そのゆえ、SBTのメモリーとレイアウト管理は完全にアプリケーション側に任されます。
SBTは複数のエントリーで構成された連続するメモリーブロックで、各エントリーはシェーダーのハンドルと、そのシェーダーで使えるリソースや定数データで構成されます。しかし、そのリソース部分はDirectXとVulkanの大きな差異の1つです。
DirectXは「Local Root Signature」という概念が導入されたので、RaytracingAccelerationStructure SceneBVH : register(t2, space1)
のように宣言したRoot SignatureのRoot Argumentは、通常のようにD3D12のコマンドリストに通じてパイプラインに渡すリソースの記述子のほかに、そのSBTの中に格納しているリソースの記述子でもあり得ます。
つまり、もしあるシェーダーがLocal Root Signatureを使うと、SBTにそのエントリーにあるリソースや定数データが「Local Root Argument」になります。そのうえ、リソースや定数データのレイアウトは、Local Root Signatureを宣言する順番と一致する必要があります。
反対に、Vulkanもリソースや定数データをSBTのエントリーに追加できますが、VulkanにはLocal Root Signature概念がないため、その部分のデータはShaderRecordBufferKHRというブロックとしてシェーダーにアクセスすることです。GLSLの場合は
layout(shaderRecordEXT) buffer SBTData
{
mat4 view;
mat4 projection;
};
のような宣言が必要なため、従来の
layout(set = 1, binding = 0) uniform CameraProperties
{
mat4 view;
mat4 projection;
} cam;
のように、setやbindingの添字を指定する必要がなくなります。DirectXとVulkanのAPIを統合する時に、ここは要注意の違いです。
新しいPipeline State Object
GraphicsとComputeパイプラインのように、Raytracingパイプラインも対応の「Pipeline State Object(PSO)」が存在しています。VulkanのAPIにはその3つのPSOが同じくVkPiplineとされ、構築関数も似ているパラメータを持ちます。しかし、DirectXのAPIにおいて、RaytracingのPSOは、GraphicsとComputeのPSOの構築方法とまったく異なります。
1. PSOの構築
VulkanのPSO構築関数は
VkResult vkCreateXxxxPipelines (
VkDevice device,
VkPipelineCache pipelineCache,
uint32_t createInfoCount,
const VkXxxxPipelineCreateInfo* pCreateInfos,
const VkAllocationCallbacks* pAllocator,
VkPipeline* pPipelines)
のようなスタイルで、「Xxxx」はPSO種類の名称です。異なるパラメーターの要求はVkXxxxPipelineCreateInfo構造体に隠れています。
一方、DirectXの従来のPSOが「ID3D12PipelineState」ですが、RaytracingのPSOに対して新しい「ID3D12StateObject」という概念が導入されました。
構築パラメーター方面はVulkanと同じく、異なるパイプライン種類に対して異なる構造体が要求されますが、ID3D12StateObjectの場合には、その構造体のメンバーがシェーダー対象やRoot Signatureではなく、一連の「Subobject」になりました。現段階はおよそ12種のSubobjectが存在し、構築するにはかなりの手間がかかります。
2. DirectX特有のGlobal/Local Root Signature問題
SBTのLocal Root Argument概念と連携するため、ID3D12StateObjectはLocal Root Signature対象を提供する役割があります。相応に、従来のSetComputeRootSignature()でパイプラインに渡すものは「Global Root Signature」といいます。従って、DrawCallが実行される前に、直接にコマンドリストでGPUに渡した各リソース記述子や定数が「Global Root Argument」となります。
こういう視点からVulkanを見ると、すべてのDescriptorSetLayoutはGlobal Root Signatureのようなものなので、DirectXとVulkanのRaytracing APIを統合しようとすると、Vulkan APIのラッパー内部に”Local” Root Signatureを”Global” Root Signatureへ変換する必要があるのではないかと思います。
3. Shader Groupの違い
Raytracing PSOを構築する時に、シェーダーとシェーダーグループの両方が必要です。DirectXはただHitGroupという1種類のシェーダーグループだけ存在していますが、Vulkanには3種類のシェーダーグループが存在しています。
シェーダーグループ種類 | シェーダーの要求 |
---|---|
General | RayGeneration or Miss |
Triangle Hit Group | Closest Hit + Any Hit (任意) Intersectionは不可 |
Procedural Hit Group | Intersection (必須) + Closest Hit (任意) + Any Hit (任意) |
つまり、Vulkanの場合には、例えRayGeneration shaderだけでも1つのシェーダーグループに含むことが必要です。
Resource Binding
PSOに関するVulkan API設計がかなりアラインされていますので、VkPipelineBindPoint列挙型に通じてvkCmdBindPipeline関数だけでも異なるパイプラインを簡単にバインドできます。
しかし、DirectX API設計の特殊性のため、PSOのバインド関数は独自のバリエーションがありながら、Root Signature/Argumentに関するコマンドリスト関数がComputeパイプラインのバージョンを使わなければならないことには注意すべきでしょう。