Types sharing between Metal and App
One of the best things about Metal is the Metal Shading Language which is a based on C++. What this means is that we can share data structures between the shader and app code. There are a few cases that we have to look out for though, which is what this post is about.
Using C++
Lets say we want to define a data structure that represents vertex data:
// common.h
#include <simd/simd.h>
typedef struct
{
simd::float4 position;
simd::float4 color;
} Vertex;
The first problem we would have to tackle is that even though the simd types are the same in both C++ code and the Metal code but in C++ the types are part of simd
namespace.
One way to fix this issue is to add using namespace simd
when calling from C++.
// common.h
#ifdef __METAL_VERSION__
#else
#include <simd/simd.h>
using namespace simd;
#endif
typedef struct
{
float4 position;
float4 color;
} Vertex;
Now we can reuse the data structure in our C++ code, which looks even better thanks to the C99 style member initialization.
// renderer.cpp
#include "common.h"
Vertex data[] = {
{
.position = { 0.0f, 1.0f, 0.0f, 1.0f },
.color = { 1.0f, 0.0f, 0.0f, 1.0f }
},
{
.position = { -1.0f, -1.0f, 0.0f, 1.0f },
.color = { 0.0f, 1.0f, 0.0f, 1.0f }
},
{
.position = { 1.0f, -1.0f, 0.0f, 1.0f },
.color = { 0.0f, 0.0f, 1.0f, 1.0f }
}
};
And on the shading side
// shader.metal
#include <metal_stdlib>
#include "common.h"
using namespace metal;
vertex Vertex vertexShader(device Vertex *data [[buffer(0)]], uint vid [[vertex_id]])
{
return data[vid];
}
fragment float4 fragShader(Vertex in [[stage_in]])
{
return in.color;
}
This has a problem. Since we are returning a struct
from the vertex shader function, we need to have an element with attribute [[position]]
. From the official docs:
If the return type of a vertex function is not void, it must include the vertex position. If the vertex return type is float4, then it always refers to the vertex position, and the [[position]] attribute must not be specified. If the vertex return type is a struct, it must include an element declared with the [[position]] attribute.
The fix is actually pretty simple, we can simply add the missing attribute to the Vertex
// common.h
typedef struct
{
float4 position [[position]];
float4 color;
} Vertex;
This should solve most of the errors except that compiler might emit a warning when compiling the C++ code:
Unknown attribute ‘position’ ignored
This could again be fixed with our beautiful preprocessor check
// common.h
#ifdef __METAL_VERSION__
#define ATTRIB_POSITION [[position]]
#else
#include <simd/simd.h>
#define ATTRIB_POSITION
using namespace simd;
#endif
typedef struct
{
float4 position ATTRIB_POSITION;
float4 color;
} Vertex;
Not the prettiest looking code out there, but gets the job done.
Using Objective-C
With Objective-C, the same trick can be applied, since for all simd
types a C variant is available.
// common.h
#ifdef __METAL_VERSION__
#define ATTRIB_POSITION [[position]]
#else
#define ATTRIB_POSITION
#endif
typedef struct _Vertex
{
vector_float4 position ATTRIB_POSITION;
vector_float4 color;
} Vertex;
Nothing changes on the Metal side.