目次 - SDL 3.0 API(機能別) - 3DレンダリングとGPUコンピューティング

3DレンダリングとGPUコンピューティング

概要

CPU APIは現代的なグラフィックハードウェアと対話するアプリケーションに環境に依存しない方法を提供する. Metal, Vulkan, Direct3D 12の形式の3Dグラフィックとコンピューティングの両方を提供する.

基本的なワークフローは次のようになる:

アプリケーションはSDL_CreateGPUDevice()でGPUデバイスを生成し, それをSDL_ClaimWindowForGPUDevice()でウィンドウと関連付ける――厳密には, 画像処理などのためにウィンドウを全く使わずに画面外でもレンダリングできる.

次に, アプリケーションは静的データ(一度生成され, 繰り返し使われるもの)を用意する. 例:

レンダリングのために, アプリケーションは1つ以上のコマンドバッファをSDL_AcquireGPUCommandBuffer()で生成する. コマンドバッファは, バッチでGPUに送られるレンダリング命令を溜める. 複雑なシーンは複数のコマンドバッファが使用でき, 正しい順序ならば恐らく複数スレッドに渡り並列に構成される. しかし, 多くのアプリケーションは1フレーム当たり1つのコマンドバッファで十分である

レンダリングは, テクスチャ(別のAPIでは「レンダーターゲット」と呼ばれる)か, スワップチェーンテクスチャ(ウィンドウのコンテキストに対する特別なテクスチャ)に対して行うことができる. SDL_WaitAndAcquireGPUSwapchainTexture()を使用するとウィンドウに対してレンダリングできる.

レンダリングは, 実際にはレンダーパスで発生し, コマンドバッファにエンコードされる. 複数のレンダーパス(またはレンダーとコンピューティングを交互に行うパス)を1つのコマンドバッファにエンコードすることもできるが, 多くのアプリケーションは単に1つのコマンドバッファには1つのレンダーパスで十分である. レンダーパスは4つのカラーテクスチャと1つの深度テクスチャを同時にレンダリングすることができる. レンダリングするテクスチャを変更する必要がある場合, レンダーパスを終了し, 新しいレンダーパスを開始する必要がある.

アプリケーションはSDL_BeginGPURenderPass()を呼ぶ. その後, 各描画で必要な過程を設定する:

その後, これらの過程の実際の描画コマンドを生成する:

パスの描画コマンドが全て完了した後, アプリケーションはSDL_EndGPURenderPass()を呼ぶ必要がある. レンダーパスが終了すると, 全てのレンダリングに関連した過程はリセットされる.

シーン全体がレンダリングされるまでは, アプリケーションは新しいレンダーパスを開始し, 同じコマンドバッファで新たに描画することができる.

シーンのレンダリングコマンドが全て完了すると, アプリケーションは処理をGPUに送信するためにSDL_SubmitGPUCommandBuffer()を呼ぶ.

アプリケーションがテクスチャやバッファからデータを読み戻す必要がある場合は, 多少の遅れを容認できるならば, それを効率的に行うAPIがある. アプリケーションがSDL_DownloadFromGPUTexture()またはSDL_DownloadFromGPUBuffer()を使い, SDL_SubmitGPUCommandBufferAndAcquireFence()でコマンドバッファを送信すると, スレッドでアプリケーションがポーリングまたは待機できるフェンスハンドルを戻す. フェンスがコマンドバッファの処理の完了を検知すると, ダウンロードされたデータを安全に読み込むことができる. このフェンスの使用を終えたとき, 必ずSDL_ReleaseGPUFence()を呼ぶこと.

このAPIは「コンピューティング」にも対応している. アプリケーションは, コンピュートシェーダで書き込まれるコンピュートライタブルテクスチャと(または)バッファを指定してSDL_BeginGPUComputePass()を呼ぶ. その後, コンピュートディスパッチで必要な過程を設定する:

その後, コンピューティング作業をGPUに送信する:

高度なユーザはこれで強力なGPU駆動のワークフローを始めることができる.

