콘텐츠로 이동

Android Swappy FPS 고착으로 느린 세션 비율이 높아진 문제 해결

  • 환경: Unity 6.x, Android (모든 버전), Samsung Galaxy (모든 One UI 버전)
  • 해결 방법: FPS 값을 Swappy 호환 값으로 변경

Google Play Console의 Android Vitals 렌더링 지표에서 앱의 느린 세션 비율이 동종 앱 중앙값보다 높게 나타났다.

  • Samsung Game Booster의 터치 방지 기능을 쓴 뒤 FPS가 30에서 복구되지 않는다.
  • FPS가 30과 45 사이를 오가며 불안정하다.
  • Android Vitals에서 Android 16 (SDK 36) 기기의 Slow Session 비율이 급증했다.
환경 영향 비고
모든 Android 버전 잠재적 영향 Swappy 사용 시 비호환 FPS를 설정하면 발생
모든 One UI 버전 잠재적 영향 Game Booster 터치 방지 기능과 충돌
Unity Optimized Frame Pacing 사용 프로젝트 해당 Swappy 라이브러리 자동 활성화

느린 세션으로 잡힌 지표의 특징(모두 삼성 갤럭시 기기, Android 16 기기에 몰림)을 바탕으로 AI 분석으로 가설을 세우고, 의심스러운 것부터 하나씩 테스트해 소거했다.

가설 확인 방법 결과
1. Optimized Frame Pacing(Swappy) 자체의 문제 옵션을 끈 빌드로 테스트 재현되지 않음. 다만 Swappy를 끄는 것은 렌더링 품질을 희생하는 타협이라 해결책이 아니다.
2. Adaptive Performance 대신 만든 Custom AdaptiveFPS 로직의 문제 배터리 절약 모드에서 동작 관찰 절약 모드는 앱을 Pause하지 않아 Reset이 호출되지 않지만, 5초 주기 검사가 FPS 상태를 정상으로 관리했다. 정상 동작
3. Graphics API(Vulkan, OpenGLES3) 호환성 문제 사용 중인 API 확인 이미 Vulkan을 쓰지 않음. 기각
4. OS가 앱을 게임으로 인식하지 못하는 문제 Game Booster에 앱을 게임으로 직접 등록 같은 문제 재현. 가능성 낮음
5. Unity와 Android 버전 호환성 문제 다음 배포용 Unity 6000.3.4f1로 빌드 같은 문제 재현. 이 과정에서 Unity 이슈 트래커의 60Hz 기기에서 45Hz 목표 프레임을 설정할 수 없는 이슈를 찾았다.

가설 1(Frame Pacing이 관여)과 가설 5(45 FPS 설정 불가)의 단서로 코드를 다시 보니, Custom AdaptiveFPS 로직이 Quality.Mid에서 45 FPS를 요청하고 있었다.

1. Android Swappy (Frame Pacing Library)란?

섹션 제목: “1. Android Swappy (Frame Pacing Library)란?”

Unity의 Optimized Frame Pacing 옵션을 켜면 Android에서 Swappy 라이브러리를 쓴다.

Player Settings → Resolution and Presentation → Optimized Frame Pacing ✅

Swappy는 Google Android Game Development Kit(AGDK)의 일부로, 프레임 타이밍을 최적화해 화면 출력을 부드럽게 만든다.

항목 ON (Swappy 활성화) OFF (Swappy 비활성화)
FPS 제어 방식 Swappy가 Refresh Rate 약수로 강제 변환 Unity 기본 프레임 제한 (VSync는 항상 ON)
45 FPS 설정 시 60Hz→30fps, 120Hz→40fps로 변환 45fps 그대로 시도 (프레임 타이밍 불균일)
프레임 일관성 프레임 간격 균일 프레임 간격 불균일 가능
배터리 효율 불필요한 화면 갱신 방지 불필요한 화면 갱신 발생
FPS 고착 문제 발생 가능 발생하지 않음

OFF로 설정하면 왜 재현되지 않는가

섹션 제목: “OFF로 설정하면 왜 재현되지 않는가”

Optimized Frame Pacing OFF 시:

  • Swappy 라이브러리가 비활성화된다.
  • Application.targetFrameRate 값이 Android 기본 VSync 메커니즘으로 처리된다.
  • Swappy의 Divisor 알고리즘이 적용되지 않아 FPS 강제 변환이 일어나지 않는다.
  • Game Booster가 Refresh Rate를 바꿔도 Swappy의 swap interval 고착이 없다.

그래도 OFF를 권장하지 않는 이유:

  • 프레임 타이밍이 불균일해져 화면 끊김(jittering)이 생길 수 있다.
  • 배터리 소모가 늘어난다 (120Hz 디스플레이에서 60fps 렌더링 시 불필요한 갱신).

Swappy는 Refresh Rate의 정수 약수인 FPS만 허용한다.

