Thomas Heeley
Thomas Heeley

Reputation: 13

Metal API View has no Render Pass Descriptor

I am currently trying to build a metal app without the use of the IB in Xcode (purely programmatically, no storyboard), and I am having some trouble with rendering using MTKView Delegate.

I have created the following 3 classes

App Delegate:

import Cocoa
import MetalKit

class AppDelegate: NSObject, NSApplicationDelegate
{
    var newWindow: NSWindow?
    var controller: ViewController?

    func applicationDidFinishLaunching(_ notification: Notification) {
        newWindow = NSWindow(contentRect: NSMakeRect(10, 10, 300, 300), styleMask: [.miniaturizable, .closable, .resizable, .titled], backing: .buffered, defer: false)

        controller = ViewController()
        let content = newWindow!.contentView! as NSView
        let view = controller!.view
        content.addSubview(view)
        newWindow!.makeKeyAndOrderFront(nil)
    }

}

View Controller:

import Cocoa
import MetalKit

class ViewController: NSViewController {

    var renderer: Renderer?

    override func loadView() {
        view = MTKView.init()
    }

    override func viewDidLoad() {
        guard let metalView = view as? MTKView else {
            fatalError("Cant Get Metal View")
        }

        super.viewDidLoad()

        renderer = Renderer(metalView: metalView)
    }
}

Renderer.swift:

import MetalKit

class Renderer: NSObject {

  static var device: MTLDevice!
  static var commandQueue: MTLCommandQueue!
  var mesh: MTKMesh!
  var vertexBuffer: MTLBuffer!
  var pipelineState: MTLRenderPipelineState!

  init(metalView: MTKView) {

    guard
      let device = MTLCreateSystemDefaultDevice(),
      let commandQueue = device.makeCommandQueue() else {
        fatalError("GPU not available")
    }

    Renderer.device = device
    Renderer.commandQueue = commandQueue
    metalView.device = device

    let mdlMesh = Primative.makeCube(device: device, size: 1)

    do {
      mesh = try MTKMesh(mesh: mdlMesh, device: device)
    } catch let error {
      print(error.localizedDescription)
    }
    vertexBuffer = mesh.vertexBuffers[0].buffer

    let library = device.makeDefaultLibrary()
    let vertexFunction = library?.makeFunction(name: "vertex_main")
    let fragmentFunction = library?.makeFunction(name: "fragment_main")

    let pipelineDescriptor = MTLRenderPipelineDescriptor()
    pipelineDescriptor.vertexFunction = vertexFunction
    pipelineDescriptor.fragmentFunction = fragmentFunction
    pipelineDescriptor.vertexDescriptor =
      MTKMetalVertexDescriptorFromModelIO(mdlMesh.vertexDescriptor)
    pipelineDescriptor.colorAttachments[0].pixelFormat = metalView.colorPixelFormat
    do {
      pipelineState = try device.makeRenderPipelineState(descriptor: pipelineDescriptor)
    } catch let error {
      fatalError(error.localizedDescription)
    }

    super.init()
    metalView.clearColor = MTLClearColor(red: 1.0, green: 1.0,
                                         blue: 0.8, alpha: 1.0)
    metalView.delegate = self
  }
}

extension Renderer: MTKViewDelegate {
  func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {

  }

  func draw(in view: MTKView) {
    // Creating Render Pass Descriptor
        if let descriptor = view.currentRenderPassDescriptor
        {
            print("Drawing")
        }
        else {
            print("Not Drawing")
        }
    }
}

These three classes render the Window fine, and the output of

Not Drawing

Shows that this value is always null, but it is doing something every frame.

Things I have tried

I know this works if I use a project based on the storyboard in the file, but was hoping to learn how to do it without it.

Any Help would be much appreciated

Upvotes: 0

Views: 728

Answers (2)

Willeke
Willeke

Reputation: 15632

The width and height of the view are 0. Set the frame size before adding the view to the window in applicationDidFinishLaunching.

controller = ViewController()
let content = newWindow!.contentView! as NSView
let view = controller!.view
view.frame = content.bounds
content.addSubview(view)

Upvotes: 0

Frank Rupprecht
Frank Rupprecht

Reputation: 10468

From the docs of MTKView.currentRenderPassDescriptor:

This property is nil if the view’s device property isn’t set or if currentDrawable is nil.

You need to give the MTKView a metal device to work with. IB is probably giving it MTLCreateSystemDefaultDevice() by default, but when you create the view programmatically you need to do it manually:

override func loadView() {
    view = MTKView(frame: .zero, device: MTLCreateSystemDefaultDevice())
}

Upvotes: 1

Related Questions