Architecture
Nozzle is organized into four layers, from platform infrastructure up to integration wrappers.
Layer Diagram
Layer 4: Integration Wrappers
py.nozzle, nozzle.rs, nozzle.swift, ofxNozzle,
jit.nozzle, nozzle-TOP, obs-nozzle, blender-nozzle,
nozzle-sokol, tcxNozzle
Layer 3: OpenGL Interop
Copy-based GL↔backend texture transfer
macOS: GL → IOSurface (GPU blit)
Windows: GL → staging → D3D11 (CPU copy)
Linux: GL → DMA-BUF mmap (CPU copy)
Layer 2: Backend-Native API
Metal/IOSurface (macOS)
D3D11 shared textures (Windows)
DMA-BUF/GBM/EGL (Linux)
Layer 1: Common API
sender, receiver, frame, texture, device, discovery
Result<T> error handling, thread-safe objects
Layer 0: Platform Infrastructure
IPC (POSIX shm / Windows CreateFileMapping)
Registry (named sender/receiver lookup)
Ring buffer synchronization (atomic counters)
Shared memory layout
Core Concepts
Named Sender/Receiver Model
Senders and receivers are identified by name. A receiver connects to a sender by matching its name. Discovery (enumerate_senders()) lists all active senders on the local machine.
Ring Buffer
Each sender maintains a ring buffer of shared textures (configurable size, default 3). This decouples sender and receiver frame rates. The sender writes to the next available slot; the receiver reads the latest completed slot.
Shared State
Sender and receiver communicate through platform-specific shared memory:
- macOS/Windows: POSIX
shm_open/ WindowsCreateFileMappingA - Linux: POSIX
shm_open
The shared state contains ring buffer metadata: frame indices, timestamps, sender info, and format descriptors.
Synchronization
| Platform | Mechanism |
|---|---|
| macOS | IOSurface lock (IOSurfaceLock/IOSurfaceUnlock) |
| Windows | Keyed mutex (IDXGIKeyedMutex) |
| Linux | Atomic counters in shared memory |
Crash Cleanup
Nozzle uses lazy detection. If a sender crashes, the receiver detects the dead sender on the next access failure (timeout or invalid shared state). No explicit cleanup protocol is needed.
Transfer Modes
| Mode | Description |
|---|---|
zero_copy_shared_texture |
GPU memory shared directly between processes |
gpu_copy |
GPU-side blit between incompatible surfaces |
cpu_copy |
Readback to CPU, upload to GPU (slowest) |
Backend-native sharing uses zero-copy. OpenGL interop on Windows uses CPU copy. GL interop on macOS uses GPU copy via IOSurface.
Error Handling
Result<T> pattern with explicit error checking:
auto result = nozzle::sender::create(desc);
if (!result.ok()) {
// result.error().code -> ErrorCode
// result.error().message -> std::string
}
auto sender = std::move(result).value();
No exceptions, no std::optional error info. Every fallible operation returns Result<T>.
Thread Safety
Sender and receiver objects are individually thread-safe. The same sender can be called from multiple threads simultaneously. The same receiver can poll for frames from multiple threads.
Repository Layout
include/nozzle/ Public C++ headers
include/nozzle/backends/ Backend-specific headers
metal.hpp Metal/IOSurface
d3d11.hpp D3D11
opengl.hpp GL interop
linux.hpp DMA-BUF
src/common/ Shared implementation
src/c_api/ C ABI wrapper
src/backends/
metal/ .mm files (ObjC++)
d3d11/ .cpp files
opengl/ .cpp files
linux/ .cpp files
libs/plog/ Header-only logging (submodule)
tests/ Unit + integration tests
examples/ Minimal examples