This specification is maintained by the AgentRules Working Group. Have enhancements or want to claim maintainership?Submit Specification →
mobilecritical PriorityID: cmp21-swapchain-metal-surfaceview-sync-guardrail

Compose Multiplatform 2.1 Metal-SurfaceView Swapchain Synchronization

Enforces non-blocking graphics buffer destruction and synchronized vsync pacing across Skiko rendering pipelines on iOS CAMetalLayer and Android SurfaceView. Prevents GPU driver starvation, triple-buffering exhaustion, and UI-thread deadlocks during cross-platform navigation transitions.

Prohibited Anti-Pattern: Synchronous swapchain teardown on UI dispatcher causing GPU presentation deadlocks and buffer starvation during navigation

Export Ready-to-Drop Configuration

Select format below to copy code or download file directly.

---
description: Compose Multiplatform 2.1 Metal-SurfaceView Swapchain Synchronization - Enforces non-blocking graphics buffer destruction and synchronized vsync pacing across Skiko rendering pipelines on iOS CAMetalLayer and Android SurfaceView. Prevents GPU driver starvation, triple-buffering exhaustion, and UI-thread deadlocks during cross-platform navigation transitions.
globs: **/iosMain/**/*.kt, **/androidMain/**/*.kt, **/commonMain/**/*PlatformView*.kt, **/ui/**/*Scene*.kt
alwaysApply: true
verified_by: "agentrules.org"
license: "CC-BY-4.0"
source: "https://agentrules.org/rules/cmp21-swapchain-metal-surfaceview-sync-guardrail"
---

# Compose Multiplatform 2.1 Metal-SurfaceView Swapchain Synchronization

> **Severity**: CRITICAL | **Category**: mobile
> **Anti-Pattern Warning**: Synchronous swapchain teardown on UI dispatcher causing GPU presentation deadlocks and buffer starvation during navigation

## System Constraints
You are a Principal Mobile Graphics and Systems Architect enforcing strict swapchain synchronization rules for Compose Multiplatform 2.1 (CMP) applications targeting iOS (CAMetalLayer/Skiko Metal) and Android (SurfaceView/TextureView). You must guarantee zero UI thread deadlocks between native display callbacks (CADisplayLink, Choreographer) and the underlying Skia/Skiko render loop during composable disposal, navigation transitions, and buffer destruction.

## Strict Guidelines
- Engineers MUST ALWAYS decouple native buffer destruction from the UI thread by awaiting in-flight Skiko frame completion signals before releasing underlying hardware buffers (CAMetalDrawable, Surface).
- Engineers MUST NOT invoke blocking thread synchronization primitives (e.g., runBlocking, dispatch_sync, CountDownLatch.await) inside Composable DisposableEffect, SurfaceHolder.Callback, or platform view cleanup hooks.
- Code SHOULD prefer TextureView over SurfaceView on Android only when hardware-accelerated cross-fade navigation transitions strictly require layer alpha blending, otherwise enforce SurfaceHolder.Callback2 with surfaceRedrawNeeded for zero-copy presentation.
- Services MUST pause CADisplayLink and Choreographer frame pacing callbacks prior to disposing ComposeScene layers to prevent presenting into recycled or orphaned CAMetalLayer/ANativeWindow instances.
- Components NEVER hold strong references to CAMetalDrawable or hardware-backed android.view.Surface instances past the active render cycle boundary.

## Bad vs Good Implementations
### Android SurfaceHolder and Skiko Tear-Down Deadlock Fix

#### Bad Antipattern (AI Common Hallucination)
> **Why it fails**: Executing blocking operations or immediate surface releases directly inside onDispose locks the UI thread while the Skiko/RenderThread pipeline is waiting for swapchain vsync acquisition or buffer presentation, triggering a mutual exclusion deadlock (ANR).
```kotlin
// ANTI-PATTERN: Synchronously releasing the surface on UI thread while Skiko render loop is in-flight
@Composable
fun InteropNativeSurface(modifier: Modifier = Modifier) {
    val context = LocalContext.current
    DisposableEffect(Unit) {
        val surfaceView = SurfaceView(context)
        val holder = surfaceView.holder
        
        onDispose {
            // Triggers fatal ANR: Blocks UI thread waiting for render thread,
            // while Skiko render thread waits for UI thread choreographer lock.
            runBlocking(Dispatchers.Main) {
                holder.surface.release()
            }
        }
    }
}
```

