11

Can I debug my metal-shading-language code using console output (like print in Swift)?

If yes, how?

If no, are there any other ways to output variables from my .metal file? (Maybe by passing data from the .metal file to my .swift file using the commandEncoder-buffer?)

I have unsuccessfully tried to pass a reference to an Int variable (which is in my .swift file) via the commandEncoder to my .metal file. In the .metal file, I assign a value to the int variable, but if I print the Int in my swift file, the assigned value is not there.

.swift file:

...
var myMetalOutput: Int = 0
...
let printBuffer = device.newBufferWithBytes(&myMetalOutput, length: sizeof(Int), options: MTLResourceOptions.CPUCacheModeDefaultCache)
commandEncoder.setBuffer(printBuffer, offset: 0, atIndex: 8)
...
commandBuffer.commit()
drawable.present()
print("myMetalOutput: \(myMetalOutput)")
...

.metal file:

...
kernel void shader(..., device int &printBuffer [[8]], ...) {
...
printBuffer = 123;
...
}

The console output is always myMetalOutput: 0

ediheld
  • 233
  • 3
  • 13

2 Answers2

7

Here's a working solution in case somebody needs it:

let device = MTLCreateSystemDefaultDevice()!
let commandQueue = device.newCommandQueue()
let defaultLibrary = device.newDefaultLibrary()!
let commandBuffer = commandQueue.commandBuffer()
let computeCommandEncoder = commandBuffer.computeCommandEncoder()

let program = defaultLibrary.newFunctionWithName("shader")

do
{
    let computePipelineFilter = try device.newComputePipelineStateWithFunction(program!)
    computeCommandEncoder.setComputePipelineState(computePipelineFilter)
    var resultdata = [Int](count: 1, repeatedValue: 0)
    let outVectorBuffer = device.newBufferWithBytes(&resultdata, length: sizeofValue(1), options: MTLResourceOptions.CPUCacheModeDefaultCache)
    computeCommandEncoder.setBuffer(outVectorBuffer, offset: 0, atIndex: 0)


    let threadsPerGroup = MTLSize(width:1,height:1,depth:1)
    let numThreadgroups = MTLSize(width:1, height:1, depth:1)
    computeCommandEncoder.dispatchThreadgroups(numThreadgroups, threadsPerThreadgroup: threadsPerGroup)


    computeCommandEncoder.endEncoding()

    commandBuffer.addCompletedHandler {commandBuffer in
        let data = NSData(bytes: outVectorBuffer.contents(), length: sizeof(NSInteger))
        var out: NSInteger = 0
        data.getBytes(&out, length: sizeof(NSInteger))
        print("data: \(out)")
    }

    commandBuffer.commit()

}
catch
{
    fatalError("newComputePipelineStateWithFunction failed ")
}

The shader:

kernel void shader(device int &printBuffer [[buffer(0)]], uint id [[ thread_position_in_grid ]]) {

    printBuffer = 123;

}
User
  • 31,811
  • 40
  • 131
  • 232
4

There are a couple of things going wrong here. First, newBufferWithBytes(_:length:) makes a copy of the data you provide, so the address that is written to is not the address of the original variable. Second, you don't seem to be waiting for the compute kernel to complete before attempting to read the result. You can either call waitUntilCompleted() on the corresponding command buffer (which blocks the current thread), or you can call addCompletedHandler() to provide a closure that will be called asynchronously when the kernel finishes running. At that point, you should be able to read back data from the buffer.

There is no facility for printing to the command line from within a Metal shader, so writing to a buffer or texture is pretty much your best option here.

warrenm
  • 31,094
  • 6
  • 92
  • 116
  • 1
    This answer is not complete. You only say using `newBufferWithBytes` is wrong but don't write what to use instead. The answer is probably `newBufferWithBytesNoCopy` but this still doesn't solve the problem, I'm trying to return the value to print using this and `addCompletedHandler` and it's still printing 0. – User Aug 18 '16 at 22:44
  • It would seem the OP considered this answer complete, due to it being accepted. It sounds like you may have a slightly different use case, for which you probably should open a new question. – warrenm Aug 19 '16 at 17:02
  • I have exactly the same use case, opening a new question would be marked as a duplicate so I think the problem is in this answer. – User Aug 19 '16 at 18:29
  • http://stackoverflow.com/help/accepted-answer "Accepting an answer is not meant to be a definitive and final statement indicating that the question has now been answered perfectly" – User Aug 19 '16 at 18:31
  • It doesn't matter how you create the buffer. The key is in the statement "[after the command buffer completes], you should be able to read back data from the buffer" using something like `bindMemory` on the buffer's `contents()`. – warrenm Aug 19 '16 at 20:34