(번역) CSS GPU 애니메이션 제대로 하기

Posted by in Research, Translation

원문: https://www.smashingmagazine.com/2016/12/gpu-animation-doing-it-right/
본 번역문은 원작자인 Sergey Chikuyonok의 동의하에 번역 및 게시되었습니다.

이제 대부분의 사람들이 모던 웹 브라우저가 GPU를 사용해서 웹 페이지의 일부를 렌더링 한다는 사실을 알고 있습니다. 예를 들어, trasnform 속성을 사용해서 CSS 애니메이션을 구현하면 left나  top 속성을 썼을 때 보다 훨씬 부드러워 보입니다. 그러나 만약 “어떻게 GPU를 사용해서 부드러운 애니메이션을 만들어 낼 수 있을까요?”라는 질문을 던져본다면, 아마 들려오는 대답은 ” transform: translateZ(0)이나 will-change를 쓰세요”라는 말일 것입니다.

우리는 마치 IE6 대응을 위해 zoom:1을사용하는 것처럼, 이 두가지 속성을 GPU가 애니메이션 작업을(혹은 브라우저 벤더들이 좋아하는 방식으로 표현하자면, ‘컴포지팅’을) 할 수 있도록 준비 하는 용도로 사용하기 시작했습니다.

하지만 간단하게 데모로 만들어 봤을 때는 부드럽고 훌륭하게 재생 되던 애니메이션이 실제 웹사이트에서는 매우 느려지는 일이 가끔가다 벌어지고는 합니다. 이 때문에 애니메이션이 우리 눈에 부자연스럽게 보이게 되거나, 심지어 충돌이 나서 브라우저 작동이 멈추기도 합니다. 왜 이런 일이 일어나는 걸까요? 어떻게 고치면 될까요? 이해해보려고 시도해 봅시다.

중요한 한가지 법적 책임 부인(Disclaimer) (원문링크)

GPU 컴포지팅을 자세히 알아보기 전에

여러분께 말씀드려야 할 한가지 중요한 사실이 있습니다. 바로 GPU 컴포지팅은 하나의 거대한 핵이라는 사실이죠. (지금 이 글을 쓰는 시점 기준으로) W3C 명세서를 봐도 컴포지팅이 어떻게 동작하는지, 직접적인 방법을 사용해 요소를 컴포짓 레이어에 어떻게 올릴 수 있는지, 심지어 레이어 자체를 어떻게 컴포지팅 하는지에 대한 설명은 찾아볼 수가 없을 겁니다. GPU 컴포지팅은 단지 브라우저가 특정 업무를 수행하도록 브라우저 벤더들이 각자 나름대로의 방법을 사용하여 적용해 둔 최적화 방식일 뿐입니다.

이 글에서 다루게 될 내용들은 모두 GPU 컴포지팅 동작법에 대한 공식적인 설명이 아닙니다. 그보다는 제가 가지고 있는 약간의 상식과 브라우저 하위 시스템 동작 방식에 대한 지식을 경험과 함께 엮어낸 결과에 가깝습니다. 어떤 내용들은 그냥 틀린 것일 수도 있고, 또 어떤 것들은 시간이 지남에 따라 바뀔 수도 있습니다. 여러분은 이제 분명 제 경고를 들으셨습니다!

페이지 내에서 GPU 애니메이션이 재생되도록 하려면, 인터넷이나 이 글에서 찾아 볼 수 있는 몇가지 조언만 임의로 따를게 아니라, 브라우저 상에서 정확히 어떤 일들이 일어나는지 이해할 필요가 있습니다.

A와 B요소가 있는 페이지가 있다고 해봅시다. 둘 다 position: absolute 로 띄워 두었고,  z-index값은 서로 다르게 매겨져 있습니다. 브라우저는 CPU에서 이들 요소를 페인트 한 후에 얻어낸 이미지를 GPU로 넘기게 됩니다. 그런 다음 화면에 이미지가 나오게 됩니다.

이제 left 속성과 CSS 애니메이션을 사용해 A 요소를 움직이기로 결정한 상황이라 해봅시다.

이 경우, 모든 CSS 애니메이션 프레임마다 브라우저는 요소의 기하 구조를 계속해서 다시 계산해야 하고, (이 과정을 리플로우라고 합니다) 그렇게 해서 얻어낸 새로운 모습의 페이지를 다시 그려내야 합니다. (이 과정은 리페인트라고 합니다.) 그 다음 변경된 페이지 상태를 화면에 반영 되도록 하기 위해 GPU로 데이터를 전송해야 합니다. 리페인팅 작업이 매우 성능 소모적인 작업인 것은 널리 알려져 있습니다. 그러나 모든 최신 브라우저는 페이지의 바뀐 부분에서만 리페인트를 발생시킬만큼 충분히 똑똑합니다. 거의 대부분의 경우에 브라우저는 매우 빠르게 리페인트 작업을 할 수 있으나, 여전히 애니메이션은 부드러워 보이지 않습니다.

애니메이션의 각 스텝마다 페이지 전체를 리플로우하고 리페인팅하는 작업은 속도가 매우 느리게 이루어질 것 같습니다. (심지어 부분적으로만 이 과정들이 진행된다 하더라도요.) 특히 복잡하고 거대한 레이아웃이 존재한다면 말이죠. 이렇게 작업하기 보다는 두 개의 별개 이미지를 따로따로 페인트 한다면 훨씬 더 효율적일 것 같습니다. 한 이미지는 A 요소를, 그리고 다른 이미지는 A 요소가 없는 전체 페이지로 구성한 후 두 요소 사이의 오프셋 값을 적용하기만 하면 될 것 같습니다. 다시 말해, 요소를 캐시한 후 이를 이미지로 만들어 조합하면 작업이 더 빠를 것 같다는 이야기입니다. 그리고 이 때가 바로 GPU의 능력이 빛을 발하는 때입니다. GPU는 이미지를 매우 빠르게 합성할 수 있는데, 여기에 매혹적인 부드러움을 애니메이션에 더해주는 서브픽셀 보정(subpixel precision)이라는 것도 함께 적용할 수 있는 능력 또한 같이 가지고 있습니다.

컴포지팅 작업을 최적화 하려면, 애니메이션이 적용되는 CSS 속성이 다음과 같은 상태임을 브라우저가 확실히 알고 있어야 합니다.

  • 문서 플로우에 영향을 미치지 않는다.
  • 문서 플로우와 의존 관계를 맺고 있지 않다.
  • 리페인트를 발생시키지 않는다.

