This specification is maintained by the AgentRules Working Group. Have enhancements or want to claim maintainership?Submit Specification →
mobilecritical PriorityID: skiko-gpu-surface-lifecycle-invalidation

Compose Multiplatform Skiko GPU Surface Lifecycle & Swapchain Invalidation

Enforces synchronous GPU swapchain invalidation, render loop detachment, and Skia GrContext resource flushing when native platforms transition to background or inactive states. Prevents fatal OS termination (such as iOS gpus_ReturnNotPermittedKillClient or Android Vulkan/EGL surface-destroyed aborts) caused by rogue draw calls on invalidated native rendering surfaces.

Prohibited Anti-Pattern: Deferred or Async SkiaLayer Teardown Permitting Background GPU Frame Submissions

Export Ready-to-Drop Configuration

Select format below to copy code or download file directly.

---
description: Compose Multiplatform Skiko GPU Surface Lifecycle & Swapchain Invalidation - Enforces synchronous GPU swapchain invalidation, render loop detachment, and Skia GrContext resource flushing when native platforms transition to background or inactive states. Prevents fatal OS termination (such as iOS gpus_ReturnNotPermittedKillClient or Android Vulkan/EGL surface-destroyed aborts) caused by rogue draw calls on invalidated native rendering surfaces.
globs: **/*ViewController.kt, **/*Activity.kt, **/*SkiaLayer*.kt, **/*Redrawer*.kt, **/iosMain/**/*.kt, **/androidMain/**/*.kt
alwaysApply: true
verified_by: "agentrules.org"
license: "CC-BY-4.0"
source: "https://agentrules.org/rules/skiko-gpu-surface-lifecycle-invalidation"
---

# Compose Multiplatform Skiko GPU Surface Lifecycle & Swapchain Invalidation

> **Severity**: CRITICAL | **Category**: mobile
> **Anti-Pattern Warning**: Deferred or Async SkiaLayer Teardown Permitting Background GPU Frame Submissions

## System Constraints
You are a Principal Software Architect enforcing zero-tolerance GPU lifecycle rules for Compose Multiplatform and Skiko. You must mandate synchronous rendering pauses, immediate swapchain detachments, and native surface invalidations prior to handling backgrounding transitions on iOS and Android.

## Strict Guidelines
- Engineers MUST ALWAYS synchronously pause Skiko redraw loops (MetalRedrawer/Direct3D/Vulkan) and flush pending GPU commands on the main thread during native lifecycle background events (e.g., applicationDidEnterBackground, onStop, or surfaceDestroyed).
- Engineers MUST NOT defer surface invalidation, SkiaLayer detachment, or texture release across asynchronous coroutine scopes or background dispatch queues during platform suspension.
- Engineers MUST bind Skiko surface destruction directly to the native host surface lifecycle (SurfaceHolder.Callback.surfaceDestroyed on Android, viewWillDisappear / ScenePhase.background on iOS) to ensure zero draw commands are emitted after host surface reclamation.
- Code SHOULD explicitly release off-screen render targets, caches, and GrDirectContext resources on low-memory and background signals to prevent OOM kills while suspended.
- Components NEVER invoke SkiaLayer.needRedraw() or trigger composable state mutations that schedule GPU render passes when the platform window or surface state is marked detached or invisible.

## Bad vs Good Implementations
### Synchronous Skiko Metal Surface Invalidation on iOS Backgrounding

#### Bad Antipattern (AI Common Hallucination)
> **Why it fails**: Offloading SkiaLayer.detach() to a coroutine decouples cleanup from the synchronous native UI lifecycle. The MetalRedrawer continues drawing on the CAMetalLayer during the background transition, resulting in an unrecoverable SIGKILL from the iOS graphics daemon.
```kotlin
import platform.UIKit.UIViewController
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.jetbrains.skiko.SkiaLayer

class ComposeMetalViewController(private val skiaLayer: SkiaLayer) : UIViewController(null, null) {
    override fun viewWillDisappear(animated: Boolean) {
        super.viewWillDisappear(animated)
        // VIO-LATION: Asynchronous cleanup allows the Metal render loop to execute
        // a frame commit after backgrounding, causing gpus_ReturnNotPermittedKillClient.
        GlobalScope.launch {
            skiaLayer.detach()
        }
    }
}
```

#### Production Standard (Correct)
> **Standard**: Synchronously executing skiaLayer.detach() on the main thread guarantees that the Metal frame timer is canceled and all queued GPU command buffers are finalized before the platform marks the surface restricted, preventing termination.
```kotlin
import platform.UIKit.UIViewController
import org.jetbrains.skiko.SkiaLayer

class ComposeMetalViewController(private val skiaLayer: SkiaLayer) : UIViewController(null, null) {
    override fun viewWillDisappear(animated: Boolean) {
        // INVARIANT: Synchronously stop the redraw loop and release swapchain handles
        // on the main thread before the OS suspends native surface permissions.
        skiaLayer.detach()
        super.viewWillDisappear(animated)
    }

    override fun viewWillAppear(animated: Boolean) {
        super.viewWillAppear(animated)
        // Re-attach hardware layer and re-initialize swapchain synchronously upon foreground
        if (!skiaLayer.isAttached) {
            skiaLayer.attachTo(this.view)
            skiaLayer.needRedraw()
        }
    }
}
```

