Accelerated Rendering in Chrome

The Layer Model

HTML5 Rocks

소개

대다수의 웹 개발자들에게 웹 페이지의 일반적인 모델은 DOM입니다. 렌더링은 일반적으로 이러한 페이지의 표현 형태를 화면상의 이미지로 변환하는 모호한 과정입니다. 모던 브라우저들은 최근 몇년동안 다음과 같은 그래픽 카드의 장점을 취하기 위해 렌더링 동작의 방식을 변경했습니다. 이는 일반적으로 "하드웨어 가속"이라고 모호한 형태로 참조됩니다. 일반 웹 페이지(예를 들어 Canvas2D나 WebGL이 아닌)에 대해 얘기할 때 이 용어는 정말 무엇을 뜻하는 것일까요? 이 글은 크롬에서 웹 컨텐츠의 하드웨어 가속 렌더링을 뒷바침하는 기본 모델에 대해 설명합니다.

몇가지 중요한 전달사항

여기서 우리는 웹킷(WebKit)에 대해 이야기하고 있으며 보다 정확히는 웹킷의 크로미움(Chromium) 포팅에 대해 이야기하고 있습니다. 이 글은 웹 플랫폼 기능이 아니라 크롬의 자세한 구현 사항에 대해 다루고 있습니다. 웹 플랫폼과 표준은 자세한 구현 사항의 수준을 나타내지 않으므로 이글을 다른 브라우저들에 적용하는 것에 대한 어떠한 보장도 할 수 없습니다만 그럼에도 불구하고 내부에 대한 지식은 디버깅의 향상 및 성능 튜닝에 있어 유용할 것입니다.

또한 이 글 전체가 굉장히 빠르게 변경되고 있는 크롬 렌더링 구조의 핵심 부분을 논의하고 있음을 주의하시기 바랍니다. 이 글은 변경되지 않을 것 같은 것들에 대해서만 다루려고 노력하고 있습니다만 6개월 뒤에도 여전히 모두가 적용 가능하다고 보장할 수는 없습니다.

크롬이 현재 얼마간의 기간동안 하드웨어-가속의 경로와 예전의 소프트웨어 경로와 같은 2개의 다른 렌더링 경로를 가지고 있음을 이해하는 것은 매우 중요합니다. 현재 쓰고 있는 모든 페이지들은 윈도우즈, 크롬OS 그리고 안드로이드용 크롬 상에서 하드웨어 가속 경로로 처리되고 있습니다. 맥과 리눅스 상에서는 그들 컨텐츠 일부를 위해 합성(Composition)이 필요한 페이지들만 가속 경로로 처리됩니다만 (무엇이 합성을 필요로 하는지에 대한 더 자세한 내용은 아래에서 볼 수 있습니다.) 맥과 리눅스에서도 곧 모든 페이지들이 하드웨어 가속 경로를 따라 처리될 것입니다.

최종적으로 렌더링 엔진을 살펴보는 중이며 성능 상의 큰 영향을 주는 기능을 확인하는 중입니다. 사이트의 성능을 개선하려는 노력은 레이어 모델을 이해하는데 도움을 주지만 이는 또한 다음과 같이 자기 무덤을 파는 일이기도 합니다. 레이어(Layers)는 유용한 구성물입니다만 지나친 레이어의 생성은 그래픽 스택의 전반적인 오버헤드를 발생시키기도 합니다. 주의하시기 바랍니다!

DOM에서 스크린으로

레이어(Layers)의 소개

일단 페이지가 로딩되고 파싱되고 나면 많은 웹 개발자에게 친숙한 DOM 구조로써 브라우저에서 표현됩니다. 그러나 페이지를 렌더링할 때 브라우저는 개발자에게 직접 보여지지 않는 중간 표현의 연속 과정을 가지게 됩니다. 이 구조들 중 가장 중요한 것은 레이어입니다.

