All API to load SceneKit .scn files appear broken.

I have tried all APIs for loading a SceneKit .scn file to create an MTKMesh. All generate an error and do not work at all. Can someone at Apple please give us status on when these will work properly?


The key API that refuses to work is: MTKMesh(mesh:,device:)


When loaded with a valid asset and device it generates the following error:

unexpectedly found nil while unwrapping an Optional value


I have used two paths to create the MDLMesh that feeds this method:


1)

let asset = MDLAsset(scnScene:scene, bufferAllocator:MTKMeshBufferAllocator(device:device))
let kids = asset.childObjects(of:MDLMesh.self)
let modelIOMesh:MDLMesh = kids[ 0 ] as! MDLMesh

2)

        guard let sceneNode = scene.rootNode.childNode(withName:nodeName, recursively:true) else {
            fatalError("Error: Can not create sceneNode")
        }
        guard let sceneGeometry = sceneNode.geometry else {
            fatalError("Error: Can not create sceneGeometry")
        }
     
        let modelIOMesh = MDLMesh(scnGeometry:sceneGeometry, bufferAllocator:MTKMeshBufferAllocator(device:device))


In each cast the resultant MDLMesh is fed to MTKMesh(mesh:,device:) and generates an error.


All data is valid and inspected in the debugger.

Answered by wcm in 196159022

1) The buffer backing an

MTKMeshBuffer
is an
MTLBuffer
, so you would create an
MTLBuffer
of the appropriate size, call
map()
on the
MDLMeshBuffer
to get a pointer to its contents, and copy those bytes into the
MTLBuffer
.


2) In the case of the primitive type and index type, those are the same types used in Metal (

MTLVertexType
and
MTLIndexType
), but you'll need to map the corresponding constants from Model I/O (e.g.,
MDLIndexBitDepth
->
MTLIndexType
). For the index buffer, you can use the approach described in (1) above.

There's a known issue in the Scene Kit to Model I/O bridge that occurs when using an

MTKMeshBufferAllocator
. I suspect that's what you're encountering. If you run this print statement after creating your MDLMesh


print("\(modelIOMesh.vertexBuffers.first?.allocator is MTKMeshBufferAllocator)")


does it print "false"? If so, the allocator you're providing is being ignored, and that's the likely source of the problems down the line. Unfortunately, this means that all of the buffers contained in your Model I/O objects are unusable in Metal and you'll need to copy them into

MTLBuffer
s (or build your own
MTKMesh
objects) manually.

Hi wcm,


I added the print statement and it prints true.


I don't mind going the route you suggest - copying buffers or building Metal meshes from scratch - however I am unclear on how to go about that. Can you give a bit more details or code snippet?


So for example if I start here, with a Model I/O mesh using it's default internal buffer allocator:


guard let sceneGeometry = sceneNode.geometry else {
    fatalError("Error: Can not create sceneGeometry")
}

let modelIOMesh = MDLMesh(scnGeometry:sceneGeometry)


I see that both MDLMesh and MTKMesh rely on vertexBuffers that adopt MDLMeshBuffer protocol, MDLMeshbufferData and MTKMeshBuffer respectively. What I don't see is how to map from the former to the latter and how to associate the resultant buffer with and MTKMesh.


Actually, I did a bit more digging. You suggest "... you'll need to copy them into

MTLBuffer
s (or build your own
MTKMesh
objects) manually ..". That does not actually seem possible. Yes, I can create and fill an
MTLBuffer from a MDLMesh vertexBuffer. But I need an MTKMesh to associate that buffer with - along with all the other state information. This is a chicken and egg situation, no?


Bottom line: there currently appears to be no way to create an MTKMesh from scratch. Thus I cannot manipulate it;s buffers by copying from the MDLMesh equivalent.

Sorry about that. You are indeed correct that it's not possible to create an

MTKMesh
object that wraps a set of manually-created buffers. You can, however, recreate the abstraction of meshes and submeshes with regular
MTLBuffer
s.


It sounds like you may be encountering a different issue from the one I'm aware of. Have you filed a Radar for this? That will raise awareness of the problem and also give you a channel to receive updates.

wcm,


Ok, I am cool with rolling my own mesh abstraction.


So, looking at my render loop I see I need these properties. I will assume a single submesh and use the MTKMesh as a reference. Also, the source MDLMesh has the vertexDescriptor that is appropriate for Metal:


var mesh: MTKMesh


1) How do I create one of these?

mesh.vertexBuffers[ 0 ].buffer


2) Can I simply copy these fields from MDLMesh? If not, how do I derive them from scratch?

mesh.submeshes[ 0 ].primitiveType

mesh.submeshes[ 0 ].indexCount

mesh.submeshes[ 0 ].indexType

mesh.submeshes[ 0 ].indexBuffer.buffer

Accepted Answer

1) The buffer backing an

MTKMeshBuffer
is an
MTLBuffer
, so you would create an
MTLBuffer
of the appropriate size, call
map()
on the
MDLMeshBuffer
to get a pointer to its contents, and copy those bytes into the
MTLBuffer
.


2) In the case of the primitive type and index type, those are the same types used in Metal (

MTLVertexType
and
MTLIndexType
), but you'll need to map the corresponding constants from Model I/O (e.g.,
MDLIndexBitDepth
->
MTLIndexType
). For the index buffer, you can use the approach described in (1) above.

It works! For those following along, on Github:

https://github.com/turner/HelloMetal


see EIOneMeshToRuleThemAll in EIMesh.swift

https://github.com/turner/HelloMetal/blob/master/Shared/EIMesh.swift

All API to load SceneKit .scn files appear broken.
 
 
Q