프레임 속도 라이브러리 Android Game Development Kit의 일부.

Swappy라고도 하는 Android 프레임 속도 라이브러리는 AGDK 라이브러리의 일부입니다. 이 라이브러리는 OpenGL 및 Vulkan 게임이 Android에서 원활한 렌더링 및 올바른 프레임 속도를 달성하는 데 도움이 됩니다. 이 문서에서는 프레임 속도를 정의하고 프레임 속도가 필요한 상황을 설명하며 라이브러리가 이러한 상황을 해결하는 방식을 보여줍니다. 게임에서 프레임 속도 구현으로 바로 이동하려면 다음 단계를 참고하세요.

배경

프레임 속도는 게임의 로직 및 렌더링 루프를 OS의 디스플레이 하위 시스템 및 기본 디스플레이 하드웨어와 동기화하는 것입니다. Android 디스플레이 하위 시스템은 업데이트 도중 디스플레이 하드웨어가 새로운 프레임으로 전환할 때 발생할 수 있는 시각적 아티팩트(테어링이라고 함)를 방지하도록 설계되었습니다. 이러한 아티팩트를 방지하기 위해 디스플레이 하위 시스템은 다음을 수행합니다.

  • 지나간 프레임을 내부적으로 버퍼링
  • 늦은 프레임 제출 감지
  • 늦은 프레임 감지 시 지나간 프레임 표시 반복

게임은 디스플레이 하위 시스템 내의 컴포지터인 SurfaceFlinger에 프레임에 필요한 모든 그리기 호출을 제출했음을 알립니다(eglSwapBuffers 또는 vkQueuePresentKHR 호출을 통해). SurfaceFlinger는 래치를 사용하여 디스플레이 하드웨어에 프레임 사용 가능 여부를 알립니다. 그러면 디스플레이 하드웨어는 지정된 프레임을 표시합니다. 디스플레이 하드웨어는 일정한 속도(예: 60Hz)로 틱하며 새 프레임이 필요할 때 그 프레임이 없다면 하드웨어는 이전 프레임을 다시 표시합니다.

게임 렌더링 루프가 네이티브 디스플레이 하드웨어와 다른 속도로 렌더링할 때 일관되지 않은 프레임 시간이 종종 발생합니다. 30FPS로 실행되는 게임이 기본적으로 60FPS를 지원하는 기기에서 렌더링하려고 하면 게임 렌더링 루프는 반복되는 프레임이 추가 16밀리초 동안 화면에 남아 있음을 인식하지 못합니다. 이러한 연결 끊김은 일반적으로 49밀리초, 16밀리초, 33밀리초와 같이 프레임 시간에 상당한 불일치를 초래합니다. 과도하게 복잡한 장면은 누락된 프레임을 발생시키므로 이 문제를 더욱 악화시킵니다.

최적화되지 않은 솔루션

프레임 속도를 위한 다음 솔루션은 과거에 게임에서 사용되어 왔으며 일반적으로 일관되지 않은 프레임 시간 및 입력 지연 시간 증가를 야기합니다.

렌더링 API가 허용하는 한 신속하게 프레임 제출

이 접근 방식은 게임을 가변적인 SurfaceFlinger 활동에 연결하고 추가 프레임 지연 문제를 초래합니다. 디스플레이 파이프라인에는 일반적으로 크기가 2인 프레임 큐가 포함되어 있으며 이 큐는 게임이 프레임을 너무 빠르게 표시하려고 하는 경우에 채워집니다. 큐에 더 이상 공간이 없으면 게임 루프(또는 최소한 렌더링 스레드)가 OpenGL 또는 Vulkan 호출에 의해 차단됩니다. 그러면 게임은 디스플레이 하드웨어가 프레임을 표시할 때까지 기다려야 하며 이 백 프레셔는 두 구성요소를 동기화합니다. 이 상황을 버퍼 스터핑 또는 큐 스터핑이라고 합니다. 렌더기 프로세스는 무슨 일이 일어나고 있는지 인식하지 못하므로 프레임 속도 불일치가 악화됩니다. 게임이 프레임 이전에 입력을 샘플링하면 입력 지연 시간이 악화됩니다.

자체 Android Choreographer 사용