크롬에서는 실제로 DOM의 서브트리와 대응되는 렌더레이어(RenderLayer)와 렌더레이어의 서브트리와 대응되는 그래픽스레이어(GraphicsLayer)과 같은 몇가지 다른 형식의 레이어들이 존재합니다. 후자가 이 글에서 우리에게 가장 흥미로운 부분입니다. 왜냐하면 그래픽스레이어는 텍스쳐로써 GPU에 업로드되는 것이기 떄문입니다. 이 글에서는 그냥 "레이어(Layer)"라고 호칭하는 것은 이 그래픽스레이어를 뜻합니다.

다음과 같이 GPU 용어들에 대해 빠르게 보도록 하겠습니다. 텍스처(Texture)는 주 기억장치(예를 들어 RAM)에서 비디오 메모리(예를 들어 GPU 상의 VRAM)로 이동하는 비트맵 이미지 같은 것이라고 생각하시기 바랍니다. 일단 GPU 상에 올라가면 그것을 메쉬 기하 구조와 매핑할 수 있습니다. -- 비디오게임이나 CAD 프로그램에서 이 기법은 스켈레탈 3D 모델들에 "스킨(Skin)"을 주기 위해 사용됩니다. 크롬은 웹 페이지 컨텐츠의 덩어리들을 GPU 상에 올리는데 텍스쳐를 사용합니다. 텍스쳐들은 그들을 정말 단순한 사각형 메쉬에 적용함으로써 다른 위치나 변환(Transformation)을 저렴한 비용으로 매핑할 수 있습니다. 이것이 3D CSS이 동작하는 방식이며 빠른 스크롤에도 훌륭합니다. 그러나 이 두가지는 뒤에서 더 알아보도록 하겠습니다.

역주: 3D 그래픽스는 일반적으로 어떠한 형태와 그 구조를 정의하기 위한 메쉬(Mesh, Wireframe 형태로 이것을 본 적이 있을 것입니다.)와 그 메쉬의 표면에 출력될 수 있는 일종의 이미지 소스인 텍스쳐(Texture, 이것이 메쉬의 표면에 그려지게 됩니다.)를 가지게 됩니다. 일단 텍스쳐가 GPU의 비디오 메모리 상에 업로드된 뒤에는 이를 출력하는 방식이 변경되더라도 대부분의 경우 이미지의 새로운 업로드없이 약간의 출력 옵션의 조정만으로도 변경된 출력이 가능하며 이 출력 또한 하드웨어 가속에 의해 매우 빠르게 처리됩니다. 따라서 변경이 일어날 경우 웹 컨텐츠를 매번 직접 출력해야 하는 경우보다 훨씬 우수한 성능을 보여줄 수 있습니다.

레이어의 개념을 실제로 보여줄 2가지 예제를 보도록 하겠습니다.

크롬에서 레이어를 학습할 때 매우 유용한 도구는 개발자도구 내 설정(즉, 작은 cog 아이콘)에서 "rendering" 항목 밑의 "show composited layer borders" 플래그입니다. 이는 레이어를 매우 간단한 하일라이트를 화면에 표시합니다. 이것을 켜보시기 바랍니다. 이 글을 쓰고 있는 현재 시점에서 아래의 스크린샷들과 예제들은 모두 최신의 크롬 카나리, 크롬 27버전부터 동작합니다.

Figure 1: 단일-레이어로 구성된 페이지. (새창으로 열기)

<!doctype html>
<html>
<body>
  <div>I am a strange root.</div>
</body>
</html>
Screenshot of composited layer render borders around the page's base layer
페이지의 기반 레이어 주변의 테두리를 렌더링하는 합성(Composited)된 레이어의 스크린샷

이 페이지는 단 하나의 레이어를 가지고 있습니다. 파란색 그리드는 크롬에서 커다란 레이어의 일부를 GPU로 한번에 업로드하기 위해 사용되는 레이어의 서브-유닛(Sub-unit)으로 간주할 수 있는 타일들을 표현합니다. 이 글에서 정말 중요한 것은 아닙니다.

