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.

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.

This does makes one wonder what was going through the minds of the Metal team at Apple. The Metal API is exposed as Objective C where you can not use float4, rather the C variant simd_float4. Though float4 is available in swift but then you can not share code between swift and Metal. The only way left is to use ObjectiveC++ and Metal.