Raytracing API統合 – DXRとVulkan Ray Tracingの差異まとめ

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はほぼ同じです。

           DirectXVulkan
ASビルド情報構造体D3D12_BUILD_RAYTRACING
_ACCELERATION_STRUCTURE_DESC
VkAccelerationStructureBuildGeometryInfoKHR
BLASのための
オメトリー記述構造体
D3D12_RAYTRACING_GEOMETRY_DESCVkAccelerationStructureGeometryDataKHR::
VkAccelerationStructureGeometryTrianglesDataKHR
+
VkAccelerationStructureGeometryDataKHR::
VkAccelerationStructureGeometryAabbsDataKHR
TLASのための
インスタンス記述構造体
D3D12_RAYTRACING_INSTANCE_DESCVkAccelerationStructureGeometryDataKHR::
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種類のシェーダーグループが存在しています。

シェーダーグループ種類シェーダーの要求
GeneralRayGeneration or Miss
Triangle Hit GroupClosest Hit + Any Hit (任意)
Intersectionは不可
Procedural Hit GroupIntersection (必須)
+ Closest Hit (任意) + Any Hit (任意)

つまり、Vulkanの場合には、例えRayGeneration shaderだけでも1つのシェーダーグループに含むことが必要です。

Resource Binding

PSOに関するVulkan API設計がかなりアラインされていますので、VkPipelineBindPoint列挙型に通じてvkCmdBindPipeline関数だけでも異なるパイプラインを簡単にバインドできます。

しかし、DirectX API設計の特殊性のため、PSOのバインド関数は独自のバリエーションがありながら、Root Signature/Argumentに関するコマンドリスト関数がComputeパイプラインのバージョンを使わなければならないことには注意すべきでしょう。

この記事をシェアする