ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
動画AppのためのCore Imageパイプラインの最適化
Core Imageの処理能力を活用し、App内の動画パフォーマンスを最適化する方法について説明します。あなたのAppで動画にエフェクトを適用するためのCore Imageパイプラインを構築する方法を紹介します。CIContextを使用するときに、Appのメモリのフットプリントを減らす方法、およびCore Imageフィルタを使用した動画再生のためのAVPlayViewまたはMTKViewビュークラスの使用に関するベストプラクティスについて学びます。さらに、独自のカスタムカーネルをMetal Shading Languageで書くべき理由について説明し、Core ImageパイプラインでのMetalコマンドキューを最適に使用するためのパフォーマンスに関するヒントを紹介します。
リソース
関連ビデオ
WWDC22
WWDC21
WWDC20
-
ダウンロード
こんにちは WWDCへようこそ
“動画アプリケーションのための Core Imageパイプラインの最適化” デヴィッド・ヘイワードです 動画アプリのエフェクト処理で Core Imageを使用し― 最適なパフォーマンスを 引き出す方法をご紹介します 3つのトピックについて説明しましょう CIContextの最適な生成方法 カスタムCIKernelの書き方と適用方法 そしてビューへの 最適なレンダリング方法です まずはCIContextの生成です CIContextの生成は1つのビューに対し 1つのCIContextのみを生成してください 初期化に時間とメモリ容量を要するため 頻繁には生成を行いません context生成時には いくつかのオプション指定が必要です 最も重要なのは中間生成物を キャッシュに格納しないことです 動画はそれぞれのフレームが 前のフレームとは違います キャッシュの無効化により メモリ使用量を抑制します
contextには名前を付けることを 推奨していますが Core Imageのデバッグ技術の 使用時に有効だからです Core ImageはMetalを使用していますが Core Imageと他のMetalのAPI群を 混在して使う場合もあります 例えば MetalTextureをCore Imageの入力値 または出力値に使う場合は CIContextをMetalCommandQueueで 生成することを推奨します 時系列のグラフで説明しましょう アプリがMetalTextureにデータを格納するために MetalCommandQueueを使ったとします この場合はCPU上でキューに入れ GPU上で実行します 次に 先ほどのMetalTextureを MetalQueueを使っているCore Imageに渡し 別のMetalTextureにデータを格納します ここでもCPU上でキューに入れ GPU上で実行します 最後に 再びMetalQueueを使い Core Imageから 出力textureをレンダリングします すべての処理が異なるキューで行われるため 正しい結果を得るには waitコマンドの発行が必要です 非効率なパイプライン処理による 無駄な時間を省くため 他のMetalレンダリング処理で使うキューと 同じキューでCIContextを生成します これによりアプリの待ちをなくし 最適なパフォーマンスを発揮する パイプライン処理が可能となります
次はCIKernelをMetalで書くご説明です 動画アプリを最適に利用するには Metalでのエフェクトの実装が重要です すべてMetalで実装されているので Core Imageのビルドインフィルタを 可能な限り使用します
ビルトインフィルタの利用に役立つ文書を アップロードしており パラメータ説明 サンプル画像 サンプルコードを記載しています
ビルトインフィルタに適用されるコードは シンプルです CIFilterBuiltinsをインポートし filterインスタンスを生成します そして 入力プロパティを設定し 出力画像を取得します カスタムCIKernelをMetalで書くことを 推奨する理由は他にもあります CIKernelの通常機能である 自動タイリングや自動連結などに加え Metalで書くことでコンパイル処理時間を 減らすことができるのです ギャザー読み取りやグループ書き込み 半精度浮動小数点などの機能も利用可能です 開発者の作業を快適化するため タイピング時の構文ハイライトや ビルド時の構文チェックが行われます MetalでCIKernelを書く例を お見せしましょう プレゼンテーションの中でお見せした kernelでデモを行い HDR動画をAVFoundationで 編集し再生します カスタムCIKernelを書くのは簡単です 最初にソースの冒頭でCoreImage.hの ヘッダーをインクルードすることで Core Imageが提供する追加クラスと同様に Metal標準すべてのクラスにアクセスができます 次に kernelのための関数 extern “C”を宣言します この例はCIColorKernelなので 関数は float4を戻り値として返す必要があります そして引数を取ることができます これは最初の引数がCore Imageのsample_tで 入力画像から得たピクセルを表すものです これはリニア色空間の乗算済み アルファ RGBA float4で SDRにもHDRにも適合します
最後の引数はCore Imageのdestinationです これは返却するピクセル座標を提供します このkernelの実装では destinationのXY座標の値を使用して どの斜め線にいるかを決定します そして 単純な数学を用い ゼブラストライプにいるかどうかを計算します
ゼブラストライプと 現在処理中のピクセルサンプルが SDRの標準的な白より明るいならば 明るい赤のピクセルを返します さもないとピクセルサンプルは 変更されません 詳細は“Build Metal-Based Core Image Kernels with Xcode”をご覧ください
最後は動画アプリの開発における 最適なビュークラスの選択についてお話しします アプリに動画エフェクト処理がある場合 UIImageViewやNSImageViewクラスは 使用を避けましょう 静的コンテンツ表示のために 設計されているからです 最も簡単に使えるAVPlayerViewは ビューにフィルタ処理した動画を 自動的に映します パフォーマンス向上のためには MetalKitViewも選択可能です これら2つをCore Imageと使う方法を 紹介します
AVPlayerViewの使用は非常に単純です キーとなるオブジェクト AVMutableVideoCompositionは 動画のアセットとハンドラブロックと共に 初期化されます このブロックは毎回呼び出され AVAsynchronousCIImageFilteringRequestに 渡されます そして CIFilterを生成し 入力値を設定すると 出力画像が取得されます 次に リクエストオブジェクトに その画像を渡します
Xcodeでは デバッグ中に CIImageの変数をマウスオーバーすると オブジェクトのアドレスを示す ポップオーバーが現れます 目のマークをクリックすると 新たなウィンドウが立ち上がり 画像を構成するレシピが表示されます 入力値の動画フレームが 10ビットHDR表示ということが分かります Core Imageは自動的に HLGからCore Image作業領域へと カラー管理されています 加工した動画を映す もう1つの選択肢はMetalKitViewです
MetalKitViewを使う場合の Core Imageの最適な利用方法を説明します 最初にframeとdeviceと一緒に initをオーバーライドします initメソッドはビューに対して CIContextを生成する最適なタイミングです Core ImageがMetal演算を使えるよう framebufferOnlyはfalseに設定してください macOSでは ビューがHDR対応の場合 colorPixelFormatを rgba16floatに設定してください wantsExtendedDynamicRangeContentも trueを設定します 次にdraw in viewメソッドを実装します CIRenderDestinationは 特殊な方法で生成されます 正しいwidth height pixelformatによって destinationを生成しますが Metal textureを渡す代わりに textureを返すブロックを渡します CIContextは 前のフレーム処理が終わる前に Metal処理のキュー追加が可能となります 次に CIContextにdestinationへの 画像レンダリングタスクを指示します
最後に 現在描画可能なビューを示すための コマンドバッファを生成します
本日はCore Imageを使用して 動画アプリのパフォーマンスを 最適化する方法をご紹介しました 本日 紹介したトピックは CIContextsの生成 CIKernelsの適用 そして ビューへのレンダリングです これらの方法で 皆様のアプリ動画は より素晴らしいものになるでしょう ご視聴ありがとうございました WWDC 2020をお楽しみください
-
-
0:52 - Creating CIContext
let context = CIContext(options: [ .cacheIntermediates : false, .name : ”MyAppView” ])
-
1:26 - Creating CIContext 2
let context = CIContext(MTLCommandQueue : queue, options: […])
-
2:59 - Use builtins Whenever possible
import CoreImage.CIFilterBuiltins func motionBlur(inputImage: CIImage) -> CIImage? { let motionBlurFilter = CIFilter.motionBlur() motionBlurFilter.inputImage = inputImage motionBlurFilter.angle = 0 motionBlurFilter.radius = 20 return motionBlurFilter.outputImage }
-
3:56 - Put your kernels in .ci.metal sources
// MyKernels.ci.metal #include <CoreImage/CoreImage.h> // includes CIKernelMetalLib.h using namespace metal; extern "C" float4 HDRZebra (coreimage::sample_t s, float time, coreimage::destination dest) { float diagLine = dest.coord().x + dest.coord().y; float zebra = fract(diagLine/20.0 + time*2.0); if ((zebra > 0.5) && (s.r > 1 || s.g > 1 || s.b > 1)) return float4(2.0, 0.0, 0.0, 1.0); return s; }
-
5:50 - Using AVPlayer View
let videoComposition = AVMutableVideoComposition( asset: asset, applyingCIFiltersWithHandler: { (request: AVAsynchronousCIImageFilteringRequest) -> Void in let filter = HDRZebraFilter() filter.inputImage = request.sourceImage let output = filter.outputImage if (output != nil) { request.finish(with: output, context: myCtx) } else { request.finish(with: err) } } )
-
7:01 - Using MTKView
class MyView : MTKView { var context: CIContext var commandQueue : MTLCommandQueue override init(frame frameRect: CGRect, device: MTLDevice?) { let dev = device ?? MTLCreateSystemDefaultDevice()! context = CIContext(mtlDevice: dev, options: [.cacheIntermediates : false] ) commandQueue = dev.makeCommandQueue()! super.init(frame: frameRect, device: dev) framebufferOnly = false // allow Core Image to use Metal Compute colorPixelFormat = MTLPixelFormat.rgba16Float if let caml = layer as? CAMetalLayer { caml.wantsExtendedDynamicRangeContent = true } }
-
7:29 - Using MTKView 2
func draw(in view: MTKView) { let size = self.convertToBacking(self.bounds.size) let rd = CIRenderDestination(width: Int(size.width), height: Int(size.height), pixelFormat: colorPixelFormat, commandBuffer: nil) { () -> MTLTexture in return view.currentDrawable!.texture } context.startTask(toRender:image, from:rect, to:rd, at:point) // Present the current drawable let cmdBuf = commandQueue.makeCommandBuffer()! cmdBuf.present(view.currentDrawable!) cmdBuf.commit() }
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。