누군가는 top과 left 속성이 absolutefixed 속성처럼 요소가 위치한 환경과 관련이 없다고 생각할 지도 모르나, 실상은 그렇지 않습니다. 예를 들어 left 속성은 .offsetParent 크기에 따른 퍼센트 값을 속성값으로 지닐 수도 있습니다. 혹은 em, vh 등등 환경에 영향을 받는 단위로 값이 설정될 수도 있습니다. 위에서 나열한 조건에 일치하는 CSS 속성은 오로지 transform 과 opacity 뿐입니다.

left 대신에 transform으로 애니메이션을 적용해 보겠습니다.

위에서 본 코드에서는 시작점과 끝점, 지속시간 등 애니메이션에 대한 속성을 일일이 나열했습니다. 이 코드는 어떤 CSS 속성들이 업데이트 되어야 할지 브라우저에게 미리 알려주는 역할을 합니다. 브라우저가 보기에는 그 어떤 속성도 리플로우나 리페인트를 일으킨다고 생각하지 않기 때문에, 여기서 컴포지팅 최적화 적용을 시도해 볼 수 있습니다. 두 이미지를 컴포짓 레이어로 페인팅 한 후 이 레이어들을 GPU로 보내는 것입니다.

이런 최적화 방식의 장점은 무엇이 있을까요?

  • 그래픽 태스크에 최적화된 특별한 유닛(GPU) 위에서 진행되는 서브픽셀 보정 덕분에 비단결 처럼 부드러운 애니메이션 효과를 얻게 됩니다. 그리고 이는 매우 빠른 속도로 진행됩니다.
  • 애니메이션이 더이상 CPU에 묶여 있지 않게 됩니다. 매우 복잡스런 자바스크립트 태스크가 돌아가고 있다 하더라도 애니메이션은 여전히 빠르게 진행이 됩니다.

모든 것이 상당히 명확하고 쉽게 다가오는 것 같습니다. 그렇나요? 이제 어떤 문제가 우리 앞을 가로막게 될까요? 최적화가 실제로 이루어지는 과정을 살펴 봅시다.

여러분이 들으면 놀라실지도 모르겠는데, GPU는 별도의 컴퓨터 입니다. 맞아요. 모든 현대 기기에서 중요한 역할을 하는 GPU는 사실 자신만의 프로세서와 메모리, 데이터 처리 모델을 가지고 있는 독립적인 유닛입니다. 그리고 다른 앱이나 게임이 GPU와 상호작용하는 것처럼, 브라우저는 외부 기기와 소통하는 것과 동일한 방식으로 GPU와 이야기를 나눌 필요가 있습니다.

이 소통 방식에 대해 좀 더 잘 이해하고 싶다면, AJAX를 떠올려 보세요. 사이트 방문자들이 웹 폼에 입력한 데이터를 가지고 그들을 회원으로 등록 시켜야 하는 상황이라고 가정해 봅시다. 원격 서버에 단지 다음과 같이 말하는 것만으로는 충분하지 않습니다. “안녕 서버야, 여기 있는 입력 필드 데이터랑 자바스크립트 변수 좀 가져다가 데이터베이스에 저장해 두렴.” 원격 서버는 사용자 브라우저의 메모리에 접근할 수 있는 권한이 없습니다. 그보다는 페이지의 데이터를 모아서, 쉽게 파싱이 가능한 간단한 데이터 포맷(예를 들면 JSON)으로 페이로드를 생성해 이것을 원격 서버로 보내는 편이 낫습니다.

이것과 굉장히 유사한 일이 컴포지팅 과정에서 일어납니다. GPU는 원격 서버와 비슷하기 때문에, 브라우저는 페이로드를 먼저 생성하여 이를 기기로 전송해야 합니다. 물론 GPU는 CPU로부터 몇천 킬로미터 떨어져 있지는 않습니다. 그러나, 원격 서버에 요청을 보내고 응답을 받는 데 걸리는 2초는 웬만해서는 용납되는 시간이지만, GPU 데이터 전송을 하는 데 3~5 밀리초(ms)가 더 걸리면 이는 곧 애니메이션의 버벅임으로 이어질 수 있습니다.

GPU 페이로드는 어떻게 생겼을까요? 대부분의 경우, 레이어 이미지를 주로 하고 여기에 부가적으로 레이어 크기와 오프셋, 애니메이션 파라미터 등 기타 부가적인 지시 사항이 붙게 됩니다. 아래 목록은 페이로드를 형성하는 것과 이 데이터를 GPU로 보내는 과정을 간략하게 요약한 것입니다.

  • 각각의 컴포짓 레이어를 개별적인 이미지로 페인트한다.
  • 레이어 데이터(크기, 오프셋, 투명도 등등)를 준비한다.
  • (적용할 수 있으면) 애니메이션에 적용할 쉐이더를 준비한다.
  • GPU로 데이터를 보낸다.

보시다시피, 여러분이 마법의 transform: translateZ(0)와 will-change: transform을 요소에 추가할 때마다, 매번 거의 동일한 과정을 다시 반복하게 됩니다. 리페인팅은 성능 비용이 매우 높은데, 이 과정은 심지어 리페인팅보다 더 느리기까지 합니다. 거의 대부분의 경우 브라우저는 리페인트를 부분적으로 할 수 없습니다. 새롭게 생성된 컴포짓 레이어를 가지고 이전에 페인트 된 영역을 다시 페인트 해야 합니다.

A와 B 요소 예제로 돌아가 봅시다. 아까는 페이지의 다른 모든 요소들 보다 상위에 위치한 요소 A에 애니메이션을 적용했었습니다. 이로 인해 컴포짓 레이어 두개가 나오게 되었습니다. 한 레이어에는 A 요소가 들어 있고, 다른 하나에는 페이지 배경과 B 요소가 들어 있습니다.

이제, A 요소 말고 B 요소에 대신 애니메이션을 적용해 봅시다.

논리 상의 문제가 발생했습니다. 요소 B는 별도의 컴포짓 레이어 위에 있어야만 하고, 화면에 최종적으로 나올 페이지 이미지는 GPU 위에서 합성이 되어야만 합니다. 그런데 A 요소는 B 요소 위에 나타나야만 하는데, 아직 A가 자신만의 컴포짓 레이어를 가지도록 하는 조치를 아무것도 취해두지 않은 상태입니다.