Figure 2: 레이어의 엘리먼트(새창으로 열기)

<!doctype html>
<html>
<body>
  <div style="transform: rotateY(30deg) rotateX(-30deg); width: 200px;">
    I am a strange root.
  </div>
</body>
</html>
Screenshot of rotated layer's render borders
레이어의 회전으로 렌더링된 테두리의 스크린샷

엘리먼트가 자체의 레이어를 가지고 있을 때 <div>를 회전하는 3D CSS 속성 설정에 의해 다음과 같이 어떻게 보여지는지 확인할 수 있습니다. 이 뷰에서 레이어를 표지하는 오렌지색 테두리에 주의를 기울이시기 바랍니다.

레이어의 생성 기준

레이어 이외에 가지는 것은 무엇일까요? 경험적으로 크롬은 오랜동안 지속적으로 진화해왔지만 현재는 다음과 같은 모든 사항에 대해 레이어를 생성합니다.

  • 3D 혹은 시점(Perspective) 변환(Transform) CSS 속성
  • 비디오 디코딩 가속을 사용하는 <video> 엘리먼트
  • 3D(WebGL) 컨텍스트나 가속되는 2D 컨텍스트를 가진 <canvas> 엘리먼트
  • (예를 들어 Flash)와 같이 포함된 플러그인
  • 투명도(Opacity)의 CSS 애니메이션이나 애니메이션되는 변환(Transform)을 사용하는 엘리먼트
  • 가속되는 CSS 필터를 가진 엘리먼트
  • 합성 레이어(Compositing layer)를 자손으로 가지는 엘리먼트 (즉, 스스로의 레이어를 가지는 자식 엘리먼트를 가진 엘리먼트)
  • 형제(Sibling)가 합성 레이어와 낮은 z-index를 가지는 엘리먼트 (즉, 합성 레이어의 상단에 렌더링되는 엘리먼트)

현실적인 영향 요소: 애니메이션

애니메이션을 매우 유용하게 만드는 레이어로 다시 옮겨가보도록 하겠습니다.

Figure 3: 애니메이션된 레이어 (새창에서 열기)

<!doctype html>
<html>
<head>
  <style>
  div {
    animation-duration: 5s;
    animation-name: slide;
    animation-iteration-count: infinite;
    animation-direction: alternate;
    width: 200px;
    height: 200px;
    margin: 100px;
    background-color: gray;
  }
  @keyframes slide {
    from {
      transform: rotate(0deg);
    }
    to {
      transform: rotate(120deg);
    }
  }
  </style>
</head>
<body>
  <div>I am a strange root.</div>
</body>
</html>

이전에도 말햇듯이, 레이어는 정적인 웹 컨텐츠를 움직이는데 실로 유용합니다. 기본적인 경우, 크롬은 레이어의 컨텐츠를 GPU에 텍스쳐로 업로드하기 전에 소프트웨어 비트맵으로 출력합니다. 만약 앞으로도 컨텐츠가 변경되지 않는다면 다시 그려야할 필요가 없습니다. 이는 다음과 같은 좋은 점들이 있습니다. 재출력(Repaint)는 자바스크립트의 실행과 같은 다른 것들에 사용될 수 있는 시간을 소모할 수 있으며 만약 출력에 긴 시간이 걸린다면 애니메이션이 벌벌 떨리거나 딜레이되는 현상을 초래할 수 있습니다

에를 들어 이관점에서 다음과 같은 개발자도구의 타임라인을 보도록 하겠습니다. 레이어가 앞뒤로 회전하는 동안 아무런 출력 동작도 일어나지 않습니다.

Screenshot of Dev Tools timeline during animation
애니메이션 중의 개발자도구 타임라인의 스크린샷

무효한! 재출력(Repainting)

그러나 만약 레이어가 변경된다면 이는 반드시 재출력(Repaint)되어야 합니다.

Figure 4: 레이어 다시 그리기(Repaints) (새창에서 열기)

