I am using a compute command encoder to draw to my drawable which is a MTKView with its frameBufferOnly property set to false.
However I am having all sorts of trouble with synchronization because apparently Metal is perfectly fine continuing on with the next frame before a compute encoder gets done.
If I just did this compute to my drawable directly I would get blank frames occasionally.
So I added an external texture for the compute and had a blit command encoder copy from the external texture to the drawable but even this caused occasional black sections (so not the whole frame like previously just chunks).
So I came up with this solution which calls
buffer.waitUntilCompleted()
twice (at different points) however it works with no dropped frames or blank chunks.
However I am not sure that this is the right way to do things because I have heard I shouldnt use the whole wait until completed thing. How would you synchronize the frame update with a compute command that needs to edit the drawable?
For reference here is my current functional but likely flawed code:
It really bothers me that I have to call the wait until completed on the Blit command it is almost as if some of these commands dont care when the frame updates in the same way a render command encoder does and that makes no sense to me.
(For reference without the second waitUntilCompleted there is a completely new effect where flickering darknes works its way down the screen and comes back up it is not random but sorta like a sin wave)
Also from where in the code could you figure out the framerate?
func draw(in view: MTKView) {
if let buffer = queue.makeCommandBuffer() {
//We must draw on an external texture because the drawable is not always the same object... pretty sure?
drawOnTexture(tex: drawOn, buffer: buffer)
buffer.commit()
buffer.waitUntilCompleted()
computeIsDone(in: view)
}
}
func computeIsDone(in view: MTKView) {
if let buffer = queue.makeCommandBuffer() {
if let drawable = view.currentDrawable {
copyTexture(tex: drawOn, onto: drawable.texture, buffer: buffer)
buffer.commit()
buffer.waitUntilCompleted()//For some reason this is crucial as well
drawable.present()
} else {
buffer.commit()
}
}
}
func copyTexture(tex: MTLTexture, onto: MTLTexture, buffer: MTLCommandBuffer) {
if let encoder = buffer.makeBlitCommandEncoder() {
let origin = MTLOriginMake(0, 0, 0)
let size = MTLSizeMake(min(tex.width, onto.width), min(tex.height, onto.height), 1)
encoder.copy(from: drawOn, sourceSlice: 0, sourceLevel: 0, sourceOrigin: origin, sourceSize: size, to: onto, destinationSlice: 0, destinationLevel: 0, destinationOrigin: origin)
encoder.endEncoding()
} else {
print("Could not preform the copy")
}
}
func drawOnTexture(tex: MTLTexture, buffer: MTLCommandBuffer) {
if let encoder = buffer.makeComputeCommandEncoder() {
encoder.setComputePipelineState(computePipeline)
....
if Settings.clearOnStep {
let imageRegion = MTLRegionMake2D(0, 0, width, height)
let imageRegion = MTLRegionMake2D(0, 0, width, height)
if let blank = blankBitmapRawData {
tex.replace(region: imageRegion, mipmapLevel: 0, withBytes: blank, bytesPerRow: bytesPerRow)
}
}
encoder.setTexture(tex, index: 0)
encoder.dispatchThreadgroups(threadgroupsPerGrid, threadsPerThreadgroup: threadgroupsPerThreadgroup)
encoder.endEncoding()
} else {
print("Could not complete texture drawaing")
}
}