IntelのGPUのJPEGエンコード機能を利用する

JPEGエンコードはXeや6世代以降のCPUのiGPUで利用できるようです。  
画像および動画の圧縮伸長機能を利用する際、10世代CPU以前ではMedia SDKを、11世代以降やXeではMedia SDK RuntimeもしくはoneVPL GPU Runtimeを介して使います。今回はそれらをうまく利用するために [libvpl](https://github.com/intel/libvpl) にある VPL Dispatcher を使います。  
APIの詳細は [Intel® VPL documentation](https://intel.github.io/libvpl/latest/index.html) にあります。  

# VPL Dispatcherのビルドなど
[Build/Install Intel® Video Processing Library (Intel® VPL) from Source](https://github.com/intel/libvpl/blob/master/INSTALL.md) に従いビルドします。  
include\vpl ディレクトリ以下のファイルと lib\vpl.lib を使います。  
グラフィクスドライバに oneVPL Dispatcher (libvpl.dll) が含まれているように思えるのですが詳細の確認はしていません。無ければビルド時にできる bin\libvpl.dll を使えばよいかもしれません。  

# エンコードの流れ
1. 画像データをエンコーダ入力に適したもの(NV12, YUY2など)に変換する  
1. 画像データをJPEGエンコードする

細かく書くと [Encoding Procedures #JPEG](https://intel.github.io/libvpl/latest/programming_guide/VPL_prg_encoding.html#jpeg) にあるような感じです。  
Media SDK(11世代より古い)の場合はフレームアロケータまわりの煩雑な記述が必要ですが、VPL(11世代以降とXe)では不要になります。  
フレームアロケータの記述例は tools\legacy\sample_common にあります。  

# サンプルコード
[GitHub](https://github.com/53544556454E/VPL_JPEG_encode) に置きました。Windows環境用です。ビルドする際は前述した libvpl に加え [Windows Implementation Librar](https://github.com/microsoft/wil) と [stb](https://github.com/nothings/stb) を用意してください。フレームはシステムメモリから割当てています。  
「使用するディスプレイアダプタの番号」「入力画像ファイルパス」「品質」を与えます。  
標準出力にYCbCr, 4:2:2のJPEGデータを出力します。  

# パフォーマンスについて
1920*1080ドットのRGB画像を入力し、VPPでYUY2へ変換を行い、品質75の4:2:2へエンコードしてみたところ、要した時間は次のようなものでした。  
```
MFXLoad: 54 [us]
MFXCreateConfig: 9 [us]
MFXSetConfigFilterProperty: 44 [us]
MFXCreateSession: 65321 [us]
MFXVideoCORE_SetFrameAllocator: 15 [us]
MFXVideoENCODE_Query: 25334 [us]
MFXVideoVPP_Query: 7506 [us]
MFXVideoVPP_QueryIOSurf: 35 [us]
MFXVideoVPP_Init: 17824 [us]
MFXVideoENCODE_Init: 2239 [us]
AllocFrames: 47 [us]
FillFrameSurface: 2861 [us]
MFXVideoVPP_RunFrameVPPAsync: 143 [us]
MFXVideoENCODE_EncodeFrameAsync: 70 [us]
MFXVideoCORE_SyncOperation: 6 [us]
finalize function calls: 19990 [us]
```
エンコード自体は0.3ミリ秒弱で完了しています。  
エンコードに使用するGPUにモニタを接続していない場合、MFXCreateSession の実行時間が60ミリ秒ほど長くなるようです。  
連続して複数の画像を処理する使い方だと有効に利用できそうですが… MFXVideoVPP_Reset, MFXVideoENCODE_Reset を行い入力画像の幅と高さのみを変更してセッションを再利用してみたのですが、幅あるいは高さが2048を超える領域を確保した場合、MFXVideoCORE_SyncOperation の実行時間が急激に長くなるようでした。こうなるとCPUよりもパフォーマンスが劣るようになるので用途はだいぶ限られたものとなりそうです。  
再利用に要した時間は次のようなものでした。MFXVideoENCODE_Query は自前で記述して最適化するとよいかもしれません。    
```
MFXVideoENCODE_Query: 1141 [us]
MFXVideoVPP_Query: 31 [us]
MFXVideoVPP_Reset: 41 [us]
MFXVideoENCODE_Reset: 843 [us]
MFXVideoVPP_QueryIOSurf: 14 [us]
```

# その他いくつか気になった点
* [Intel® VPL Dispatcher](https://intel.github.io/libvpl/latest/programming_guide/VPL_prg_session.html#vpl-short-name-dispatcher) にあるようにセッションを生成するまでの手順がMedia SDKの頃から大幅に変わりました。  
* MFXCreateConfig で作成した…便宜上コンフィグフィルタとしましょう。これを任意に削除できないので、異なる実装(iGPUとdGPUなど)のセッションを作りたいといった場合は使い勝手が悪いです。異なる内容のコンフィグフィルタを使いたい場合は作成済みのものを MFXSetConfigFilterProperty で上書きして使うことになります。  
* `MFX_FOURCC_RGB4         = MFX_MAKEFOURCC('R','G','B','4'),   /*!< RGB4 (RGB32) color planes. BGRA is the order, 'B' is 8 MSBs, then 8 bits for 'G' channel, then 'R' and 'A' channels. */` との記述がありますが、下位アドレスから順にBGRAなので正しくは **'B' is 8 LSBs** です。  
* エンコード時の mfxExtVideoSignalInfo の設定方法がわからなかったので、フォーマット変換時に mfxExtVPPVideoSignalInfo で MFX_TRANSFERMATRIX_BT601, MFX_NOMINALRANGE_16_235 としています。[Wikipedia: JPEG File Interchange Format: Color space](https://en.wikipedia.org/wiki/JPEG_File_Interchange_Format#Color_space)などにあるように、JPEGではフルレンジ入力を扱うべきです。  
* UHD Graphics 630 では 4:2:0(NV12), 4:0:0(NV12), 4:2:2(YUY2) でのエンコードはできるのですが、4:1:1, 4:2:2V, 4:4:4 のエンコードは行えません。また、扱える画像の最大サイズは8192*8192のようです。