This MVP Metal main.swift shows up blank - swift

So in a main.swift file in your project, you can create a window (and go from there) like this:
let nsapp = NSApplication.shared
let window = NSWindow(
contentRect: NSMakeRect(0, 0, 200, 200),
styleMask: .fullSizeContentView,
backing: NSWindow.BackingStoreType.buffered,
defer: false
)
window.cascadeTopLeft(from:NSMakePoint(20,20))
nsapp.run()
I'm wondering how to do the same thing but with a Metal triangle. I've been looking through github.com/topics/metalkit but the closest thing I've found so far wasn't there but in a gist.
import Cocoa
import MetalKit
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate, MTKViewDelegate {
weak var window: NSWindow!
weak var metalView: MTKView!
let device = MTLCreateSystemDefaultDevice()!
var commandQueue: MTLCommandQueue!
var pipelineState: MTLRenderPipelineState!
func applicationDidFinishLaunching(_ aNotification: Notification) {
metalView = MTKView(frame: NSRect(origin: CGPoint.zero, size: window.frame.size), device: device)
metalView.delegate = self
window.contentView = metalView
commandQueue = device.makeCommandQueue()
let shaders = """
#include <metal_stdlib>
using namespace metal;
struct VertexIn {
packed_float3 position;
packed_float3 color;
};
struct VertexOut {
float4 position [[position]];
float4 color;
};
vertex VertexOut vertex_main(device const VertexIn *vertices [[buffer(0)]],
uint vertexId [[vertex_id]]) {
VertexOut out;
out.position = float4(vertices[vertexId].position, 1);
out.color = float4(vertices[vertexId].color, 1);
return out;
}
fragment float4 fragment_main(VertexOut in [[stage_in]]) {
return in.color;
}
"""
do {
let library = try device.makeLibrary(source: shaders, options: nil)
let pipelineDescriptor = MTLRenderPipelineDescriptor()
pipelineDescriptor.colorAttachments[0].pixelFormat = metalView.colorPixelFormat
pipelineDescriptor.vertexFunction = library.makeFunction(name: "vertex_main")
pipelineDescriptor.fragmentFunction = library.makeFunction(name: "fragment_main")
pipelineState = try device.makeRenderPipelineState(descriptor: pipelineDescriptor)
} catch {}
}
func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
}
func draw(in view: MTKView) {
guard let commandBuffer = commandQueue.makeCommandBuffer() else { return }
guard let passDescriptor = view.currentRenderPassDescriptor else { return }
guard let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: passDescriptor) else { return }
let vertexData: [Float] = [ -0.5, -0.5, 0, 1, 0, 0,
0.5, -0.5, 0, 0, 1, 0,
0, 0.5, 0, 0, 0, 1 ]
encoder.setVertexBytes(vertexData, length: vertexData.count * MemoryLayout<Float>.stride, index: 0)
encoder.setRenderPipelineState(pipelineState)
encoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 3)
encoder.endEncoding()
commandBuffer.present(view.currentDrawable!)
commandBuffer.commit()
}
}
It at least builds an MTKView from scratch. But I'm not sure yet what the minimum viable product is for getting a metal thing working without any controllers, delegates, applications, I'm going to start just doing trial and error to get it working but it's going to probably take a few days and thought it might be helpful for others if someone's already figured this out.
I have combined the two but it isn't rendering anything from what I can tell.
import AVFoundation
import AudioToolbox
import Foundation
import QuartzCore
import Security
import WebKit
import Cocoa
import Metal
import MetalKit
import Swift
let device = MTLCreateSystemDefaultDevice()!
// Our clear color, can be set to any color
let clearColor = MTLClearColor(red: 0.1, green: 0.57, blue: 0.25, alpha: 1)
let nsapp = NSApplication.shared
let appName = ProcessInfo.processInfo.processName
let window = NSWindow(
contentRect: NSMakeRect(0, 0, 1000, 1000),
styleMask: .fullSizeContentView,
backing: NSWindow.BackingStoreType.buffered,
defer: false
)
window.cascadeTopLeft(from:NSMakePoint(20,20))
window.title = appName;
window.makeKeyAndOrderFront(nil)
struct Vertex {
var position: float3
var color: float4
}
let view = MTKView(frame: NSRect(origin: CGPoint.zero, size: window.frame.size), device: device)
window.contentView = view
view.device = device
view.colorPixelFormat = .bgra8Unorm
view.clearColor = clearColor
let queue = device.makeCommandQueue()!
var vertexBuffer: MTLBuffer!
var vertices: [Vertex] = [
Vertex(position: float3(0,1,0), color: float4(1,0,0,1)),
Vertex(position: float3(-1,-1,0), color: float4(0,1,0,1)),
Vertex(position: float3(1,-1,0), color: float4(0,0,1,1))
]
let shaders = """
#include <metal_stdlib>
using namespace metal;
// Basic Struct to match our Swift type
// This is what is passed into the Vertex Shader
struct VertexIn {
float3 position;
float4 color;
};
// What is returned by the Vertex Shader
// This is what is passed into the Fragment Shader
struct VertexOut {
float4 position [[ position ]];
float4 color;
};
vertex VertexOut basic_vertex_function(const device VertexIn *vertices [[ buffer(0) ]],
uint vertexID [[ vertex_id ]]) {
VertexOut vOut;
vOut.position = float4(vertices[vertexID].position,1);
vOut.color = vertices[vertexID].color;
return vOut;
}
fragment float4 basic_fragment_function(VertexOut vIn [[ stage_in ]]) {
return vIn.color;
}
"""
let library = try device.makeLibrary(source: shaders, options: nil)
let pipelineDescriptor = MTLRenderPipelineDescriptor()
pipelineDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm
pipelineDescriptor.vertexFunction = library.makeFunction(name: "basic_vertex_function")
pipelineDescriptor.fragmentFunction = library.makeFunction(name: "basic_fragment_function")
let pipelineState = try device.makeRenderPipelineState(descriptor: pipelineDescriptor)
vertexBuffer = device.makeBuffer(
bytes: vertices,
length: MemoryLayout<Vertex>.stride * vertices.count,
options: []
)
enum MetalErrors: Error {
case commandBuffer
case passDescriptor
case encoder
}
guard let drawable = view.currentDrawable else { throw MetalErrors.commandBuffer }
guard let commandBuffer = queue.makeCommandBuffer() else { throw MetalErrors.commandBuffer }
guard let passDescriptor = view.currentRenderPassDescriptor else { throw MetalErrors.passDescriptor }
guard let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: passDescriptor) else { throw MetalErrors.encoder }
nsapp.run()
// let vertexData: [Float] = [ -0.5, -0.5, 0, 1, 0, 0,
// 0.5, -0.5, 0, 0, 1, 0,
// 0, 0.5, 0, 0, 0, 1 ]
encoder.setRenderPipelineState(pipelineState)
// encoder.setVertexBytes(vertexData, length: vertexData.count * MemoryLayout<Float>.stride, index: 0)
encoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: vertices.count)
encoder.endEncoding()
commandBuffer.present(drawable)
commandBuffer.commit()
It's blank for me. I tried following this as well.
This is getting closer.

