Thanks for being a part of WWDC25!

How did we do? We’d love to know your thoughts on this year’s conference. Take the survey here

How to properly pass a Metal layer from SwiftUI MTKView to C++ for use with metal-cpp?

Hello! I'm currently porting a videogame console emulator to iOS and I'm trying to make the renderer (tested on MacOS) work on iOS as well.

The emulator core is written in C++ and uses metal-cpp for rendering, whereas the iOS frontend is written in Swift with SwiftUI. I have an Objective-C++ bridging header for bridging the Swift and C++ sides.

On the Swift side, I create an MTKView. Inside the MTKView delegate, I run the emulator for 1 video frame and pass it the view's backing layer for it to render the final output image with. The emulator runs and returns, but when it returns I get a crash in Swift land (callstack attached below), inside objc_release, which indicates I'm doing something wrong with memory management.

My bridging interface (ios_driver.h):

#pragma once
#include <Foundation/Foundation.h>
#include <QuartzCore/QuartzCore.h>

void iosCreateEmulator();
void iosRunFrame(CAMetalLayer* layer);

Bridge implementation (ios_driver.mm):

#import <Foundation/Foundation.h>

extern "C" {
#include "ios_driver.h"
}

<...>

#define IOS_EXPORT extern "C" __attribute__((visibility("default")))

std::unique_ptr<Emulator> emulator = nullptr;
IOS_EXPORT void iosCreateEmulator() { ... }

// Runs 1 video frame of the emulator and 
IOS_EXPORT void iosRunFrame(CAMetalLayer* layer) {
	void* layerBridged = (__bridge void*)layer;

    // Pass the CAMetalLayer to the emulator
	emulator->getRenderer()->setMTKLayer(layerBridged);
    // Runs the emulator for 1 frame and renders the output image using our layer
	emulator->runFrame();
}

My MTKView delegate:

class Renderer: NSObject, MTKViewDelegate {
    var parent: ContentView
    var device: MTLDevice!
    
    init(_ parent: ContentView) {
        self.parent = parent
        if let device = MTLCreateSystemDefaultDevice() {
            self.device = device
        }
        
        super.init()
    }
    
    func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {}
    
    func draw(in view: MTKView) {
        var metalLayer = view.layer as! CAMetalLayer
        // Run the emulator for 1 frame & display the output image
        iosRunFrame(metalLayer)
    }
}

Finally, the emulator's render function that interacts with the layer:

void RendererMTL::setMTKLayer(void* layer) {
	metalLayer = (CA::MetalLayer*)layer;
}

void RendererMTL::display() {
	CA::MetalDrawable* drawable = metalLayer->nextDrawable();
	if (!drawable) {
		return;
	}

	MTL::Texture* texture = drawable->texture();
   <rest of rendering follows here using the drawable & its texture>
}

This is the Swift callstack at the time of the crash:

To my understanding, I shouldn't be violating ARC rules as my bridging header uses CAMetalLayer* instead of void* and Swift will automatically account for ARC when passing CoreFoundation objects to Objective-C. However I don't have any other idea as to what might be causing this. I've been trying to debug this code for a couple of days without much success.

If you need more info, the emulator code is also on Github

Metal renderer: https://github.com/wheremyfoodat/Panda3DS/blob/ios/src/core/renderer_mtl/renderer_mtl.cpp#L58-L68

Bridge implementation: https://github.com/wheremyfoodat/Panda3DS/blob/ios/src/ios_driver.mm

Bridging header: https://github.com/wheremyfoodat/Panda3DS/blob/ios/include/ios_driver.h

Any help is more than appreciated. Thank you for your time in advance.

How to properly pass a Metal layer from SwiftUI MTKView to C&#43;&#43; for use with metal-cpp?
 
 
Q