Search code examples
iosswiftmetal

Where do I properly initialize my MTLCommandBuffer?


After debugging using the GPU Capture button, a warning is displayed "Your application created a MTLBuffer object during GPU work. Create buffers at load time for best performance."

The only thing related to a MTLBuffer in my code is the creation of a MTLCommandBuffer every time draw is called:

override func draw(_ rect: CGRect){

    let commandBuffer = commandQueue.makeCommandBuffer()

    guard var
        image = image,
        let targetTexture:MTLTexture = currentDrawable?.texture else
    {
        return
    }

    let customDrawableSize:CGSize = drawableSize

    let bounds = CGRect(origin: CGPoint.zero, size: customDrawableSize)

    let originX = image.extent.origin.x
    let originY = image.extent.origin.y

    let scaleX = customDrawableSize.width / image.extent.width
    let scaleY = customDrawableSize.height / image.extent.height

    let scale = min(scaleX*IVScaleFactor, scaleY*IVScaleFactor)
    image = image
        .transformed(by: CGAffineTransform(translationX: -originX, y: -originY))
        .transformed(by: CGAffineTransform(scaleX: scale, y: scale))

    ciContext.render(image,
                     to: targetTexture,
                     commandBuffer: commandBuffer,
                     bounds: bounds,
                     colorSpace: colorSpace)


    commandBuffer?.present(currentDrawable!)

    commandBuffer?.commit()

}

My first thought was to move that to a different scope, where I can define my command buffer as a variable, and then make it equal commandQueue.makeCommandBuffer() when the frame is initialized. This immediately crashes the application.

I'm not sure how to initialize this properly without a warning or crash. The MTLCommandQueue is a lazy var.

Here are the changes that makes it crash:

class MetalImageView: MTKView
{
    let colorSpace = CGColorSpaceCreateDeviceRGB()

    var textureCache: CVMetalTextureCache?

    var sourceTexture: MTLTexture!

    var commandBuffer: MTLCommandBuffer?

    lazy var commandQueue: MTLCommandQueue =
        {
            [unowned self] in

            return self.device!.makeCommandQueue()
            }()!
    ...




override init(frame frameRect: CGRect, device: MTLDevice?)
{
    super.init(frame: frameRect,
               device: device ?? MTLCreateSystemDefaultDevice())



    if super.device == nil
    {
        fatalError("Device doesn't support Metal")
    }

    CVMetalTextureCacheCreate(kCFAllocatorDefault, nil, self.device!, nil, &textureCache)

    framebufferOnly = false

    enableSetNeedsDisplay = true

    isPaused = true

    preferredFramesPerSecond = 30

    commandBuffer = commandQueue.makeCommandBuffer()

}

Then I of course remove the definition of commandBuffer in my draw function.


Solution

  • This warning is related to MTLBuffers, not MTLCommandBuffers.

    You should certainly not proactively create a command buffer in your initializer. Create command buffers precisely when you're about to encode GPU work (i.e. as you were doing initially, in your draw method).

    As for the diagnostic message, it's probably the case that Core Image is creating a temporary buffer on your behalf when rendering your image. There isn't much you can do about this, but depending on the size of the buffer and the frequency of drawing, it probably isn't a big deal.