The main problem here is that NSApplication's run method doesn't return until the app terminates, so your render command encoding never happens. You can subclass MTKView and override its draw method to do your drawing instead:
import Cocoa
import MetalKit
let device = MTLCreateSystemDefaultDevice()!
// Our clear color, can be set to any color
let clearColor = MTLClearColor(red: 0.1, green: 0.57, blue: 0.25, alpha: 1)
let shaders = """
#include <metal_stdlib>
using namespace metal;
// Basic Struct to match our Swift type
// This is what is passed into the Vertex Shader
struct VertexIn {
float3 position;
float4 color;
};
// What is returned by the Vertex Shader
// This is what is passed into the Fragment Shader
struct VertexOut {
float4 position [[ position ]];
float4 color;
};
vertex VertexOut basic_vertex_function(const device VertexIn *vertices [[ buffer(0) ]],
uint vertexID [[ vertex_id ]]) {
VertexOut vOut;
vOut.position = float4(vertices[vertexID].position,1);
vOut.color = vertices[vertexID].color;
return vOut;
}
fragment float4 basic_fragment_function(VertexOut vIn [[ stage_in ]]) {
return vIn.color;
}
"""
let library = try device.makeLibrary(source: shaders, options: nil)
let pipelineDescriptor = MTLRenderPipelineDescriptor()
pipelineDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm
pipelineDescriptor.vertexFunction = library.makeFunction(name: "basic_vertex_function")
pipelineDescriptor.fragmentFunction = library.makeFunction(name: "basic_fragment_function")
let pipelineState = try device.makeRenderPipelineState(descriptor: pipelineDescriptor)
struct Vertex {
var position: float3
var color: float4
}
let queue = device.makeCommandQueue()!
var vertexBuffer: MTLBuffer!
var vertices: [Vertex] = [
Vertex(position: float3(0,1,0), color: float4(1,0,0,1)),
Vertex(position: float3(-1,-1,0), color: float4(0,1,0,1)),
Vertex(position: float3(1,-1,0), color: float4(0,0,1,1))
]
vertexBuffer = device.makeBuffer(
bytes: vertices,
length: MemoryLayout<Vertex>.stride * vertices.count,
options: []
)
enum MetalErrors: Error {
case commandBuffer
case passDescriptor
case encoder
}
class MyMTKView : MTKView {
override func draw() {
guard let drawable = currentDrawable else { return }
guard let passDescriptor = currentRenderPassDescriptor else { return }
guard let commandBuffer = queue.makeCommandBuffer() else { return }
guard let encoder = commandBuffer.makeRenderCommandEncoder(descriptor: passDescriptor) else { return }
encoder.setRenderPipelineState(pipelineState)
encoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0 )
encoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: vertices.count)
encoder.endEncoding()
commandBuffer.present(drawable)
commandBuffer.commit()
}
}
let nsapp = NSApplication.shared
let appName = ProcessInfo.processInfo.processName
let window = NSWindow(
contentRect: NSMakeRect(0, 0, 1000, 1000),
styleMask: [.titled, .closable, .resizable],
backing: NSWindow.BackingStoreType.buffered,
defer: false
)
window.cascadeTopLeft(from:NSMakePoint(20,20))
window.title = appName;
let view = MyMTKView(frame: NSRect(origin: CGPoint.zero, size: window.frame.size), device: device)
window.contentView = view
view.device = device
view.colorPixelFormat = .bgra8Unorm
view.clearColor = clearColor
window.makeKeyAndOrderFront(nil)
nsapp.run()

