In XCode 9's Metal template, there is one part where it's setting attributes
and layouts
on a MTLVertexDescriptor
.
let mtlVertexDescriptor = MTLVertexDescriptor()
mtlVertexDescriptor.attributes[VertexAttribute.position.rawValue].format = ...
mtlVertexDescriptor.attributes[VertexAttribute.texcoord.rawValue].format = ...
I tried hard, but cannot figure out where the magic keywords position
, texcoord
and later meshPositions
, meshGenerics
are coming from.
I guess it's not from the source code, but I didn't find any documentation where these would be specified. For VertexAttribute
all I got was the reference page, without any mention about position
and texcoord
.
XCode points me to ShaderTypes.h
, namely this section:
typedef NS_ENUM(NSInteger, VertexAttribute) {
VertexAttributePosition = 0,
VertexAttributeTexcoord = 1,
};
I feel this is the key for understanding this, but I have multiple problems:
As a developer who started with Swift, in a Swift project template this Obj-C part is a bit confusing. Can you explain me clearly what it does exactly?
How does VertexAttributePosition
in an NS_ENUM add a magical property .position
and VertexAttributeTexcoord
-> .texcoord
?
Why are none of these documented (Google finds mostly OpenGL related pages), nor can XCode find any help / jump to definition on them?
The clue to understanding what's going on here can be found on this line in the ShaderTypes.h file:
// Header containing types and enum constants shared between Metal shaders and Swift/ObjC source
The express intent of ShaderTypes.h is to declare types that can be used by both the shaders (which are written in Metal Shading Language, a dialect of C++) and the renderer class (which is written in Objective-C or Swift, depending on which you select when opening the template). The way this is achieved is by constructing a header file that can be included in both. The twist comes when using Swift, because Swift lacks a notion of header files except for one special case: bridging headers.
When incorporating C or Objective-C code into a Swift app, you provide a bridging header that imports or declares types and methods you want to use in Swift. In Xcode, you configure this with the "Objective-C Bridging Header" setting in the "Swift Compiler - General" portion of your project's Build Settings. It's kinda buried, but if you go there, you'll see that it's populated with the "ShaderTypes.h" header from the template. That's how your Swift code knows about the VertexAttribute
enum type.
In Objective-C, to get the attribute index, you'd use one of the defined enum values directly: VertexAttributePosition
, which is functionally equivalent to a literal 0
. When that gets imported into Swift, the name of the enum (VertexAttribute
) gets stripped off the front, and the values get transformed into lower camel case, per Swift style, e.g. position
. You can read more about the particulars here.
The upshot of this is that even though there is no enum value named "position
" anywhere in the code, that name gets synthesized for you when you use the Objective-C enumeration from Swift. The rawValue
property is the integer value associated with that particular enum value, which can then be used as an index on an attribute or vertex descriptor (again, in this case, it's equal to 0
).
None of these are documented because they're defined exclusively in this template. They're not part of the Metal API or really any API; they're just names that provide a convenient label for the underlying constants, in order to make the shader and app code consistent with one another.