2015-12-02 13 views
5

メタルを使用して手続き型ゲームを作成しようとしています。レベルの詳細実装にオクツリーベースのチャンクアプローチを使用しています。メタルのチャンクレンダリング

私が使用している方法では、地形のオクツリーノードを作成する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() 
} 

答えて

5

あなたが注意してください。

私はMTLParallelRenderCommandEncoderのポイントは、各MTLRenderCommandEncoderを作成することであることを読みました別のスレッド...

あなたは正しいです。あなたがやっているのはです。コマンドエンコーダーを作成、エンコーディング、エンディングを終了しています - ここでは何も並行していませんので、MTLParallelRenderCommandEncoderは何もしていません。並列エンコーダを削除し、forループを通過するたびにrenderCommandEncoderWithDescriptor(_:)のエンコーダを作成した場合、おおよそ同じパフォーマンスが得られます。つまり、作成するオーバーヘッドのために同じパフォーマンスの問題が残っていますこれらすべてのエンコーダ。

したがって、順次エンコードする場合は、同じエンコーダを再使用してください。また、できるだけ多くの共有状態を再利用する必要があります。

let passDescriptor = MTLRenderPassDescriptor() 

// call this once before your render loop 
func setup() { 
    makeDepthTexture() 

    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 

    // set up render pipeline state and depthStencil state 
} 

func drawNodes(nodes: [OctreeNode], inView view: AHMetalView) { 

    updateUniformsForView(view, duration: view.frameDuration) 

    // Set up completed handler ahead of time 
    let commandBuffer = commandQueue.commandBuffer() 
    commandBuffer.addCompletedHandler { _ in // unused parameter 
     self.uniformBufferIndex = (self.uniformBufferIndex + 1) % AHInFlightBufferCount 
     dispatch_semaphore_signal(self.displaySemaphore) 
    } 

    // Semaphore should be tied to drawable acquisition 
    dispatch_semaphore_wait(displaySemaphore, DISPATCH_TIME_FOREVER) 
    guard let drawable = layer.nextDrawable() 
     else { return } 

    // Set up the one part of the pass descriptor that changes per-frame 
    passDescriptor.colorAttachments[0].texture = drawable.texture 

    // Get one render pass descriptor and reuse it 
    let renderPass = commandBuffer.renderCommandEncoderWithDescriptor(passDescriptor) 
    renderPass.setTriangleFillMode(.Lines) 
    renderPass.setRenderPipelineState(renderPipelineState) 
    renderPass.setDepthStencilState(depthStencilState) 

    for node in nodes { 
     // Update offsets and draw 
     let uniformBufferOffset = sizeof(AHUniforms) * uniformBufferIndex 
     renderPass.setVertexBuffer(node.vertexBuffer, offset: 0, atIndex: 0) 
     renderPass.setVertexBuffer(uniformBuffer, offset: uniformBufferOffset, atIndex: 1) 
     renderPass.drawIndexedPrimitives(.Triangle, indexCount: AHMaxIndicesPerChunk, indexType: AHIndexType, indexBuffer: node.indexBuffer, indexBufferOffset: 0) 

    } 
    renderPass.endEncoding() 

    commandBuffer.presentDrawable(drawable) 
    commandBuffer.commit() 
} 

はその後、楽器を持つプロファイルは、あなたが持っているかもしれないものがあれば、さらにパフォーマンスの問題を参照するには:ここでは(未テスト)の可能なリファクタリングで簡単にパスです。一般的な「落書き」のいくつかを示し、プロファイリングでそれらを診断する方法、およびそれらを修正する方法について、大きなWWDC 2015 sessionがあります。

+0

これは素晴らしいアドバイスです。魅力のように動作します。 CPU上のフレームのために16-18msから3-4msくらいになった。おかげさまで、私はまだ金属の使い方を学んでいて、ほとんどのコードはMetal by Exampleから取り除かれました。基本的なチュートリアルです。あなたは本当に助けてくれました、ありがとう。 –

+0

また、なんらかの理由で、私は1つのレンダリングコマンドエンコーダ - > 1つの描画プリミティブ呼び出しを持っていました:S –

+0

* "私は1つのレンダリングコマンドエンコーダ - > 1つの描画プリミティブ呼び出し"これは、OpenGLの純粋な使用で効果的に起こることです。各描画呼び出しは、暗黙のうちに高価なコンパイルとGPU状態のアップロードに変わる可能性があります。しかし、メタルは物事をより明確にします。あなたが冗長で非効率的であるように見えたら、あなたはおそらくそうです。 :) – rickster

関連する問題