Related

Metal program crashed

import PlaygroundSupport
import MetalKit
guard let device = MTLCreateSystemDefaultDevice() else {
fatalError("GPU is not supported")
}
let frame = CGRect(x: 0, y: 0, width: 600, height: 600)
let view = MTKView(frame: frame, device:device)
view.clearColor = MTLClearColor(red: 1, green: 1, blue: 0.8, alpha: 1)
//1
let allocator = MTKMeshBufferAllocator(device: device)
//2
let mdlMesh = MDLMesh(sphereWithExtent: [0.75, 0.75, 0.75], segments: [100, 100], inwardNormals: false, geometryType: .triangles, allocator: allocator)
//3
let mesh = try MTKMesh(mesh: mdlMesh, device: device)
guard let commandQueue = device.makeCommandQueue() else {
fatalError("Could not create a command queue")
}
//shader
let shader = """
#include <metal_stdlib>
using namespace metal;
struct VertexIn {
float4 position [[attribute(0)]];
};
vertex float4 vertex_main(const VertexIn vertex_in [[stage_in]])
{
return vertex_in.position;
}
fragment float4 fragment_main() {
return float4(1, 0, 0, 1);
}
"""
let library = try device.makeLibrary(source: shader, options: nil)
let vertexFunction = library.makeFunction(name: "vertex_main")
let fragmentFunction = library.makeFunction(name: "fragment_main")
//Pipeline
let pipelineDescriptor = MTLRenderPipelineDescriptor()
pipelineDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm
pipelineDescriptor.vertexFunction = vertexFunction
pipelineDescriptor.fragmentFunction = fragmentFunction
pipelineDescriptor.vertexDescriptor = MTKMetalVertexDescriptorFromModelIO(mesh.vertexDescriptor)
let pipelineState = try device.makeRenderPipelineState(descriptor: pipelineDescriptor)
// 1
guard let commandBuffer = commandQueue.makeCommandBuffer(),
//2
let renderPassDescriptor = view.currentRenderPassDescriptor,
//3
let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor)
else { fatalError() }
guard let submesh = mesh.submeshes.first else {
fatalError()
}
//Here is where crashed.
renderEncoder.drawIndexedPrimitives(type: .triangle,
indexCount: submesh.indexCount,
indexType: submesh.indexType,
indexBuffer: submesh.indexBuffer.buffer,
indexBufferOffset: 0)
//1.u tell the render encoder that there are no more draw calls and end the render pass.
renderEncoder.endEncoding()
//2 u get the drawable from the MTKView.The MTKView is backed by a Core Animation CAMetalLayer and the layer owns a drawable texture which Metal can read and write to.
guard let drawable = view.currentDrawable else {
fatalError()
}
//3.Ask the command Buffer to present the MTKVIew's drawable and commit to the GPU
commandBuffer.present(drawable)
commandBuffer.commit()
PlaygroundPage.current.liveView = view
error: Execution was interrupted, reason: EXC_BAD_ACCESS (code=1, address=0x220).
The process has been left at the point where it was interrupted, use "thread return -x" to return to the state before expression evaluation.
I rewrite again in Xcode but still can not find where the problem is.
error: Execution was interrupted, reason: EXC_BAD_ACCESS (code=1, address=0x220). The process has been left at the point where it was interrupted, use "thread return -x" to return to the state before expression evaluation.
I rewrite again in Xcode but still can not find where the problem is.
error: Execution was interrupted, reason: EXC_BAD_ACCESS (code=1, address=0x220). The process has been left at the point where it was interrupted, use "thread return -x" to return to the state before expression evaluation.
I rewrite again in Xcode but still can not find where the problem is.
First there seems to be some XCode bug which forbids giving useful error information once your code crashes more than once, you just quit and restart it.
Now coming to main issues, you missed couple of things:
Setting render pipeline state on command encoder so that your shaders actually get to run by GPU
And setting vertex buffer to command encoder, so that GPU know what points of the mesh are needed to be rendered
I have updated your code below, also, you don't have to create most of those objects like pipeline, library, command queue every time. I'm assuming you are doing so just because it's a playground. I tried to add some basic structure to it in my sample below, you build upon it, like use MTKView delgates etc.
import UIKit
import PlaygroundSupport
import MetalKit
class BasicRenderer {
var mesh: MTKMesh
var pipelineState: MTLRenderPipelineState
var device: MTLDevice
var commandQueue: MTLCommandQueue
var metalView: MTKView
init() {
guard let device = MTLCreateSystemDefaultDevice() else {
fatalError("Can't create device")
}
self.device = device
guard let commandQueue = device.makeCommandQueue() else {
fatalError("Can't create commandQueue")
}
self.commandQueue = commandQueue
metalView = MTKView(frame: .zero, device: device)
metalView.clearColor = MTLClearColor(red: 1, green: 1, blue: 0.8, alpha: 1)
let meshAllocator = MTKMeshBufferAllocator(device: device)
let mdlMesh = MDLMesh(sphereWithExtent: [0.75, 0.75, 0.75],
segments: [100, 100],
inwardNormals: false,
geometryType: .triangles,
allocator: meshAllocator)
guard let mesh = try? MTKMesh(mesh: mdlMesh, device: device) else {
fatalError("Can't create mesh")
}
self.mesh = mesh
let shader = """
#include <metal_stdlib>
using namespace metal;
struct VertexIn {
float4 position [[attribute(0)]];
};
vertex float4 vertex_main(const VertexIn vertex_in [[stage_in]])
{
return vertex_in.position;
}
fragment float4 fragment_main() {
return float4(1, 0, 0, 1);
}
"""
guard let library = try? device.makeLibrary(source: shader, options: nil) else {
fatalError("Can't create library")
}
let vertexFunction = library.makeFunction(name: "vertex_main")
let fragmentFunction = library.makeFunction(name: "fragment_main")
let pipelineDescriptor = MTLRenderPipelineDescriptor()
pipelineDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm
pipelineDescriptor.vertexFunction = vertexFunction
pipelineDescriptor.fragmentFunction = fragmentFunction
pipelineDescriptor.vertexDescriptor = MTKMetalVertexDescriptorFromModelIO(mesh.vertexDescriptor)
guard let pipelineState = try? device.makeRenderPipelineState(descriptor: pipelineDescriptor) else {
fatalError("Can't create pipelineState")
}
self.pipelineState = pipelineState
}
func render(in size: CGSize) {
metalView.frame = CGRect(origin: .zero, size: size)
guard let commandBuffer = commandQueue.makeCommandBuffer(),
let renderPassDescriptor = metalView.currentRenderPassDescriptor,
let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor)
else {
fatalError("Can't create commandBuffer/renderPassDescriptor/renderEncoder")
}
guard let subMesh = mesh.submeshes.first else {
fatalError("Can't get submesh")
}
guard let vertexBuffer = mesh.vertexBuffers.first?.buffer else {
fatalError("Can't get vertexBuffer")
}
// MARK: You missed setting pipeline state and vertex buffer on commandEncoder
renderEncoder.setRenderPipelineState(pipelineState)
renderEncoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
renderEncoder.drawIndexedPrimitives(type: .triangle,
indexCount: subMesh.indexCount,
indexType: subMesh.indexType,
indexBuffer: subMesh.indexBuffer.buffer,
indexBufferOffset: 0)
renderEncoder.endEncoding()
guard let drawable = metalView.currentDrawable else {
fatalError("Can't get currentDrawable")
}
commandBuffer.present(drawable)
commandBuffer.commit()
}
}
class MyViewController : UIViewController {
var basicRenderer = BasicRenderer()
override func loadView() {
self.view = basicRenderer.metalView
basicRenderer.render(in: CGSize(width: 600, height: 600))
}
}
PlaygroundPage.current.liveView = MyViewController()

