← Study Hub

08 · 그래픽스와 UI 렌더링

한 프레임이 화면에 뜨기까지의 렌더 파이프라인 — Core Animation 커밋, Render Server, GPU, 그리고 프레임 예산과 이미지 처리
진행률
0 / 0 완료

← 챕터 목록

이 챕터는 "한 프레임이 어떤 경로로 화면에 뜨는가"를 뼈대로, 프레임 예산·CPU와 GPU의 분업·이미지 디코드와 다운샘플링·커밋 트랜잭션 단계별 병목까지 렌더링 성능의 공통 언어를 정리한다. 스크롤이 왜 끊기는지, 어디를 재보고 어디를 고쳐야 하는지를 판단하는 기준을 만드는 게 목표다.

Q1. 화면의 한 Frame은 어떤 단계를 거쳐 표시되는가?

🔑 30초 답변

앱 프로세스는 런루프가 한 바퀴 돌 때마다 끝에서 CATransaction을 커밋한다. 이 커밋 트랜잭션은 Layout → Display → Prepare → Commit 순서로 진행되고, 완성된 CALayer 트리를 IPC로 별도 프로세스인 Render Server에 넘긴다. Render Server는 그 트리를 디코드해 GPU draw call로 바꾸고, GPU가 정점·픽셀 처리와 합성으로 프레임버퍼를 채운다. 디스플레이 컨트롤러는 VSync마다 그 프레임버퍼를 스캔아웃한다. 이 단계들은 파이프라인이라, 커밋된 프레임이 실제로 표시되기까지 보통 2~3 VSync가 걸린다.

CS 원리

래스터 디스플레이는 프레임버퍼(픽셀 배열)를 일정 주기(refresh rate)로 위에서 아래로 스캔아웃해 화면을 갱신한다. 렌더러가 버퍼를 쓰는 도중에 스캔아웃이 겹치면 화면이 위아래로 어긋나는 tearing이 생긴다. 이를 막으려고 더블/트리플 버퍼링을 두고, 스캔아웃 중인 front buffer는 건드리지 않은 채 back buffer에 렌더한 뒤 VSync 순간에 교체(swap)한다. 렌더링 자체도 "장면 기술 → 기하 변환 → 래스터화 → 합성 → 출력"처럼 여러 단계로 나뉘고, 이를 파이프라인으로 겹쳐 돌리면 처리량은 오르지만 한 프레임의 지연(latency)은 여러 주기로 늘어난다.

iOS에서는

iOS는 앱이 픽셀을 직접 그리지 않고 CALayer 트리만 기술하게 하는 Core Animation 아키텍처다. 실제 픽셀 렌더는 앱과 분리된 Render Server 프로세스가 담당한다. 런루프에 등록된 CATransaction 옵저버가 프레임 끝에서 커밋을 트리거하면, 커밋 트랜잭션 4단계가 돌고 그 결과가 Render Server로 넘어간다. Render Server는 이를 Metal 커맨드로 인코딩해 GPU에 제출하고, 화면 출력은 VSync에 동기화된다. 이 흐름은 WWDC 테크톡 Explore UI animation hitches and the render loop에서 Commit → Render Prepare → Render Execute 단계로 정의한다. 핵심 함의: 앱이 커밋 뒤 잠깐 멈춰도 이미 넘어간 애니메이션은 Render Server가 계속 진행할 수 있다.

VSync VSync 앱: Commit Render Prepare GPU: Execute 디스플레이 프레임 N N+1 프레임 N 프레임 N 프레임 N
렌더 루프. 커밋된 프레임 N은 Render Prepare · GPU Execute를 지나 약 3 VSync 뒤에 화면에 뜬다. 파이프라인이라 앱은 곧바로 다음 프레임(N+1) 커밋을 시작한다.

실험 · 도구

Instruments의 Animation Hitches / Core Animation 템플릿으로 hitch(예상보다 늦게 표시된 프레임)를 시간(ms) 단위로 잡는다. 어느 프로세스가 늦었는지 — 앱의 커밋인지 Render Server인지 — 를 트랙별로 볼 수 있다. Xcode의 디버그 옵션 "Color Blended Layers", "Color Offscreen-Rendered"로 GPU 쪽 부담 원인을 눈으로 확인하고, os_signpost로 셀 구성처럼 앱 코드의 구간 시간을 직접 계측한다. 예상 결과: 앱이 프레임 예산을 넘기면 커밋 트랙이 길어지고 그 프레임이 드롭돼 화면 갱신 간격이 벌어진다.