<!doctype html>
<html>
<head>
  <style>
  div {
    animation-duration: 5s;
    animation-name: slide;
    animation-iteration-count: infinite;
    animation-direction: alternate;
    width: 200px;
    height: 200px;
    margin: 100px;
    background-color: gray;
  }
  @keyframes slide {
    from {
      transform: rotate(0deg);
    }
    to {
      transform: rotate(120deg);
    }
  }
  </style>
</head>
<body>
  <div id="foo">I am a strange root.</div>
  <input id="paint" type="button" value="repaint">
  <script>
    var w = 200;
    document.getElementById('paint').onclick = function() {
      document.getElementById('foo').style.width = (w++) + 'px';
    }
  </script>
</body>
</html>

Input 엘리먼트는 클릭이 될 때마다 회전되는 엘리먼트를 폭을 1px씩 넓게 합니다. 이는 재배치(Relayout)와 이 경우 전체 레이어에 해당하는 전체 엘리먼트의 재출력을 발생합니다.

무엇이 출력되는지를 볼 수 있는 좋은 방법은 개발자도구 설정의 "Rendering" 항목 밑의 "show paint rects"입니다. 이를 켠 뒤에 버튼을 클릭할 때마다 애니메이션되는 엘리먼트와 버튼이 둘다 빨간색으로 반짝이는 것을 주의하여 보시기 바랍니다.

Screenshot of show paint rects checkbox
show paint rect 화면

출력(paint) 이벤트 역시 개발자도구의 타임라인에 나타납니다. 날카로운 눈을 가진 독자들은 그곳에 다음과 같은 2개의 출력(Paint) 이벤트가 있다는 사실을 알아챌 것입니다. 하나는 레이어에 대한 것이고 다른 하나는 (버튼이) 눌린 상태로(혹은 부터) 변경되었을 때 재출력되는 버튼 그 자체에 대한 것입니다.

Screenshot of Dev Tools Timeline repainting a layer
개발자도구 타임라인의 레이어 재출력(Repainting) 스크린샷

크롬은 항상 전체 레이어에 대한 재출력을 필요로하지는 않는다는 점에 주의하여야 하며 DOM이 무효화(Invalid)된 DOM의 일부만 재출력함으로써 영리해지려 합니다. 이 경우 수정된 DOM 엘리먼트는 전체 레이어의 크기입니다. 그러나 다른 대부분의 경우 여러개의 DOM 엘리먼트들이 하나의 레이어 안에 있을 것입니다.

그 다음으로 뻔한 질문은 무엇이 무효화(Invalidation)을 발생하고 재출력(Repaint)를 강제로 하게 하는가입니다. 무효화(Invalidation)을 발생할 수 있는 극단적인 많은 경우있기 때문에 완전하게 답하기는 곤란합니다. 가장 일반적인 발생은 CSS 스타일의 조작이나 레이아웃-재정렬(Relayout)의 발생으로 인해 DOM이 갱신되어야 하는 경우입니다. Tony Gentilcore가 작성한 무엇이 레이아웃-재정렬(relayout)을 발생하는가에 대한 훌륭한 블로그 포스트와 Stoyan Stefanov가 작성한 페인팅을 다룬 글은 보다 자세한 사항을 담고 있습니다. (그러나 이 훌륭한 합성(Compositing)과 관련된 것이 아닌 그냥 출력(Painting)에 대해서만 다루는 것으로 끝납니다.)

만약 그것이 작업 중인 무엇인가에 영향을 주는지를 이해할 수 있는 가장 좋은 방법은 원하지 않는 재출력 여부를 확인할 수 있고 재정렬/재출력 직전에 DOM의 갱신 필요 여부를 증명하기 위한 개발자도구의 타임라인과 출력 사각형 보기(Show Paint Rect) 도구를 사용하는 것입니다. 만약 출력이 불가피하지만 이유없이 길게 보인다면 개발자도구의 연속적인 페인팅 모드에 대한 Eberhard Gräther의 글을 확인해보시기 바랍니다.