MTKView always render the background color to red

I want to use MTKView on Mac app. After init the MTKView, it appears red instead of the expected clear color on My laptop is MacBook Pro of macOS Catalina 10.15.2, and it render black background on a iMac of macOS Mojave 10.14.6
I try to exchange the metalView's pixelFormat (MTLPixelFormat.bgra8Unorm) to MTLPixelFormat.rgba16Float or other. the background color is incorrect too.
The MTLTexture create function is MTLTextureDescriptor.texture2DDescriptor(pixelFormat:metalView.colorPixelFormat, width: Int(metalView.frame.size.width), height: Int(metalView.frame.size.height), mipmapped: false)
It could be a problem with the .metal file, but I don't understand it
I have post the project to Github. here
import Cocoa
import MetalKit
import simd
struct Vertex {
var position: vector_float4
var textCoord: vector_float2
init(position: CGPoint, textCoord: CGPoint) {
self.position = [Float(position.x), Float(position.y), 0 ,1]
self.textCoord = [Float(textCoord.x), Float(textCoord.y)]
}
}
class Matrix {
private(set) var m: [Float]
static var identity = Matrix()
private init() {
m = [1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
]
}
#discardableResult
func translation(x: Float, y: Float, z: Float) -> Matrix {
m[12] = x
m[13] = y
m[14] = z
return self
}
#discardableResult
func scaling(x: Float, y: Float, z: Float) -> Matrix {
m[0] = x
m[5] = y
m[10] = z
return self
}
}
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate, MTKViewDelegate {
#IBOutlet weak var window: NSWindow!
#IBOutlet weak var metalView: MTKView!
private var commandQueue: MTLCommandQueue?
private var pipelineState: MTLRenderPipelineState!
private var render_target_vertex: MTLBuffer!
private var render_target_uniform: MTLBuffer!
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
metalView.device = MTLCreateSystemDefaultDevice()
metalView.delegate = self
commandQueue = metalView.device?.makeCommandQueue()
setupTargetUniforms()
do {
try setupPiplineState()
} catch {
fatalError("Metal initialize failed: \(error.localizedDescription)")
}
}
private func setupTargetUniforms() {
let size = metalView.frame.size
let w = size.width, h = size.height
let vertices = [
Vertex(position: CGPoint(x: 0 , y: 0), textCoord: CGPoint(x: 0, y: 0)),
Vertex(position: CGPoint(x: w , y: 0), textCoord: CGPoint(x: 1, y: 0)),
Vertex(position: CGPoint(x: 0 , y: h), textCoord: CGPoint(x: 0, y: 1)),
Vertex(position: CGPoint(x: w , y: h), textCoord: CGPoint(x: 1, y: 1)),
]
render_target_vertex = metalView.device?.makeBuffer(bytes: vertices, length: MemoryLayout<Vertex>.stride * vertices.count, options: .cpuCacheModeWriteCombined)
let metrix = Matrix.identity
metrix.scaling(x: 2 / Float(size.width), y: -2 / Float(size.height), z: 1)
metrix.translation(x: -1, y: 1, z: 0)
render_target_uniform = metalView.device?.makeBuffer(bytes: metrix.m, length: MemoryLayout<Float>.size * 16, options: [])
}
private func setupPiplineState() throws {
let library = try metalView.device?.makeDefaultLibrary(bundle: Bundle.main)
let vertex_func = library?.makeFunction(name: "vertex_render_target")
let fragment_func = library?.makeFunction(name: "fragment_render_target")
let rpd = MTLRenderPipelineDescriptor()
rpd.vertexFunction = vertex_func
rpd.fragmentFunction = fragment_func
rpd.colorAttachments[0].pixelFormat = metalView.colorPixelFormat
// rpd.colorAttachments[0].isBlendingEnabled = true
// rpd.colorAttachments[0].alphaBlendOperation = .add
// rpd.colorAttachments[0].rgbBlendOperation = .add
// rpd.colorAttachments[0].sourceRGBBlendFactor = .sourceAlpha
// rpd.colorAttachments[0].sourceAlphaBlendFactor = .one
// rpd.colorAttachments[0].destinationRGBBlendFactor = .oneMinusSourceAlpha
// rpd.colorAttachments[0].destinationAlphaBlendFactor = .oneMinusSourceAlpha
pipelineState = try metalView.device?.makeRenderPipelineState(descriptor: rpd)
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
}
func draw(in view: MTKView) {
let renderPassDescriptor = MTLRenderPassDescriptor()
let attachment = renderPassDescriptor.colorAttachments[0]
attachment?.clearColor = metalView.clearColor
attachment?.texture = metalView.currentDrawable?.texture
attachment?.loadAction = .clear
attachment?.storeAction = .store
let commandBuffer = commandQueue?.makeCommandBuffer()
let commandEncoder = commandBuffer?.makeRenderCommandEncoder(descriptor: renderPassDescriptor)
commandEncoder?.setRenderPipelineState(pipelineState)
commandEncoder?.setVertexBuffer(render_target_vertex, offset: 0, index: 0)
commandEncoder?.setVertexBuffer(render_target_uniform, offset: 0, index: 1)
let textureDescriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: metalView.colorPixelFormat,
width: Int(metalView.frame.size.width),
height: Int(metalView.frame.size.height),
mipmapped: false)
textureDescriptor.usage = [.renderTarget, .shaderRead]
let texture = metalView.device?.makeTexture(descriptor: textureDescriptor)
commandEncoder?.setFragmentTexture(texture, index: 0)
commandEncoder?.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4)
commandEncoder?.endEncoding()
if let drawable = metalView.currentDrawable {
commandBuffer?.present(drawable)
}
commandBuffer?.commit()
}
}
Could someone help me find the problem?
You're sampling from an uninitialized texture.
In your MetalView's draw method, you bind the texture from the screenTarget variable, which has never been rendered to.
When a Metal texture is created, its contents are undefined. They might be red, black, random noise, or anything else.

