MetalVideoRenderer

The GPU renderer: one Metal core for macOS and iOS, drawing into a caller-owned CAMetalLayer.

The division of labour is exactly the CG renderers': the engine hands frames to present, which stores the newest in a single slot and returns at once; a dedicated render thread takes the slot, resolves the frame through the caller's MetalPictureResolver (a CVPixelBuffer with no copy, or planes with one memcpy each and no CPU colour conversion), and encodes through MetalFrameComposer into the layer's next drawable. Newest wins; the displaced frame is closed and counted, which at 4K is the difference between a smooth picture and a memory storm.

Presentation is snapped by the layer itself: presentDrawable on a CAMetalLayer with display sync enabled lands on the display's own refresh, so vsyncIntervalNanos honestly answers null and the engine's clock keeps pacing DECODE while Metal paces the glass.

HDR. Under HdrPolicy.Auto, on a display that can go beyond standard range white, an HDR frame is drawn into a half-float, extended-range layer as linear light, tone mapped only down to the display's current headroom, and the renderer reports RendererEvent.HdrShown. Anywhere else HDR is tone mapped to standard range, as before, and the renderer reports RendererEvent.ToneMapEngaged. The layer goes back to its standard format for SDR frames.

Constructors

Link copied to clipboard
constructor(layer: CAMetalLayer, resolver: MetalPictureResolver)

Draws into layer, whose display the renderer reads for its HDR headroom.

Properties

Link copied to clipboard
open override val events: Flow<RendererEvent>
Link copied to clipboard

Frames that reached no drawable: resolver refusal, encode failure, or a closed renderer.

Link copied to clipboard
open override val outputSize: VideoSize?

The same two numbers the encode path already resolves: the reported viewport when a host gave one, and the layer's own drawable size otherwise. Both are physical pixels, which is what the engine needs to rasterise subtitles at 1:1 instead of stretching them.

Link copied to clipboard

Frames whose picture reached the layer.

Link copied to clipboard
Link copied to clipboard
open val showsHdr: Boolean
Link copied to clipboard

Frames replaced by a newer one before the render thread could draw them.

Functions

Link copied to clipboard
open fun accepts(shape: FrameShape): Boolean
Link copied to clipboard
open override fun clearPicture()

Lets go of a frame waiting to draw, counted superseded, and has the render thread give back the retained picture and draw the background with the cues over it.

Link copied to clipboard
open override fun close()
Link copied to clipboard
open suspend override fun present(frame: VideoFrame, targetNanos: Long): Boolean
Link copied to clipboard
open override fun setAdjustments(adjustments: VideoAdjustments)
Link copied to clipboard
open override fun setHdrPolicy(policy: HdrPolicy)

A paused HDR picture re-encodes at once, in the range the new policy asks for.

Link copied to clipboard
open suspend override fun setOverlay(overlay: SubtitleOverlay?)
Link copied to clipboard
open override fun setRenderQuality(quality: RenderQuality)

The render-quality passes. The target is BGRA8Unorm, so eight bits is what the dither spreads a value across; a paused picture re-encodes so a change is visible at once.

Link copied to clipboard
open override fun setScaleMode(mode: VideoScale)
Link copied to clipboard
open override fun setTransform(transform: VideoTransform)
Link copied to clipboard
open override fun setViewport(width: Int, height: Int, scale: Float)
Link copied to clipboard

The zero-copy claim of this renderer, and the reason the download twin goes unused here.

Link copied to clipboard
open override fun supports(format: PlayerPixelFormat): Boolean

Opaque is drawable exactly when the frame carries a surface this renderer wraps.

Link copied to clipboard
Link copied to clipboard
open override fun vsyncIntervalNanos(): Long?