프로젝트 적용

⚠️ 흔한 오해

"내 코드가 화면 픽셀까지 다 렌더한다"는 오해. 앱은 레이어 트리를 기술할 뿐이고, 실제 픽셀 합성은 별도 프로세스(Render Server)와 GPU가 한다. 그래서 앱이 순간 멈춰도 이미 커밋된 암시적 애니메이션은 계속 돌 수 있다. 반대로 drawRect(_:)를 오버라이드하면 그 콘텐츠만은 앱이 CPU로 직접 래스터화한다 — 이 경우엔 앱의 일이 맞다.

꼬리 질문

Q2. 60Hz와 120Hz에서 Frame budget은 각각 얼마인가?

🔑 30초 답변

frame budget은 리프레시 주기의 역수다. 60Hz면 1000÷60 ≈ 16.67ms, 120Hz면 1000÷120 ≈ 8.33ms가 한 프레임을 만들 수 있는 총 시간이다. 이 예산 안에 앱의 커밋 작업과 GPU 렌더가 각자 자기 몫을 끝내야 VSync를 놓치지 않는다. 파이프라인이라 각 단계가 대략 한 주기를 갖지만, 어느 단계든 자기 주기를 넘기면 그 프레임은 드롭되고 hitch가 생긴다. ProMotion(iPhone Pro, iPad Pro)은 고정 120이 아니라 가변(iPhone Pro는 10~120Hz)이라는 점도 중요하다.

CS 원리

budget = 1 / refresh rate. 이 시간 안에 렌더 파이프라인의 각 stage가 완료돼야 다음 VSync에 맞춰 새 프레임을 내보낼 수 있다. 넘기면 그 주기에는 이전 프레임이 한 번 더 표시되고(프레임 반복), 체감상 끊김으로 나타난다. 리프레시가 2배가 되면 예산은 정확히 절반이 된다.

60 Hz 120 Hz 16.67 ms 8.33 ms 120Hz budget = 60Hz의 1/2
frame budget = 1 ÷ 리프레시. 리프레시가 2배가 되면 한 프레임을 만들 시간은 절반으로 줄어든다.
리프레시프레임 budget대표 예
60 Hz≈ 16.67 ms대부분의 iPhone, 표준 iPad
90 Hz≈ 11.11 ms일부 Android 기기 (Apple ProMotion의 네이티브 rate는 아님)
120 Hz≈ 8.33 msProMotion (iPhone Pro, iPad Pro)

iOS에서는

대부분의 iPhone은 60Hz, ProMotion iPhone은 10~120Hz 가변이다(iPad Pro는 하한이 더 높다). 주의할 함정: ProMotion iPhone에서도 앱 애니메이션은 기본적으로 60fps로 캡된다. 120Hz까지 쓰려면 Info.plist에 CADisableMinimumFrameDurationOnPhone을 true로 넣고, CADisplayLinkpreferredFrameRateRange로 원하는 프레임레이트 범위를 명시해야 한다. 또 하나: 16.67ms를 앱이 통째로 쓸 수 있는 건 아니다. Render Server·GPU 몫이 별도 주기로 필요하므로, 실무 목표치는 그보다 짧게(60Hz에서 커밋 8~10ms 이내 등) 잡는 게 안전하다. 참고로 ProMotion의 실제 리프레시 rate는 연속값이 아니라 최소 프레임 지속시간에 맞춰 양자화된 이산값(120·80·60·48·40·30·24·20·16·15·12·10Hz 등)으로 정해진다.

실험 · 도구

실제 프레임 간격은 CADisplayLink의 timestamp 차이로 직접 잴 수 있다. 정상이면 16.67 또는 8.33ms 근처, 드롭이 나면 그 배수(33.3ms 등)로 튄다.

CADisplayLink로 실제 프레임 간격 측정 (Swift)
final class FPSMonitor {
    private var link: CADisplayLink?
    private var last: CFTimeInterval = 0

