Triangle mesh in Metal - swift

I'm trying to create triangle 2D mesh using SwiftUI & Metal. I have lists of vertices[929 items] and indices[1750 items] to draw a mesh. I have used method .drawIndexedPrimitives, but it isn't correct for this purpose. I think this problem should be solved by using MTKMesh, but I don't know how to apply this to 2D mesh with lists of vertices and indices. Please suggest/give some ideas to solve this problem
The code of Renderer Class below
import Foundation
import MetalKit
class Renderer: NSObject, MTKViewDelegate {
var parent: MetalViewComponent
var metalDevice: MTLDevice!
var metalCommandQueue: MTLCommandQueue!
let pipelineState: MTLRenderPipelineState
let vertexBuffer: MTLBuffer?
// let mesh = MTKMesh(mesh: <#T##MDLMesh#>, device: <#T##MTLDevice#>)
let indexBuffer: MTLBuffer?
init(_ parent: MetalViewComponent) {
self.parent = parent
if let metalDevice = MTLCreateSystemDefaultDevice() {
self.metalDevice = metalDevice
}
self.metalCommandQueue = metalDevice.makeCommandQueue()
let pipelineDescriptor = MTLRenderPipelineDescriptor()
let library = metalDevice.makeDefaultLibrary()
pipelineDescriptor.vertexFunction = library?.makeFunction(name: "vertexShader")
pipelineDescriptor.fragmentFunction = library?.makeFunction(name: "fragmentShader")
pipelineDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm
do {
try pipelineState = metalDevice.makeRenderPipelineState(descriptor: pipelineDescriptor)
} catch {
fatalError()
}
let vertices = [Vertex(position: [-3.49195e-17, -0.570297], color: [0, 1, 1, 1]),
Vertex(position: [-0.245024, -0.578218], color: [0, 1, 1, 1]),
Vertex(position: [-0.529488, -0.52307], color: [0, 1, 1, 1]),...
let indices: [UInt32] = [470,469,433,
433,469,432,
470,433,434,
506,469,507,
469,470,507, // from 1 to 928
vertexBuffer = metalDevice.makeBuffer(bytes: vertices, length: vertices.count * MemoryLayout<Vertex>.stride, options: [])!
indexBuffer = metalDevice.makeBuffer(bytes: indices, length: indices.count * MemoryLayout<UInt16>.size, options: [])
super.init()
print("Число вертексов: \(vertices.count)")
}
func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
}
func draw(in view: MTKView) {
guard let drawable = view.currentDrawable else {
return
}
let commandBuffer = metalCommandQueue.makeCommandBuffer()
let renderPassDescriptor = view.currentRenderPassDescriptor
renderPassDescriptor?.colorAttachments[0].clearColor = MTLClearColorMake(0, 0, 0, 1.0)
renderPassDescriptor?.colorAttachments[0].loadAction = .clear
renderPassDescriptor?.colorAttachments[0].storeAction = .store
let renderEncoder = commandBuffer?.makeRenderCommandEncoder(descriptor: renderPassDescriptor!)
renderEncoder?.setRenderPipelineState(pipelineState)
renderEncoder?.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
renderEncoder?.drawIndexedPrimitives(type: .lineStrip, indexCount: 1750, indexType: .uint32, indexBuffer: indexBuffer!, indexBufferOffset: 0)
renderEncoder?.endEncoding()
commandBuffer?.present(drawable)
commandBuffer?.commit()
}
}//
This is what's drawing for now
this is what I expect

Related

How to create a SCNNode with a custom geometry?

I am trying to create a SCNNode whose geometry is created using an array of x, y, and z positions. I am using the following code; however, this is not showing up. Does anyone know what's wrong?
class CustomShapeNode: SCNNode {
init(positions: [(Double, Double, Double)]) {
super.init()
// Create an array of SCNVector3 from the positions array
var scnPositions = [SCNVector3]()
for position in positions {
scnPositions.append(SCNVector3(Float(position.0), Float(position.1), Float(position.2)))
}
// Create a geometry from the positions array
let geometry = SCNGeometry(sources: [SCNGeometrySource(vertices: scnPositions)], elements: [SCNGeometryElement(indices: Array(0..<scnPositions.count), primitiveType: .triangles)])
// Set the geometry to the node
self.geometry = geometry
// Set the color of the node geometry
self.geometry?.firstMaterial?.diffuse.contents = UIColor.red
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Use the following code:
import SceneKit
class ViewController: UIViewController {
var sceneView: SCNView? = nil
let node = CustomShapeNode([SCNVector3( 0,0,0), // CCW
SCNVector3( 1,1,0),
SCNVector3(-1,1,0)])
override func viewDidLoad() {
super.viewDidLoad()
sceneView = self.view as? SCNView
sceneView?.scene = SCNScene()
sceneView?.backgroundColor = .darkGray
sceneView?.allowsCameraControl = true
sceneView?.scene?.rootNode.addChildNode(node)
}
}
class CustomShapeNode: SCNNode {
init(_ positions: [SCNVector3]) {
super.init()
let normalsPerFace = 1
let indices: [Int32] = [0, 1, 2]
let source = SCNGeometrySource(vertices: [positions[0],
positions[1],
positions[2]])
let normals = [positions[0],positions[1],positions[2]].map {
[SCNVector3](repeating: $0, count: normalsPerFace)
}.flatMap { $0 }
let normalSource = SCNGeometrySource(normals: normals)
let data = Data(bytes: indices,
count: indices.count * MemoryLayout<Int32>.size)
let element = SCNGeometryElement(data: data,
primitiveType: .triangles,
primitiveCount: 1,
bytesPerIndex: MemoryLayout<Int32>.size)
let p1 = CGPoint(x: CGFloat(positions[0].x),
y: CGFloat(positions[0].y))
let p2 = CGPoint(x: CGFloat(positions[1].x),
y: CGFloat(positions[1].y))
let p3 = CGPoint(x: CGFloat(positions[2].x),
y: CGFloat(positions[2].y))
let texCoord = SCNGeometrySource(textureCoordinates: [p1, p2, p3])
self.geometry = SCNGeometry(sources: [source, normalSource, texCoord],
elements: [element])
self.geometry?.firstMaterial?.diffuse.contents = UIColor.systemOrange
self.geometry?.firstMaterial?.lightingModel = .constant
self.geometry?.firstMaterial?.isDoubleSided = true
}
required init?(coder: NSCoder) {
fatalError("Hasn't been implemented yet")
}
}

How to setup bone hierarchy for SCNSkinner from glb data?

I'm trying to show a 3d model of a human body scan in SceneKit. The model is stored in a glb file. Because SceneKit does not support the import of that file format I manually read the file in. I'm trying to use the SCNSkinner from SceneKit to rig the model with the Mixamo-rig included in the glb file. The model is correctly shown as long as I don't apply any bone positions unequal (0, 0, 0), rotations or set boneInverseBindTransforms unequal the Identity matrix. But as soon as I apply any of the afore mentioned properties, the model starts to look unnatural.
Here is the code to create the SCNSkinner object:
private func createSCNSkinnerFrom(bgArmature: BgArmature, baseGeometry: SCNGeometry) -> SCNSkinner? {
var joints: [UInt8] = bgArmature.mesh.joints.joined().map { element in UInt8(element) }
var weights: [Float] = bgArmature.mesh.weights.joined().map { element in element }
let bones: [SCNNode] = self.createBoneNodesList(bgRig: bgArmature.rig!)
let boneIndicesData = Data(bytesNoCopy: &joints, count: joints.count * MemoryLayout<UInt8>.size, deallocator: .none)
let boneIndicesGeometrySource = SCNGeometrySource(data: boneIndicesData, semantic: .boneIndices, vectorCount: joints.count/4, usesFloatComponents: false, componentsPerVector: 4, bytesPerComponent: MemoryLayout<UInt8>.size, dataOffset: 0, dataStride: MemoryLayout<UInt8>.size * 4)
let boneWeightsData = Data(bytesNoCopy: &weights, count: weights.count * MemoryLayout<Float>.size, deallocator: .none)
let boneWeightsGeometrySource = SCNGeometrySource(data: boneWeightsData, semantic: .boneWeights, vectorCount: weights.count/4, usesFloatComponents: true, componentsPerVector: 4, bytesPerComponent: MemoryLayout<Float>.size, dataOffset: 0, dataStride: MemoryLayout<Float>.size * 4)
let boneInverseBindTransforms: [NSValue]? = self.createListOfBoneInverseBindTransforms(bgBones: bgArmature.rig!.bones)
let skinner = SCNSkinner(baseGeometry: baseGeometry,
bones: bones,
boneInverseBindTransforms: boneInverseBindTransforms,
boneWeights: boneWeightsGeometrySource,
boneIndices: boneIndicesGeometrySource)
return skinner
}
Here the function to create a bone node and the list of bones:
private func createBoneNodeWithoutChildren(bgBone: BgBone) -> SCNNode {
let bone = SCNNode()
if let name = bgBone.name {
bone.name = name
}
if let translation = bgBone.translation {
bone.simdPosition = SIMD3<Float>(translation[0]!, translation[1]!, translation[2]!)
}
if let rotation = bgBone.rotation {
bone.simdOrientation = simd_quatf(ix: rotation[0]!, iy: rotation[1]!, iz: rotation[2]!, r: rotation[3]!)
}
if let scale = bgBone.scale {
bone.simdScale = SIMD3<Float>(scale[0]!, scale[1]!, scale[2]!)
}
return bone
}
private func createBoneNodesList(bgRig: BgRig) -> [SCNNode] {
var bonesList: [SCNNode] = []
for bone in bgRig.bones {
bonesList.append(self.createBoneNodeWithoutChildren(bgBone: bone))
}
return bonesList
}
The function to create the boneInverseTransforms:
private func createListOfBoneInverseBindTransforms(bgBones: [BgBone]!) -> [NSValue]? {
var boneInverseBindTransforms: [NSValue]? = []
for bone in bgBones {
boneInverseBindTransforms?.append(NSValue(scnMatrix4: bone.inverseBindMatrix!.getSCNMatrix4()))
}
return boneInverseBindTransforms
}
And the BgBone class with the BgMat4 struct:
class BgBone {
var index: Int?
var children: [Int?]?
var name: String?
var rotation: [Float?]?
var scale: [Float?]?
var translation: [Float?]?
var inverseBindMatrix: BgMat4?
init(index: Int? = nil, children: [Int?]? = nil, name: String? = nil, rotation: [Float?]? = nil, scale: [Float?]? = nil, translation: [Float?]? = nil) {
self.index = index
self.children = children
self.name = name
self.rotation = rotation
self.scale = scale
self.translation = translation
}
}
struct BgMat4: sizeable {
var r1c1: Float
var r2c1: Float
var r3c1: Float
var r4c1: Float
var r1c2: Float
var r2c2: Float
var r3c2: Float
var r4c2: Float
var r1c3: Float
var r2c3: Float
var r3c3: Float
var r4c3: Float
var r1c4: Float
var r2c4: Float
var r3c4: Float
var r4c4: Float
init(fromData: Data) {
/// Accessors of matrix type have data stored in column-major order; start of each column MUST be aligned to 4-byte boundaries.
/// Specifically, when ROWS * SIZE_OF_COMPONENT (where ROWS is the number of rows of the matrix) is not a multiple of 4,
/// then (ROWS * SIZE_OF_COMPONENT) % 4 padding bytes MUST be inserted at the end of each column.
self.r1c1 = fromData.withUnsafeBytes { rawBuffer in rawBuffer.load(fromByteOffset: 0, as: Float32.self) }
self.r2c1 = fromData.withUnsafeBytes { rawBuffer in rawBuffer.load(fromByteOffset: 4, as: Float32.self) }
self.r3c1 = fromData.withUnsafeBytes { rawBuffer in rawBuffer.load(fromByteOffset: 8, as: Float32.self) }
self.r4c1 = fromData.withUnsafeBytes { rawBuffer in rawBuffer.load(fromByteOffset: 12, as: Float32.self) }
self.r1c2 = fromData.withUnsafeBytes { rawBuffer in rawBuffer.load(fromByteOffset: 16, as: Float32.self) }
self.r2c2 = fromData.withUnsafeBytes { rawBuffer in rawBuffer.load(fromByteOffset: 20, as: Float32.self) }
self.r3c2 = fromData.withUnsafeBytes { rawBuffer in rawBuffer.load(fromByteOffset: 24, as: Float32.self) }
self.r4c2 = fromData.withUnsafeBytes { rawBuffer in rawBuffer.load(fromByteOffset: 28, as: Float32.self) }
self.r1c3 = fromData.withUnsafeBytes { rawBuffer in rawBuffer.load(fromByteOffset: 32, as: Float32.self) }
self.r2c3 = fromData.withUnsafeBytes { rawBuffer in rawBuffer.load(fromByteOffset: 36, as: Float32.self) }
self.r3c3 = fromData.withUnsafeBytes { rawBuffer in rawBuffer.load(fromByteOffset: 40, as: Float32.self) }
self.r4c3 = fromData.withUnsafeBytes { rawBuffer in rawBuffer.load(fromByteOffset: 44, as: Float32.self) }
self.r1c4 = fromData.withUnsafeBytes { rawBuffer in rawBuffer.load(fromByteOffset: 48, as: Float32.self) }
self.r2c4 = fromData.withUnsafeBytes { rawBuffer in rawBuffer.load(fromByteOffset: 52, as: Float32.self) }
self.r3c4 = fromData.withUnsafeBytes { rawBuffer in rawBuffer.load(fromByteOffset: 56, as: Float32.self) }
self.r4c4 = fromData.withUnsafeBytes { rawBuffer in rawBuffer.load(fromByteOffset: 60, as: Float32.self) }
}
func getSCNMatrix4() -> SCNMatrix4 {
return SCNMatrix4(m11: self.r1c1,
m12: self.r2c1,
m13: self.r3c1,
m14: self.r4c1,
m21: self.r1c2,
m22: self.r2c2,
m23: self.r3c2,
m24: self.r4c2,
m31: self.r1c3,
m32: self.r2c3,
m33: self.r3c3,
m34: self.r4c3,
m41: self.r1c4,
m42: self.r2c4,
m43: self.r3c4,
m44: self.r4c4)
}
}
With this code the model looks like this:
With Positions==(0, 0, 0), Rotation(0, 0, 0), bindInverseTransformation=Identity it looks like this:
But with the Positions, Rotations and bindInverseTransformations applied I expect the model to be in the A-Pose (similar to the following image):
What am I doing wrong with my bones (SCNNodes) or skinner (SCNSkinner) object?

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

Why is the triangle being rendered with rough edges and not smooth edges? Metal, Swift, Xcode

I am using this code to render the "Hello Triangle" triangle. On my iPhone, though, the triangle has very rough edges, not smooth edges, like in the example.
import UIKit
import Metal
import MetalKit
import simd
class MBEMetalView: UIView {
// // // // // MAIN // // // // //
var metalDevice: MTLDevice! = nil
var metalLayer: CAMetalLayer! = nil
var commandQueue: MTLCommandQueue! = nil
var vertexBuffer: MTLBuffer! = nil
var pipelineState: MTLRenderPipelineState! = nil
var displayLink: CADisplayLink! = nil
override class var layerClass : AnyClass {
return CAMetalLayer.self
}
// override func didMoveToWindow() {
// self.redraw()
// }
override func didMoveToSuperview() {
super.didMoveToSuperview()
if self.superview != nil {
self.displayLink = CADisplayLink(target: self, selector: #selector(displayLinkFired))
self.displayLink.add(to: RunLoop.main, forMode: .common)
} else {
self.displayLink.invalidate()
}
}
#objc func displayLinkFired() {
self.redraw()
}
// // // // // INIT // // // // //
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.prepareDeviceLayerAndQueue()
self.makeBuffers()
self.makePipeline()
}
func prepareDeviceLayerAndQueue() {
metalLayer = (self.layer as! CAMetalLayer)
metalDevice = MTLCreateSystemDefaultDevice()
metalLayer.device = metalDevice
metalLayer.pixelFormat = .bgra8Unorm
commandQueue = metalDevice.makeCommandQueue()
}
func makeBuffers() {
var vertices: [MBEVertex] = [
MBEVertex(position: vector_float4(0, 0.5, 0, 1) , color: vector_float4(1, 0, 0, 1)),
MBEVertex(position: vector_float4(-0.5, -0.5, 0, 1) , color: vector_float4(0, 1, 0, 1)),
MBEVertex(position: vector_float4(0.5, -0.5, 0, 1) , color: vector_float4(0, 0, 1, 1))
]
self.vertexBuffer = metalDevice.makeBuffer(bytes: &vertices, length: 56, options: .storageModeShared)
}
func makePipeline() {
guard let library = metalDevice.makeDefaultLibrary() else { print("COULD NOT CREATE LIBRARY") ; return }
guard let vertexFunction = library.makeFunction(name: "vertex_main") else { print("COULD NOT CREATE A VERTEX FUNCTION") ; return }
guard let fragmentFunction = library.makeFunction(name: "fragment_main") else { print("COULD NOT CREATE LIBRARY") ; return }
let pipelineDescriptor = MTLRenderPipelineDescriptor()
pipelineDescriptor.vertexFunction = vertexFunction
pipelineDescriptor.fragmentFunction = fragmentFunction
pipelineDescriptor.colorAttachments[0].pixelFormat = metalLayer.pixelFormat
pipelineState = try? metalDevice.makeRenderPipelineState(descriptor: pipelineDescriptor)
if pipelineState == nil { print("COULD NOT CREATE PIPELINE STATE") ; return }
}
// // // // // FUNCTIONS // // // // //
func redraw() {
guard let drawable = metalLayer.nextDrawable() else { print("COULD NOT CREATE A DRAWABLE") ; return }
let texture = drawable.texture
let renderPassDescriptor = MTLRenderPassDescriptor()
renderPassDescriptor.colorAttachments[0].texture = texture
renderPassDescriptor.colorAttachments[0].loadAction = .clear
renderPassDescriptor.colorAttachments[0].storeAction = .store
renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColor(red: 0.1, green: 0.1, blue: 0.1, alpha: 1)
guard let commandBuffer = commandQueue.makeCommandBuffer() else { print("COULD NOT CREATE A COMMAND BUFFER") ; return }
guard let commandEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor) else { print("COULD NOT CREATE AN ENCODER") ; return }
commandEncoder.setRenderPipelineState(pipelineState)
commandEncoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
commandEncoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 3)
commandEncoder.endEncoding()
commandBuffer.present(drawable)
commandBuffer.commit()
}
// // // // // TYPES // // // // //
struct MBEVertex {
var position: vector_float4
var color: vector_float4
}
}
I have tried to render the triangle a few different times with different methods (sometimes use a MetalKit view from interface builder, sometimes create the view manually)... each time, though, the triangle comes out with rough edges.
The main issue here is that the drawable size of your layer is much smaller than the resolution of your screen. You can get them to match by taking the following steps:
When your Metal view moves to a new superview, update its contentsScale property to match that of the hosting display:
layer.contentsScale = self.window?.screen.scale ?? 1.0
Add a property to your view subclass that computes the ideal drawable size based on the bounds of the view and its scale:
var preferredDrawableSize: CGSize {
return CGSize(width: bounds.size.width * layer.contentsScale,
height: bounds.size.height * layer.contentsScale)
}
Update the drawableSize of your layer when you detect that it doesn't match the computed preferred size:
func redraw() {
if metalLayer.drawableSize != preferredDrawableSize {
metalLayer.drawableSize = preferredDrawableSize
}
...
}
By the way, these days there's really no good reason not to use MTKView for this purpose. It abstracts all of these details for you and is much nicer to work with.