앞에서 말씀드린 중요한 책임 부인 사항을 기억하시나요? CSS 명세서에는 이 특별한 GPU 컴포징 모드에 대한 설명이 존재하지 않습니다. 이건 단지 브라우저가 내부적으로 적용해 둔 최적화 방식일 뿐입니다. 우리는 반드시 z-index를 따라서 정확하게 B 요소 위에 A 요소가 위치하도록 만들어야 합니다. 그러면 브라우저는 무엇을 해야 할까요?

맞춰보세요! 브라우저는 A 요소를 위해 강제적으로 새로운 컴포짓 레이어를 생성합니다. 그리고 당연히, 이 때문에 힘겨운 리페인트 과정을 한번 더 겪겠네요.

이것이 바로 암묵적 컴포지팅이라고 불리는 과정입니다. 아직 컴포짓 레이어에 올라가지 않은 요소가 하나 이상 존재할 때, 쌓임 순서(stacking order) 때문에 이들이 컴포짓 레이어 위에 존재하는 요소보다 위에 와야 하는 경우, 이 요소들 때문에 컴포짓 레이어가 추가적으로 생성됩니다. 별도의 이미지로 페인팅 되어서 GPU로 보내지게 되는 것이죠.

생각하시는 것보다 브라우저는 상당히 자주 암묵적인 컴포지팅을 행하고 있습니다. 수많은 이유 때문에 브라우저는 요소를 컴포짓 레이어 위로 보냅니다. 다음은 목록은 그 이유들 중 일부를 추린 것들입니다.

  • 3D 변형: translate3dtranslateZ 등등
  • <video><canvas>, <iframe> 요소
  • Element.animate();를 사용해 구현한 transform과opacity 애니메이션
  • CSS 트랜지션과 애니메이션을 사용해 구현한 transform과opacity 애니메이션
  • position: fixed;
  • will-change;
  • filter;

이 외에도 더 많은 이유들이 크로미움 프로젝트의 “CompositingReasons.h” 파일 안에 기록되어 있습니다.

GPU 애니메이션의 주요 문제점은 예상치 못하게 무거운 리페인트 비용인 것 같네요. 그런데 그렇지 않습니다. 더 큰 문제는 바로…

다시 한번 GPU가 독립적인 컴퓨터임을 살짝 언급하고 넘어가야 겠습니다. 렌더링된 레이어 이미지는 GPU로 보내져야 하면서도, 나중에 애니메이션 때문에 이를 다시 사용하려면 저장 해 둘 필요가 있습니다.

컴포짓 레이어 하나가 메모리를 얼마나 차지할까요? 간단한 예시를 들어보도록 하겠습니다. 320 x 240 픽셀 크기의 #ff0000 단색 배경 정사각형 이미지를 저장하려면 메모리 용량이 얼마나 필요할지 예상해 보세요.

보통 웹 개발자라면 “음, 단색 컬러 이미지네. PNG로 저장해서 크기를 확인해 봐야 겠다. 1KB 보다는 작을 테지”라고 생각할 것입니다. 그리고 그런 생각은 아주 정확하게 맞는 말입니다. PNG 확장자로 이미지를 저장하면 크기가 104 바이트 나옵니다.

문제는 JPEG, GIF와 더불어 PNG 등의 이미지 확장자가 이미지 데이터를 저장하고 전달하는 데 능숙하다는 데에 있습니다. 이런 이미지를 화면 상에 그려내려면 컴퓨터는 이미지 포맷으로부터 데이터를 받아 풀어낸 다음, 이를 픽셀의 배열로 나타내주어야 합니다. 따라서, 여기서 가정한 샘플 이미지는 컴퓨터 메모리 중 320 x 240 x 3 = 230,400 바이트를 차지하게 됩니다. 식이 왜 이렇게 나왔는지는 다음과 같습니다. 이미지의 가로 너비에다 높이를 곱해 이미지 픽셀수를 일단 얻어냅니다. 그리고 여기에 3을 곱해주는데, 왜냐하면 픽셀은 하나당 3바이트(RGB)로 이루어져 있기 때문입니다. 만약 이미지가 투명한 영역을 포함하고 있다면, 추가로 투명도에 대한 정보를 말해 줄 필요가 있기 때문에 여기에 1 바이트를 더해서 총 4(RGBa)를 곱해 줍니다. 그러면 320 x 240 x 4 = 307,200 바이트가 되겠네요.

브라우저는 언제나 컴포짓 레이어를 RGBa 이미지로 페인트 합니다. 요소의 투명 영역 포함 여부를 판단할 효율적인 방법은 아직까지 없는 것 같습니다.

좀 더 현실성 있어 보이는 예시를 들어보겠습니다. 800 x 600 픽셀 사이즈의 사진 10장이 담긴 캐로셀 요소가 있다고 해봅시다. 사용자가 드래그 등의 인터렉션을 실행하면 이미지들이 부드럽게 전환이 되도록 만들기로 했습니다. 그러므로 모든 이미지에 will-change: transform 속성을 추가하겠습니다. 이렇게 해두면 애니메이션 실행 전에 앞서 이미지 요소들이 컴포지트 레이어 위로 올라가게 되므로, 사용자 인터렉션이 발생하면 트랜지션 효과가 바로 시작됩니다. 이제, 이 캐로셀 요소 하나를 화면 상에 나오도록 하는 데 메모리가 추가적으로 얼마나 드는지 계산해 봅시다. 800 x 600 x 4 x 10 해서 약 19MB입니다.

컨트롤 요소 하나 렌더링하는 데 19MB 메모리가 추가로 들다니요! 그리고 만약 여러분이 애니메이션 효과를 지닌 컨트롤, 패럴랙스 효과, 고화질 이미지 및 그 외의 다른 시각적 요소들로 가득한 싱글 페이지 웹앱을 만드는 개발자라고 한다면, 페이지 당 100~200MB 메모리가 더 드는 것은 시작에 불과합니다. 여기에 암묵적 컴포지팅(이 글을 읽기 전에는 이것에 대해 생각해 본 적이 전혀 없었다고 인정하세요)이 더해진다면 여러분의 페이지는 사용자 기기의 사용가능 메모리를 전부 차지하게 될 것입니다.

게다가 많은 경우 이렇게 차지하게 된 메모리가 낭비될 수 있기 때문에 다음 예시와 매우 유사한 결과를 초래할 수 있습니다.

