Thanks for your answer. Actually, I've spent the last week researching and understanding the Swift / Game App / Metal template 🙂
After a week, it's clear how it works with a loaded mesh, which is also the scenario what you've described, if I understand right. My problem is that I do not have any loaded object, everything is generated on-the-fly. Luckily nothing complicated, only points and lines in 3D space.
For example, for drawing lines, here is my current solution:
1. Creating a toBuffer() method on simd types:
extension float2 {
func toBuffer() -> [Float] {
return [x, y]
}
}
extension float3 {
func toBuffer() -> [Float] {
return [x, y, z, 0]
}
}
extension float4 {
func toBuffer() -> [Float] {
return [x, y, z, w]
}
}
2. Shared parts go in Bridge Header:
struct MyPoint {
vector_float3 position;
vector_float4 color;
};
struct MyLine {
struct MyPoint start;
struct MyPoint end;
};
3. Then extending these types in Swift:
extension MyPoint {
func toBuffer() -> [Float] {
return position.toBuffer() + color.toBuffer()
}
}
extension MyLine {
func toBuffer() -> [Float] {
return start.toBuffer() + end.toBuffer()
}
}
4. Allowing me to generate 3D objects in code and generate the buffer with a [Float] array:
func buildLineArray() {
let pointA = MyPoint(position: float3(sqrt(8 / 9), 0, -1 / 3), color: float4(1, 1, 1, 1))
let pointB = MyPoint(position: float3(-sqrt(2 / 9), sqrt(2 / 3), -1 / 3), color: float4(1, 0, 0, 1))
let pointC = MyPoint(position: float3(-sqrt(2 / 9), -sqrt(2 / 3), -1 / 3), color: float4(0, 1, 0, 1))
let pointD = MyPoint(position: float3(0, 0, 1), color: float4(0, 0, 1, 1))
let lineAB = MyLine(start: pointA, end: pointB)
let lineAC = MyLine(start: pointA, end: pointC)
let lineAD = MyLine(start: pointA, end: pointD)
let lineBC = MyLine(start: pointB, end: pointC)
let lineBD = MyLine(start: pointB, end: pointD)
let lineCD = MyLine(start: pointC, end: pointD)
lineArray = [lineAB, lineAC, lineAD, lineBC, lineBD, lineCD]
}
func buildBuffer() {
var vertexData = [Float]()
for line in lineArray {
vertexData += line.toBuffer()
}
let dataSize = vertexData.count * MemoryLayout.size(ofValue: vertexData[0])
vertexBuffer = device.makeBuffer(bytes: vertexData, length: dataSize, options: [])
}
5. And for example do instanced drawing:
renderEncoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4, instanceCount: lineArray!.count)
Now this one isn't perfect, but the bridged header at least solves half the problem. Generally I feel quite efficient in working with this code.
Do you think this could be achieved with VertexDescriptors as well? My problematic part would be step 4., I simply don't see how can I generate vertices for a VertexDescriptor object in code.