게임은 동기화를 위해 Android Choreographer도 사용합니다. API 16의 경우 자바에서, API 24의 경우 C++에서 사용할 수 있는 이 구성요소는 디스플레이 하위 시스템과 동일한 빈도로 정기적인 틱을 제공합니다. 이 틱이 실제 하드웨어 VSYNC와 관련하여 언제 제공되는지에 관해서는 여전히 미묘한 차이가 있으며 이러한 오프셋은 기기에 따라 다릅니다. 긴 프레임의 경우 버퍼 스터핑이 여전히 발생할 수 있습니다.

Frame Pacing 라이브러리의 이점

Frame Pacing 라이브러리는 동기화를 위해 Android Choreographer를 사용하고 틱 제공의 가변성을 자동으로 처리합니다. 또한 프레젠테이션 타임스탬프를 사용하여 프레임이 적절한 시간에 표시되는지 확인하고 펜스를 동기화하여 버퍼 스터핑을 방지합니다. 이 라이브러리는 NDK Choreographer가 사용 가능하다면 NDK Choreographer를 사용하고 사용 가능하지 않다면 자바 Choreographer로 대체합니다.

기기에서 여러 새로고침 빈도를 지원한다면 이 라이브러리는 여러 새로고침 빈도를 처리하며 이를 통해 게임에 프레임을 더욱 유연하게 표시할 수 있습니다. 예를 들어 90Hz뿐만 아니라 60Hz 새로고침 빈도를 지원하는 기기의 경우 초당 60프레임을 구현할 수 없는 게임은 원활한 유지를 위해 30FPS 대신 45FPS로 떨어질 수 있습니다. 이 라이브러리는 예상 게임 프레임 속도를 감지하고 그 속도에 따라 프레임 프레젠테이션 시간을 자동으로 조정합니다.

작동 방식

다음 섹션에서는 Frame Pacing 라이브러리가 올바른 프레임 속도를 달성하기 위해 긴 게임 프레임 및 짧은 게임 프레임을 처리하는 방식을 보여줍니다.

30Hz에서 올바른 프레임 속도

60Hz 기기에서 30Hz로 렌더링할 때 Android의 이상적인 상황은 그림 1에 나와 있습니다. SurfaceFlinger는 새 그래픽 버퍼가 있는 경우 이를 래치합니다(다이어그램의 NB는 '버퍼 없음'을 나타내며 이 경우 이전 버퍼가 반복됨).

60Hz 기기에서 30Hz의 이상적인 프레임 속도

그림 1. 60Hz 기기에서 30Hz의 이상적인 프레임 속도

짧은 게임 프레임으로 인해 끊김 현상 발생

대부분의 최신 기기에서 게임 엔진은 프레임 제출을 촉진하기 위해 틱을 제공하는 플랫폼 Choreographer를 사용합니다. 그러나 그림 2와 같이 짧은 프레임으로 인해 프레임 속도 저하가 발생할 가능성이 여전히 있습니다. 짧은 프레임 이후에 긴 프레임이 나오면 플레이어는 끊김 현상으로 인식합니다.

짧은 게임 프레임

그림 2. 짧은 게임 프레임 C로 인해 프레임 B가 한 프레임만 표시하게 되며 이후 여러 C 프레임이 표시됨

Frame Pacing 라이브러리는 프레젠테이션 타임스탬프를 사용하여 이 문제를 해결합니다. 이 라이브러리는 프레젠테이션 타임스탬프 확장, EGL_ANDROID_presentation_timeVK_GOOGLE_display_timing을 사용하므로 그림 3과 같이 프레임이 일찍 표시되지 않습니다.

프레젠테이션 타임스탬프

그림 3. 더 원활한 표시를 위해 게임 프레임 B가 두 번 표시됨

긴 프레임으로 인해 끊김 현상 및 지연 시간 발생

디스플레이 워크로드가 애플리케이션 워크로드보다 더 오래 걸리면 추가 프레임이 큐에 추가됩니다. 이로 인해 다시 한번 끊김 현상이 발생하고, 버퍼 스터핑으로 인해 추가 프레임 지연이 발생할 수 있습니다(그림 4 참고). 라이브러리는 끊김 현상과 추가 프레임 지연 문제를 모두 해소합니다.

긴 게임 프레임

그림 4. 긴 프레임 B는 2개 프레임(A 및 B)에 잘못된 속도를 제공함

라이브러리는 동기화 펜스(EGL_KHR_fence_syncVkFence)를 사용해 애플리케이션에 대기를 삽입하여 백 프레셔가 쌓이지 않도록 하고 디스플레이 파이프라인이 따라잡을 수 있도록 함으로써 이 문제를 해결합니다. 그림 5와 같이 프레임 A는 여전히 추가 프레임을 표시하지만 이제 프레임 B는 올바르게 표시됩니다.

