This specification is maintained by the AgentRules Working Group. Have enhancements or want to claim maintainership?Submit Specification →
mobilecritical PriorityID: cmp-offscreen-rendernode-graphic-buffer-disposal-invariant

Compose Multiplatform Off-Screen RenderNode & Graphic Buffer Disposal Invariant

Enforces deterministic lifecycle-bound GPU surface and RenderNode buffer reclamation during navigation transitions in Compose Multiplatform (CMP) across Android and iOS (Metal/Skiko). Prohibits the retention of hardware-accelerated graphic layers and off-screen render targets on inactive backstack destinations to eliminate VRAM bloat and prevent iOS jetsam kills.

Prohibited Anti-Pattern: Retaining Hardware-Accelerated graphicsLayers and Unbounded Skia/Metal Surface Allocations on Backstack Routes

Export Ready-to-Drop Configuration

Select format below to copy code or download file directly.

---
description: Compose Multiplatform Off-Screen RenderNode & Graphic Buffer Disposal Invariant - Enforces deterministic lifecycle-bound GPU surface and RenderNode buffer reclamation during navigation transitions in Compose Multiplatform (CMP) across Android and iOS (Metal/Skiko). Prohibits the retention of hardware-accelerated graphic layers and off-screen render targets on inactive backstack destinations to eliminate VRAM bloat and prevent iOS jetsam kills.
globs: **/commonMain/**/*.kt, **/androidMain/**/*.kt, **/iosMain/**/*.kt
alwaysApply: true
verified_by: "agentrules.org"
license: "CC-BY-4.0"
source: "https://agentrules.org/rules/cmp-offscreen-rendernode-graphic-buffer-disposal-invariant"
---

# Compose Multiplatform Off-Screen RenderNode & Graphic Buffer Disposal Invariant

> **Severity**: CRITICAL | **Category**: mobile
> **Anti-Pattern Warning**: Retaining Hardware-Accelerated graphicsLayers and Unbounded Skia/Metal Surface Allocations on Backstack Routes

## System Constraints
You are a Principal Mobile Architect specializing in Compose Multiplatform runtime internals, Skiko rendering pipelines, and low-level GPU memory management. You enforce zero-tolerance invariants regarding hardware buffer lifecycle management. In Compose Multiplatform (especially on iOS via Metal and Android via HardwareRenderer), retaining active `Modifier.graphicsLayer` allocations, rasterization caches, or off-screen drawing surfaces (`RenderNode`, SkSurface, SkPicture) on non-resumed navigation destinations causes acute VRAM exhaustion and immediate OS termination. You must ensure all off-screen graphic buffers are pruned immediately upon lifecycle transition out of the foreground.

## Strict Guidelines
- Engineers MUST ALWAYS tie expensive `Modifier.graphicsLayer` transformations (such as render effects, camera distance, rotation, or hardware layer caching) to active screen lifecycle states (e.g., `Lifecycle.State.RESUMED`).
- Engineers MUST NOT retain native graphic buffers, cached bitmaps, or Skia render targets inside retained ViewModels or long-lived remembered lambdas across navigation transitions.
- Code SHOULD conditionally disable `renderEffect`, alpha blending layers, and clip bounds when a screen transitions from active foreground to backstack state to trigger immediate Skia/Metal buffer deallocation.
- Services MUST ensure navigation hosts wrap backstack entries with explicit buffer disposal policies, evicting Skiko off-screen rendering textures when an entry is obscured.
- Components NEVER instantiate unbounded custom canvas off-screen caches (`DrawScope.drawIntoCanvas` with manual surface allocations) without explicit `DisposableEffect` cleanup releasing the backing GPU textures on disposal.

## Bad vs Good Implementations
### Hardware Layer Allocation on Navigation Backstack Entries

#### Bad Antipattern (AI Common Hallucination)
> **Why it fails**: Applying `Modifier.graphicsLayer` with hardware-heavy parameters (e.g., BlurEffect, shadowElevation, layer clipping) unconditionally causes the underlying platform (Skiko/Metal on iOS, HardwareRenderer on Android) to retain a persistent off-screen GPU frame buffer. When navigated away from, the layer remains in VRAM, scaling linearly with every screen added to the backstack.
```kotlin
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.BlurEffect
import androidx.compose.ui.graphics.graphicsLayer

@Composable
fun HeavyMediaScreen(isBlurred: Boolean) {
    // ANTI-PATTERN: graphicsLayer allocates a dedicated RenderNode and off-screen Metal/Vulkan texture.
    // When this screen is pushed into the navigation backstack, the GPU surface remains permanently allocated,
    // causing memory pressure and eventual EXC_RESOURCE / OOM kills.
    Box(
        modifier = Modifier
            .fillMaxSize()
            .graphicsLayer {
                clip = true
                shadowElevation = 16f
                renderEffect = if (isBlurred) BlurEffect(25f, 25f) else null
            }
    ) {
        // Heavy composable hierarchy
    }
}
```

