Using a compute encoder to render to a drawable. What am I doing wrong?

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

Hey, I don’t have the time to go over this very carefully now, but...what is that tex.replace call doing there? Operations like that (when CPU does something to the texture) are not synchronised with normal Metal command buffers and so usually clash when mixed with “normal” frame rendering. Be aware of doing anything which doesn't involve command buffers or command encoders in render loop. And if that won’t help, next time please post link to zipped Xcode project which shows the issue.

The replace call is simply to clear the texture each frame. The with bytes portion is just a bunch of zeroes. That is the only way I know how to clear a texture other than making a render pipeline which I dont need here.

Accepted Answer

Whatever, don’t do it this way. It is probably causing your sync errors. Calls like that are NOT synchronised with command buffer execution. I learned about this in very hard way :-) If you want to clear rendering target, then you can use LoadAction. Other than that, yeah, write compute kernel or setup rendering pipeline, or have “template” texture and copy content over using blit encoder.

Using a compute encoder to render to a drawable. What am I doing wrong?
 
 
Q