2015-12-02 14 views
5

Tôi đang cố gắng tạo một trò chơi thủ tục sử dụng Metal, và tôi đang sử dụng một phương pháp tiếp cận dựa trên octree để thực hiện Cấp độ chi tiết.Chunk Rendering in Metal

Phương pháp tôi đang sử dụng liên quan đến CPU tạo các nút octree cho địa hình, sau đó lưới của nó được tạo trên GPU bằng cách sử dụng trình đổ bóng tính toán. Lưới này được lưu trữ trong một bộ đệm đỉnh và bộ đệm chỉ mục trong đối tượng chunk để render.

Tất cả điều này dường như hoạt động khá tốt, tuy nhiên khi nói đến các khối hiển thị, tôi sẽ gặp vấn đề về hiệu suất từ ​​rất sớm. Hiện tại tôi thu thập một mảng khối để vẽ, sau đó gửi cho trình kết xuất của tôi, điều đó sẽ tạo ra một MTLParallelRenderCommandEncoder để sau đó tạo một MTLRenderCommandEncoder cho mỗi đoạn, sau đó được gửi tới GPU.

Bằng vẻ bề ngoài, khoảng 50% thời gian CPU được dùng để tạo MTLRenderCommandEncoder cho mỗi đoạn. Hiện tại tôi chỉ đang tạo ra một khối lập phương 8 đỉnh đơn giản cho mỗi đoạn, và tôi có một mảng 4x4x4 và tôi giảm xuống khoảng 50fps trong những giai đoạn đầu. (Trên thực tế có vẻ như chỉ có có thể lên đến 63 MTLRenderCommandEncoder trong mỗi MTLParallelRenderCommandEncoder do đó, nó không hoàn toàn tạo của cầu 4x4x4)

Tôi đã đọc rằng điểm của MTLParallelRenderCommandEncoder là để tạo ra từng MTLRenderCommandEncoder trong một thread riêng biệt, nhưng tôi đã không có nhiều may mắn với việc này để làm việc. Ngoài ra đa luồng nó sẽ không nhận được xung quanh nắp của 63 khối được trả lại như là một tối đa.

Tôi cảm thấy rằng bằng cách nào đó hợp nhất các bộ đệm đỉnh và chỉ mục cho từng đoạn thành một hoặc hai bộ đệm lớn hơn để gửi sẽ giúp ích, nhưng tôi không chắc chắn làm thế nào để thực hiện điều này mà không cần gọi số memcpy(). hiệu quả.

Dưới đây là mã của tôi mà mất trong mảng các nút và lôi kéo họ:

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() 
} 

Trả lời

5

Bạn lưu ý:

Tôi đã đọc rằng điểm của MTLParallelRenderCommandEncoder là để tạo ra từng MTLRenderCommandEncoder trong một chủ đề riêng biệt ...

Và bạn đã đúng. Những gì bạn đang làm là tuần tự tạo, mã hóa bằng và mã hóa lệnh kết thúc - không có gì song song xảy ra ở đây, vì vậy MTLParallelRenderCommandEncoder không làm gì cho bạn. Bạn sẽ có hiệu năng tương tự nếu bạn loại bỏ bộ mã hóa song song và chỉ tạo bộ mã hóa với renderCommandEncoderWithDescriptor(_:) trên mỗi lần truyền qua vòng lặp của bạn ... điều đó có nghĩa là bạn vẫn gặp phải vấn đề về hiệu năng do chi phí tạo tất cả các bộ mã hóa đó.

Vì vậy, nếu bạn định mã hóa tuần tự, chỉ cần sử dụng lại cùng một bộ mã hóa. Ngoài ra, bạn nên sử dụng lại nhiều trạng thái được chia sẻ khác nhất có thể. Dưới đây là một cách nhanh chóng để có thể tái cấu trúc (chưa được kiểm tra):

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() 
} 

Sau đó, hãy liên hệ với Công cụ để xem bạn có thể gặp phải vấn đề gì nữa. Có một số tuyệt vời WWDC 2015 session về việc hiển thị một số "gotchas" phổ biến, cách chẩn đoán chúng trong hồ sơ và cách khắc phục chúng.

+0

Đó là một số lời khuyên tuyệt vời, hoạt động như một sự quyến rũ. Đã đi từ 16-18ms cho một khung trên CPU đến khoảng 3-4ms. Cảm ơn rất nhiều, tôi vẫn đang học cách sử dụng kim loại và hầu hết mã của tôi đã bị loại bỏ khỏi Metal bằng ví dụ và đó là hướng dẫn cơ bản. Bạn đã thực sự giúp đỡ, cảm ơn bạn. –

+0

Ngoài ra, vì một lý do nào đó tôi đã có nó trong đầu của tôi là một bộ mã hóa lệnh -> một mồi gọi nguyên thủy: S –

+0

* "Tôi đã có nó trong đầu của tôi là một bộ mã hóa lệnh -> một mồi gọi" * ... đó là những gì hiệu quả xảy ra với việc sử dụng OpenGL ngây thơ - mỗi cuộc gọi vẽ có thể biến thành một bản dịch đắt tiền và tải lên trạng thái GPU. Nhưng Metal làm cho mọi thứ rõ ràng hơn. Nếu có vẻ như bạn đang thừa kế và không hiệu quả, có thể bạn đang có. :) – rickster