How to modify default Metal template for multiple objects

I'm trying to add multiple objects to the scene, starting from Xcode 9's Metal Game App template.


I've modified the template as little as possible, to render multiple objects, based on this answer: https://stackoverflow.com/a/37424817/518169


My problems is that the second object doesn't appear, and I cannot debug this issue. Adding print lines show the buffer is updated, but I'm not sure it's the same state on the GPU as well.


Why is the second object not drawn? Is it something about how I'm handling the buffers, or it's something in the draw pipeline (like clearing the screen twice for example)?


_ = inFlightSemaphore.wait(timeout: DispatchTime.distantFuture)

if let commandBuffer = commandQueue.makeCommandBuffer() {

  let semaphore = inFlightSemaphore
  commandBuffer.addCompletedHandler { (_) -> Swift.Void in
    semaphore.signal()
  }

  updateDynamicBufferState()

  scene.updateGameState()
  uniforms[0].projectionMatrix = scene.projectionMatrix


  if let renderPassDescriptor = view.currentRenderPassDescriptor,
    let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor) {
    renderEncoder.label = "Primary Render Encoder"

    for node in scene.nodes { // ------ FOR LOOP START
      uniforms[0].modelViewMatrix = node.modelViewMatrix

      renderEncoder.pushDebugGroup("Draw Node: \(node.name)")

      renderEncoder.setCullMode(node.cullMode)
      renderEncoder.setFrontFacing(node.frontFacing)
      renderEncoder.setRenderPipelineState(node.pipelineState)
      renderEncoder.setDepthStencilState(depthState)
      renderEncoder.setVertexBuffer(dynamicUniformBuffer, offset: uniformBufferOffset, index: 2)
      renderEncoder.setFragmentBuffer(dynamicUniformBuffer, offset: uniformBufferOffset, index: 2)

      node.render(renderEncoder: renderEncoder) // setVertexBuffer, setFragmentTexture, drawIndexedPrimitives

      renderEncoder.popDebugGroup()
    } // ------ FOR LOOP END

    renderEncoder.endEncoding()

    if let drawable = view.currentDrawable {
      commandBuffer.present(drawable)
    }
  }
  commandBuffer.commit()
}

Accepted Reply

Metal is an asynchronous API. The GPU doen't read the contents of a buffer until the command buffer has been commited and it has actually executed commands using the contents of the buffer. The only way to be sure the GPU has executed command and it's okay to write new contents into the buffer is when the completion handler for cmmand buffer which read from the buffer has triggered.


If you have 2 objects in different places you need 2 matrices in dynamicUniformBuffers (one for each object). Otherwise you're overwritting the first matrix with the second before you've submitted your command buffer. And then they're both drawn but in the exact same place (I guessing these objects are eactly the same so it doesn't appear as if the 2nd one is drawn).

Replies

Metal is an asynchronous API. The GPU doen't read the contents of a buffer until the command buffer has been commited and it has actually executed commands using the contents of the buffer. The only way to be sure the GPU has executed command and it's okay to write new contents into the buffer is when the completion handler for cmmand buffer which read from the buffer has triggered.


If you have 2 objects in different places you need 2 matrices in dynamicUniformBuffers (one for each object). Otherwise you're overwritting the first matrix with the second before you've submitted your command buffer. And then they're both drawn but in the exact same place (I guessing these objects are eactly the same so it doesn't appear as if the 2nd one is drawn).