#### Production Standard (Correct)
> **Standard**: Vsync pacing is cleanly suspended, allowing the current in-flight frame to retire before the underlying hardware surface buffer is destroyed asynchronously without blocking the UI thread.
```kotlin
// PRODUCTION STANDARD: Asynchronous swapchain drain with deferred surface retirement
@Composable
fun InteropNativeSurface(modifier: Modifier = Modifier) {
    val context = LocalContext.current
    val coroutineScope = rememberCoroutineScope()
    
    DisposableEffect(Unit) {
        val surfaceView = SurfaceView(context)
        val renderCoordinator = SwapchainRenderCoordinator.acquire()
        
        onDispose {
            coroutineScope.launch(Dispatchers.Default) {
                // 1. Drain pending in-flight render passes
                renderCoordinator.pauseVsyncPacing()
                renderCoordinator.awaitFrameCompletion(timeoutMs = 150L)
                
                // 2. Safe release on non-UI graphics queue
                withContext(Dispatchers.Main.immediate) {
                    if (surfaceView.holder.surface.isValid) {
                        surfaceView.holder.surface.release()
                    }
                    renderCoordinator.dispose()
                }
            }
        }
    }
}
```

### iOS Metal Pipeline CAMetalLayer Presentation and Resize Synchronization

#### Bad Antipattern (AI Common Hallucination)
> **Why it fails**: Calling waitUntilCompleted() on the main thread pauses the main run-loop while waiting for the GPU to finish. This stalls CAMetalLayer display synchronization and leads to frozen UI transitions during CMP navigation.
```kotlin
// ANTI-PATTERN: Mutating CAMetalLayer geometry without command buffer synchronization
fun resizeMetalLayer(metalLayer: CAMetalLayer, targetBounds: CValue<CGRect>) {
    // Immediate bounds change starves Metal drawable pool during transition
    metalLayer.frame = targetBounds
    val drawable = metalLayer.nextDrawable() ?: return
    val commandQueue = metalLayer.device?.newCommandQueue() ?: return
    val commandBuffer = commandQueue.commandBuffer() ?: return
    
    // Blocking wait on main thread locks CADisplayLink
    commandBuffer.presentDrawable(drawable)
    commandBuffer.commit()
    commandBuffer.waitUntilCompleted() // FATAL: Deadlocks compositor pipeline
}
```

#### Production Standard (Correct)
> **Standard**: Utilizes CATransaction for atomic boundary adaptation and registers an asynchronous addCompletedHandler on the MTLCommandBuffer, ensuring the UI run loop and CADisplayLink continue ticking uninterrupted.
```kotlin
// PRODUCTION STANDARD: Non-blocking Metal drawable presentation with scheduled handler
fun resizeMetalLayer(metalLayer: CAMetalLayer, targetBounds: CValue<CGRect>) {
    CATransaction.begin()
    CATransaction.setDisableActions(true)
    metalLayer.frame = targetBounds
    CATransaction.commit()
    
    val drawable = metalLayer.nextDrawable() ?: return
    val commandQueue = metalLayer.device?.newCommandQueue() ?: return
    val commandBuffer = commandQueue.commandBuffer() ?: return
    
    commandBuffer.presentDrawable(drawable)
    commandBuffer.addCompletedHandler { buffer ->
        // Asynchronously inspect buffer status without blocking compositor
        if (buffer?.status == MTLCommandBufferStatusError) {
            Logger.e("Metal swapchain error during transition")
        }
    }
    commandBuffer.commit()
}
```

Architectural Specification & Invariant Rationale

Architectural Invariant Design: Compose Multiplatform 2.1 Metal-SurfaceView Swapchain Synchronization

Compose Multiplatform 2.1 Metal-SurfaceView Swapchain Synchronization defines an industrial-grade behavioral standard for mobile engineering. Enforces non-blocking graphics buffer destruction and synchronized vsync pacing across Skiko rendering pipelines on iOS CAMetalLayer and Android SurfaceView. Prevents GPU driver starvation, triple-buffering exhaustion, and UI-thread deadlocks during cross-platform navigation transitions. Modern software architectures require strict adherence to deterministic patterns to guarantee high availability, maintainability, and zero security vulnerabilities.

Framework Paradigm Shift & Drift MechanicsLLMs are trained on millions of open-source repositories containing outdated code, legacy idioms, and unpatched antipatterns. When coding assistants generate implementations for this stack, they default to common statistical tokens: Synchronous swapchain teardown on UI dispatcher causing GPU presentation deadlocks and buffer starvation during navigation Without an explicit system instruction file, developers spend hours debugging silent failures.
Failure Modes & Production Blast Radius

Hazard Analysis: What Breaks Without This Rule

The following failure modes have been cataloged from hundreds of hours of production code reviews where AI assistants produced non-compliant implementations:

