メタルを使用して手続き型ゲームを作成しようとしています。レベルの詳細実装にオクツリーベースのチャンクアプローチを使用しています。メタルのチャンクレンダリング
私が使用している方法では、地形のオクツリーノードを作成するCPUを使用します。このオクツリーノードは、計算シェーダーを使用してGPUにメッシュを作成します。このメッシュは、レンダリングのためにチャンクオブジェクトの頂点バッファとインデックスバッファに格納されます。
このすべてはかなりうまくいくようですが、チャンクをレンダリングする場合は早い段階でパフォーマンスの問題が発生します。現在は描画するチャンクを集めてレンダラーに提出し、MTLParallelRenderCommandEncoder
を作成して各チャンクにMTLRenderCommandEncoder
を作成し、それをGPUに送信します。
CPU時間の約50%が、各チャンクのMTLRenderCommandEncoder
の作成に費やされます。現在のところ、各チャンクに対して単純な8頂点キューブメッシュを作成しています.4x4x4のチャンク配列があり、これらの初期段階では約50fpsに落としています。私はMTLParallelRenderCommandEncoder
のポイントは、別のスレッドで各MTLRenderCommandEncoder
を作成することであることを読んだ、まだ私は」
(実際にはそれだけで、各MTLParallelRenderCommandEncoder
で最大63 MTLRenderCommandEncoder
ことができるので、それは完全には4x4x4がないということらしいです)これを稼働させることに多大な恵みはありませんでした。また、マルチスレッドの場合、最大63個のチャンクを上限として表示されません。
私は何とか役立つだろう提出用の1つのまたは2つの大きなバッファに各チャンクの頂点とインデックスバッファを統合することを感じるが、私はおびただしいmemcpy()
呼び出しなしで、これも改善するかどうか、これを行う方法がわからないんだけど効率。
ここでは、ノードの配列を取り込み、それらを描く私のコードです:
func drawNodes(nodes: [OctreeNode], inView view: AHMetalView){
// For control of several rotating buffers
dispatch_semaphore_wait(displaySemaphore, DISPATCH_TIME_FOREVER)
makeDepthTexture()
updateUniformsForView(view, duration: view.frameDuration)
let commandBuffer = commandQueue.commandBuffer()
let optDrawable = layer.nextDrawable()
guard let drawable = optDrawable else{
return
}
let passDescriptor = MTLRenderPassDescriptor()
passDescriptor.colorAttachments[0].texture = drawable.texture
passDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(0.2, 0.2, 0.2, 1)
passDescriptor.colorAttachments[0].storeAction = .Store
passDescriptor.colorAttachments[0].loadAction = .Clear
passDescriptor.depthAttachment.texture = depthTexture
passDescriptor.depthAttachment.clearDepth = 1
passDescriptor.depthAttachment.loadAction = .Clear
passDescriptor.depthAttachment.storeAction = .Store
let parallelRenderPass = commandBuffer.parallelRenderCommandEncoderWithDescriptor(passDescriptor)
// Currently 63 nodes as a maximum
for node in nodes{
// This line is taking up around 50% of the CPU time
let renderPass = parallelRenderPass.renderCommandEncoder()
renderPass.setRenderPipelineState(renderPipelineState)
renderPass.setDepthStencilState(depthStencilState)
renderPass.setFrontFacingWinding(.CounterClockwise)
renderPass.setCullMode(.Back)
let uniformBufferOffset = sizeof(AHUniforms) * uniformBufferIndex
renderPass.setVertexBuffer(node.vertexBuffer, offset: 0, atIndex: 0)
renderPass.setVertexBuffer(uniformBuffer, offset: uniformBufferOffset, atIndex: 1)
renderPass.setTriangleFillMode(.Lines)
renderPass.drawIndexedPrimitives(.Triangle, indexCount: AHMaxIndicesPerChunk, indexType: AHIndexType, indexBuffer: node.indexBuffer, indexBufferOffset: 0)
renderPass.endEncoding()
}
parallelRenderPass.endEncoding()
commandBuffer.presentDrawable(drawable)
commandBuffer.addCompletedHandler { (commandBuffer) -> Void in
self.uniformBufferIndex = (self.uniformBufferIndex + 1) % AHInFlightBufferCount
dispatch_semaphore_signal(self.displaySemaphore)
}
commandBuffer.commit()
}
これは素晴らしいアドバイスです。魅力のように動作します。 CPU上のフレームのために16-18msから3-4msくらいになった。おかげさまで、私はまだ金属の使い方を学んでいて、ほとんどのコードはMetal by Exampleから取り除かれました。基本的なチュートリアルです。あなたは本当に助けてくれました、ありがとう。 –
また、なんらかの理由で、私は1つのレンダリングコマンドエンコーダ - > 1つの描画プリミティブ呼び出しを持っていました:S –
* "私は1つのレンダリングコマンドエンコーダ - > 1つの描画プリミティブ呼び出し"これは、OpenGLの純粋な使用で効果的に起こることです。各描画呼び出しは、暗黙のうちに高価なコンパイルとGPU状態のアップロードに変わる可能性があります。しかし、メタルは物事をより明確にします。あなたが冗長で非効率的であるように見えたら、あなたはおそらくそうです。 :) – rickster