데스크탑 사용자들에게는 이것이 별 문제가 안될 수도 있으나, 모바일 사용자들에게는 매우 치명적인 손해를 입힙니다. 그 이유는 우선, 대부분의 최신 기기들은 고밀도 디스플레이를 탑재하고 있습니다. 때문에 컴포짓 레이어 이미지의 크기가 4~9배 늘어나게 됩니다. 두번째로, 모바일 기기는 데스크탑처럼 메모리 용량이 크지 않습니다. 예를 들어 출시된지 그닥 오래 되지 않은 아이폰 6의 경우 1GB 용량의 공유 메모리(RAM과 VRAM에서 모두 사용하는 메모리)를 탑재하고 있습니다. 공유 메모리가 최소한 3분의 1이 OS와 백그라운드 프로세스에 의해 사용중이고, 나머지 3분의 1은 브라우저와 현재 페이지에 의해 사용중인 상황이라 한다면, GPU 가속화를 위한 메모리는 많아봐야 200~300MB 정도 남은 셈입니다. 게다가 iPhone 6는 매우 비싼 고가의 기기입니다. 이보다 가격이 낮은 폰들은 iPhone 6보다 더 적은 용량의 메모리가 탑재되어 있습니다.

아마 “메모리 사용량(footprint)을 줄이기 위해 PNG 이미지를 GPU에 저장할 수 있지 않을까?”라고 생각이 들 수도 있습니다. 기술적으로는 맞는 이야기입니다. 가능합니다. 유일한 문제점은 바로, GPU는 스크린에 픽셀을 한땀 한땀 그리기 때문에, PNG 이미지를 스크린 위에 나타내려면 전체를 디코딩하여 필요한 모든 픽셀을 다시 그리고 또 그리고 계속 반복해야 한다는 것입니다. 이런 경우에는 애니메이션 성능이 1FPS라도 나올지 의문이네요.

GPU만의 이미지 압축 포맷이 존재한다는 사실은 모르고 있어도 되는데, 압축비율로 따져보면 이 포맷은 PNG와 JPEG에 비할 수가 없습니다. 그리고 이 포맷은 하드웨어 지원 문제 때문에 사용이 한정되어 있습니다.

이제 GPU 애니메이션 기초를 일부 알아보았으니, 장점과 단점을 정리해 봅시다.

  • 애니메이션이 빠르고 부드럽다 (60FPS).
  • 적절히 신경써서 만든 애니메이션은 독립된 스레드 상에서 재생된다. 또한 이는 무거운 자바스크립트 코드 연산 때문에 방해를 받지 않는다.
  • 3D 트랜스폼은 비용이 “싸다”.
  • 컴포짓 레이어에 요소를 올리려면 추가적인 리페인팅 작업을 해야 한다. 때때로(추가된 것만 그리는 대신에 레이어 전체를 리페인팅 해야 할 때) 이 과정은 매우 느리게 진행된다.
  • 페인팅 처리된 레이어는 GPU로 보내져야 한다. 이 레이어의 크기와 갯수에 따라 전송 과정 역시 매우 느려질 수 있다. 그 결과로 중저가 기기에서 요소 깜빡임이 발생할 수 있다.
  • 모든 컴포짓 레이어는 추가적으로 메모리를 소모한다. 메모리는 모바일 기기에서 매우 귀중한 자원이다. 메모리 과다 사용은 브라우저 충돌을 일으킬 수 있다.
  • 암묵적 컴포지팅을 고려사항에 넣지 않는다면, 느린 리페인팅, 초과 메모리 사용 및 브라우저 충돌이 발생할 가능성이 매우 높다.
  • 사파리 텍스트 렌더링처럼 부자연스런 시각 요소가 나타날 수 있으며, 일부 경우 페이지 컨텐츠가 사라지거나 왜곡되어 나타날 가능성이 있다.

웹킷 텍스트 렌더링 이슈

버튼에 SVG 요소가 있고, CSS 애니메이션을 여기에 추가했더니 전체 페이지가 리페인팅 되는 바람에 텍스트가 깜빡이면서 위치 뒤틀림이 발생하고 있다. 그래서 해결책으로 버튼 래퍼 요소에 position:relative; z-index:1;을 추가했더니 깜빡임이 더 이상 발생하지 않는다.


보시다시피, GPU 애니메이션은 매우 유용하고 독특한 장점들도 가지고 있지만, 일부 아주 고약한 문제점들 역시 지니고 있습니다. 가장 중대한 문제점은 바로 리페인팅과 초과 메모리 사용이 발생한다는 것입니다. 따라서, 성능 문제 해결을 위해 아래에 나오는 최적화 방법 적용을 시도해 볼 수 있습니다.

최적화 작업을 시도하기 전에, 먼저 페이지의 컴포짓 레이어가 얼만큼 있는지 조사해 보고 최적화에 따른 성능 향상도가 어느 정도일지 정확한 피드백을 줄 수 있는 도구에 대한 지식이 있어야 합니다.

사파리 브라우저의 웹 개발자 도구(Web Inspector)에는 멋진 “Layers” 사이드바가 있는데, 여기에서 모든 컴포짓 레이어와 이들의 메모리 사용량을 볼 수 있습니다. 컴포지팅이 일어나는 이유 역시 나옵니다. 이 사이드 바를 보려면 다음과 같은 절차를 따르면 됩니다.

  1. 사파리에서, ⌘ + ⌥ + I를 눌러 웹 개발자 도구를 엽니다. 만약 나오지 않는다면, “환경설정(Preferences)” → “고급(Advanced)”에서 “메뉴 막대에서 개발자용 메뉴 보기(Show Develop Menu in menu bar)” 옵션에 체크를 해주시면 됩니다. 그리고 다시 열어 보세요.
  2. 개발자 도구가 열리면, “요소(Elements)” 탭을 선택한 후 오른 쪽 사이드바에서 “레이어(Layers)”를 선택합니다.
  3. 이제 메인 “요소” 패널에서 DOM 노드를 클릭하면, 선택된 요소(만약 이 요소가 컴포지팅 되어 있다면)의 레이어 정보와, 이 요소의 모든 자손 컴포짓 레이어 목록이 나열됩니다.
  4. 각각의 자손 레이어가 컴포짓 된 이유가 궁금하다면 클릭 해 보세요. 브라우저가 왜 이 요소를 컴포짓 레이어 위에 올려 두었는지 이유가 나올 것입니다.

레이어 패널에는 현재 페이지의 활성화된 모든 컴포짓 레이어가 트리 구조로 표시됩니다. 레이어를 선택하면, 크기, 메모리 사용량, 리페인트 횟수, 컴포지팅 된 이유 등 관련 정보를 볼 수 있습니다.