"Cannot load module 'metal' as 'Metal'"

I'm following a tutorial - https://www.raywenderlich.com/7475-metal-tutorial-getting-started - to learn how to use metal. I've done exactly what the tutorial said to do and no errors pop up before I try to build it, then it says build failed, along with the error - cannot load module 'metal' as 'Metal'.
I can't find an answer anywhere else, so can someone help me fix this? I'm new to coding and I'm expecting this to have a straightforward solution.
Edit: So I just discovered that there was indeed a very simple solution. I hadn't downloaded the materials for the tutorial. But now that I have, another error has shown up, saying Use of unresolved identifier 'vertexBuffer'
Here's my entire code, just to resolve any confusion -
viewControllerer.swift -
import Metal
var device: MTLDevice!
var metalLayer: CAMetalLayer!
var pipelineState: MTLRenderPipelineState!
var commandQueue: MTLCommandQueue!
var timer: CADisplayLink!
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
metalLayer = CAMetalLayer() // 1
metalLayer.device = device // 2
metalLayer.pixelFormat = .bgra8Unorm // 3
metalLayer.framebufferOnly = true // 4
metalLayer.frame = view.layer.frame // 5
view.layer.addSublayer(metalLayer) // 6
let dataSize = vertexData.count * MemoryLayout.size(ofValue: vertexData[0]) // 1
vertexBuffer = device.makeBuffer(bytes: vertexData, length: dataSize, options: []) // 2
// 1
let defaultLibrary = device.makeDefaultLibrary()!
let fragmentProgram = defaultLibrary.makeFunction(name: "basic_fragment")
let vertexProgram = defaultLibrary.makeFunction(name: "basic_vertex")
// 2
let pipelineStateDescriptor = MTLRenderPipelineDescriptor()
pipelineStateDescriptor.vertexFunction = vertexProgram
pipelineStateDescriptor.fragmentFunction = fragmentProgram
pipelineStateDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm
// 3
pipelineState = try! device.makeRenderPipelineState(descriptor: pipelineStateDescriptor)
device = MTLCreateSystemDefaultDevice()
commandQueue = device.makeCommandQueue()
timer = CADisplayLink(target: self, selector: #selector(gameloop))
timer.add(to: RunLoop.main, forMode: .default)
}
let vertexData: [Float] = [
0.0, 1.0, 0.0,
-1.0, -1.0, 0.0,
1.0, -1.0, 0.0
]
func render() {
guard let drawable = metalLayer?.nextDrawable() else { return }
let renderPassDescriptor = MTLRenderPassDescriptor()
renderPassDescriptor.colorAttachments[0].texture = drawable.texture
renderPassDescriptor.colorAttachments[0].loadAction = .clear
renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColor(
red: 0.0,
green: 104.0/255.0,
blue: 55.0/255.0,
alpha: 1.0)
let commandBuffer = commandQueue.makeCommandBuffer()!
let renderEncoder = commandBuffer
.makeRenderCommandEncoder(descriptor: renderPassDescriptor)!
renderEncoder.setRenderPipelineState(pipelineState)
renderEncoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
renderEncoder
.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 3, instanceCount: 1)
renderEncoder.endEncoding()
commandBuffer.present(drawable)
commandBuffer.commit()
}
#objc func gameloop() {
autoreleasepool {
self.render()
}
}
}
Shaders.metal
#include <metal_stdlib>
using namespace metal;
vertex float4 basic_vertex( // 1
const device packed_float3* vertex_array [[ buffer(0) ]], // 2
unsigned int vid [[ vertex_id ]]) { // 3
return float4(vertex_array[vid], 1.0); // 4
}
fragment half4 basic_fragment() { // 1
return half4(1.0); // 2
}
Note - Shaders.metal is a file that the tutorial says to create
The compiler detected a similar name to Metal, which is metal. Sometimes libraries change name in their own different versions, that's why it can be different from the tutorial you're following.
try doing as the error suggest and replacing the import with: import metal
You need to import Metal framework as below (mentioned under Creating an MTLDevice),
import Metal
Currently you are doing as below which is wrong,
import metal
You need to have a var vertexBuffer: MTLBuffer variable

