Sample CodeiOS, iPadOS, Mac Catalyst, macOS, tvOSReviewed 2026-07-21View on Apple Developer

Achieving smooth frame rates with a Metal display link

At a glance

Item Summary
Purpose Pace rendering with minimal input latency while providing essential information to the operating system for power-efficient rendering, thermal mitigation, and the scheduling of sustainable workloads.
App architecture Objective-C, Objective-C++ host code with Metal shaders; GameViewController hands work to GameView, which owns display-link pacing before per-frame Metal submission.
Main patterns Delegate-driven frame loop, Platform adapter boundary, Bounded frames in flight
Scope High-level review of 23 scanned source files and 26 detected declarations; build assets are omitted.

Project structure

Source bundle/
├── Shared/
│   ├── GameViewController.mm
│   ├── AssetLoader.mm
│   ├── Shaders.metal
│   ├── Renderer.mm
│   ├── WindowSceneDelegate.m
│   └── ShaderTypes.h
├── iOS/
│   └── main.m
├── macOS/
│   └── main.m
└── tvOS/
    └── main.m

Structure observations

  • The entry/composition boundary and renderer or operation boundary are separate in the source; GameView is the principal feature coordinator.
  • GPU-specific logic stays in Metal shader files; shared headers bridge host/shader layouts where present.
  • The tree above is intentionally pruned to composition, resource, and shader files.

Overall architecture

Reference code

Shared/GameView.m:165 — display-link creation, pacing policy, and callback ownership

    _displayLink = [[CAMetalDisplayLink alloc] initWithMetalLayer:metalLayer];
    _displayLink.preferredFrameRateRange = CAFrameRateRangeMake(120.0, 120.0, 120.0);
    _displayLink.preferredFrameLatency = 2;
    _displayLink.paused = NO;
    // Assign the delegate to receive the display update callback.
    _displayLink.delegate = self;

Interpretation

This is the dominant control/data path: platform code composes GameView; that object controls display-link pacing; GPU-visible work ends in per-frame Metal submission. The arrows summarize responsibility transfer, not a claim that every node directly calls the next.

Ownership and state

Ownership evidence

Shared/GameView.m:165GameView creates, configures, and stores _displayLink

    _displayLink = [[CAMetalDisplayLink alloc] initWithMetalLayer:metalLayer];
    _displayLink.preferredFrameRateRange = CAFrameRateRangeMake(120.0, 120.0, 120.0);
    _displayLink.preferredFrameLatency = 2;
    _displayLink.paused = NO;
    // Assign the delegate to receive the display update callback.
    _displayLink.delegate = self;
Owner Object or state Relationship Mutation authority
GameView _displayLink / CAMetalDisplayLink Creates, configures, and stores the display-link object; the source-visible construction supports composition. GameView controls pacing, pause state, delegate assignment, and teardown.
GameView Feature-specific framework and Metal resources Operation-local calls or stored state; exclusive lifetime is not assumed beyond cited evidence. Feature setup/encoding code controls mutation and command submission.

Class and protocol design

Type Responsibility Depends on or conforms to Source
GameViewController selects/configures the view and composes the feature objects. PlatformViewController, GameViewDelegate Shared/GameViewController.h:20
Renderer owns pipeline/resource setup and per-frame command encoding. NSObject Shared/Renderer.h:14
GameView owns presentation timing/drawable behavior and forwards callbacks. PlatformView, CALayerDelegate, CAMetalDisplayLinkDelegate Shared/GameView.h:33
AppDelegate handles application/window lifecycle callbacks. UIResponder, UIApplicationDelegate, NSObject Shared/AppDelegate.h:15
GameViewDelegate handles application/window lifecycle callbacks. NSObject Shared/GameView.h:22
WindowSceneDelegate handles application/window lifecycle callbacks. UIResponder, UIWindowSceneDelegate Shared/WindowSceneDelegate.h:15