이제 환경 설정을 마쳤으니, 컴포짓 레이어 최적화 작업을 시작해 볼 수 있습니다. 이미 우리는 벌써 컴포지팅의 두 가지 주요 문제점을 알고 있습니다. 리페인트가 추가로 발생하고(이것 때문에 GPU 데이터 전송 또한 일어납니다), 메모리 또한 추가로 사용됩니다. 그러므로 아래에 나오는 모든 최적화 팁들은 이 두가지 문제 해결에 초점을 두었습니다.

이 팁은 가장 간단하면서도 뻔한 것 같지만, 여전히 매우 중요합니다. 확실하게 컴포지팅이 된(예를 들어, position: fixed, video, CSS 애니메이션 등등에 의해) 요소들 보다 위에 위치하지만, 자기 자신은 컴포지팅이 되지 않은 DOM 요소들은 모두 ‘GPU에서의 최종 이미지 컴포지션’이라는 하나의 이유로 인해 자신만의 컴포짓 레이어를 강제로 가지게 된다는 사실을 다시 한번 상기시켜 드리겠습니다. 모바일 기기에서는 이 때문에 애니메이션의 시작이 매우 느려질 수도 있습니다.

간단한 예를 하나 들어보겠습니다.

A 요소는 사용자의 인터렉션이 발생하면 적용된 애니메이션이 재생되어야 합니다. “Layers” 패널에서 이 페이지를 본다면, 추가 레이어가 없다는 것을 알 수 있습니다. 그렇지만 “Play” 버튼을 누르면, 곧바로 몇개의 레이어가 더 나타나는데, 이 레이어들은 애니메이션이 끝나자마자 사라집니다. “Timeline” 패널에서 이 과정을 지켜본다면, 애니메이션의 시작과 끝에서 넓은 리페인트 영역이 등장하는 것을 보게 됩니다.

이제 브라우저가 무슨 일을 했는지 단계별로 알아봅시다.

  1. 페이지 로드가 된 직후에는 아직 브라우저가 컴포지팅을 해야 하는 이유를 전혀 찾지 못합니다. 따라서 이 경우에서 할 수 있는 최적의 전략을 선택하기로 합니다. 단일 배경 레이어 위에 페이지의 모든 컨텐츠를 그리는 것입니다.
  2. 이제 “Play” 버튼을 누르면, 요소 A에 컴포지팅 과정(transform 속성으로 인한 변형)이 명확하게 추가됩니다. 그러나 브라우저는 이 때 쌓임 순서(stacking order)를 따져본 후, 요소 A가 요소 B 밑에 존재하고 있음을 파악했기 때문에 요소 B를 별도의 컴포짓 레이어 위에 올려놓습니다. (암묵적 컴포지팅이 일어났습니다.)
  3. 컴포짓 레이어에 위에 요소를 올리게 되면 이 때마다 리페인트가 항상 발생합니다. 브라우저는 이 요소 때문에 텍스쳐를 새로 만들고, 이 전의 레이어에서는 해당 요소를 지웁니다.
  4. 사용자가 화면에서 볼 최종 이미지 합성 때문에 새로운 레이어 이미지를 반드시 GPU로 전송해야 합니다. 레이어 갯수, 텍스쳐의 사이즈와 컨텐츠의 복잡도, 리페인팅과 데이터 전송 과정 등에 시간이 상당히 걸릴 수가 있습니다. 이 때문에 애니메이션 시작이나 종료 시점에서 요소 깜빡임이 가끔 발생하는 것입니다.
  5. 애니메이션이 끝나면 곧바로 A 요소에서 컴포지션의 원인을 제거해 냅니다. 다시 한번, 브라우저는 컴포지션 과정에서 자원을 낭비할 필요가 없다고 생각하기 때문에 최적의 전략을 또 선택합니다. 페이지의 전체 컨텐츠를 하나의 레이어 위에 모두 놓아두는 것입니다. 이 때문에 배경 레이어 위에 요소 A와 B를 그려서(여기서 리페인팅이 또 발생합니다) GPU에 새롭게 그려진 텍스쳐를 전송해야 합니다. 4번 단계와 마찬가지로, 여기서도 깜빡임이 발생할 수 있습니다.

암묵적 컴포지팅과 부자연스러운 시각 요소 발생을 막으려면 다음과 같은 방법을 적용해 보는 것을 추천드립니다.

  • 애니메이션 처리된 오브젝트의 z-index 값을 최대한 높게 유지되도록 해봅니다. 이 요소들이 body 요소의 직계 자식 요소이면 이상적인 환경입니다. 당연히, 애니메이션 요소들이 DOM 트리 깊숙하게 위치해 있고 일반적인 흐름(normal flow) 위에 위치해 있다면, 마크업 구조로 보았을 때 항상 가능하지는 않은 얘기입니다. 이런 경우에는 요소를 복제해서 애니메이션만을 위해 body의 자식 요소로 넣어 보는 시도를 해볼 수 있습니다.
  • 브라우저에게 will-change CSS 속성을 사용해 내가 이제 컴포지팅을 할 예정이라고 귀뜸해 줄 수 있습니다. 이 속성이 요소에 적용이 된 상태라면, 브라우저는 이 요소를 사전에 컴포짓 레이어 위에 올려 두기 때문에, (그런데 항상 이러지는 않습니다!) 원활하게 애니메이션이 시작하고 멈출 수 있도록 만듭니다. 그러나 이 속성을 오용하지는 마세요. 만약 그렇게 한다면, 메모리 사용량이 무지막지하게 증가해 버립니다!

Normal Flow, Out of Flow

Out of Flow is any element that has been positioned relatively or absolutely or anything that has been floated. The rest would be considered Normal Flow. Look at CSS Positioning and Layout and pay close attention to the “Methods of Positioning Elements” section.

Out of Flow(흐름 외) 요소는 상대 혹은 절대 위치를 가지는 요소 혹은 플로팅 된 요소를 말한다. 이 외 나머지 요소들은 모두 Normal Flow(정상 흐름)에 속한다고 여긴다. CSS 위치 지정 및 레이아웃 문서와 “요소 위치 지정 방법” 섹션을 참조하라.

https://stackoverflow.com/questions/3293340/what-is-the-meaning-of-the-terms-normal-flow-and-out-of-flow-in-terms-of-ht


