Skip to content
Switch branches/tags
Go to file
Cannot retrieve contributors at this time
layout title
Metal FAQ

Drew's Metal FAQ

This page documents various odd Metal behaviors that I have not found anywhere else.

Using Deployment Target of "Fall 2020" removes non-C++ features

After raising my deployment target to iOS 14 (macOS 11, etc...) I got numerous new compile errors. These errors do not occur with the same compiler with a deployment target of iOS 13 (macOS 10.15, etc...)

It appears this is because various C99/C11 features were 'removed' (I gather they were never really supported) in Metal, which is based on C++, not on C. In particular, this affects restrict, which is not a valid C++ keyword.

I am advised this works as intended. For restrict anyway, the compiler keyword __restrict still compiles, although I don't know if it has any effect.



As of iOS 14, assert is defined as

#define assert(condition) ((void) 0)

For this reason it has no effect.

If you want assert-like behavior in Metal, you can use

#define assert(X) if (__builtin_expect(!(X),0)) {float device *f = 0; *f = 0;}

along with the "metal shader validation" diagnostic in Debug GPU-side errors in Metal. It will not trip without this diagnostic.

For a production-ready solution, stdmetal ships with SM_ASSERT and SM_PRECONDITION cross-platform macros.


replayer terminated unexpectedly with error code 5

This is usually caused by performing a metal capture programmatically during application launch. The workaround is to perform the capture "later", such as with .asyncAfter.


cross-compiling C code to metal

The best way I know of to do this is to concatenate .c files into a .metal file, and compile that.

  1. Create a 'run script' phase
  2. COUNTER=0
    rm -f "${SCRIPT_OUTPUT_FILE_0}"
    while [ $COUNTER -lt ${SCRIPT_INPUT_FILE_COUNT} ]; do
        cat "$FILE" >> "${SCRIPT_OUTPUT_FILE_0}"
        let COUNTER=COUNTER+1
  3. Set the input files to all your .c input files. You need to keep them up to date, alterantively you can use a file list
  4. Set the output files to your .metal file. After this file is built, drag into xcode and attach it to the right project

See blitcurveMetal.xcodeproj for an example.

distributing a library for use inside a metal shader

The best way I know of to do this is to build a .a file, or a script to build one, and distribute that.

  1. Create a "run script" phase
  2. metal-libtool -static ${TARGET_TEMP_DIR}/Metal/mylib.air  -o ${BUILT_PRODUCTS_DIR}/libmylib.a
3.  Set the "input files" to `${TARGET_TEMP_DIR}/Metal/mylib.air`
4.  Set the "output files" to `${BUILT_PRODUCTS_DIR}/libmylib.a`

### If you are also doing this as part of a Swift package,

...nobody can use your xcodeproj directly, because the xcodeproj is readonly and you will get errors about "The file "project.pbxproj" could not be unlocked" (FB8095945)

Instead, you need to create a build script that calls xcodebuild:

if [ "$PLATFORM_NAME" = "macosx" ]; then
if [ "$PLATFORM_NAME" = "iphonesimulator" ]; then
SDK_ARGS="-sdk iphonesimulator"
# we want to use BUILT_PRODUCTS_DIR directly.  This avoids a lot of tricky problems
# about how to predict where the output will be on a per-target per-architecture basis
xcodebuild build -project ${MYLIB_DIR}/project.xcodeproj -configuration ${CONFIGURATION} -target "$INNER_TARGET" ${SDK_ARGS} BUILT_PRODUCTS_DIR=${BUILT_PRODUCTS_DIR}

Then, instruct users to

  1. Set MYLIB_DIR to the path to the script as a custom build setting. For scripts in the root of a Swift package managed in git by xcode, the value of this is typically ${SHARED_PRECOMPS_DIR}/../../../SourcePackages/Checkouts/packagename
  2. Set the MTLLINKER_FLAGS to -L ${BUILT_PRODUCTS_DIR} -l mylib. This assumes that your library has the libmylib.a naming scheme. Also, this build setting is undocumented.
  3. Set the "Metal Compiler - BuildOptions" MTL_HEADER_SEARCH_PATHS to include ${HEADER_SEARCH_PATHS}. This will let Metal sources find the header files from the Swift package.

See blitcurve for a complete example.

  • FB7776777
  • FB7744335


Yeah, so it turns out there is no good environment variable to get a path to the swift package is on disk.

In general, a project's dependencies can be specified by a local path, a git url, or a mix of both strategies. So to start with there's not a single location where you can expect your Swift package dependencies.

The git urls are resolved by Xcode to path like ~/Library/Developer/DerivedData/YourProject-stuffhere/SourcePackages/Checkouts/packagename, so you can theoretically get a fixed path for that case specifically. However,

  • This is undocumented and could change
  • There is no environment variable that gets the Checkouts, SourcePackages or even YourProject-stuffhere paths.
  • There are environment variables that return an arbitrary folder inside the YourProject-stuffhere path. These include plausible candidates like BUILD_DIR and BUILD_ROOT. So in theory, you concatenate BUILD_DIR, some ../../ updiring and then the undocumented SourcePackages/Checkouts/packagename at the end.

However, in various situations Xcode will increase the directory depth of these variables. For example, when building your scheme as part of running a playground, BUILD_DIR is


but when built on its own, it's


SHARED_PRECOMPS_DIR is pretty much the only value I'm aware of that seems to have a fixed number of subdirectories and is suitable for this purpose.

I'm sorry.

  • FB8102669 - environment variable for swift packages
  • FB8095382 - environment variable for metal projects

My "Metal Library" target does not have a product

The best solution I'm aware of on this problem is to create a custom phase for copying the .metallib into the target manually.


    cp ${INPUT} ${OUTPUT}

  • input files: ${BUILT_PRODUCTS_DIR}/my.metallib
  • output files: ${BUILT_PRODUCTS_DIR}/${EXECUTABLE_FOLDER_PATH}/my.metallib


MTLCaptureError Code=1

Error Domain=MTLCaptureError Code=1 "Capturing is not supported.

Add MetalCaptureEnabled=1 to Info.plist.

Apple documents that this happens automatically, but I'm aware of some cases where it doesn't.

FB7870713 – works as designed