종합: DOM에서 스크린으로

그럼 크롬은 어떻게 DOM을 스크린 이미지로 변경할까요? 개념적으로는 다음과 같습니다.

  1. DOM을 얻고 그것들을 레이어들로 분리합니다.
  2. 이 레이어들 각각을 독립적인 소프트웨어 비트맵으로 출력합니다.
  3. 그것들을 GPU에 텍스쳐로써 업로드합니다.
  4. 다양한 레이어를 최종 스크린 이미지로 함께 합성합니다.

이것이 크롬이 처음에 웹페이지의 프레임을 생성할 떄 일어나는 모든 것입니다. 그러나 그 이후로부터 발생하는 프레임들에 대해서는 다음과 같이 몇가지 손쉬운 방법을 사용할 수 있습니다.

  • 정확한 CSS 속성이 변경되면 아무것도 재출력할 필요가 없습니다. 크롬은 그저 이미 GPU에 텍스쳐로 저장되어 있지만 다르게 합성될 속성들(예를 들어 다른 위치에서 다른 투명도 등)을 가진 기존 레이어들을 다시 합성할 수 있습니다.
  • 만약 레이어의 일부가 무효화되면 그는 다시 출력되고 업로드됩니다. 만약 컨텐츠가 똑같이 남아있는데 합성 속성들이 변경(예를 들어 이동하거나 투명도가 변하는 등)되면 크롬은 GPU의 것을 그대로 두고 새로운 프레임을 생성하기 위해 다시 합성을 수행할 수도 있습니다.

역주: '정확한 CSS 속성이 변경될 경우 재출력될 필요가 없다.'는 뜻은 CSS의 속성 변화가 재출력의 회피를 보장한다는 뜻이 아닙니다. 여기서의 정확한 CSS 속성은 합성(Composition)의 발생 조건만을 가지는 CSS 속성들을 뜻합니다. 이러한 대표적인 속성은 trasform의 translate(), rotate(), scale()과 opacity 등이 있습니다. 자세한 사항은 Paul Lewis와 Paul Irish가 쓴 고성능 애니메이션을 참조하시기 바랍니다.

이제 확실하게 말하자면 레이어-기반의 합성 모델(Layer-based compositing model)은 렌더링 성능에 깊은 영향을 가지고 있습니다. 합성(Compositing)은 아무것도 출력(Paint)이 필요하지 않을 때 비교적 저렴하므로 레이어의 재출력을 피하는 것은 렌더링 성능을 디버깅하고자 할 때 전체적인 목표가 됩니다. 요령있는 개발자들은 위의 합성을 발생하는 것들의 리스트를 보고 강제로 레이어의 생성하는 것이 쉽게 가능함을 깨달을 것입니다. 그러나 다음과 같이 그는 공짜가 아니기 때문에 맹목적으로 그들을 생성하기만 하는 것을 주의해야 합니다. (모바일 장비에서는 특히 제한된) 시스템과 GPU의 메모리를 잡아먹고 지나치게 많은 생성은 가시성 추적을 유지하는 또다른 로직 상의 부하를 발생합니다. 많은 레이어는 또한 그들이 크거나 이전에 있지 않던 곳에서 많이 겹치게될 때 픽셀화(Rasterizing)에 소모되는 시간을 실제로 증가시키고 때로는 "과도한 출력(Overdraw)"이라 언급되는 무언가로 이어집니다. 그러므로 이 지식을 현명하게 사용하여야 합니다!

이제 전부 끝났습니다. 레이어 모델의 실제적인 영향에 대한 더 많은 글들을 보시기 바랍니다.

추가적인 리소스들

  1. 스크롤 성능
  2. 개발자도구의 연속 페인팅 모드를 이용한 긴 출력시간 프로파일링하기
  3. http://jankfree.com
  4. http://aerotwist.com/blog/on-translate3d-and-layer-creation-hacks/

Comments

0