MetalPetal Documentation

repository·master·Indexed 24 days ago

https://github.com/metalpetal/metalpetal

A high-performance image processing framework built on Metal for real-time processing of still images and video on iOS, tvOS, and macOS. It features a render graph optimization system and core abstractions including MTIContext, MTIImage, MTIFilter, and MTIKernel. The framework provides built-in filters for color adjustments, blends, transformations, and blurs, and supports Core Image compatibility. It includes a type-safe Swift API for filter chains and integration with AVPlayer and VideoIO for video processing and export.

Tokens
5.6K
Snippets
12
Records
23
Agent score
30%

What's inside MetalPetal

  1. Core Concepts of MetalPetal

    master

    MetalPetal is built around four primary abstractions that work together to perform image processing:

    • MTIContext: The evaluation context used for rendering MTIImages. It manages caches and state information. For optimal performance, you should reuse a single MTIContext instance whenever possible rather than creating new ones for every operation.
    • MTIImage: A representation of an image (or a recipe to produce one). It does not hold raw bitmap data directly; instead, it contains an MTIImagePromise (the recipe) and metadata like cachePolicy and samplerDescriptor. It is immutable and thread-safe.
    • MTIFilter: Represents an image processing effect with controllable parameters. A filter takes input MTIImages and produces an output MTIImage. Note that MTIFilter objects are mutable and are not thread-safe.
    • MTIKernel: The underlying image processing routine. It is responsible for creating the Metal pipeline states (render or compute) and building the MTIImagePromise for the resulting MTIImage.
  2. Optimize MetalPetal Render Graphs

    master

    MetalPetal can automatically optimize your image processing pipeline to save memory, energy, and time. It analyzes the render graph to find the minimal number of intermediate textures required and can concatenate multiple "recipes" to eliminate redundant render passes.

    To enable or disable this optimization, use the isRenderGraphOptimizationEnabled property on your MTIContext instance.

  3. Concurrency and Thread Safety in MetalPetal

    master

    When using MetalPetal in multi-threaded environments, follow these safety rules:

    • MTIImage: Safe to share among threads because they are immutable.
    • MTIContext: Safe to share among threads because it uses a built-in thread-safe mechanism for its internal states and caches.
    • MTIFilter: NOT safe to share among threads. Because filters are mutable (you change their parameters or inputs), you must ensure a filter is only accessed by one thread at a time or create unique filter instances per thread.
  4. Connect filters using the FilterGraph Swift API

    master

    MetalPetal provides a type-safe Swift API for building filter chains using the => operator within a FilterGraph.makeImage block.

    • Unary Filters: MTIUnaryFilter instances can be connected directly using =>.
    • Multi-input Filters: For filters with multiple inputs, you must connect to a specific inputPort (e.g., filter.inputPorts.inputImage).
    • Constraints: The => operator is only valid inside FilterGraph.makeImage. Exactly one filter's output must be connected to the output parameter.
    // Simple chain
    let image = try? FilterGraph.makeImage { output in
        inputImage => saturationFilter => exposureFilter => output
    }
    
    // Complex chain with multiple inputs
    let image = try? FilterGraph.makeImage { output in
        inputImage => saturationFilter => exposureFilter => contrastFilter => blendFilter.inputPorts.inputImage
        exposureFilter => blendFilter.inputPorts.inputBackgroundImage
        blendFilter => output
    }
  5. Best practices for MTIContext and MTIImage

    master

    MTIContext

    MTIContext is a heavyweight object. Reuse a MTIContext whenever possible. Create it early and use it for all subsequent rendering tasks.

    MTIImage Cache Policy

    Use MTIImage.cachePolicy to manage memory efficiency:

    • MTIImageCachePolicyTransient: Use this for intermediate results in a filter chain. It allows the underlying texture to be reused, making it the most memory-efficient option. Note that requesting the output of a previously rendered image might trigger a re-render.
    • MTIImageCachePolicyPersistent: Use this when you want to prevent the underlying texture from being reused (e.g., for images created from external sources).

    MTIFilter.outputImage

    MTIFilter.outputImage is a compute property. Accessing it may return a new object even if inputs haven't changed. To optimize, reuse output images when multiple filters share the same input:

    // BETTER: Reuse the output image
    let filterOutputImage = filterA.outputImage
    filterB.inputImage = filterOutputImage
    filterC.inputImage = filterOutputImage
    
    // AVOID: This may create multiple redundant objects
    filterB.inputImage = filterA.outputImage
    filterC.inputImage = filterA.outputImage
  6. Integrate MetalPetal with SceneKit, SpriteKit, and Core Image

    master

    MetalPetal provides extensions for common Apple frameworks:

    • SceneKit: Use MTISCNSceneRenderer to generate MTIImages from an SCNScene.
    • SpriteKit: Use MTISKSceneRenderer to generate MTIImages from an SKScene.
    • Core Image:
      • Create MTIImages from CIImages.
      • Render an MTIImage to a CIImage using an MTIContext.
      • Use a CIFilter directly with MTICoreImageKernel or MTICoreImageUnaryFilter (Swift only).
  7. Export video with MetalPetal and VideoIO

    master

    To export a video with filters applied, use AssetExportSession from the VideoIO library. You must provide a configuration that includes the videoComposition generated by MTIVideoComposition.makeAVVideoComposition().

    import VideoIO
    
    var configuration = AssetExportSession.Configuration(fileType: .mp4, videoSettings: .h264(videoSize: composition.renderSize), audioSettings: .aac(channels: 2, sampleRate: 44100, bitRate: 128 * 1000))
    configuration.videoComposition = composition.makeAVVideoComposition()
    self.exporter = try! AssetExportSession(asset: asset, outputURL: outputURL, configuration: configuration)
    exporter.export(progress: { progress in
        
    }, completion: { error in
        
    })
  8. Handle color spaces for output images

    master

    When specifying an output color space, MetalPetal treats it as a tag to communicate how to represent color values to the rest of the system. No actual color space conversion is performed during output.

    • Output CGImage: Specify the colorSpace parameter in MTIContext.makeCGImage... or MTIContext.startTaskTo... methods.
    • Output CIImage: Use MTICIImageCreationOptions.
    • Default: If no output color space is specified, MetalPetal assumes output values are in the device RGB color space.
  9. Handle color spaces for input images

    master

    Metal textures do not store color space information. MetalPetal handles color space conversion during input. When loading images, you can specify a target color space to convert the source values during texture creation.

    • From URL or CGImage: Use MTICGImageLoadingOptions.
      • MTICGImageLoadingOptions.default: Uses the device RGB color space.
      • nil: Disables color matching (uses the input image's color space).
      • If the specified color space is not RGB, it falls back to device RGB.
    • From CIImage: Use MTICIImageRenderingOptions.
      • MTICIImageRenderingOptions.default: Uses the device RGB color space.
      • nil: Disables color matching; values are loaded in the CIContext working color space.
  10. Process video files with AVPlayer and MTIVideoComposition

    master

    To apply filters to an AVPlayer stream, use MTIVideoComposition. You provide a closure to MTIVideoComposition where you define the filter graph using FilterGraph.makeImage. The request.anySourceImage provides the frame from the asset.

    let context = try MTIContext(device: device)
    let asset = AVAsset(url: videoURL)
    let composition = MTIVideoComposition(asset: asset, context: context, queue: DispatchQueue.main, filter: { request in
        return FilterGraph.makeImage { output in
            request.anySourceImage! => filterA => filterB => output
        }!
    }
    
    let playerItem = AVPlayerItem(asset: asset)
    playerItem.videoComposition = composition.makeAVVideoComposition()
    player.replaceCurrentItem(with: playerItem)
    player.play()
  11. Build Fully Custom Filters

    master

    For complex filters involving multiple inputs or specific logic, create a kernel (MTIRenderPipelineKernel, MTIComputePipelineKernel, or MTIMPSKernel) and apply it to your input images.

    In Objective-C, you typically implement the <MTIFilter> protocol and provide a kernel class method that returns a pre-initialized kernel instance. The outputImage method then calls applyToInputImages:parameters:outputTextureDimensions:outputPixelFormat: on that kernel.

    ```objc
    @interface MTIChromaKeyBlendFilter : NSObject <MTIFilter>
    @property (nonatomic, strong, nullable) MTIImage *inputImage;
    @property (nonatomic, strong, nullable) MTIImage *inputBackgroundImage;
    @property (nonatomic) float thresholdSensitivity;
    @property (nonatomic) float smoothing;
    @property (nonatomic) MTIColor color;
    @end
    
    @implementation MTIChromaKeyBlendFilter
    
    @synthesize outputPixelFormat = _outputPixelFormat;
    
    + (MTIRenderPipelineKernel *)kernel {
        static MTIRenderPipelineKernel *kernel;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            kernel = [[MTIRenderPipelineKernel alloc] initWithVertexFunctionDescriptor:[[MTIFunctionDescriptor alloc] initWithName:MTIFilterPassthroughVertexFunctionName] fragmentFunctionDescriptor:[[MTIFunctionDescriptor alloc] initWithName:@
  12. Install MetalPetal via CocoaPods

    master

    Add the following to your Podfile. It is highly recommended to include the Swift and AppleSilicon sub-pods.

    • MetalPetal/Swift: Provides improved Objective-C to Swift mappings.
    • MetalPetal/AppleSilicon: Required for programmable blending support on Apple silicon Macs (includes default shader library).
    use_frameworks!
    
    pod 'MetalPetal'
    
    # Required if you are using Swift.
    pod 'MetalPetal/Swift'
    
    # Recommended if you'd like to run MetalPetal on Apple silicon Macs.
    pod 'MetalPetal/AppleSilicon'