Failure ModeAI Symptom (The Anti-Pattern)SeverityProduction Impact & Remedy
AI Hallucination & Deprecation DriftSynchronous swapchain teardown on UI dispatcher causing GPU presentation deadlocks and buffer starvation during navigationCritical
Violating this industrial standard introduces regression bugs, security risks, and unexpected runtime failures in production.
Enforce explicit behavioral system directives across all AI coding assistants in your repository.
Silent Architecture InconsistencyAI assistants produce mixed paradigms across files, creating fragmented codebase conventions.Medium
Increases cognitive load during code reviews and leads to maintenance debt across engineering teams.
Pin project conventions in .cursor/rules, CLAUDE.md, and AGENTS.md so all team agents follow identical guardrails.
Architectural Directives in Detail

Step-by-Step Production Directives

Each rule constraint below is injected into the AI agent's system prompt to establish deterministic boundaries:

1

Engineers MUST ALWAYS decouple native buffer destruction from the UI thread by awaiting in-flight Skiko frame completion signals before releasing underlying hardware buffers (CAMetalDrawable, Surface).

Engineers MUST ALWAYS decouple native buffer destruction from the UI thread by awaiting in-flight Skiko frame completion signals before releasing underlying hardware buffers (CAMetalDrawable, Surface).

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
2

Engineers MUST NOT invoke blocking thread synchronization primitives (e.g., runBlocking, dispatch_sync, CountDownLatch.await) inside Composable DisposableEffect, SurfaceHolder.Callback, or platform view cleanup hooks.

Engineers MUST NOT invoke blocking thread synchronization primitives (e.g., runBlocking, dispatch_sync, CountDownLatch.await) inside Composable DisposableEffect, SurfaceHolder.Callback, or platform view cleanup hooks.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
3

Code SHOULD prefer TextureView over SurfaceView on Android only when hardware-accelerated cross-fade navigation transitions strictly require layer alpha blending, otherwise enforce SurfaceHolder.Callback2 with surfaceRedrawNeeded for zero-copy presentation.

Code SHOULD prefer TextureView over SurfaceView on Android only when hardware-accelerated cross-fade navigation transitions strictly require layer alpha blending, otherwise enforce SurfaceHolder.Callback2 with surfaceRedrawNeeded for zero-copy presentation.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
4

Services MUST pause CADisplayLink and Choreographer frame pacing callbacks prior to disposing ComposeScene layers to prevent presenting into recycled or orphaned CAMetalLayer/ANativeWindow instances.

Services MUST pause CADisplayLink and Choreographer frame pacing callbacks prior to disposing ComposeScene layers to prevent presenting into recycled or orphaned CAMetalLayer/ANativeWindow instances.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
5

Components NEVER hold strong references to CAMetalDrawable or hardware-backed android.view.Surface instances past the active render cycle boundary.

Components NEVER hold strong references to CAMetalDrawable or hardware-backed android.view.Surface instances past the active render cycle boundary.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
Technical Questions & Answers

Frequently Asked Technical Questions

Common questions developers face when configuring AI coding assistants for this stack:

Q:Why is Compose Multiplatform 2.1 Metal-SurfaceView Swapchain Synchronization critical for AI coding assistants?

Enforces non-blocking graphics buffer destruction and synchronized vsync pacing across Skiko rendering pipelines on iOS CAMetalLayer and Android SurfaceView. Prevents GPU driver starvation, triple-buffering exhaustion, and UI-thread deadlocks during cross-platform navigation transitions. Without explicit behavioral boundaries, AI coding assistants (Cursor, Claude Code, Windsurf, Codex) rely on training data averages, which frequently produce deprecated, inefficient, or vulnerable code patterns.

Q:How does adopting this rule prevent AI hallucinations?

This rule explicitly declares what the AI must avoid (Synchronous swapchain teardown on UI dispatcher causing GPU presentation deadlocks and buffer starvation during navigation) and prescribes verified positive examples. Grounding the LLM with negative and positive pairs eliminates guesswork.

Q:Which AI coding environments are supported?

This specification is multi-platform compatible: Cursor (.cursor/rules/*.mdc & .cursorrules), Anthropic Claude Code (CLAUDE.md), Windsurf (.windsurfrules), Codex / Cline (AGENTS.md), and GitHub Copilot (.github/copilot-instructions.md).

Q:Can I combine this rule with other technology stacks?

Yes. Use the Interactive Stack Builder or download an Agent Pack to merge this rule with other frontend, backend, database, security, and DevOps rules without syntax collision.

Open Specification · CC BY 4.0·Free for Personal, Team & Commercial In-Repo Use

Citation & Open Source Attribution Guidelines

This specification is licensed under Creative Commons Attribution 4.0 International (CC BY 4.0). You are completely free to adapt and share this rule in your workspace. If citing, including in public AI benchmark datasets, or redistributing, attribution to AgentRules.org is required.

Related MOBILE Rules

View all rules →
1 Rule in Stack

Compose