transform과 opacity 속성은 노멀 플로우나 DOM 환경에 영향을 주지도, 받지도 않음이 보장되어 있습니다. (즉, 이 두 속성은 리플로우나 리페인트를 일으키지 않으므로 이 둘을 사용한 애니메이션은 전적으로 GPU에게 떠맡겨진 것 입니다.) 따라서 움직임이나, 크기 조정, 회전, 투명도 및 아핀 변환(affine transform) 애니메이션만은 효율적으로 해낼 수 있습니다. 때로는 이 속성들을 가지고 다른 종류의 애니메이션을 모사하고 싶을 때도 있을 것입니다.

매우 흔한 예제를 하나 봅시다. 배경색상 트랜지션입니다. transition 속성을 사용해 애니메이션 효과를 주는 것이 일반적인 방식입니다.

이 경우, 애니메이션은 완전히 CPU 위에서 돌아가기 때문에 애니메이션의 각 스텝마다 리페인트가 발생하게 됩니다. 그러나 우리는 이런 애니메이션을 GPU 위에서 돌아가게끔 만들 수 있죠. background-color 속성에 애니메이션을 주는 대신, 맨 위에 레이어를 하나 추가해서 이 레이어의 투명도에 애니메이션 효과를 줘 봅시다.

이 애니메이션 전보다 더 빨라지고 부드러워질 수는 있으나, 암묵적인 컴포지팅이나 추가 메모리를 필요로 할 수 있다는 것을 염두하여 두시기 바랍니다. 그러나 이렇게 하면 메모리 사용량은 크게 줄어들 수 있습니다.

아래의 이미지를 봅시다. 뭔가 차이를 느끼시나요?

우리 두 눈에는 이 두 컴포짓 레이어가 완전히 동일한 것처럼 보이지만, 첫번째 레이어 용량은 40,000 바이트(39KB)인데 비해 두번째 레이어는 고작 400 바이트에 불과합니다. 100분의 1 크기입니다. 왜 그럴까요? 코드를 봅시다.

차이는 바로 #a는 실제 크기가 100 x 100 픽셀 (100 x 100 x 4 = 40,000 바이트)인데 비해, #b는 실제로는 고작 10 x 10 픽셀 (10 x 10 x 4 = 400 바이트)이면서 transform: scale(10) 을 적용해 100 x 100 픽셀로 키운 것입니다. #b는 will-change 덕분에 컴포짓 레이어가 되어서, 최종 이미지 페인팅을 할 동안 transform은 오로지 GPU에서만 진행 되게 됩니다.

여기서 사용한 트릭은 매우 간단합니다. width 와 height 속성을 사용해 컴포지트 레이어의 실제 크기를 줄인 후, transform: scale(...) 을 사용해 텍스쳐의 크기를 확대합니다. 물론 이 트릭은 매우 간단하게 단색으로 이루어진 레이어에서 사용하는 경우에만 메모리 사용량을 극적으로 줄일 수 있습니다. 그러나 예를 들어, 큰 사진에 애니메이션 효과를 주고 싶다면 5~10% 정도 사진 크기를 줄인 후 다시 확대해 보세요. 사용자는 차이를 알아채지 못할 것이나, 여러분은 귀중한 메모리를 단 몇 MB라도 줄여볼 수 있습니다.

transformopacity를 사용한 CSS 트랜지션 및 애니메이션은 자동적으로 컴포짓 레이어를 생성하고, GPU 위에서 행해진다는 사실은 이미 알고 있습니다. 자바스크립트를 사용해서 애니메이션을 적용해 볼 수도 있는데, transform: translateZ(0)이나 will-change: transform, opacity를 먼저 적용해 주어야 요소가 자신만의 컴포짓 레이어 위에 올라가게 되었음을 확실하게 알 수 있습니다.

자바스크립트 애니메이션은 각 스텝이 requestAnimationFrame 콜백으로 일일이 계산이 된 후에 실행됩니다. Element.animate()를 사용한 애니메이션은 선언적인(declarative) CSS 애니메이션의 한 분파입니다.

간단하고 재사용 가능한 애니메이션은 CSS 트랜지션이나 애니메이션을 사용하면 매우 손쉽게 만들 수 있습니다. 이와는 반대로, 화려한 패스를 곁들인 복잡한 애니메이션은 자바스크립트 애니메이션이 CSS보다 훨씬 쉽게 만들 수 있습니다. 그리고, 자바스크립트 애니메이션은 사용자의 입력에 반응할 수 있는 유일한 애니메이션 방법입니다.

어떤 것이 더 나을까요? 그냥 모든 것에 애니메이션을 적용할 수 있는 범용 자바스크립트 라이브러리 하나만 사용하면 안되나요?

CSS를 근간으로 하는 애니메이션은 매우 중요한 특징이 하나 있습니다. 완전히 GPU 위에서 돌아간다는 점입니다. 애니메이션이 어떻게 시작되고 끝나야 하는지 우리가 알려주므로, 브라우저는 이에 필요한 모든 지시 사항을 애니메이션 시작과 끝에 앞서서 GPU에 미리 준비시켜 둘 수 있습니다. 명령적으로 코드를 짜야 하는 자바스크립트의 경우, 브라우저가 확실히 알아두고 있어야만 하는 것은 바로 현재 프레임의 상태입니다. 부드러운 애니메이션을 만들려면, 메인 브라우저 스레드의 새로운 프레임을 계산하여 GPU로 보내는 작업을 적어도 1초에 60번은 해야 합니다. 이 계산 과정과 데이터 전송 과정은 CSS로 애니메이션을 할 때보다 훨씬 늦게 이루어 지는데, 메인 스레드에 올라온 작업량에 의해 시간이 좌우되기도 합니다.

위의 일러스트를 보면, 막중한 자바스크립트 연산량 때문에 메인 스레드가 막히면 어떻게 되는지 알아볼 수 있습니다. CSS 애니메이션은 여기에 영향을 받지 않습니다. 왜냐하면 새로운 프레임 계산은 다른 스레드 위에서 이루어지기 때문입니다. 이에 반해 자바스크립트 애니메이션은 많은 양의 연산이 끝나기를 기다려야지만 새로운 프레임 계산을 수행할 수 있습니다.

그러므로, 최대한 많이 CSS 기반 애니메이션을 사용해 보도록 하세요. 특히 로딩이나 진행 상태 표시 아이콘에 이를 적용해 보세요. 자바스크립트 애니메이션보다 훨씬 빠를 뿐만 아니라, 자바스크립트 연산량이 많아도 지체되지 않습니다.

