Home 브라우저는 VSync를 어떻게 활용하고 있을까?
Post
Cancel

브라우저는 VSync를 어떻게 활용하고 있을까?

모니터는 화면을 어떻게 업데이트 하는가?

간단하게 요약하면 애플리케이션이 그래픽 드라이버에게 화면을 그려달라고 요청하면 그대로 그려서 모니터에게 전달하고 전달된 내용을 우리가 보게된다.

image

좀 더 자세히 살펴보자

그래픽 드라이버

그래픽 드라이버는 애플리케이션에서 받은 요청을 바로 모니터로 전달하는 것이 아니라 버퍼라는 저장 공간에서 미리 이미지를 만들어 둔다.

image

그래픽 드라이버는 Front Buffer와 Back Buffer 의 버퍼를 두 개 가지고 있다.

앞서서 얘기한 이미지가 생성시에는 Back Buffer에서 이미지가 그려지다가 다 그려지게 되면 Front Buffer와 역할을 바꾸는데 이를 Buffer Swap이라고 부른다.

Back Buffer에서 이미지가 완성되고 나면 Front Buffer와 위치를 바꾸게되는데 이를 Buffer Swap이라고 한다.

Buffer Swap이 끝나고 나면 사용자는 업데이트되는 화면을 볼 수 있게 된다.

왜 이렇게 복잡할까?

모니터는 60Hz, 144Hz 같은 화면 재생 빈도(display refresh rate)를 가지고 있다.

이 빈도수만큼 (1초에 60번, 144번 만큼) 화면을 Front Buffer에 있는 이미지로 업데이트 해주는데 모니터가 화면을 업데이트하는 도중에 Buffer Swap이 일어나게 되면 이전 Front Buffer를 그리던 화면 위에 Buffer Swap으로 인해서 바뀐 Front Buffer의 내용을 이어서 그리게 된다.

b5c0cfbc52808a65dfc4a9699f48d43f

이런 현상을 티어링 현상이라고 부르고, 당연히 이를 막기 위해 나온 기술도 존재한다.

수직 동기화(VSync)

게임을 자주 하는 사람이라면 티어링은 조금 익숙한 단어일 것이고 수직 동기화도 어느정도 들어봤을 것이다.

수직 동기화는 모니터가 업데이트되는 동안 Buffer Swap이 일어나지 못하게 해서 티어링 현상을 막는 기술이지만 단점이 존재한다.

수직 동기화는 모니터의 화면 업데이트 주기에 맞추어서 Buffer Swap을 발생시켜야 하기 때문에 어플리케이션에서 전달 받은 이미지를 Back Buffer에 다 그렸음에도 불구하고 Front Buffer에 가지 못하고 그렇게 되면 당연히 모니터에 그릴 수 없게 된다. 그 사이에 새로운 요청이 들어온다면..? Back Buffer에는 한 줄기의 빛 조차 보지 못하고 새로운 이미지로 덧씌워지게 된다.

이로 인한 프레임이 누락에 일부 FPS 유저들은 분명히 내가 먼저 쐈는데 내가 먼저 죽는 억울한 상황을 맞이하며 모니터에 돈을 투자하게 된다.

프레임

이 파트를 들어가기 전에 간단히 단어들을 정의하고 가자

  • 프레임: 화면을 업데이트하는데 사용할 픽셀 데이터
  • 프레임 타이밍: 프레임 생성/삭제 등의 시간 정보
  • 프레임 드랍: 모니터 업데이트 주기에 새로운 프레임을 생성하지 못해서 동일한 프레임이 계속해서 보여지는 상황

깔끔한 화면 움직임, 애니메이션을 위해서는 모니터 업데이트 주기 마다 새로운 프레임을 생성해주어서 프레임 드랍이 발생하지 않게 해야한다.

프레임 타이밍

image

모니터 업데이트 주기에 맞춰서 프레임 타이밍이 잘 컨트롤되고 있는 사진이다.

프레임 생성이 완료되었다고 해서 다음 프레임을 생성하는게 아니라 기다렸다가 다음 주기에 맞추어서 새로 생성되고 있어서 사용자가 보지 못하는 프레임 없이 부드러운 애니메이션이 재생되고 있을 것이다.

image

위의 사진보다 많은 프레임을 생성하고 있으니 더 부드러운 애니메이션일까?

그렇지 않다 프레임 타이밍을 컨트롤하고 있지 않아서 프레임이 생성되고나면 바로 다음 프레임을 생성해버린다 그 결과 다음 모니터 주기에 반영되지 않아서 오히려 리소스만 낭비해버리게되었다.

브라우저에서의 프레임 타이밍 컨트롤

그럼 이제 드디어 웹 개발과 관련된 부분으로 돌아와서 질문을 던져보자면 브라우저에서 어떻게 프레임 타이밍을 컨트롤해서 부드러운 애니메이션을 보여줄 수 있을까?

브라우저는 한 번의 프레임을 생성하기위해 굉장히 많은 작업을 거쳐야한다. (흔히 면접 단골 질문인 style -> layout -> paint -> composite 가 그 것이다.)

대부분의 모니터는 60fps를 보여주고 있으니까 setInterval를 16.6ms마다 찍어내면 되는 걸까?

물론 이렇게 하면 고정된 주기를 가지고 프레임은 생성하겠지만 이 주기가 모니터 업데이트 주기와 동일함을 보장받지는 못한다.

VSync

여기서 다시 VSync가 등장하는데 앞선 VSync를 설명할 때 모니터 업데이트 주기에 맞게 Buffer Swap을 제한한다고 했었다.

모니터 업데이트 주기에 대한 정보가 VSync에 들어있기 때문에 VSync를 이용하면 모니터 업데이트 주기에 맞게 프레임을 생성해 줄 수 있다.

Display refresh주기에 맞춰 호출되는 콜백

어디서 많이 들어본 문장이지 않은가?

프레임에 맞춰서 호출되는 콜백.. 그렇다 우리가 애니메이션을 만들 때 자주 사용하는 함수인 requestAnimationFrame(rAF)다.

크로미움에서 해당 파트를 담당하고 있는 개발자분은 rAF가 만능은 아니며 모든 경우에서 모니터 업데이트 주기에 콜백이 실행되지는 않고 브라우저 스펙이 잘 맞춰져야하고 해당하는 프레임에서도 JavaScript 코드가 너무 커서도 안된다고 얘기한다.

image

보너스 - requestIdleCallback

rAF는 많이 들어봤겠지만 이건 조금 생소한 함수다.

cooperatively schedule background tasks such that they do not introduce delays to other high priority tasks that share the same event loop, such as input processing, animations and frame compositing

https://w3c.github.io/requestidlecallback/

간단히 요약하면 input, animation과 같은 이벤트 루프를 사용하지만 그들의 실행을 지연시키지 않으며 그들의 실행이 모두 끝나면 idleCallback을 실행한다

어떻게 같은 이벤트 루프를 쓰면서 이러한 실행이 가능한 걸까?

image

main thread에서의 일이 빠르게 끝난다면 다음 프레임까지 시간이 남게되는데 같은 이벤트 루프를 사용하고 있는 idleCallback가 사용하게된다. 이 역시도 브라우저에서 정확한 프레임 주기를 알고 있어야 정확한 작동이 보장된다.


참고

브라우저는 vsync를 어떻게 활용하고 있을까

This post is licensed under CC BY 4.0 by the author.