Unity 2019.3から、あれこれ自作せずともやっと、ようやく、悲願の非同期スクリーンキャプチャが出来るようになりました。
前提知識として、Unity APIの呼び出しは、一部のAPIを除いてメインスレッドからの呼び出しのみが許可されています。レガシーな同期アプローチは検索すると吐いて捨てるほど出てくるのでそっちを見てください。
スクリーンキャプチャの流れは3工程あり、バージョンアップを経て段階的に非同期可能になっています。
- レンダーテクスチャのシステムメモリへの読み戻し
- 画像ファイルフォーマットへのエンコード
- ファイル書き込み
1は、ScreenCapture.CaptureScreenshotIntoRenderTexture が2019.1で追加され、ようやく非同期でシステムメモリに読み戻せるようになりました。
1で読み戻したメモリはレンダーテクスチャに指定したフォーマットのバイト配列(NativeArray<byte>)ですが、これを一般的な画像ファイルフォーマットにエンコードするには 2017.1から追加されているImageConversionクラスのAPI、ImageConversion.EncodeToPNG など(フォーマット別)を使用します。しかし、これは引数にバイト配列ではなくTexture2D を要求する上に、メインスレッドのみ呼び出し可能なので同期処理を避けられません。
流石に使えないAPIだと思ったのか、2019.3 でバイト配列(byte[])を使用する ImageConversion.EncodeArrayToPNG が追加されました。リファレンスにも”This method is thread safe.”と書かれていて、メインスレッド以外からの呼び出しが許可されています。
3は、.Net APIで以前から非同期可能です。
これらを組み合わせれば、”ほぼ”非同期にスクリーンキャプチャを行えます。
今後改善して欲しいところ。
- ScreenCapture.CaptureScreenshotIntoRenderTexture リファレンスのサンプルコードが間違っている。
using (var imageBytes = request.GetData<byte>()) で使用されているが、ユーザがこのNativeArrayに対してusingや明示的なDisposeの呼び出しを行うと、例外が出力される(InvalidOperationException: The NativeArray can not be Disposed because it was not allocated with a valid allocator.)。AsyncGPUReadbackRequest が管理しているはず? - NativeArray<byte>からbyte[]の変換をメインスレッドで行う必要があるが、そこで巨大なマネージドメモリのアロケートが走る。
NativeArray<byte>のまま処理するAPI、つまりJob System with Burstでエンコードを行って欲しい。 - ImageConversion.EncodeArrayToPNG のエンコード済みバイト列として巨大なマネージドメモリのアロケートが走る。
必要メモリ量の計算と、事前確保したメモリを使用するAPIが欲しい。NativeArrayでの非同期ファイル読み書きをサポートして欲しい。 - 要するに、最初から最後までNativeArrayのまま処理が完結するようにして欲しい。
- ImageConversion.EncodeArrayToPNG は出力フォーマットの指定ができない。つまり入力フォーマットと出力フォーマットは同一になる。また、RGBAのデータをRGBしかもたないJPGにすると、rowBytesを指定してもRGBAからRGBへ変換してくれないようだ。
- 上下が反転している。グラフィクスAPI依存?ImageConversion.EncodeToPNG はTexture2D の段階でそれが打ち消されている?
ユーザが面倒を見るべき処理ではないので、内部で処理して欲しい。
追記:
2019.4で ImageConversion.EncodeNativeArrayToPNG が追加されていました。やったね。
>NativeArrayでの非同期ファイル読み書きをサポートして欲しい。
AsyncReadManagerがUnityから公式に提供されています。
UniUnsafeIOが非同期NativeArray書き込みを可能にします。
https://github.com/pCYSl5EDgo/UniUnsafeIO
こちらのサイトのおかげで非同期スクリーンキャプチャできました!
上下反転は
Buffer.BlockCopy( managed, pitch * y, image, ((int)height – 1 – y) * pitch, pitch );
を
Buffer.BlockCopy( managed, pitch * y, image, y * pitch, pitch );
にすると正位置の画像で出せました!
最近のバージョンでは試せていないですが、上下反転するのは解消していて自力で逆にしなくてもよくなったんですね