グラフィックとコンピューティングパイプラインは, 上で記述したようにGPU上の小さなプログラムであるシェーダの使用を必要とする. それぞれのバックエンド(Vulkan, Metal, D3D12)は異なるシェーダの形式を必要とする. GPUデバイスを生成するとき, アプリケーションはどのシェーダの形式を提供できるかをデバイスに知らせる. 次に, 利用可能なシェーダ形式と, 実行環境で利用可能なバックエンドに依存した適切なバックエンドを選択する. シェーダを生成すると, アプリケーションは選択したバックエンドに正しいシェーダ形式で提供する必要がある. APIがこのように動作する理由を詳しく学びたいならば, 詳しく説明されたブログの記事がある.

最もよいのは, 使用するシェーダ形式を事前にコンパイルすることだが, 簡単に使えるようにSDLはランタイムシェーダのクロスコンパイルのための別のプロジェクトSDL_shadercrossを提供している. オフラインの事前コンパイルのためのCLIインターフェースも備えている.

これは重要な部分をいくつも省略した概要に過ぎない. それでもGPUプログラミングはかなり複雑だと感じたことだろう. シンプルな2Dグラフィックのみ必要ならば, レンダーAPIの方が簡単で, ハードウェアアクセラレーションも活用できる. しかし, 2DアプリケーションでもGPU APIのパフォーマンスと表現力は重要である.

GPU APIの機能は幅広いハードウェアの対応と移植のしやすさに重点を置いている. アプリケーションが機能をチェックして場合分けしなくてもよいように設計されている. もし対応するハードウェアが限定された最新の機能が必要ならば, 恐らくこのAPIは向いていない.

このAPIの使い方のデモはここにある.

パフォーマンスに関する考察

レンダリングのパフォーマンスを高める基本的なヒント:

一般的に, パフォーマンスの黄金律を覚えておくこと: 何かをすることは, 何もしないよりも高価である. ドライバに触れてはならない!

FAQ

質問: レイトレーシングやメッシュシェーダのような高度な機能はいつ追加されるのか?

回答: 今すぐ最先端の機能を追加する予定はない. しかし, その機能に追加する価値があり, 複数の環境で実装可能な基盤となるAPIとするのが合理的と判断したならば, 将来的にはありうる. 「決してない」ことはないが, 「近い将来」でもない.

質問: 私のシェーダが動かないのはなぜか?

回答: シェーダを使う際のよくあるミスは, シェーダの資源/レジスタが適切に配置されていないというものである. GPU APIは資源の配置に非常に厳格で, また互換性のある配置かをAPIが自動的に判断するのは困難である. 必要なレイアウトの情報はSDL_CreateGPUShader()とSDL_CreateGPUComputePipeline()のドキュメントを参照すること.

他によくあるのは, SDL_GPUShaderCreateInfoのサンプラ, テクスチャ, バッファの数が正しく設定されていない場合である. 可能ならば, 構造体の値を手動で設定するのではなく, シェーダリフレクションを使用してシェーダから自動的に必要な情報を得ること.

質問: 私のアプリケーションのパフォーマンスはあまりよくない. GPU APIの間違いか?

回答: 違う. 詳しい回答: GPU APIは基礎的なグラフィックAPIの上にある比較的薄いレイヤーである. 非効率なことを行った可能性が, GPUレンダリングの経験が少ない場合は特にありうる. 上のパフォーマンスのヒントを参考にして, それに従っているか確認すること. さらに, RenderDocのようなツールは正しくない振る舞いの診断やパフォーマンスの問題の手助けになりうる.

システム要件

Vulkan

SDLドライバ名: "vulkan" (SDL_CreateGPUDevice()とSDL_PROP_GPU_DEVICE_CREATE_NAME_STRINGで使用する)

Windows, Linux, Nintendo Switch, 一部のAndroidに対応している. 以下の拡張が行われたVulkan 1.0と対応したデバイスが必要である:

D3D12

SDLドライバ名: "direct3d12"

Windows 10以降, Xbox One (GDK), Xbox Series X|S (GDK)に対応している. DirectX 12 Feature Level 11_0とResource Binding Tier 2またはそれ以上に対応したGPUが必要である.

Metal

SDLドライバ名: "metal"

macOS 10.14以降, iOS/tvOS 13.0以降に対応している. オペレーティングシステムと合ったハードウェアが必要である:

座標系

GPU APIは, D3D12とMetalに合わせて左手系の座標系を採用している. 特に:

バックエンドのドライバが異なる座標系(例えばVulkanのデバイス正規化座標系は+Yが下)の場合でも, SDLは背後で自動的に座標系を変換するため, 座標を入れ替える処理を自身で行う必要はない.