이 글은 Chaos Fighters 웹 페이지를 개발할 때 겪었던 경험들과 조사 자료를 정리한 결과물입니다. 이 페이지는 애니메이션이 매우 많이 들어간 반응형 모바일 게임 프로모션 페이지입니다. 개발을 막 시작했을 때에 저는 오직 GPU 기반 애니메이션을 적용하려면 어떻게 하면 되는지만 알았지, 이것의 원리는 모르고 있었습니다. 그랬기 때문에, 제일 첫번째 데모 버전은 iPhone5(그 당시 가장 최신 아이폰 이였습니다)에서 페이지가 로딩되고 몇 초 지나지 않아 충돌이 발생했습니다. 이제 이 페이지는 제대로 동작합니다. 심지어 아이폰5보다 예전 기기에서도요.

이 웹사이트 작업 중에 제가 생각하기에 가장 인상적 이였던 최적화 작업을 말해보도록 하겠습니다.

페이지 가장 상단에는 게임 설명글이 있습니다. 배경에 붉은 햇빛 광선같은 것이 돌아가고 있네요. 무한정 반복되고 있고, 사용자 인터렉션은 줄 수 없는 회전체 입니다. 간단한 CSS 애니메이션을 적용해 보기에 아주 좋은 후보입니다. 제일 먼저 떠오르는 (그리고 잘못된) 생각은 빛 광선 이미지를 저장해서 페이지에 img 요소로 위치 시킨 후, 무한 CSS 애니메이션을 사용하는 것입니다.

모든 것이 예상한 대로 돌아갈 것 같습니다. 그러나 태양 이미지의 용량이 꽤 나가네요. 모바일 사용자들이 기뻐하지 않을 것 같아요.

이미지를 좀 더 자세히 살펴봅시다. 기본적으로 이 이미지는 중심점에서 밖으로 향하는 광선 몇줄기로 이루어져 있습니다. 광선은 모두 똑같이 생겼으니, 하나만 이미지로 저장해서 최종 이미지를 만드는 데 계속 사용할 수 있습니다. 우리는 이제 광선 하나만 이미지로 만들어서 사용하겠습니다. 처음 이미지보다 크기가 몇십분의 일로 줄어들었습니다.

이렇게 최적화를 진행하려면 마크업을 조금 더 복잡하게 만들어야 합니다. .sun은 광선 이미지 요소들의 컨테이너입니다. 각각의 광선은 일정한 각도로 회전할 것입니다.

눈에 보이는 결과는 동일하지만, 네트워크로 전송되는 데이터양은 훨씬 적습니다. 여전히, 컴포지트 레이어 크기는 아까와 똑같네요. 500 x 500 x 4, 약 977 KB입니다.

좀 더 일을 간단히 하려고, 여기 이 예제에서는 광선 이미지 크기를 상당히 작은 크기인 500 x 500 픽셀로만 만들었습니다. 그러나 실제 웹사이트에서는 다양한 크기의 디바이스(핸드폰, 태블릿, 데스크탑)와 픽셀 밀도를 대응해야 하기 때문에, 최종 이미지가 3000 x 3000 x 4 해서 36MB 정도 되었습니다! 그리고 이건 단지 페이지 상에서 애니메이션 처리된 요소 중 하나일 뿐입니다.

“Layers” 패널에서 마크업을 다시 한번 봐봅시다. 전체 태양 컨테이너 회전은 쉽게 할 수 있게 되었습니다. 그럼 이제 이 컨테이너는 컴포짓 레이어 위에 올라오게 되었으며, 하나의 큰 텍스쳐 이미지로 페인팅이 됩니다. 이 텍스쳐 이미지는 후에 GPU로 보내집니다. 그러나 여기서는 단순화 하였기 대문에, 텍스쳐는 이제 광선 사이의 간격이라는 쓸모없는 데이터를 가지고 있습니다.

게다가, 이 쓸모 없는 데이터는 유용한 데이터보다 크기가 훨씬 큽니다! 이래서는 매우 제한적인 메모리 자원을 가장 효율적으로 쓰는 방법이 될 수 없습니다.

이 문제를 해결하는 방법은 네트워크 전송 최적화를 하는 방식과 동일합니다. GPU로 유용한 데이터(즉, 광선)만 보내는 것이죠. 메모리를 얼마나 아낄 수 있을지 계산해 볼 수 있습니다.

  • 전체 태양 컨테이너: 500 x 500 x 4, 약 977 KB
  • 광선 12개만: 250 x 40 x 4 x 12, 약 469 KB

메모리 사용량이 반절은 줄어들 수 있습니다. 이렇게 하려면 컨테이너에 애니메이션을 주는 대신, 각각의 광선에 따로 애니메이션을 주어야 합니다. 그렇게 함으로써 광선 이미지만 GPU로 보내게 될 것입니다. 광선 사이의 공간에는 아무런 리소스도 할당 되지 않을 것입니다.

각각의 광선에 애니메이션을 따로 적용하려면 마크업을 조금 더 복잡하게 만들어야 하고 CSS 역시 여기서는 더 방해물이 될 것 같습니다. 이미 transform을 써서 광선이 초기에 회전하도록 만들었으므로, 완전히 동일한 각도로 애니메이션을 하나 더 적용해서 전체가 360도 회전하도록 해야 합니다. 이렇게 하려면 광선 하나하나마다 @keyframes를 따로 만들어 적용해야 할 것 같은데, 이렇게 하면 네트워크로 전송되어야 할 코드가 너무 많아집니다.

이보다는 짧은 자바스크립트 코드를 작성해서 광선의 초기 위치 확정을 담당하도록 해서, 애니메이션과 광선 개수 등을 좀 더 세밀하게 조절할 수 있도록 하면 훨씬 더 편할 것 같습니다.

새로운 애니메이션은 아까 만들었던 것과 동일해 보이나 메모리 사용량은 반절로 줄었습니다.

이게 다가 아닙니다. 레이아웃 컴포지션 관점에서 보면, 애니메이션 적용된 태양 보다는 배경 요소가 주인공 요소에 가깝다고 볼 수 있습니다. 그리고 광선 이미지에는 아주 선명하게 보여야 하는 부분이 없습니다. 이말인 즉, 저해상도 광선 텍스쳐를 GPU로 보내면 후에 이를 확대해서 사용해도 된다는 것입니다. 덕분에 메모리 사용량을 조금 줄일 수 있습니다.

