MTLRenderCommandEncoder でカメラアプリ
以前 MetalKitでカメラアプリを作ってみた という投稿をしました。
前回は http://qiita.com/shu223/items/3301a1e64757c0bd73ef に倣って MTLComputeCommandEncoder を使ってみましたが、今回は MTLRenderCommandEncoder を使って作り直してみたのでそのメモです。
まず、 MTLComputeCommandEncoder と MTLRenderCommandEncoder が何なのかというとそれぞれ以下のようになります。
画像処理やデータ並列演算処理用のコマンドエンコーダー
グラフィックスのレンダリング処理用のコマンドエンコーダー
ただのカメラアプリなら画像処理などもしないし MTLRenderCommandEncoder で十分ということになります。
では MTLRenderCommandEncoder でどう実装が変わるかというと大きくは以下の3点です。
Metalシェーダがコンピュートシェーダーからバーテックスシェーダーとフラグメントシェーダーに変わる
MTLComputePipelineState から MTLRenderPipelineState に変更
MTLCommandEncoder の生成方法が変更
このように描画する頂点情報をフラグメントシェーダーが受け取って処理する形になりました。
typedef struct { float4 renderedCoordinate [[position]]; float2 textureCoordinate; } TextureMappingVertex; vertex TextureMappingVertex mapTexture(unsigned int vertex_id [[ vertex_id ]]) { float4x4 renderedCoordinates = float4x4(float4( -1.0, -1.0, 0.0, 1.0 ), float4( 1.0, -1.0, 0.0, 1.0 ), float4( -1.0, 1.0, 0.0, 1.0 ), float4( 1.0, 1.0, 0.0, 1.0 )); float4x2 textureCoordinates = float4x2(float2( 0.0, 1.0 ), float2( 1.0, 1.0 ), float2( 0.0, 0.0 ), float2( 1.0, 0.0 )); TextureMappingVertex outVertex; outVertex.renderedCoordinate = renderedCoordinates[vertex_id]; outVertex.textureCoordinate = textureCoordinates[vertex_id]; return outVertex; } fragment half4 displayTexture(TextureMappingVertex mappingVertex [[ stage_in ]], texture2d<float, access::sample> texture [[ texture(0) ]]) { constexpr sampler s(address::clamp_to_edge, filter::linear); return half4(texture.sample(s, mappingVertex.textureCoordinate)); }
パイプラインステートの生成が以下の用に変わりました。
- guard let function = library?.makeFunction(name: "kernel_passthrough") else { + guard + let vertex = library?.makeFunction(name: "mapTexture"), + let fragment = library?.makeFunction(name: "displayTexture") + else { fatalError() } - pipeline = try! device?.makeComputePipelineState(function: function) + let descriptor = MTLRenderPipelineDescriptor() + descriptor.colorAttachments[0].pixelFormat = colorPixelFormat + descriptor.sampleCount = sampleCount + descriptor.vertexFunction = vertex + descriptor.fragmentFunction = fragment + pipeline = try! device?.makeRenderPipelineState(descriptor: descriptor)
currentRenderPassDescriptor を指定して MTLRenderCommandEncoder を生成するようになりました。
画面全体(四角形)に描画したいので、 drawPrimitives の type は triangleStrip 、 vertexCount は 4 となります。
- let encoder: MTLComputeCommandEncoder = commandBuffer.makeComputeCommandEncoder() - encoder.setComputePipelineState(pipeline!) - encoder.setTexture(texture, at: 0) - encoder.setTexture(drawable.texture, at: 1) - let threads = MTLSize(width: 16, height: 16, depth: 1) - let threadgroups = MTLSize(width: texture.width / threads.width, - height: texture.height / threads.height, - depth: 1) - encoder.dispatchThreadgroups(threadgroups, threadsPerThreadgroup: threads) + let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: currentRenderPassDescriptor!) + encoder.setRenderPipelineState(pipeline!) + encoder.setFragmentTexture(texture, at: 0) + encoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4, instanceCount: 1) encoder.endEncoding()
ちなみに、前回の投稿でCPUが右肩上がりだと書いていましたが、 MTLRenderCommandEncoder 版でも計測してみたところ以下のようになりました。
今回は30分間計測を続けてみたところ(前回は10分間)、最初のうちはMTLComputeCommandEncoder 同様CPU使用率は上昇するのですが、一定のところで頭打ちになるようです。
の計測結果(30分間)と比べるとこんな感じですこんな感じです。
どちらの場合も無駄にスレッドが増えているというようなことも無いようですが、こういうものなのでしょうか?
ちなみに、このCPUはInstrumentsのActivity Monitorの% CPUの値を取っています。