허용 FPS = Refresh Rate / 정수
Refresh Rate 허용 FPS (정수 약수)
60Hz 60, 30, 20, 15, 12, 10…
120Hz 120, 60, 40, 30, 24, 20…

기존 코드는 Mid Quality를 45 FPS로 설정했다.

public int GetFrameRate(Quality quality)
{
if (quality == Quality.Mid)
return 45; // ❌ 문제!
else if (quality >= Quality.High)
return 60;
return 30;
}

45 FPS는 Swappy 비호환이다.

60Hz / 45 = 1.33... (정수 아님!) ❌
120Hz / 45 = 2.67... (정수 아님!) ❌

Swappy는 비호환 FPS 요청을 받으면 가장 가까운 유효 약수로 자동 변환한다.

Refresh Rate 요청 FPS Swappy 변환 결과 이유
60Hz 45 FPS 30 FPS 가장 가까운 약수 (60/2)
120Hz 45 FPS 40 FPS 가장 가까운 약수 (120/3)

One UI 5, 120Hz 기기에서 테스트한 결과는 다음과 같다.

  • targetFrameRate = 45로 설정하면 실제 FPS가 40 FPS로 측정된다.
  • Swappy가 120Hz에서 유효한 약수인 40 FPS(120/3=40)로 자동 변환했기 때문이다.
  • 60Hz 기기에서는 같은 설정이 30 FPS로 변환된다(60/2=30).

One UI 6 이하

  • Game Booster에 48Hz 옵션이 있다.
  • 45 FPS가 48Hz 모드에서 부분적으로 동작한다.

One UI 7 (Android 15) / One UI 8 (Android 16)

  • 48Hz 옵션이 제거됐다.
  • 60Hz 또는 120Hz만 선택할 수 있다.
  • 45 FPS는 60Hz와 120Hz 어느 쪽의 정수 약수도 아니다.

5. Game Booster 터치 방지 시나리오

섹션 제목: “5. Game Booster 터치 방지 시나리오”
1. 정상 상태: 60Hz, 60 FPS (Divisor = 1)
↓
2. 터치 방지 활성화: Game Center가 절전 모드 적용
↓
3. 시스템이 30Hz/30 FPS로 강제 변경
↓
4. Swappy 내부 swap interval = 33.3ms (30 FPS)
↓
5. 터치 방지 해제: 시스템은 60Hz로 복귀
↓
6. 하지만 Swappy의 swap interval은 33.3ms로 "고착"
↓
7. targetFrameRate = 60 설정해도 실제 30 FPS 유지 ❌

변경 전 (문제)

// High: 60, Mid: 45, Low: 30
public int GetFrameRate(Quality quality)
{
if (quality == Quality.Mid)
return 45; // ❌ Swappy 비호환
else if (quality >= Quality.High)
return 60;
return 30;
}

변경 후 (해결)

// High: 60, Mid: 30, Low: 20
public int GetFrameRate(Quality quality)
{
if (quality == Quality.Mid)
return 30; // ✅ 60Hz/2, 120Hz/4
else if (quality >= Quality.High)
return 60; // ✅ 60Hz/1, 120Hz/2
return 20; // ✅ 60Hz/3, 120Hz/6
}
Quality FPS 60Hz Divisor 120Hz Divisor 결과
High 60 60/60 = 1 120/60 = 2 호환
Mid 30 60/30 = 2 120/30 = 4 호환
Low 20 60/20 = 3 120/20 = 6 호환
  • One UI 5 (Android 13): 문제 해결 확인
  • One UI 8 (Android 16, Galaxy S25): 문제 해결 확인
  • 터치 방지 해제 후 FPS가 정상 복구된다.
  1. Swappy는 정수 약수 FPS만 허용한다
    • 45, 50 같은 비표준 FPS는 Swappy가 가장 가까운 약수로 바꿔, 요청한 값과 실제 FPS가 달라진다.
  2. One UI 7+에서 48Hz 옵션이 제거됐다
    • One UI 6 이하의 48Hz 모드에서 부분적으로 동작하던 45 FPS 설정이 One UI 7 이상에서는 맞는 모드가 없다.
  3. 디바이스 Refresh Rate를 반드시 고려한다
    • 60Hz와 120Hz 모두에서 정수 약수가 되는 FPS를 고른다.
  4. Android Vitals를 모니터링한다
    • 특정 OS 버전에서 급증하는 문제는 플랫폼 변경을 의심한다.

프로젝트의 Swappy 호환성을 확인한다.

  • Optimized Frame Pacing을 사용 중인가?
  • 모든 FPS 설정값이 60Hz/120Hz의 정수 약수인가?
  • 45, 50 같은 비표준 FPS를 쓰고 있지 않은가?
  • Android 16 (One UI 8) 기기에서 테스트했는가?
  • Game Booster 터치 방지 시나리오를 테스트했는가?