    // CADisplayLink는 target(self)을 강하게 retain한다. self가 link를,
    // link가 self를 서로 잡아 순환 참조가 생기므로 반드시 stop()으로 끊어야 한다.
    func start() {
        let link = CADisplayLink(target: self, selector: #selector(tick(_:)))
        link.add(to: .main, forMode: .common)
        self.link = link
    }

    func stop() {
        link?.invalidate()   // 순환을 끊어 FPSMonitor(self)가 해제되게 한다
        link = nil
    }

    @objc private func tick(_ link: CADisplayLink) {
        guard last != 0 else { last = link.timestamp; return }  // 첫 프레임은 기준만 잡고 건너뜀
        let delta = link.timestamp - last            // 두 프레임 사이 실제 간격(초)
        last = link.timestamp
        print("frame interval: \(delta * 1000) ms")  // 16.67 / 8.33 근처여야 정상
    }
}
CADisplayLink는 target을 강하게 잡아 self와 순환 참조가 생긴다. 화면이 사라질 때(예: viewDidDisappear) stop()을 명시적으로 호출해야 하며, 순환 때문에 deinit은 저절로 불리지 않는다. 순환이 싫으면 weak proxy 타깃을 쓴다.

프로젝트 적용

⚠️ 흔한 오해

"120Hz 기기니까 항상 120fps로 부드럽다"는 오해. ProMotion은 콘텐츠에 따라 리프레시를 낮춰 전력을 아끼는 가변 방식이고, 앱은 기본적으로 60fps로 캡돼 있어 명시적으로 opt-in해야 한다. 또 "앱이 16.67ms를 다 써도 된다"도 위험하다 — 커밋·Render Prepare·GPU Execute는 파이프라인이라 각자 별도 주기를 갖지만, 프레임 간 변동성·시스템 CPU 경합·커밋에 딸린 준비 작업 때문에 예산을 꽉 채우면 조금만 튀어도 드롭된다. 그래서 헤드룸을 두고 목표치를 더 짧게 잡는다.

꼬리 질문

Q3. CPU와 GPU는 렌더링에서 각각 무엇을 담당하는가?

🔑 30초 답변

CPU는 "무엇을 그릴지" 준비한다 — 오토레이아웃 제약 풀이, 텍스트·경로 레이아웃, drawRect(_:)의 Core Graphics 래스터화, 이미지 디코드, 레이어 트리 구성과 커밋. GPU는 "픽셀을 실제로 채운다" — 텍스처 샘플링, 레이어 합성(블렌딩), 트랜스폼·마스크·그라디언트, 프래그먼트 셰이딩, 프레임버퍼 출력. Core Animation의 합성은 GPU 가속이고, drawRect는 CPU 래스터화라 남발하면 비싸다. 병목이 CPU냐 GPU냐에 따라 처방이 완전히 달라지므로, 최적화 전에 어느 쪽인지부터 판별해야 한다.

CS 원리

CPU는 소수의 강력한 코어로 분기 많은 순차 로직에 강하고, GPU는 수많은 작은 코어로 같은 연산을 대량 병렬(SIMD)로 처리하는 데 강하다. 래스터화(삼각형을 픽셀로)와 프래그먼트 셰이딩(픽셀별 색 계산)은 픽셀마다 독립적이라 병렬성이 높아 GPU에 적합하다. 반대로 "이 뷰가 어디에 얼마 크기로 놓이는가" 같은 준비·판단은 CPU가 맡는다.

CPU 무엇을 그릴지 준비 GPU 픽셀을 실제로 채우기 · 오토레이아웃 제약 풀이 · 텍스트 · 경로 레이아웃 · drawRect 래스터화 (Core Graphics) · 이미지 디코드 · 레이어 트리 구성 · 커밋 · 텍스처 샘플링 · 레이어 합성 · 블렌딩 · 트랜스폼 · 마스크 · 그라디언트 · 프래그먼트 셰이딩 · 프레임버퍼 출력 레이어 트리 + 텍스처
Core Animation은 합성을 GPU로 가속한다. 반면 drawRect(Core Graphics)는 CPU 래스터화라 남발하면 비싸다.

iOS에서는

Core Graphics(Quartz 2D)는 CPU로 비트맵을 그린다. 이 비트맵이 백킹스토어가 되어 GPU 텍스처로 올라간다. Core Animation의 레이어 합성·트랜스폼·투명도 처리는 Metal을 통해 GPU가 한다. 여기서 비싼 패턴이 offscreen rendering이다 — 마스크와 그림자를 함께 쓰거나(예: cornerRadius+masksToBounds+shadow 조합), group opacity를 켜면 GPU가 별도 버퍼에 먼저 그린 뒤 합치는 추가 패스를 강제한다. shouldRasterize는 그 결과를 캐시해 재사용하되, 매 프레임 내용이 바뀌면 캐시가 계속 무효화돼 오히려 손해다.

실험 · 도구

Instruments의 GPU/Metal System Trace로 GPU 시간을, Time Profiler로 CPU 시간을 각각 확인한다. Xcode 디버그 옵션으로 눈으로 판별할 수도 있다 — "Color Offscreen-Rendered"(오프스크린 패스가 노란색으로 표시), "Color Blended Layers"(빨간 영역이 블렌딩 부담), "Color Hits Green and Misses Red"(rasterization 캐시 적중/실패). GPU가 붉게 도배되면 합성·블렌딩·오프스크린이 병목, 반대로 메인 스레드 Time Profiler가 무거우면 CPU 쪽(레이아웃·디코드·drawRect)이 병목이다.

프로젝트 적용

⚠️ 흔한 오해

"모든 렌더는 GPU가 다 한다"거나 "cornerRadius는 항상 오프스크린을 유발한다"는 오해. 준비 단계(레이아웃·디코드·drawRect)의 상당 부분은 CPU가 한다. 그리고 최신 iOS에서는 단순한 cornerRadius(이미지·복잡한 서브레이어 없이)는 최적화되어 오프스크린을 강제하지 않는다 — 문제가 되는 건 마스킹·그림자 등과 조합될 때다. "항상/절대"로 외우지 말고 도구로 확인하라.

꼬리 질문

Q4. 이미지 Decode가 스크롤을 끊기게 만드는 이유는 무엇인가?

🔑 30초 답변

JPEG·PNG·HEIC는 압축 포맷이라 GPU가 바로 못 쓴다. UIImage에 파일을 담아도 실제 디코드(압축 해제 → 픽셀 비트맵)는 지연되어, Core Animation이 렌더 직전 커밋의 Prepare 단계에서, 기본적으로 메인 스레드에서 일어난다. 스크롤 도중 큰 이미지가 셀에 등장하면 이 디코드가 런루프를 수 ms~수십 ms 붙잡아 커밋이 프레임 예산을 넘긴다 → 프레임 드롭 → 끊김. 해법은 디코드를 미리 백그라운드로 옮기거나(byPreparingForDisplay), 다운샘플링으로 디코드 비용 자체를 줄이는 것이다.

CS 원리

압축 이미지 디코드는 엔트로피 디코딩·역변환(JPEG의 IDCT 등)을 포함한 CPU 집약 작업이다. 결과 비트맵은 폭×높이×픽셀당바이트만큼의 메모리를 차지한다(8비트 RGBA면 픽셀당 4바이트). 예로 12MP 사진(4032×3024)은 디코드하면 약 48MB다. 이 무거운 작업이 메인 스레드(=UI 런루프)에서 돌면 그 시간만큼 화면 갱신이 멈춘다.

iOS에서는

디코드는 이미지가 실제로 화면에 붙어 렌더될 때 lazy하게 일어난다 — imageView.image = ... 시점이 아니라 그 레이어가 커밋/Prepare될 때다. 이걸 앞당겨 백그라운드에서 끝내는 공식 API가 iOS 15+의 byPreparingForDisplay()(비동기) / preparingForDisplay()다. 예전에는 이미지를 CGContext에 한 번 그려 강제 디코드하는 트릭을 썼다. 이미지 처리 모범 사례는 WWDC 2018 Image and Graphics Best Practices에 정리돼 있다.

디코드를 백그라운드에서 미리 끝내고 메인은 표시만 (Swift, iOS 15+)
let raw = UIImage(contentsOfFile: path)!

Task.detached(priority: .utility) {
    let ready = await raw.byPreparingForDisplay()   // 백그라운드에서 디코드 완료
    await MainActor.run {
        if let ready { cell.imageView.image = ready } // 메인 스레드는 표시만
    }
}

실험 · 도구

Time Profiler로 스크롤 중 메인 스레드 스택을 보면 ImageIO/CGImageSource 계열 디코드 프레임이 잡힌다 — 이게 병목의 증거다. Core Animation FPS gauge로 셀이 등장하는 순간 드롭이 몰리는지 확인하고, os_signpost로 셀 구성 시간을 구간 계측한다. 백그라운드 디코드로 바꾸면 메인 스레드에서 디코드 스택이 사라지고 드롭이 줄어드는 걸 확인할 수 있다.

프로젝트 적용

⚠️ 흔한 오해

"UIImage를 만들면 디코드가 끝난다"는 오해. 디코드는 표시 직전까지 미뤄진다(lazy). 또 "백그라운드 스레드에서 UIImage를 생성만 하면 해결된다"도 부족하다 — 생성만으로는 디코드가 트리거되지 않으므로, 실제 픽셀을 강제로 만들어 두는 byPreparingForDisplay()나 다운샘플처럼 픽셀을 확정하는 처리가 있어야 메인 스레드 부담이 사라진다.

꼬리 질문

Q5. 이미지 Downsampling은 무엇이며 왜 필요한가?

🔑 30초 답변

다운샘플링은 원본 이미지를 표시할 크기에 맞춰 더 작은 픽셀 해상도로 디코드하는 것이다. 100×100 뷰에 4000×3000 원본을 그대로 디코드하면 약 48MB짜리 비트맵을 메모리에 올리고, GPU는 매 프레임 그걸 축소 샘플링해야 한다. ImageIO의 CGImageSourceCreateThumbnailAtIndex로 목표 크기만큼만 디코드하면, 애초에 full-resolution 비트맵을 메모리에 만들지 않아 메모리와 디코드 비용이 함께 급감한다. 리스트·그리드 썸네일에서 특히 효과가 크다.

CS 원리

디코드된 비트맵의 메모리는 파일 용량이 아니라 픽셀 수에 비례한다. 표시 크기보다 큰 해상도를 들고 있는 만큼은 순수 낭비다. 목표 크기로 리샘플(다운스케일)해 두면 메모리 점유, 메모리 대역폭, 그리고 GPU가 샘플링할 텍스처 크기가 모두 줄어든다.

iOS에서는

핵심은 CGImageSourceCreateThumbnailAtIndexkCGImageSourceThumbnailMaxPixelSize로 최대 픽셀 크기를 지정하고 kCGImageSourceShouldCacheImmediately로 그 시점에 디코드까지 끝내는 것이다. 이렇게 하면 full-resolution 이미지를 거치지 않고 곧장 축소된 비트맵을 얻는다. UIGraphicsImageRenderer로 다시 그려 축소할 수도 있지만, 그 방식은 원본을 먼저 full-decode해야 하는 경우가 많아 메모리 피크를 줄이는 목적에는 ImageIO가 유리하다. (WWDC 2018 Image and Graphics Best Practices)

ImageIO 다운샘플 — full decode를 거치지 않고 목표 크기로 (Swift)
func downsample(at url: URL, to pointSize: CGSize, scale: CGFloat) -> UIImage? {
    let srcOpt = [kCGImageSourceShouldCache: false] as CFDictionary
    guard let src = CGImageSourceCreateWithURL(url as CFURL, srcOpt) else { return nil }

    let maxPixel = max(pointSize.width, pointSize.height) * scale
    let opt = [
        kCGImageSourceCreateThumbnailFromImageAlways: true,
        kCGImageSourceShouldCacheImmediately: true,       // 이 시점에 디코드까지 완료
        kCGImageSourceCreateThumbnailWithTransform: true, // EXIF 회전 반영
        kCGImageSourceThumbnailMaxPixelSize: maxPixel
    ] as CFDictionary

    guard let cg = CGImageSourceCreateThumbnailAtIndex(src, 0, opt) else { return nil }
    return UIImage(cgImage: cg)
}

실험 · 도구

Instruments Allocations / VM Tracker(또는 Memory graph)로 이미지가 차지하는 메모리를 다운샘플 전후로 비교한다. 예상 결과: 원본 그대로 로드하면 이미지 하나가 수십 MB를 잡지만, 목표 크기(스케일 포함)로 다운샘플하면 그 크기 제곱에 비례해 수십~수백 KB 수준으로 떨어진다. 스크롤 중 메모리 증가 곡선과 디코드 시간이 함께 줄어드는 걸 볼 수 있다.

프로젝트 적용

⚠️ 흔한 오해

"UIImageView가 알아서 작게 보여주니 메모리도 그만큼 준다"는 오해. 화면에 작게 표시돼도 디코드된 비트맵은 원본 해상도 그대로 메모리를 점유한다. 표시 크기와 메모리 점유는 별개다. 그래서 표시 전에 다운샘플로 픽셀 수 자체를 줄여야 한다.

꼬리 질문

Q6. Layout, Display, Commit 단계의 병목은 어떻게 다른가?

🔑 30초 답변

세 단계는 모두 커밋 트랜잭션의 하위 단계지만 병목 원인이 다르다. LayoutlayoutSubviews와 오토레이아웃 제약 풀이가 CPU 시간을 먹는다(깊은 뷰 계층·제약 폭증에 취약). DisplaydrawRect/Core Graphics로 백킹스토어 비트맵을 CPU 래스터화하며 커스텀 드로잉·텍스트·넓은 영역에 비싸고 메모리도 쓴다. Commit은 완성된 레이어 트리를 재귀적으로 인코딩해 Render Server로 IPC 전송하며 레이어 개수·백킹스토어 전송량에 비례한다. 한마디로 Layout=계산, Display=래스터화+메모리, Commit=트리 크기+IPC.

CS 원리

파이프라인의 각 stage는 병목의 성격이 다르다 — 어떤 단계는 순수 계산량, 어떤 단계는 픽셀 생성과 메모리, 어떤 단계는 직렬화와 프로세스 간 전송이 지배한다. 그래서 "느리다"는 관찰만으로는 부족하고 어느 stage가 예산을 먹는지 특정해야 올바른 처방이 나온다.

Layout 제약 풀이·layoutSubviews Display drawRect·백킹스토어 Prepare 이미지 디코드 Commit 인코딩→Render Server CPU 계산 CPU 래스터 · 메모리 CPU 디코드 직렬화 · IPC
커밋 트랜잭션 4단계. 질문의 Layout·Display·Commit은 병목 원인이 서로 다르다 — 계산 / 래스터·메모리 / 직렬화·IPC. (Prepare는 이미지 디코드·변환 단계로, Q4·Q5가 여기에 걸린다.)

iOS에서는

커밋 트랜잭션은 Layout → Display → Prepare → Commit 4단계다. Layout에서 오토레이아웃은 제약 기반 solver(Cassowary 계열)로 제약 수가 늘수록 비용이 초선형으로 뛴다. Display는 drawRect(_:)를 오버라이드한 뷰에서만 실제로 실행되며 백킹스토어 비트맵을 만든다 — 오버라이드가 없으면 이 단계는 대부분 건너뛴다. Prepare는 이미지 디코드·GPU가 못 쓰는 포맷 변환을 처리한다(그래서 Q4·Q5가 여기 연결된다). Commit은 레이어 트리를 재귀적으로 인코딩해 IPC로 넘기므로 트리가 깊고 넓을수록 비싸다.

단계주요 작업병목 원인주 리소스완화
LayoutlayoutSubviews, 오토레이아웃 제약 풀이깊은 뷰 계층, 제약 수 폭증(초선형 solver)메인 스레드 CPU계층 얕게, 셀 높이 선계산, 제약 재사용
DisplaydrawRect·Core Graphics로 백킹스토어 비트맵 생성커스텀 드로잉·텍스트·넓은 draw 영역, 메모리메인 CPU + 메모리레이어 속성으로 대체, 텍스트·이미지 캐시
Commit레이어 트리 인코딩 → Render Server로 IPC레이어 개수, 백킹스토어 전송량CPU 직렬화 + IPC레이어 수 축소, 큰 백킹스토어 회피

실험 · 도구

Time Profiler에서 메인 스레드 스택을 보면 어느 단계가 무거운지 이름으로 드러난다 — layoutSubviews/오토레이아웃 엔진(Layout), -[CALayer display]·Core Graphics(Display), CA::Transaction::commit(Commit). Core Animation 템플릿의 FPS와 병행해 보고, os_signpost로 의심 구간을 감싸 구간별 시간을 직접 잰다. 뷰 계층이 병목이면 Xcode View Debugger로 실제 계층 깊이를 확인하라.

프로젝트 적용

⚠️ 흔한 오해

"프레임이 느리면 다 GPU 탓"이라는 오해. Layout·Display·Commit은 대부분 메인 스레드의 CPU/메모리/IPC 병목이라 GPU와 무관한 경우가 많다. 반대로 이 세 단계가 예산 안에 끝나도 GPU 합성이 느려 드롭될 수 있다. 그래서 "CPU 쪽(커밋)이 늦었나, GPU 쪽이 늦었나"를 도구로 나눠 보는 게 최적화의 출발점이다.

꼬리 질문

출처 · 참고자료