### Android SurfaceView Teardown Synchronized with Skiko DirectContext

#### Bad Antipattern (AI Common Hallucination)
> **Why it fails**: Allowing surfaceDestroyed to return without synchronously terminating the Skiko render thread leads to drawing against a dead EGLSurface or VkSurfaceKHR, raising fatal native SIGSEGV errors.
```kotlin
import android.view.SurfaceHolder
import android.view.SurfaceView
import org.jetbrains.skiko.SkiaLayer

class SkikoSurfaceCallback(private val skiaLayer: SkiaLayer) : SurfaceHolder.Callback {
    override fun surfaceCreated(holder: SurfaceHolder) {
        skiaLayer.attachTo(holder.surface)
    }

    override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {}

    override fun surfaceDestroyed(holder: SurfaceHolder) {
        // VIOLATION: No-op or delayed teardown. Skiko's render thread attempts to 
        // present to an already destroyed Surface, causing native EGL/Vulkan crash.
    }
}
```

#### Production Standard (Correct)
> **Standard**: Invoking skiaLayer.detach() synchronously blocks surfaceDestroyed from completing until the Skiko rendering pipeline stops referencing the underlying native surface, ensuring thread-safe GPU shutdown.
```kotlin
import android.view.SurfaceHolder
import org.jetbrains.skiko.SkiaLayer

class SkikoSurfaceCallback(private val skiaLayer: SkiaLayer) : SurfaceHolder.Callback {
    override fun surfaceCreated(holder: SurfaceHolder) {
        skiaLayer.attachTo(holder.surface)
    }

    override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
        skiaLayer.update(width, height)
    }

    override fun surfaceDestroyed(holder: SurfaceHolder) {
        // INVARIANT: Block until the rendering loop stops and the native context disowns the surface.
        skiaLayer.detach()
    }
}
```

Recommended MCP Servers (mcp-serverlist.com)

Equip agent with system tools to verify code for this stack.

Recommended Agent Skills (askills.dev)

Ready-to-use executable skill functions tailored to this stack.

Architectural Specification & Invariant Rationale

Architectural Invariant Design: Compose Multiplatform Skiko GPU Surface Lifecycle & Swapchain Invalidation

Compose Multiplatform Skiko GPU Surface Lifecycle & Swapchain Invalidation defines an industrial-grade behavioral standard for mobile engineering. Enforces synchronous GPU swapchain invalidation, render loop detachment, and Skia GrContext resource flushing when native platforms transition to background or inactive states. Prevents fatal OS termination (such as iOS gpus_ReturnNotPermittedKillClient or Android Vulkan/EGL surface-destroyed aborts) caused by rogue draw calls on invalidated native rendering surfaces. 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: Deferred or Async SkiaLayer Teardown Permitting Background GPU Frame Submissions 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 DriftDeferred or Async SkiaLayer Teardown Permitting Background GPU Frame SubmissionsCritical
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 synchronously pause Skiko redraw loops (MetalRedrawer/Direct3D/Vulkan) and flush pending GPU commands on the main thread during native lifecycle background events (e.g., applicationDidEnterBackground, onStop, or surfaceDestroyed).

Engineers MUST ALWAYS synchronously pause Skiko redraw loops (MetalRedrawer/Direct3D/Vulkan) and flush pending GPU commands on the main thread during native lifecycle background events (e.g., applicationDidEnterBackground, onStop, or surfaceDestroyed).

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 defer surface invalidation, SkiaLayer detachment, or texture release across asynchronous coroutine scopes or background dispatch queues during platform suspension.

Engineers MUST NOT defer surface invalidation, SkiaLayer detachment, or texture release across asynchronous coroutine scopes or background dispatch queues during platform suspension.

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

Engineers MUST bind Skiko surface destruction directly to the native host surface lifecycle (SurfaceHolder.Callback.surfaceDestroyed on Android, viewWillDisappear / ScenePhase.background on iOS) to ensure zero draw commands are emitted after host surface reclamation.

Engineers MUST bind Skiko surface destruction directly to the native host surface lifecycle (SurfaceHolder.Callback.surfaceDestroyed on Android, viewWillDisappear / ScenePhase.background on iOS) to ensure zero draw commands are emitted after host surface reclamation.

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

Code SHOULD explicitly release off-screen render targets, caches, and GrDirectContext resources on low-memory and background signals to prevent OOM kills while suspended.

Code SHOULD explicitly release off-screen render targets, caches, and GrDirectContext resources on low-memory and background signals to prevent OOM kills while suspended.

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 invoke SkiaLayer.needRedraw() or trigger composable state mutations that schedule GPU render passes when the platform window or surface state is marked detached or invisible.

Components NEVER invoke SkiaLayer.needRedraw() or trigger composable state mutations that schedule GPU render passes when the platform window or surface state is marked detached or invisible.

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 Skiko GPU Surface Lifecycle & Swapchain Invalidation critical for AI coding assistants?

Enforces synchronous GPU swapchain invalidation, render loop detachment, and Skia GrContext resource flushing when native platforms transition to background or inactive states. Prevents fatal OS termination (such as iOS gpus_ReturnNotPermittedKillClient or Android Vulkan/EGL surface-destroyed aborts) caused by rogue draw calls on invalidated native rendering surfaces. 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 (Deferred or Async SkiaLayer Teardown Permitting Background GPU Frame Submissions) 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