ユニフォームデータ

ユニフォームはシェーダに渡されるデータである. ユニフォームデータはシェーダの全ての実行に渡り同一である.

1シェーダステージ(頂点, フラグメント, コンピューティング)につき4つのユニフォームスロットが利用できる. ステージのスロットに入れられたユニフォームデータは, 再びスロットに入れる関数を呼ぶまでコマンドバッファに値が維持される.

例えば, スロット0にバインドされたユニフォームからカメラ行列として読み込むために頂点シェーダを書き込むことができる. コマンドバッファに最初に入れられたカメラ行列は, 各サブシーケンスの描画の呼び出しのたびに使われる.

レンダリングやコンピューティングパスの間にユニフォームデータを入れるのは適切な方法である.

ユニフォームは少ないデータを入れるのが最もよい. 複数の行列を1回の呼び出しで入れたいならば, 代わりにストレージバッファの使用を考えるべきだろう.

サイクルに関する注意

コマンドバッファを使う場合, 命令は即座には実行されない――コマンドバッファが送信された後, しばらくしてから実行される.

リソースが待ち状態または処理中のコマンドバッファで使用されている場合, これを「バインドされている」と見なす. リソースが待ち状態または処理中のコマンドバッファで使用されなくなった場合, これを「バインドされていない」と見なす.

データリソースがバインドされているとき, 送信中のコマンドバッファをフェンスで待っていない限り, いつデータのバインドが解除されるかは不定である. しかし, これはリソースの使用を自分で監視しなければならないということではない.

リソースの書き込みに関連する全ての関数と構造体には, ブール値の"cycle"が存在する. SDL_GPUTransferBuffer, SDL_GPUBuffer, SDL_GPUTextureは, 効率化のため全て内部リソースのリングバッファを使用している. cycleが真の場合, リソースがバインドされていれば, サイクルは次のバインドされていない内部リソースへと移る. 利用可能なリソースがなければ, 新しいリソースが生成される. これは適切にサイクルされている限り, 複雑な状態遷移の追跡と同期に困難する必要がないことを意味する.

例: SDL_MapGPUTransferBuffer()を呼びテクスチャデータを書き込み, SDL_UnmapGPUTransferBuffer()を呼び, 次にSDL_UploadToGPUTexture()を呼ぶとする. 次にバッファにテクスチャをバッファに書き込むとき, cycleパラメータを真にすると, まだ送信されていないデータを上書きしてしまう心配はない.

別の例: 毎フレームのレンダーパスで1つのテクスチャを使用している場合, フレーム間でデータの依存関係が発生しうる. SDL_GPUColorTargetInfo構造体のcycleに真を設定すると, このデータ依存を避けることができる.

サイクルが既にバインドされたデータを未定義にすることはない. サイクル中, リソースの全てのデータはデータが再び書き込まれるまでは, 後のコマンドからは未定義と見なされる. 未定義のデータを読み込まないように注意する必要がある.

テクスチャをサイクルする場合, たとえ呼び出しでテクスチャの一部のみ使用する場合でもテクスチャ全体がサイクルされる. よって, サイクルの後はテクスチャ全体が未定義と見なすべきである.

また, 最初にサイクル外のコマンドで参照されたデータのセクションを上書きしないように注意する必要がある. サイクル外でリソースにバインドされた参照されていないデータを上書きするのはよいが, 既に参照されたデータのセクションを上書きすると予期しない結果となる.

デバッグ

GPUを使用していると, 通常のデバッガでは追跡できない問題に遭遇するだろう――例えば, コンパイルしたが何も表示されない, または実行中にシェーダが失敗するなどである.

このような場合のデバッグのために, 各描画の呼び出し, バインドされたリソース, メモリバッファ等のGPUフレーム全体の調査を可視化できるツールが存在する:

それとは別に,元のGPUバックエンドのより詳細なエラーメッセージを受信する追加のデバッグレイヤを有効にしたいだろう:

実行中に問題に遭遇した, または画面に予期しない出力があった場合は, RenderDocのようなツールを使うことを躊躇してはならない. 素早いGPUフレームの調査は, このような問題の重要な部分の修正の助けになる.

