← Home
iOS · Rendering · Performance

GPU 렌더링 · 오프스크린 · VSync Q&A

CPU/GPU 렌더링 파이프라인, 오프스크린 렌더링, VSync와 프레임 드랍 이해

1. 오프스크린 렌더링이란?

GPU가 화면에 바로 합성하지 못하고 별도 버퍼에 미리 그린 뒤 다시 합성해야 하는 경우. 컨텍스트 전환 비용이 커서 스크롤에서 특히 치명적.

정상 렌더링 (On-screen)

Layer 1 ─┐
Layer 2 ─┼─→ GPU Frame Buffer → 화면
Layer 3 ─┘
         한 번에 합성

오프스크린 렌더링

Layer 1 ─→ [별도 버퍼] ─┐
                       ├─→ Frame Buffer → 화면
Layer 2, 3 ────────────┘

2단계 (버퍼 전환 비용 발생)

왜 오프스크린이 필요한가?

마스킹 (cornerRadius + clipsToBounds):

이미지를 먼저 그리고 → 모서리를 잘라야 함 → "그린 다음 자르기" = 별도 버퍼 필요

그림자 (shadowPath 없을 때):

그림자 모양 = View 모양 기반 → View를 먼저 그려서 모양을 알아야 그림자 계산 가능 → 별도 버퍼 필요

왜 스크롤에서 치명적인가?

60fps = 16.67ms마다 새 프레임

정상:      [합성] 5ms ────────── 여유 11ms ✓

오프스크린: [버퍼1] 8ms + [버퍼2] 6ms + [합성] 5ms = 19ms
                                              ↑
                                         16.67ms 초과 = 프레임 드랍!

오프스크린 피하는 방법

원인해결
cornerRadius + clipsToBoundslayer.shouldRasterize = true (캐싱)
shadow (path 없음)shadowPath 지정
mask가능하면 이미지로 대체
shadowPath 예시:
// ❌ 오프스크린 발생
layer.shadowOpacity = 0.5
layer.shadowRadius = 4

// ✅ 오프스크린 없음
layer.shadowOpacity = 0.5
layer.shadowRadius = 4
layer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: 12).cgPath
디버깅: Simulator → Debug → Color Off-screen Rendered (노란색 = 오프스크린)

2. CPU/GPU 렌더링 파이프라인

Q: GPU 렌더는 동기적으로 기다리지 않더라도 늦게 처리되면 문제인가?
A: 네. CPU가 기다리냐 아니냐 문제가 아니라, VSync 데드라인(16.67ms)을 GPU가 못 맞추면 프레임 드랍
Q: Run Loop와 GPU VSync는 별개인가?
A: 별개 프로세스지만 VSync 신호에 동기화됨. 둘 다 16.67ms 주기

전체 흐름

VSync 신호 (디스플레이 하드웨어, 매 16.67ms)
           │                              │
           ▼                              ▼
┌─────────────────────┐      ┌─────────────────────┐
│   Run Loop (CPU)     │      │  Render Server (GPU) │
│   앱 프로세스         │      │  별도 시스템 프로세스  │
│                     │      │                     │
│  1. Event 처리       │      │                     │
│  2. layoutSubviews   │      │                     │
│  3. draw(_:)        │      │                     │
│  4. commit ─────────│─────→│  렌더링 & 합성        │
│                     │      │  → 화면에 표시        │
└─────────────────────┘      └─────────────────────┘
순서 주의! layoutSubviews → draw → commit (commit이 마지막)
Run Loop (CPU)Render Server (GPU)
위치앱 프로세스별도 시스템 프로세스
트리거CADisplayLink (VSync 기반)VSync
주기~16.67ms~16.67ms
하는 일레이아웃, 드로우, commit합성, 화면 출력

3. CPU/GPU 파이프라인 동작

CPU와 GPU는 파이프라인으로 동시에 다른 프레임 작업:

시간 →   0ms      16.67ms     33.33ms     50ms
         │          │           │          │
         ▼          ▼           ▼          ▼
        VSync      VSync       VSync      VSync

CPU:    [Frame 1]  [Frame 2]   [Frame 3]
        layout     layout      layout
        draw       draw        draw
        commit     commit      commit
            │          │           │
            ▼          ▼           ▼
GPU:        ─────[Frame 1]  [Frame 2]   [Frame 3]
                 렌더링     렌더링      렌더링
                     │          │           │
                     ▼          ▼           ▼
화면:                ─────[Frame 1]  [Frame 2]   [Frame 3]
핵심: CPU와 GPU가 각각 16.67ms 안에 끝내야 60fps 유지

4. CPU가 16.67ms 초과하면?

Q: CPU 작업이 다 끝나지 못하면 다음 16.67ms에서 마무리?
A: 네, CPU는 계속 작업. 그동안 GPU는 commit이 없어서 대기
Q: 그럼 화면은?
A: 이전 프레임 반복 = 버벅임
정상:
    0ms         16.67ms       33.33ms
     │            │             │
CPU: [Frame 1]    [Frame 2]     [Frame 3]
          └commit      └commit
GPU:      [Frame 1]   [Frame 2]
화면:          F1          F2       ← 매 VSync 새 프레임


CPU 느릴 때:
    0ms         16.67ms       33.33ms
     │            │             │
CPU: [Frame 1──────────────]   [Frame 2]
                    아직 작업중    └commit
GPU:      [할 게 없음...]      [Frame 1]
화면:          F0(이전)     F0(반복)    F1
               ↑            ↑
            프레임 드랍!   2번 반복됨
상황CPUGPU화면
CPU 16.67ms 초과계속 작업commit 없어서 대기이전 프레임 반복
commit 되면다음 프레임 시작이제 렌더링다음 VSync에 표시
결론: CPU가 commit 안 하면 → GPU는 할 일이 없음 → 화면은 이전 프레임 반복 = 버벅임

핵심 요약