텍스쳐 크기를 10% 줄여봅시다. 광선 이미지의 실제 크기는 이제 250 x 0.9 x 40 x 0.9해서 225 x 36 픽셀이 될 것입니다. 광선 크기가 250 x 20 처럼 보이게 하려면, 이미지를 250 나누기 225해서 1.111배 정도 늘려야 합니다.

코드 한줄을 추가해 봅시다. .sun-ray 클래스에 background-size: cover를 추가하면 배경 이미지가 자동으로 요소 크기대로 조정이 되며, transform: scale(1.111)을 추가해 광선 애니메이션을 조정해 줍시다.

여기서 오직 요소의 크기만 바뀌었다는 사실을 기억해 주세요. PNG 이미지 크기는 여전히 변화없이 똑같습니다. DOM 요소가 만든 직사각형은 PNG 이미지가 아니라, GPU 텍스쳐로 렌더됩니다.

새롭게 만들어져 GPU에 올라가게 된 태양 광선 컴포지션 용량은 이제 225 x 36 x 4 x 12, 약 380 KB입니다. (아까는 469 KB였습니다.) 메모리 사용량을 19% 정도 줄였으며 매우 유연하게 수정 가능한 코드를 작성해 두었기 때문에 메모리 대비 최적의 이미지 품질을 얻을 때까지 이미지 축소를 해 볼 수 있습니다. 결과적으로, 처음에는 너무나 간단해 보이던 애니메이션의 복잡도를 증가시킴으로써 메모리 사용량을 약 5분의 2정도로 줄일 수 있었습니다. (380 % 977)

이미 눈치를 채셨을 것 같기도 한데, 이 방법은 엄청난 단점이 하나 있습니다. 애니메이션이 이제 CPU 위에서 돌아가기 때문에 자바스크립트 연산량이 엄청 많을 경우 애니메이션 동작이 중단될 수도 있습니다. 만약 GPU 애니메이션 최적화를 좀 더 알아보고 싶다면, 제가 작은 숙제를 하나 내드리겠습니다. 코드펜에 제가 올린 태양 광선 애니메이션을 포크떠서, 애니메이션이 전부 GPU 위에서 돌아가도록 만들어 보세요. 그렇다해도 원본 예제처럼 메모리 사용도 효율적으로 해야 하고, 코드 역시 유연성을 띄고 있어야 합니다. 댓글에 여러분의 예제를 올려주시면 피드백을 해 드리겠습니다.

Chaos Fighters 페이지 최적화 방법에 대한 조사는 저에게 요즘 웹 페이지 개발 과정에 대해 완전히 다시 한번 생각해 보는 계기를 마련해 주었습니다. 다음은 제가 따른 주요 원칙들입니다.

  • 항상 클라이언트와 디자이너와 함께 웹사이트의 애니메이션과 효과에 대해 협상하라. 이는 페이지의 마크업 구조에 크게 영향을 줄 수 있으며, 더 효율적인 컴포지팅을 할 기회가 될 수 있다.
  • 맨 처음 작업부터 컴포지트 레이어의 갯수와 크기에 신경을 쓰라. 특히 암묵적 컴포지팅에 의해 생성되는 레이어에 신경을 쓰자. 개발자 도구의 “Layers” 패널을 가장 친한 친구처럼 대하라.
  • 요즘 브라우저는 애니메이션 뿐만 아니라 페이지 요소 페인팅 과정도 최적화 하려고 컴포지팅을 많이 사용한다. 예를 들어, position: fixed 와 iframe 요소, video 요소에는 컴포지팅이 필요하다.
  • 컴포짓 레이어 크기가 레이어 갯수보다 더 중요하게 작용할 가능성이 높다. 일부 경우, 브라우저는 컴포지트 레이어 갯수를 줄이려고 노력하는데(“크롬 브라우저의 GPU 가속화 컴포지팅” 글의 “레이어 스쿼싱” 항목 참조), 이는 “레이어 폭발(explosion)”이라 불리는 현상 발생을 예방 및 메모리 소모량 절약을 위한 것이다. 특히 레이어가 서로 겹치는 부분이 많을 때 브라우저는 이런 시도를 한다. 그러나 때로는 이러한 최적화 과정이 부작용을 발생시킬 수도 있다. 예를 들면, 매우 큰 텍스쳐가 작은 레이어 몇장이 소모하는 메모리보다 훨씬 더 많은 메모리를 소모할 때이다. 이 최적화 과정을 우회하기 위해 나는 각각의 요소에 translateZ() 값을 고유값으로 작게 매겼다. 예를 들어 translateZ(0.001px) , translateZ(0.002px) 등의 값을 사용했다. 이렇게 하면 브라우저는 요소가 3D 공간의 각기 다른 평면 위에 놓여 있다고 생각해서 최적화 과정을 건너뛰게 된다.
  • 아무 고려 없이 transform: translateZ(0)이나 will-change: transform를 임의의 요소에 추가해서 애니메이션 성능을 임시로 올리거나 부자연스러운 시각 요소를 제거하는 데 사용할 수는 없다. GPU 컴포지션은 사용할 때 고려해 보아야 하는 많은 장단점이 있다. 절제해서 사용하지 않는다면 적어도 컴포지션 때문에 전반적인 성능이 저하될 수 있으며, 최악의 경우에는 브라우저 충돌이 일어날 수 있다.

맨 앞에서 말한 중대 사항을 다시 한번 여러분께 말씀드리는 것을 부디 허락해 주세요. GPU 컴포지션에 대한 공식 스펙 문서가 없기 때문에 브라우저에서 저마다 같은 문제를 다르게 해결하고 있습니다. 이 글의 일부는 몇 달 후에 구식이 될 수도 있습니다. 그 예로, 구글 크롬 개발자들은 CPU와 GPU간 데이터 전송 과정에서 발생하는 오버헤드(overhead)를 줄일 방법을 모색중입니다. 오버헤드 복사본이 하나도 들어 있지 않는 특별한 공유 메모리 사용도 고려하고 있다고 합니다. 이미 사파리에서는 CPU 위에 요소 이미지를 생성해 놓지 않고, 간단한 요소(예를 들어 background-color 값이 있는 빈 DOM 요소)를 GPU에 그리는 과정을 지연시킬 수 있습니다.

이 글을 읽는 독자 여러분들의 상황이 어떤 지는 잘 모르겠으나, 이 글이 여러분들이 브라우저가 GPU를 사용해 어떤 식으로 렌더링을 하는지 이해하고, 그렇게 함으로써 모든 기기에서 재빠르게 동작하는 인상적인 웹사이트를 만들 수 있게 되기를 바랍니다.