---
title: "TN3133: Packaging a Metal renderer"
framework: technotes
role: article
role_heading: Article
path: technotes/tn3133-packaging-a-renderer
---

# TN3133: Packaging a Metal renderer

Distribute a Metal renderer in a Swift package.

## Overview

Overview Several individual pieces make up a Metal renderer: The CPU-side app code, the GPU-side shaders, and the structures that the app code and the shaders share. Bundling these pieces together in a single Swift package is an excellent way to modularize a renderer for use in multiple projects. Read this technote to discover a Swift package structure that shares C structs between Swift and Metal code, and to learn how to access the compiled Metal source as a MTLLibrary. Configure the package manifest The package structure that enables you to package Swift, Metal, and shared C sources in a single Swift package requires two Target declarations, as shown in the following example package manifest: // swift-tools-version:5.5

import PackageDescription

let package = Package(     name: "MyRenderer",     products: [         .library(             name: "MyRenderer",             targets: ["MyRenderer"]),     ],     targets: [         // MyRenderer contains .swift and .metal files.         .target(             name: "MyRenderer",             dependencies: ["MySharedTypes"]),

// MySharedTypes contains a .h file nested inside of a folder named "include", and an empty .m file, specifying that the target should be compiled as an Obj-C target.         .target(name: "MySharedTypes")     ] ) The MyRenderer target contains the Swift source files, as well as the Metal source files. The MySharedTypes target contains the shared C structs within a header file. Store this header in the directory specified as the publicHeadersPath for this target, so that the header is accessible from the MyRenderer target. It’s also important to have at least one Obj-C, C, or C++ implementation file in this target. note: A target cannot have source files from both Swift and C-family languages, but it’s OK to have Swift and Metal sources in the same target because SwiftPM treats Metal files as resource files. Add the MySharedTypes target as a dependency of the MyRenderer target to access the shared C structs in the MyRenderer target. Here is a visual representation of the file structure described in the example above: . └── MyRenderer     ├── Package.swift     ├── README.md     └── Sources         ├── MyRenderer         │   ├── Renderer.swift         │   └── Shaders.metal         └── MySharedTypes             ├── SharedTypes.m             └── include                 └── SharedTypes.h Accessing the shared C structs in Swift Swift Package Manager creates a module that contains the C structs found in the publicHeadersPath, and because the Swift target is dependent on the C target, the C structs are directly accessible from Swift. Continuing with the same naming from the example above, consider the following header located at MyRenderer/Sources/MySharedTypes/include/SharedTypes.h: #ifndef SharedTypes_h #define SharedTypes_h

#import <simd/simd.h>

typedef struct {     vector_float2 position;     vector_float4 color; } AAPLVertex;

#endif /* SharedTypes_h */ The shared types are accessible in Swift files after importing the MySharedTypes module, for example: import MySharedTypes

let vertex = AAPLVertex(position: .init(250, -250), color: .init(1, 0, 0, 1)) Accessing the shared C structs in Metal Using the same package structure defined above, the shared types are accessible in Metal files after importing the appropriate header file: // A relative path to SharedTypes.h. #import "../MySharedTypes/include/SharedTypes.h"

// Use any C types found in the imported header in this Metal file. Retrieving the precompiled Metal library Swift Package Manager compiles the Metal source to a .metallib and stores it in the resource bundle of the target. This resource bundle is accessible from Swift through the Bundle.module static property. To create a MTLLibrary from this bundle in the Swift target: do {   // device is a `MTLDevice`.   let library = try device.makeDefaultLibrary(bundle: Bundle.module) } catch {   // Handle the error. } For more information about the Bundle.module static property, see Bundling resources with a Swift package. Introducing a custom Metal compilation step You might want to invoke the metal command-line tool yourself, and provide it with arguments that fit your specific needs. For example, you could compile your Metal source with debug symbols to enable shader debugging in a client app. To introduce a custom Metal compilation step to the build process, create a Swift Package Build Tool Plugin that invokes the metal command-line tool with custom arguments, precompiles a .metallib, and stores it in the target’s resources directory by specifying it as an output file of the build command. Then, apply the plugin to the target that contains your .metal files. For more information about creating and applying a Swift Package Build Tool Plugin, see Create Swift Package plugins. Revision History 2022-11-08 First published.

## See Also

### Latest

- [TN3210: Optimizing your app for iPhone Mirroring](technotes/tn3210-optimizing-your-app-for-iphone-mirroring.md)
- [TN3211: Resolving SwiftUI source incompatibilities for State and ContentBuilder](technotes/tn3211-resolving-swiftui-source-incompatibilities-for-state-and-contentbuilder.md)
- [TN3212: Adopting gesture recognizers for Sidecar touch support](technotes/tn3212-adopting-gesture-recognizers-for-sidecar-touch-support.md)
- [TN3208: Preparing your app’s launch screen to meet App Store requirements](technotes/tn3208-preparing-your-apps-launch-screen-to-meet-app-store-requirements.md)
- [TN3205: Low-latency communication with RDMA over Thunderbolt](technotes/tn3205-low-latency-communication-with-rdma-over-thunderbolt.md)
- [TN3206: Updating Apple Pay certificates](technotes/tn3206-updating-apple-pay-certificates.md)
- [TN3179: Understanding local network privacy](technotes/tn3179-understanding-local-network-privacy.md)
- [TN3190: USB audio device design considerations](technotes/tn3190-usb-audio-device-design-considerations.md)
- [TN3194: Handling account deletions and revoking tokens for Sign in with Apple](technotes/tn3194-handling-account-deletions-and-revoking-tokens-for-sign-in-with-apple.md)
- [TN3193: Managing the on-device foundation model’s context window](technotes/tn3193-managing-the-on-device-foundation-model-s-context-window.md)
- [TN3115: Bluetooth State Restoration app relaunch rules](technotes/tn3115-bluetooth-state-restoration-app-relaunch-rules.md)
- [TN3192: Migrating your iPad app from the deprecated UIRequiresFullScreen key](technotes/tn3192-migrating-your-app-from-the-deprecated-uirequiresfullscreen-key.md)
- [TN3151: Choosing the right networking API](technotes/tn3151-choosing-the-right-networking-api.md)
- [TN3111: iOS Wi-Fi API overview](technotes/tn3111-ios-wifi-api-overview.md)
- [TN3191: IMAP extensions supported by Mail for iOS, iPadOS, and visionOS](technotes/tn3191-imap-extensions-supported-by-mail.md)
