そこで今回は、Intel / NVIDIA / AMD のいずれのGPUでも使用できるようにQSVEnc/VCEEncのOpenCL実装をAviutlに移植したOpenCLフィルタにしてみた。
対応しているフィルタはいまのところ11種類で、適用順は固定で下記の通り。
- 色空間変換
- nnedi
- ノイズ除去(knn)
- ノイズ除去(pmd)
- ノイズ除去(smooth)
- リサイズ
- unsharp
- エッジレベル調整
- warpsharp
- バンディング低減
- 色調補正
将来的には適用順を変更できるようにしたいところ。
また、現状時間方向に参照が必要なフィルタ(3Dフィルタ)には対応できていない。ちょっとどう実装したらよいかわかっていないので、当面は非対応の予定。
QSVEnc / NVEnc / VCEEnc フィルタとの違い
基本的には、QSVEnc / NVEnc / VCEEncのVppフィルタをほぼそのまま移植したものなの。ただ、QSVEnc / NVEnc / VCEEncでは速度重視なので、出力色空間に応じてなるべく軽い色フォーマットで処理している。(YUV420 8bit/16bitが多いと思う)。
一方、今回作成したAviutl版は、一貫してYUV444 16bitで処理するので、特に色差成分について少し結果が違いそう。(そう大きな差ではないはずなので確認用としては十分だとは思う)
ダウンロード>>clfiltersの詳細
処理概要
Aviutlの内部フォーマット(YC48)からGPUで扱いやすいYUV444 16bitに変換てから、GPUに転送してフィルタ処理を行う。その後、処理結果をCPUに転送し、YC48に戻して処理を完了させている。
YC48 → YUV444 16bit → GPUに転送 → VppフィルタをGPUで連続処理 → CPUに転送 → YUV444 16bit → YC48
GPUフィルタの高速化
以前のCUDA版ではcufiltersでは行っていなかった最適化を真面目に行った。
GPUフィルタの課題GPUで高速にフィルタ処理ができたとしても、残念ながらGPUフィルタが高速とは限らない。
- CPU-GPU間転送が遅い Aviutlでは基本的にはフレームのデータがCPUにあるため、GPUフィルタを適用する場合、CPU-GPU間の転送が往復で必要になる。ところがこれはかなり遅い処理で、転送開始のレイテンシも大きいし、転送にも時間がかかる。
- そもそもGPUでの計算開始が遅い。 すぐに計算を行ってくれるCPUと異なり、GPUに計算を発行してから実際に開始されるまでにそれなりの遅延がある。
CPU-GPU間転送や、GPUの処理開始遅延の影響で、計算を直列に並べると、フィルタによっては普通にCPU版をAVX2とかで最適化したほうが高速ということになってしまう。GPU版を高速にするためには、
- CPU-GPU間転送を減らす- CPU-GPU間転送やGPU計算を並行して実行するなどの対策が必要になる。
実施済みの最適化
いくつかの最適化に段階的に取り組んでみた。
初期版: CPU-GPU間転送を減らす複数のGPUフィルタを一度に適用することで、CPU-GPU間転送を削減。GPUフィルタをひとつひとつ適用するのに比べ、転送回数を減らすのが狙い。
初期版: 内蔵GPU使用時のCPU-GPU間転送の削減Intel GPUなど、内蔵GPUを使用する場合、OpenCLのAPIを適切に使うことでCPU-GPU間転送をなくすようにした (いわゆるZero Copy)。
最適化1: CPU-GPU間転送とGPU計算を並行して実行保存時にはフレームを先読みすることで、CPU-GPU間転送とGPU計算を並行して行うことで、CPU-GPU間転送や処理開始の遅延を抑制した。(実装の単純化のため、保存時のみ行っており、編集時は行っていない。)
最適化2: メモリ確保・解放の削減 (NVIDIA)もともとのOpenCL実装はIntel/AMD GPU向けで、これらのGPUでは cl_khr_image2d_from_buffer というOpenCL拡張が問題なく使えたので、これを前提にした最適化をしてしまっていた。
一方、NVIDIAはこの拡張には対応していないようで(CUDAでは似たようなことできるのに何で?)、この拡張を使わずに動作させるために、メモリ確保・コピー・解放を毎フレーム行ってしまっていた。GPUのメモリ確保・解放はかなり遅いので、メモリを使いまわすようにしてメモリ確保・解放をなるべくしないようにした。
最適化3: さらなるメモリ確保・解放の削減意図せずメモリ確保・解放を連発していた個所が残っていたので修正。
最適化4: 不要な同期の削除まだGPUが遊んでいる時間が多い状況だったので、VTuneなどを使って、OpenCL APIの呼ばれ方などをチェック。OpenCLリソースの解放漏れにより、無駄な同期がかかってしまっているようだったので、これを解消して高速化。
保存時の処理速度
実際に
ベンチマークプラグインを使って処理速度を測定してみた。
入力sakura_op.mpg 1280x720 30fps 3501frame
環境Win11 x64
Aviutl 1.10
Intel i9 12900K + Intel iGPU HDG770
NVIDIA GeForce RTX2070
有効フィルタ (3つ)pmd_mt 回数:2, 強さ:100, 閾値:100
リサイズ 1280x720→1920x1080 spline36
エッジレベル調整 強さ:5, 閾値:20, 黒:0, 白:0
え~と、CPUのほうが速くね...? 悲しみ。
まあ、それはおいといて、最適化1~4によってだいぶ高速化できていて、特にRTX2070では最初の実装から比べると3.3倍速くすることができた。GPUではこうした細かな最適化の効果が大きいようで、やはりGPUフィルタではいろいろチェックすべきことが多そう。
ただ、そうは言っても、現状でも最適化されたCPUフィルタよりは遅くなってしまっている。やはりCPU-GPU間の転送や色フォーマットの変換など、GPUフィルタ特有のオーバーヘッドがいろいろ載ってくるため、今回試したような比較的軽量なフィルタではAVX2等で最適化CPUフィルタのほうが高速、ということになってしまった。
もちろん、現時点で最新のCPUのi9 12900Kと、1世代前のGPUのRTX2070での戦いなので、CPUとGPUの性能比によっては逆転することもありそうだし、もっと重いフィルタ(nnediやsmooth)を使えば、GPUのほうが有利になっていくとは思う。
また、今回はベンチマークプラグインで計測したので、デコードとフィルタだけがぶん回っている状況だけど、本来はエンコードも行うことになると思う。なので、GPUフィルタがCPUより遅いから無意味ということはなくて、CPUでの演算をGPUに移すことで、CPUがエンコードに回せる時間が増えるといったメリットもあるかもしれない。
# まあもともとはGPUフィルタの効果の確認用なので、このぐらい出ていれば十分?