#### Production Standard (Correct)
> **Standard**: By observing the screen's lifecycle state via `LocalLifecycleOwner.current`, the composable dynamically tears down the `Modifier.graphicsLayer` when transitioning away from `Lifecycle.State.RESUMED`. This triggers the immediate disposal of the backing Skiko Metal texture or Android RenderNode graphic buffer while the screen resides in the backstack.
```kotlin
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.BlurEffect
import androidx.compose.ui.graphics.graphicsLayer
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.compose.collectAsStateWithLifecycle

@Composable
fun HeavyMediaScreen(isBlurred: Boolean) {
    val lifecycleOwner = LocalLifecycleOwner.current
    val lifecycleState by lifecycleOwner.lifecycle.currentStateFlow.collectAsStateWithLifecycle()
    val isForeground = lifecycleState.isAtLeast(Lifecycle.State.RESUMED)

    // PRODUCTION FIX: Gate GPU surface allocations to active foreground states.
    // When descending to backstack (STARTED or CREATED), strip the hardware layer
    // to force Skiko/Metal/Android RenderNode texture deallocation.
    Box(
        modifier = Modifier
            .fillMaxSize()
            .then(
                if (isForeground) {
                    Modifier.graphicsLayer {
                        clip = true
                        shadowElevation = 16f
                        renderEffect = if (isBlurred) BlurEffect(25f, 25f) else null
                    }
                } else {
                    Modifier
                }
            )
    ) {
        // Heavy composable hierarchy
    }
}
```

Recommended MCP Servers (mcp-serverlist.com)

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

Architectural Specification & Invariant Rationale

Architectural Invariant Design: Compose Multiplatform Off-Screen RenderNode & Graphic Buffer Disposal Invariant

Compose Multiplatform Off-Screen RenderNode & Graphic Buffer Disposal Invariant defines an industrial-grade behavioral standard for mobile engineering. Enforces deterministic lifecycle-bound GPU surface and RenderNode buffer reclamation during navigation transitions in Compose Multiplatform (CMP) across Android and iOS (Metal/Skiko). Prohibits the retention of hardware-accelerated graphic layers and off-screen render targets on inactive backstack destinations to eliminate VRAM bloat and prevent iOS jetsam kills. 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: Retaining Hardware-Accelerated graphicsLayers and Unbounded Skia/Metal Surface Allocations on Backstack Routes 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 DriftRetaining Hardware-Accelerated graphicsLayers and Unbounded Skia/Metal Surface Allocations on Backstack RoutesCritical
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 tie expensive `Modifier.graphicsLayer` transformations (such as render effects, camera distance, rotation, or hardware layer caching) to active screen lifecycle states (e.g., `Lifecycle.State.RESUMED`).

Engineers MUST ALWAYS tie expensive `Modifier.graphicsLayer` transformations (such as render effects, camera distance, rotation, or hardware layer caching) to active screen lifecycle states (e.g., `Lifecycle.State.RESUMED`).

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 retain native graphic buffers, cached bitmaps, or Skia render targets inside retained ViewModels or long-lived remembered lambdas across navigation transitions.

Engineers MUST NOT retain native graphic buffers, cached bitmaps, or Skia render targets inside retained ViewModels or long-lived remembered lambdas across navigation transitions.

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 conditionally disable `renderEffect`, alpha blending layers, and clip bounds when a screen transitions from active foreground to backstack state to trigger immediate Skia/Metal buffer deallocation.

Code SHOULD conditionally disable `renderEffect`, alpha blending layers, and clip bounds when a screen transitions from active foreground to backstack state to trigger immediate Skia/Metal buffer deallocation.

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 ensure navigation hosts wrap backstack entries with explicit buffer disposal policies, evicting Skiko off-screen rendering textures when an entry is obscured.

Services MUST ensure navigation hosts wrap backstack entries with explicit buffer disposal policies, evicting Skiko off-screen rendering textures when an entry is obscured.

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 instantiate unbounded custom canvas off-screen caches (`DrawScope.drawIntoCanvas` with manual surface allocations) without explicit `DisposableEffect` cleanup releasing the backing GPU textures on disposal.

Components NEVER instantiate unbounded custom canvas off-screen caches (`DrawScope.drawIntoCanvas` with manual surface allocations) without explicit `DisposableEffect` cleanup releasing the backing GPU textures on disposal.

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 Off-Screen RenderNode & Graphic Buffer Disposal Invariant critical for AI coding assistants?

Enforces deterministic lifecycle-bound GPU surface and RenderNode buffer reclamation during navigation transitions in Compose Multiplatform (CMP) across Android and iOS (Metal/Skiko). Prohibits the retention of hardware-accelerated graphic layers and off-screen render targets on inactive backstack destinations to eliminate VRAM bloat and prevent iOS jetsam kills. 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 (Retaining Hardware-Accelerated graphicsLayers and Unbounded Skia/Metal Surface Allocations on Backstack Routes) 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