関数

  1. SDL_AcquireGPUCommandBuffer
  2. SDL_AcquireGPUSwapchainTexture
  3. SDL_BeginGPUComputePass
  4. SDL_BeginGPUCopyPass
  5. SDL_BeginGPURenderPass
  6. SDL_BindGPUComputePipeline
  7. SDL_BindGPUComputeSamplers
  8. SDL_BindGPUComputeStorageBuffers
  9. SDL_BindGPUComputeStorageTextures
  10. SDL_BindGPUFragmentSamplers
  11. SDL_BindGPUFragmentStorageBuffers
  12. SDL_BindGPUFragmentStorageTextures
  13. SDL_BindGPUGraphicsPipeline
  14. SDL_BindGPUIndexBuffer
  15. SDL_BindGPUVertexBuffers
  16. SDL_BindGPUVertexSamplers
  17. SDL_BindGPUVertexStorageBuffers
  18. SDL_BindGPUVertexStorageTextures
  19. SDL_BlitGPUTexture
  20. SDL_CalculateGPUTextureFormatSize
  21. SDL_CancelGPUCommandBuffer
  22. SDL_ClaimWindowForGPUDevice
  23. SDL_CopyGPUBufferToBuffer
  24. SDL_CopyGPUTextureToTexture
  25. SDL_CreateGPUBuffer
  26. SDL_CreateGPUComputePipeline
  27. SDL_CreateGPUDevice
  28. SDL_CreateGPUDeviceWithProperties
  29. SDL_CreateGPUGraphicsPipeline
  30. SDL_CreateGPUSampler
  31. SDL_CreateGPUShader
  32. SDL_CreateGPUTexture
  33. SDL_CreateGPUTransferBuffer
  34. SDL_DestroyGPUDevice
  35. SDL_DispatchGPUCompute
  36. SDL_DispatchGPUComputeIndirect
  37. SDL_DownloadFromGPUBuffer
  38. SDL_DownloadFromGPUTexture
  39. SDL_DrawGPUIndexedPrimitives
  40. SDL_DrawGPUIndexedPrimitivesIndirect
  41. SDL_DrawGPUPrimitives
  42. SDL_DrawGPUPrimitivesIndirect
  43. SDL_EndGPUComputePass
  44. SDL_EndGPUCopyPass
  45. SDL_EndGPURenderPass
  46. SDL_GDKResumeGPU
  47. SDL_GDKSuspendGPU
  48. SDL_GenerateMipmapsForGPUTexture
  49. SDL_GetGPUDeviceDriver
  50. SDL_GetGPUDeviceProperties
  51. SDL_GetGPUDriver
  52. SDL_GetGPUShaderFormats
  53. SDL_GetGPUSwapchainTextureFormat
  54. SDL_GetGPUTextureFormatFromPixelFormat
  55. SDL_GetNumGPUDrivers
  56. SDL_GetPixelFormatFromGPUTextureFormat
  57. SDL_GPUSupportsProperties
  58. SDL_GPUSupportsShaderFormats
  59. SDL_GPUTextureFormatTexelBlockSize
  60. SDL_GPUTextureSupportsFormat
  61. SDL_GPUTextureSupportsSampleCount
  62. SDL_InsertGPUDebugLabel
  63. SDL_MapGPUTransferBuffer
  64. SDL_PopGPUDebugGroup
  65. SDL_PushGPUComputeUniformData
  66. SDL_PushGPUDebugGroup
  67. SDL_PushGPUFragmentUniformData
  68. SDL_PushGPUVertexUniformData
  69. SDL_QueryGPUFence
  70. SDL_ReleaseGPUBuffer
  71. SDL_ReleaseGPUComputePipeline
  72. SDL_ReleaseGPUFence
  73. SDL_ReleaseGPUGraphicsPipeline
  74. SDL_ReleaseGPUSampler
  75. SDL_ReleaseGPUShader
  76. SDL_ReleaseGPUTexture
  77. SDL_ReleaseGPUTransferBuffer
  78. SDL_ReleaseWindowFromGPUDevice
  79. SDL_SetGPUAllowedFramesInFlight
  80. SDL_SetGPUBlendConstants
  81. SDL_SetGPUBufferName
  82. SDL_SetGPUScissor
  83. SDL_SetGPUStencilReference
  84. SDL_SetGPUSwapchainParameters
  85. SDL_SetGPUTextureName
  86. SDL_SetGPUViewport
  87. SDL_SubmitGPUCommandBuffer
  88. SDL_SubmitGPUCommandBufferAndAcquireFence
  89. SDL_UnmapGPUTransferBuffer
  90. SDL_UploadToGPUBuffer
  91. SDL_UploadToGPUTexture
  92. SDL_WaitAndAcquireGPUSwapchainTexture
  93. SDL_WaitForGPUFences
  94. SDL_WaitForGPUIdle
  95. SDL_WaitForGPUSwapchain
  96. SDL_WindowSupportsGPUPresentMode
  97. SDL_WindowSupportsGPUSwapchainComposition

  1. SDL_GPUBuffer
  2. SDL_GPUBufferUsageFlags
  3. SDL_GPUColorComponentFlags
  4. SDL_GPUCommandBuffer
  5. SDL_GPUComputePass
  6. SDL_GPUComputePipeline
  7. SDL_GPUCopyPass
  8. SDL_GPUDevice
  9. SDL_GPUFence
  10. SDL_GPUGraphicsPipeline
  11. SDL_GPURenderPass
  12. SDL_GPUSampler
  13. SDL_GPUShader
  14. SDL_GPUShaderFormat
  15. SDL_GPUTexture
  16. SDL_GPUTextureUsageFlags
  17. SDL_GPUTransferBuffer