Passing a float or color into a Metal fragment shader from Swift

I'm trying to write a fragment shader in Metal but can't get my head around how to pass in single values (e.g. float, float4 or half4). My shaders are as follows:
#include <metal_stdlib>
using namespace metal;
typedef struct {
float4 renderedCoordinate [[position]];
} FullscreenQuadVertex;
vertex FullscreenQuadVertex fullscreenQuad(unsigned int vertex_id [[ vertex_id ]]) {
float4x4 renderedCoordinates = float4x4(float4( -1.0, -1.0, 0.0, 1.0 ),
float4( 1.0, -1.0, 0.0, 1.0 ),
float4( -1.0, 1.0, 0.0, 1.0 ),
float4( 1.0, 1.0, 0.0, 1.0 ));
FullscreenQuadVertex outVertex;
outVertex.renderedCoordinate = renderedCoordinates[vertex_id];
return outVertex;
}
fragment float4 displayColor(device float4 *color [[ buffer(0) ]]) {
// return float4(0.2, 0.5, 0.8, 1);
return *color;
}
And I'm passing the color in from an MTKView subclass like this:
import MetalKit
class MetalView: MTKView {
var color = NSColor(deviceRed: 0.2, green: 0.4, blue: 0.8, alpha: 1)
var pipeline: MTLRenderPipelineState!
var colorBuffer: MTLBuffer!
init() {
super.init(frame: CGRect.zero, device: nil)
setup()
}
required init(coder: NSCoder) {
super.init(coder: coder)
setup()
}
func setup() {
device = MTLCreateSystemDefaultDevice()
colorPixelFormat = .bgra8Unorm
// setup render pipeline for displaying the off screen buffer
guard let library = device?.makeDefaultLibrary() else {
fatalError("Failed to make Metal library")
}
let pipelineDescriptor = MTLRenderPipelineDescriptor()
pipelineDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm
pipelineDescriptor.colorAttachments[0].isBlendingEnabled = false
pipelineDescriptor.vertexFunction = library.makeFunction(name: "fullscreenQuad")
pipelineDescriptor.fragmentFunction = library.makeFunction(name: "displayColor")
do {
pipeline = try device?.makeRenderPipelineState(descriptor: pipelineDescriptor)
} catch {
fatalError("Failed to make render pipeline state")
}
colorBuffer = device?.makeBuffer(length: MemoryLayout<float4>.stride, options: .storageModeManaged)
updateBackgroundColor()
}
func updateBackgroundColor() {
var colorArray = [color.blueComponent, color.greenComponent, color.redComponent, color.alphaComponent].map { Float($0) }
var data = Data(buffer: UnsafeBufferPointer(start: &colorArray, count: colorArray.count))
colorBuffer.contents().copyMemory(from: &data, byteCount: data.count)
colorBuffer.didModifyRange(0..<data.count)
}
override func draw(_ dirtyRect: NSRect) {
drawColor()
}
func drawColor() {
guard let commandQueue = device?.makeCommandQueue() else { return }
guard let commandBuffer = commandQueue.makeCommandBuffer() else { return }
guard let renderPassDescriptor = currentRenderPassDescriptor else { return }
guard let currentDrawable = currentDrawable else { return }
guard let commandEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor) else { return }
commandEncoder.setRenderPipelineState(pipeline)
commandEncoder.setFragmentBuffer(colorBuffer, offset: 0, index: 0)
commandEncoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4)
commandEncoder.endEncoding()
commandBuffer.present(currentDrawable)
commandBuffer.commit()
}
}
Despite passing in what I’d expect to be a shade of blue, all I see is black. Testing a hard coded colour returned from the the fragment shader works fine.
I'm not the most fluent in the use use UnsafePointers with Data types, so not sure if the problem is in setting the buffer data, or the way I'm actually trying to pass the buffer data into the shader. I did try the shader with an attribute type of [[ color(0) ]], however as far as I can see these aren't supported on macOS (or I was doing it wrong 🤷🏻‍♂️).
Rather than using a buffer for a single color, why not just send the color directly using setFragmentBytes()? Here's how it would look:
var fragmentColor = vector_float4(Float(color.redComponent), Float(color.greenComponent), Float(color.blueComponent), Float(color.alphaComponent))
commandEncoder.setFragmentBytes(&fragmentColor, length: MemoryLayout.size(ofValue: fragmentColor), index: 0)
And your shader would still use:
fragment float4 displayColor(constant float4 &color [[ buffer(0) ]])

