Raster

Framework / How Raster works

Filters & Kernels

Learn how a mutable filter and a shared GPU program produce an immutable output image.

A filter holds images and parameters and exposes an output image. The filter object is small and mutable. A kernel is the GPU program reused across every filter instance that needs it. Those roles stay separate so you can change a filter's parameters without recompiling shaders or rebuilding pipeline state every time you create another filter instance.

Filters

MTIFilter has an outputPixelFormat and a nullable outputImage, and MTIUnaryFilter adds one inputImage. Concrete filters expose the parameters their operation needs:

let filter = MTIVibranceFilter()
filter.inputImage = input
filter.amount = 0.45
filter.outputPixelFormat = .rgba16Float
 
guard let output = filter.outputImage else {
    throw PipelineError.imageUnavailable
}

outputImage is usually nil when a required input is missing or the requested dimensions are invalid. Reading it snapshots the current configuration into a recipe. Changing the filter afterward does not change the image you already took.

Filters are not thread-safe. Their inputs and parameters mutate. A filter belongs on a single actor, queue, or lock-protected path. The output MTIImage is immutable, so you can hand it across that boundary.

Kernels

Raster ships several kernel families:

KernelUse it for
MTIRenderPipelineKernelVertex/fragment work, blending, multiple render targets, and draw geometry.
MTIComputePipelineKernelGeneral compute grids and writable output textures.
MTIMPSKernelOperations implemented with Metal Performance Shaders.
MTICoreImageKernelA Core Image operation inside a Raster graph.

A kernel holds function descriptors and pipeline configuration, while apply calls pass input images, parameter values, and output descriptors. Filters typically store that kernel on the class in static storage so every instance shares the same program description.

Function descriptors

MTIFunctionDescriptor names a Metal function, optional function constants, and the library that contains it. Built-in filters use Raster's default library. A custom filter points at the bundle that has its compiled Metal library. Bundle.main is often some other app and has nothing to do with the shader source:

let fragment = MTIFunctionDescriptor(
    name: "vignette",
    bundle: Bundle(for: ShaderBundleToken.self)
)

The right bundle matters in test hosts, extensions, dynamic frameworks, and Swift packages.

Output descriptors

Render and compute kernels need texture dimensions and a pixel format. Render-pass output descriptors also set load and store actions, which is how you get multiple draw calls in one pass. Alpha lives on the image. Texture descriptors leave it alone.

See Custom Filters for an end-to-end downstream implementation and Pipelines & Argument Encoding before you change kernel internals.