構造体

  1. SDL_GPUBlitInfo
  2. SDL_GPUBlitRegion
  3. SDL_GPUBufferBinding
  4. SDL_GPUBufferCreateInfo
  5. SDL_GPUBufferLocation
  6. SDL_GPUBufferRegion
  7. SDL_GPUColorTargetBlendState
  8. SDL_GPUColorTargetDescription
  9. SDL_GPUColorTargetInfo
  10. SDL_GPUComputePipelineCreateInfo
  11. SDL_GPUDepthStencilState
  12. SDL_GPUDepthStencilTargetInfo
  13. SDL_GPUGraphicsPipelineCreateInfo
  14. SDL_GPUGraphicsPipelineTargetInfo
  15. SDL_GPUIndexedIndirectDrawCommand
  16. SDL_GPUIndirectDispatchCommand
  17. SDL_GPUIndirectDrawCommand
  18. SDL_GPUMultisampleState
  19. SDL_GPURasterizerState
  20. SDL_GPUSamplerCreateInfo
  21. SDL_GPUShaderCreateInfo
  22. SDL_GPUStencilOpState
  23. SDL_GPUStorageBufferReadWriteBinding
  24. SDL_GPUStorageTextureReadWriteBinding
  25. SDL_GPUTextureCreateInfo
  26. SDL_GPUTextureLocation
  27. SDL_GPUTextureRegion
  28. SDL_GPUTextureSamplerBinding
  29. SDL_GPUTextureTransferInfo
  30. SDL_GPUTransferBufferCreateInfo
  31. SDL_GPUTransferBufferLocation
  32. SDL_GPUVertexAttribute
  33. SDL_GPUVertexBufferDescription
  34. SDL_GPUVertexInputState
  35. SDL_GPUViewport

列挙体

  1. SDL_GPUBlendFactor
  2. SDL_GPUBlendOp
  3. SDL_GPUCompareOp
  4. SDL_GPUCubeMapFace
  5. SDL_GPUCullMode
  6. SDL_GPUFillMode
  7. SDL_GPUFilter
  8. SDL_GPUFrontFace
  9. SDL_GPUIndexElementSize
  10. SDL_GPULoadOp
  11. SDL_GPUPresentMode
  12. SDL_GPUPrimitiveType
  13. SDL_GPUSampleCount
  14. SDL_GPUSamplerAddressMode
  15. SDL_GPUSamplerMipmapMode
  16. SDL_GPUShaderStage
  17. SDL_GPUStencilOp
  18. SDL_GPUStoreOp
  19. SDL_GPUSwapchainComposition
  20. SDL_GPUTextureFormat
  21. SDL_GPUTextureType
  22. SDL_GPUTransferBufferUsage
  23. SDL_GPUVertexElementFormat
  24. SDL_GPUVertexInputRate

SDL Wikiへのリンク

SDL3/CategoryGPU - SDL Wiki