osx metal doesn't render

I followed this tutorial, extrapolating from iOS to OS X and everything compiles just fine, except I don't get anything rendered (even the clear color) without any errors. Could anyone please take a look and tell me what am I doing wrong here? I couldn't test this out on iOS like in the tutorial because iOS Simulator doesn't support Metal yet.
I have a custom view for Metal Rendering.
I'm not adding a sublayer (like in the tutorial) because the layer is nil. I suppose I need to activate it somehow and I don't know how.
import Cocoa
import Metal
import QuartzCore
class MetalView: NSView {
var device: MTLDevice!
var pipelineState: MTLRenderPipelineState!
var commandQueue: MTLCommandQueue!
var renderPassDescriptor: MTLRenderPassDescriptor!
var vertexBuffer: MTLBuffer!
var drawable: CAMetalDrawable {
return (layer as! CAMetalLayer).nextDrawable()!
}
override func awakeFromNib() {
// Device
device = MTLCreateSystemDefaultDevice()
// Layer
let metalLayer = CAMetalLayer()
metalLayer.device = device
metalLayer.pixelFormat = .BGRA8Unorm
metalLayer.framebufferOnly = true
metalLayer.frame = frame
layer = metalLayer
// Pipeline State
let defaultLibrary = device.newDefaultLibrary()
let fragmentProgram = defaultLibrary!.newFunctionWithName("basic_fragment")
let vertexProgram = defaultLibrary!.newFunctionWithName("basic_vertex")
let pipelineStateDescriptor = MTLRenderPipelineDescriptor()
pipelineStateDescriptor.vertexFunction = vertexProgram
pipelineStateDescriptor.fragmentFunction = fragmentProgram
pipelineStateDescriptor.colorAttachments[0].pixelFormat = .BGRA8Unorm
do
{
try pipelineState = device.newRenderPipelineStateWithDescriptor(pipelineStateDescriptor)
}
catch let error as NSError {
NSLog("Failed to create pipeline state, error \(error)")
}
// Command Queue
commandQueue = device.newCommandQueue()
// Render Pass Descriptor
renderPassDescriptor = MTLRenderPassDescriptor()
renderPassDescriptor.colorAttachments[0].texture = drawable.texture
renderPassDescriptor.colorAttachments[0].loadAction = .Clear
renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColor(red: 0.75, green: 0.5, blue: 0.0, alpha: 1.0)
// Vertex Buffer
let vertexData:[Float] = [
0.0, 1.0, 0.0,
-1.0, -1.0, 0.0,
1.0, -1.0, 0.0
]
vertexBuffer = device.newBufferWithBytes(vertexData, length: vertexData.count * sizeofValue(vertexData[0]), options: MTLResourceOptions())
}
override func drawRect(dirtyRect: NSRect) {
let commandBuffer = commandQueue.commandBuffer()
let renderEncoder = commandBuffer.renderCommandEncoderWithDescriptor(renderPassDescriptor)
renderEncoder.setRenderPipelineState(pipelineState)
renderEncoder.setVertexBuffer(vertexBuffer, offset: 0, atIndex: 0)
renderEncoder.drawPrimitives(.Triangle, vertexStart: 0, vertexCount: 3, instanceCount: 1)
renderEncoder.endEncoding()
commandBuffer.presentDrawable(drawable)
commandBuffer.commit()
}
}
And I have the following shader code:
#include <metal_stdlib>
using namespace metal;
struct VertexIn
{
packed_float3 position;
};
vertex float4 basic_vertex(
const device VertexIn* vertex_array [[ buffer(0) ]],
unsigned int vertex_id [[ vertex_id ]])
{
return float4(vertex_array[vertex_id].position, 1.0);
}
fragment half4 basic_fragment()
{
return half4(1.0);
}
Directly setting the view's layer property is necessary but not sufficient for creating a so-called layer-hosting view. You can read about the distinction between layer hosting and layer backing here.
You almost certainly want a layer-backed view. To do this, override the makeBackingLayer method, in which you will create and configure your Metal layer and then return it. Then, early in your view's lifecycle (ideally in its initializer(s)), set the wantsLayer property to YES. This should be sufficient for getting your layer on the screen.