A local protocol/conformance boundary is present, so protocol-oriented collaboration is claimed only for that seam.

Access control

Symbol Access Verified effect Design reason
AppDelegate header-visible available to translation units that import the header; this is not Swift public. Keep the usable surface no wider than the collaboration requires. (Shared/AppDelegate.h:15)
Renderer implementation details implementation-only native helpers, stored state, or registration code stays out of the imported header contract. Hide native implementation details from importing translation units. (Shared/Renderer.mm:1)
Shaders entry points Metal library boundary host code resolves named shader entry points; Swift access modifiers do not apply. Expose only named shader entry points needed by pipeline creation. (Shared/Shaders.metal:22)

This sample does not use Swift private, fileprivate, or public for its native boundary. Objective-C/Objective-C++ use header versus implementation placement, C++ uses access specifiers/linkage, Python uses module conventions, and Metal entry points cross a compiled-library boundary; these are not Swift access levels.

Logic ownership and placement

Logic Owning type or file Why it lives there
Selects/configures the view and composes the feature objects GameViewControllerShared/GameViewController.h:20 The type’s callbacks and stored state align with this responsibility.
Owns pipeline/resource setup and per-frame command encoding RendererShared/Renderer.h:14 The type’s callbacks and stored state align with this responsibility.
Owns presentation timing/drawable behavior and forwards callbacks GameViewShared/GameView.h:33 The type’s callbacks and stored state align with this responsibility.
Handles application/window lifecycle callbacks AppDelegateShared/AppDelegate.h:15 The type’s callbacks and stored state align with this responsibility.
Display-link pacing GameView / Shared/GameView.m:165 Keeps display timing policy beside the view that owns the CAMetalLayer.
Per-frame Metal submission Shared/Shaders.metal:22 GPU-parallel code remains in the Metal compilation boundary.

Shader boundary reference

Shared/Shaders.metal:22 — representative GPU entry/helper

[[ vertex ]]

Design patterns

Pattern Source evidence Purpose or tradeoff
Delegate-driven frame loop Shared/GameView.h:9 A view/display callback triggers updates and command encoding; timing is inverted into the renderer.
Platform adapter boundary Shared/GameView.m:165 Encapsulates CAMetalDisplayLink configuration and delegate delivery behind the cross-platform view.
Bounded frames in flight Shared/Renderer.mm:31 A semaphore/ring limits CPU writes from overtaking GPU reads.

Naming conventions

  • Role suffixes make ownership visible: GameViewController, Renderer, GameView, AppDelegate, GameViewDelegate, WindowSceneDelegate.
  • Method names describe setup or encoding actions: application, applicationShouldTerminateAfterLastWindowClosed, drawableResize, renderTo, initCommon, resizeDrawable, stopRenderLoop.
  • Shader entry points use stage/operation names: vertexShader, fragmentShader.
  • Names favor concrete domain roles and target-local types; no broad public library namespace is introduced.

Architecture takeaways

  • Keep GameViewController focused on composition; GameView is the owner of display-link pacing.
  • Treat per-frame Metal submission as a separate execution/compilation boundary with explicit resource and data-layout contracts.
  • The verified ownership edge is AssetLoader_textureLoader; broader exclusive ownership is not inferred.
  • Access-control rationale follows concrete language boundaries rather than translating every header or shader symbol into Swift terms.

Source map

Source file Architectural role
Shared/GameViewController.mm GameViewController
Shared/AssetLoader.mm AssetLoader
Shared/Shaders.metal ColorInOut
iOS/main.m entry point or feature implementation
Shared/Renderer.mm Renderer
Shared/WindowSceneDelegate.m WindowSceneDelegate
Shared/ShaderTypes.h FrameData, ModelConstantsData, VertexPosition, VertexGenerics, MeshConstantData
macOS/main.m entry point or feature implementation
tvOS/main.m entry point or feature implementation