Categories
Uncategorized

Using MetalKit part 7

One of our readers contacted me about an apparently weird behavior he was seeing: when running the code from our tutorial the MTLLibrary returned nil after a few hundred draw calls. That made me realize I was not taking into consideration the fact that some of the Metal objects are transient and some are not, according to the Metal documentation. Thanks Mike for bringing this to my attention!

To deal with this matter, we need to re-organize the code, again! And that is always a good thing to do. We need to get the non-transient Metal objects (devices, queues, data buffers, textures, states and pipelines) out of drawRect(_:) and put them in a method that only runs once when the view loads. The command buffers and encoders are the only two transient objects designed for a single use, so we can thus create them with each draw call.

We will pick up where we left off in part 5 of the series. To start, let’s create a new method – an initializer – that only runs once when the view is loaded:

 
required init(coder: NSCoder) {
    super.init(coder: coder)
    device = MTLCreateSystemDefaultDevice()
    createBuffers()
    registerShaders()
}

Next, delete the render() method as well as its call inside drawRect(_:) as we don’t need it anymore. Then move all the code from sendToGPU() to drawRect(_:) and delete sendToGPU() as we don’t need this one either. This way we moved all the non-transient objects away from drawRect(_:) and only kept the command buffer and encoder inside, which are the only two transient objects.

 
override func drawRect(dirtyRect: NSRect) {
    super.drawRect(dirtyRect)
    if let rpd = currentRenderPassDescriptor, drawable = currentDrawable {
        rpd.colorAttachments[0].clearColor = MTLClearColorMake(0.5, 0.5, 0.5, 1.0)
        let command_buffer = device!.newCommandQueue().commandBuffer()
        let command_encoder = command_buffer.renderCommandEncoderWithDescriptor(rpd)
        command_encoder.setRenderPipelineState(rps)
        command_encoder.setVertexBuffer(vertex_buffer, offset: 0, atIndex: 0)
        command_encoder.setVertexBuffer(uniform_buffer, offset: 0, atIndex: 1)
        command_encoder.drawPrimitives(.Triangle, vertexStart: 0, vertexCount: 3, instanceCount: 1)
        command_encoder.endEncoding()
        command_buffer.presentDrawable(drawable)
        command_buffer.commit()
    }
}

Finally, let’s create a new class named MathUtils and move both structs to it so we have a cleaner view class.

 
import simd

struct Vertex {
    var position: vector_float4
    var color: vector_float4
}

struct Matrix {
    var m: [Float]
    
    init() {
        m = [1, 0, 0, 0,
            0, 1, 0, 0,
            0, 0, 1, 0,
            0, 0, 0, 1
        ]
    }
    
    func translationMatrix(var matrix: Matrix, _ position: float3) -> Matrix {
        matrix.m[12] = position.x
        matrix.m[13] = position.y
        matrix.m[14] = position.z
        return matrix
    }
    
    func scalingMatrix(var matrix: Matrix, _ scale: Float) -> Matrix {
        matrix.m[0] = scale
        matrix.m[5] = scale
        matrix.m[10] = scale
        matrix.m[15] = 1.0
        return matrix
    }
    
    func rotationMatrix(var matrix: Matrix, _ rot: float3) -> Matrix {
        matrix.m[0] = cos(rot.y) * cos(rot.z)
        matrix.m[4] = cos(rot.z) * sin(rot.x) * sin(rot.y) - cos(rot.x) * sin(rot.z)
        matrix.m[8] = cos(rot.x) * cos(rot.z) * sin(rot.y) + sin(rot.x) * sin(rot.z)
        matrix.m[1] = cos(rot.y) * sin(rot.z)
        matrix.m[5] = cos(rot.x) * cos(rot.z) + sin(rot.x) * sin(rot.y) * sin(rot.z)
        matrix.m[9] = -cos(rot.z) * sin(rot.x) + cos(rot.x) * sin(rot.y) * sin(rot.z)
        matrix.m[2] = -sin(rot.y)
        matrix.m[6] = cos(rot.y) * sin(rot.x)
        matrix.m[10] = cos(rot.x) * cos(rot.y)
        matrix.m[15] = 1.0
        return matrix
    }
    
    func modelMatrix(var matrix: Matrix) -> Matrix {
        matrix = rotationMatrix(matrix, float3(0.0, 0.0, 0.1))
        matrix = scalingMatrix(matrix, 0.25)
        matrix = translationMatrix(matrix, float3(0.0, 0.5, 0.0))
        return matrix
    }
}

Run the program to make sure you are still seeing the glorious triangle as we have seen it in the previous part. The source code is posted on Github as usual.

Until next time!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s