애플리케이션 레이어에 추가된 대기

그림 5. 프레임 C 및 D가 표시되기를 기다림

지원되는 작동 모드

다음 세 가지 모드 중 하나에서 작동하도록 Frame Pacing 라이브러리를 구성할 수 있습니다.

  • 자동 모드 사용 중지 + 파이프라인
  • 자동 모드 사용 + 파이프라인
  • 자동 모드 사용 + 자동 파이프라인 모드(파이프라인/비파이프라인)

자동 모드 및 파이프라인 모드로 실험할 수 있지만 먼저, 이러한 모드를 사용 중지하고 Swappy를 초기화한 후 다음을 포함합니다.

  swappyAutoSwapInterval(false);
  swappyAutoPipelineMode(false);
  swappyEnableStats(false);
  swappySwapIntervalNS(1000000000L/yourPreferredFrameRateInHz);

파이프라인 모드

라이브러리는 엔진 워크로드를 조정하기 위해 일반적으로 VSYNC 경계를 넘어 CPU 및 GPU 워크로드를 분리하는 파이프라이닝 모델을 사용합니다.

파이프라인 모드

그림 6. 파이프라인 모드

비파이프라인 모드

일반적으로 이 접근 방식을 사용하면 입력 화면 지연 시간이 더 짧아지고 예측 가능하게 됩니다. 게임의 프레임 시간이 매우 짧은 경우 CPU 워크로드와 GPU 워크로드가 모두 단일 스왑 간격에 적합할 수 있습니다. 이 경우 파이프라인을 사용하지 않는 접근 방식은 실제로 더 짧은 입력 화면 지연 시간을 제공합니다.

비파이프라인 모드

그림 7. 비파이프라인 모드

자동 모드

대부분의 게임은 각 프레임이 표시되는 기간(예: 30Hz의 경우 33.3ms)인 스왑 간격을 선택하는 방법을 모릅니다. 일부 기기에서는 게임이 60FPS로 렌더링될 수 있지만 다른 기기에서는 FPS를 더 낮은 값으로 내려야 할 수 있습니다. 자동 모드는 다음을 처리하기 위해 CPU 및 GPU 시간을 측정합니다.

  • 자동으로 스왑 간격 선택: 일부 장면에서는 30Hz를, 다른 장면에서는 60Hz를 구현하는 게임을 사용하면 라이브러리가 이 간격을 동적으로 조정할 수 있습니다.
  • 초고속 프레임을 위한 파이프라이닝 비활성화: 모든 경우에 최적의 입력 화면 지연 시간을 제공합니다.

여러 새로고침 빈도

여러 새로고침 빈도를 지원하는 기기는 원활하고 자연스럽게 보이는 스왑 간격을 선택할 수 있도록 다음과 같이 더 높은 유연성을 제공합니다.

  • 60Hz 기기: 60FPS/30FPS/20FPS
  • 60Hz + 90Hz 기기: 90FPS/60FPS/45FPS/30FPS
  • 60Hz + 90Hz + 120Hz 기기: 120FPS/90FPS/60FPS/45FPS/40FPS/30FPS

라이브러리는 게임 프레임의 실제 렌더링 기간과 가장 적합한 새로고침 빈도를 선택하여 더 나은 시각적 환경을 구현합니다.

여러 새로고침 빈도 프레임 속도에 관한 자세한 내용은 Android의 높은 새로고침 빈도 렌더링 블로그 게시물을 참고하세요.

프레임 통계

Frame Pacing 라이브러리는 디버깅 및 프로파일링 목적으로 다음 통계를 제공합니다.

  • 렌더링이 완료된 후 프레임이 컴포지터 큐에서 대기한 화면 새로고침 수에 관한 히스토그램
  • 요청된 프레젠테이션 시간과 실제 표시 시간 사이에 전달된 화면 새로고침 수에 관한 히스토그램
  • 연속된 두 프레임 사이에 전달된 화면 새로고침 수에 관한 히스토그램
  • 이 프레임에 대한 CPU 작업 시작과 실제 표시 시간 사이에 전달된 화면 새로고침 수에 관한 히스토그램

다음 단계

Android Frame Pacing 라이브러리를 게임에 통합하려면 다음 가이드 중 하나를 참고하세요.