Metal / IOSurface Backend

The macOS backend uses Metal for GPU rendering and IOSurface for cross-process texture sharing.

Overview

Textures are backed by IOSurface objects, which are kernel-managed shared memory buffers that can be used by both Metal and OpenGL. The sender creates IOSurface-backed Metal textures in a ring buffer. The receiver looks up the IOSurface by ID and creates its own Metal texture view.

How It Works

Sender Process                              Receiver Process
─────────────────                          ─────────────────
Metal Device                               Metal Device
    │                                           │
    ├── Create IOSurface                         │
    ├── Wrap as MTLTexture                       │
    ├── Render into texture                      │
    ├── Publish IOSurface ID                     │
    │      via shared state                      │
    │                                           ├── Read IOSurface ID
    │                                           ├── Create MTLTexture view
    │                                           ├── Read texture
    │                                           │
    └── Ring buffer (N surfaces)                 └── Frame acquisition

Thread Safety

IOSurface locking (IOSurfaceLock/IOSurfaceUnlock) provides synchronization between processes. The sender locks before writing, unlocks after. The receiver locks before reading.

Zero-Copy

Texture data is never copied between processes. Both sides read/write the same IOSurface-backed memory. Transfer mode is zero_copy_shared_texture.

Backend API

#include <nozzle/backends/metal.hpp>

namespace nozzle::metal {

Result<device> wrap_device(const device_desc &desc);
Result<texture> wrap_texture(const texture_wrap_desc &desc);
mtl_texture_handle get_texture(const texture &tex);
surface_handle get_io_surface(const texture &tex);

}

Types

using mtl_device_handle = void *;       // id<MTLDevice>
using mtl_texture_handle = void *;      // id<MTLTexture>
using surface_handle = void *;          // IOSurfaceRef
using pixel_format_value = uint32_t;    // MTLPixelFormat
using surface_id = uint32_t;            // IOSurfaceID

device_desc

struct device_desc {
    mtl_device_handle device{nullptr};
};

texture_wrap_desc

struct texture_wrap_desc {
    mtl_texture_handle texture{nullptr};
    surface_handle io_surface{nullptr};
    pixel_format_value format{0};
    uint32_t width{0};
    uint32_t height{0};
};

wrap_device

Wrap an existing Metal device for use with nozzle.

Result<device> wrap_device(const device_desc &desc);
auto dev = nozzle::metal::wrap_device({
    .device = mtl_device  // id<MTLDevice>
}).value();

wrap_texture

Wrap an existing IOSurface-backed Metal texture.

Result<texture> wrap_texture(const texture_wrap_desc &desc);

get_texture / get_io_surface

Extract native handles from a nozzle texture.

mtl_texture_handle get_texture(const texture &tex);
surface_handle get_io_surface(const texture &tex);

OpenGL Interop

macOS GL interop uses CGLTexImageIOSurface2D for GPU-side blit between GL textures and IOSurface:

#include <nozzle/backends/opengl.hpp>

nozzle::gl::gl_texture_desc gl_desc{
    .name = gl_texture_name,
    .target = GL_TEXTURE_2D,
    .width = 1920,
    .height = 1080,
    .format = nozzle::texture_format::bgra8_unorm
};

// Publish GL texture to nozzle sender
nozzle::gl::publish_gl_texture(sender, gl_desc);

// Copy received frame to GL texture
auto frame = receiver.acquire_frame({.timeout_ms = 1000}).value();
nozzle::gl::copy_frame_to_gl_texture(frame, gl_desc);

Format Mapping

Nozzle Format MTLPixelFormat
r8_unorm MTLPixelFormatR8Unorm
rg8_unorm MTLPixelFormatRG8Unorm
rgba8_unorm MTLPixelFormatRGBA8Unorm
bgra8_unorm MTLPixelFormatBGRA8Unorm
rgba8_srgb MTLPixelFormatRGBA8Unorm_sRGB
r16_float MTLPixelFormatR16Float
rgba16_float MTLPixelFormatRGBA16Float
r32_float MTLPixelFormatR32Float
rgba32_float MTLPixelFormatRGBA32Float
depth32_float MTLPixelFormatDepth32Float

Requirements

  • macOS 12+
  • Metal framework
  • IOSurface framework
  • CoreFoundation framework