Chrome의 가속 렌더링

계층 모델

Tom Wiltzius
Tom Wiltzius

소개

대부분의 웹 개발자에게 있어 웹페이지의 기본 모델은 DOM입니다. 렌더링은 페이지의 이러한 표현을 화면의 사진으로 변환하는 모호한 과정인 경우가 많습니다. 최신 브라우저는 최근 몇 년 동안 그래픽 카드를 활용하기 위해 렌더링 작동 방식을 변화시켰습니다. 이를 모호하게 "하드웨어 가속"이라고 합니다. Canvas2D나 WebGL이 아닌 일반 웹페이지에 대해 이야기할 때 이 용어는 실제로 무엇을 의미하나요? 이 도움말에서는 Chrome에서 웹 콘텐츠의 하드웨어 가속 렌더링을 지원하는 기본 모델을 설명합니다.

크고 지방이 많은 주의사항

여기서 WebKit에 대해 이야기하고 있으며, 더 구체적으로는 WebKit의 Chromium 포트에 대해 이야기하고 있습니다. 이 도움말에서는 웹 플랫폼 기능이 아닌 Chrome 구현 세부정보를 설명합니다. 웹 플랫폼과 표준은 이러한 수준의 구현 세부정보를 코드화하지 않으므로 이 도움말의 내용이 다른 브라우저에 적용된다는 보장은 없습니다. 하지만 내부 기능에 대한 지식은 고급 디버깅 및 성능 조정에 유용할 수 있습니다.

또한 이 전체 글에서는 빠르게 변화하는 Chrome 렌더링 아키텍처의 핵심 부분에 대해 설명합니다. 이 도움말에서는 변경될 가능성이 낮은 항목에 대해서만 다루고 있으며, 6개월이 지나도 모두 적용된다는 보장은 없습니다.

한동안 Chrome에는 하드웨어 가속 경로와 이전 소프트웨어 경로라는 두 가지 다른 렌더링 경로가 있었다는 점을 이해해야 합니다. 이 문서를 작성하는 시점에는 모든 페이지가 Windows, ChromeOS, Android용 Chrome의 하드웨어 가속 경로를 따릅니다. Mac 및 Linux에서는 일부 콘텐츠에 대해 합성이 필요한 페이지에서만 가속 경로로 이동하지만 (합성에 필요한 항목에 대한 자세한 내용은 아래를 참조), 곧 모든 페이지가 가속 경로로 이동하게 됩니다.

마지막으로, 렌더링 엔진의 후드를 엿보고 성능에 큰 영향을 미치는 엔진 기능을 살펴보겠습니다. 자체 사이트의 성능을 개선하려고 할 때 레이어 모델을 이해하는 것이 도움이 될 수 있으며, 레이어 모델을 이해하는 것도 쉽습니다. 레이어는 유용한 구성이지만 많은 레이어를 생성하면 그래픽 스택 전체에 오버헤드가 발생할 수 있습니다. 미리 경고드립니다.

DOM에서 화면으로

레이어 소개

페이지가 로드되고 파싱되면 브라우저에서 많은 웹 개발자가 잘 알고 있는 구조인 DOM으로 표현됩니다. 그러나 페이지를 렌더링할 때 브라우저에는 개발자에게 직접 노출되지 않는 일련의 중간 표현이 있습니다. 이러한 구조에서 가장 중요한 것은 레이어입니다.

Chrome에는 실제로 DOM의 하위 트리를 담당하는 RenderLayers와 RenderLayers의 하위 트리를 담당하는 GraphicsLayers 등 여러 유형의 레이어가 있습니다. 여기서 후자가 가장 흥미롭습니다. GraphicsLayers가 GPU에 텍스처로 업로드되기 때문입니다. 지금부터는 GraphicsLayer를 의미하기 위해 '레이어'라고 하겠습니다.

GPU 용어 제외: 텍스처란? 기본 메모리 (예: RAM)에서 동영상 메모리 (즉, GPU의 VRAM)로 이동하는 비트맵 이미지로 생각하면 됩니다. GPU에 배치한 후에는 메시 도형에 매핑할 수 있습니다. 비디오 게임 또는 CAD 프로그램에서 이 기법은 골격 3D 모델에 '스킨'을 제공하는 데 사용됩니다. Chrome은 텍스처를 사용하여 웹페이지 콘텐츠 청크를 GPU로 가져옵니다. 텍스처를 매우 간단한 직사각형 메시에 적용하여 다양한 위치와 변환에 저렴하게 매핑할 수 있습니다. 이는 3D CSS가 작동하는 방식이며 빠른 스크롤에도 좋습니다. 두 가지 모두 나중에 자세히 살펴보겠습니다.

계층 개념을 설명하기 위해 몇 가지 예를 살펴보겠습니다.

Chrome에서 레이어를 연구할 때 매우 유용한 도구는 개발자 도구의 '렌더링' 제목 아래에 있는 설정 (예: 작은 톱니바퀴 아이콘)의 '합성 레이어 테두리 표시' 플래그입니다. 화면에서 레이어가 있는 위치만 강조 표시할 뿐입니다. 사용 설정해 보겠습니다. 아래 스크린샷과 예는 모두 이 문서의 작성 시점을 기준으로 최신 Chrome Canary인 Chrome 27에서 가져온 것입니다.

그림 1: 단일 레이어 페이지

<!doctype html>
<html>
<body>
  <div>I am a strange root.</div>
</body>
</html>
<ph type="x-smartling-placeholder">합성된 레이어가 페이지의 기본 레이어 주위로 테두리를 렌더링하는 스크린샷</ph>
페이지의 기본 레이어 주위로 합성된 레이어 렌더링 테두리 스크린샷

이 페이지에는 레이어가 하나만 있습니다. 파란색 그리드는 카드를 나타냅니다. Chrome은 큰 레이어의 일부를 GPU에 한 번에 업로드하는 데 사용하는 레이어의 하위 단위로 생각할 수 있습니다. 여기서는 그다지 중요하지 않습니다.

그림 2: 자체 레이어에 있는 요소

<!doctype html>
<html>
<body>
  <div style="transform: rotateY(30deg) rotateX(-30deg); width: 200px;">
    I am a strange root.
  </div>
</body>
</html>
<ph type="x-smartling-placeholder">회전된 레이어의 렌더링 테두리 스크린샷</ph>
회전된 레이어의 렌더링 테두리 스크린샷

요소를 회전하는 <div>에 3D CSS 속성을 배치하면 요소가 자체 레이어를 만들 때 어떻게 표시되는지 확인할 수 있습니다. 이 뷰에서 레이어의 윤곽선을 표시하는 주황색 테두리를 확인하세요.

레이어 생성 기준

또 무엇이 자체 레이어를 얻게 될까요? 여기에서 Chrome의 휴리스틱은 시간이 지남에 따라 발전해 왔으며 계속 진화하고 있지만, 현재는 다음과 같은 트리거 레이어 생성이 가능합니다.

  • 3D 또는 원근 변환 CSS 속성
  • 가속 동영상 디코딩을 사용하는 요소 <video>
  • 3D (WebGL) 컨텍스트 또는 가속 2D 컨텍스트가 있는 <canvas> 요소
  • 합성된 플러그인 (예: 플래시)
  • 불투명도에 CSS 애니메이션이 있거나 애니메이션 변환을 사용하는 요소
  • 가속 CSS 필터가 포함된 요소
  • 요소에 합성 레이어가 있는 하위 요소가 있음 (즉, 자체 레이어에 있는 하위 요소가 요소에 있는 경우)
  • 요소에 합성 레이어가 있는 더 낮은 Z-색인의 동위 요소가 있습니다 (즉, 합성된 레이어 위에 렌더링됨).

실질적인 의미: 애니메이션

레이어를 이동할 수도 있으므로 애니메이션에 매우 유용합니다.

그림 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>

앞서 언급했듯이 레이어는 정적 웹 콘텐츠에서 이동하는 데 매우 유용합니다. 기본적인 경우 Chrome은 레이어의 콘텐츠를 GPU에 텍스처로 업로드하기 전에 소프트웨어 비트맵에 페인팅합니다. 콘텐츠가 향후 변경되지 않으면 다시 페인트할 필요가 없습니다. 이는 좋은 일입니다. 다시 페인트하는 데 JavaScript 실행과 같은 다른 작업에 시간이 걸릴 수 있고 페인트가 길면 애니메이션에 문제나 지연이 발생합니다.

예를 들어 다음과 같은 Dev Tools 타임라인 뷰를 볼 수 있습니다. 이 레이어가 앞뒤로 회전하는 동안에는 페인트 작업이 없습니다.

<ph type="x-smartling-placeholder">
</ph> 애니메이션 도중의 개발자 도구 타임라인 스크린샷
애니메이션이 진행되는 동안의 개발자 도구 타임라인 스크린샷

잘못되었습니다. 다시 칠하기

그러나 레이어의 콘텐츠가 변경되면 다시 페인트해야 합니다.

그림 4: 레이어 다시 그리기

<!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>

입력 요소가 클릭될 때마다 회전 요소가 1픽셀씩 커집니다. 이로 인해 전체 요소(이 경우에는 전체 레이어)가 다시 레이아웃되고 다시 페인트됩니다.

페인트되는 내용을 확인하는 좋은 방법은 개발자 도구의 '페인트 rects 표시' 도구를 사용하는 것입니다. 개발자 도구 설정의 '렌더링' 제목 아래에 있습니다. 사용 설정한 후 버튼을 클릭하면 애니메이션 요소와 버튼이 모두 빨간색으로 깜박입니다.

<ph type="x-smartling-placeholder">
</ph> 페인트 직사각형 표시 체크박스 스크린샷
페인트 rect 표시 체크박스 스크린샷

페인트 이벤트는 개발자 도구 타임라인에도 표시됩니다. 눈이 예리한 독자는 두 가지 페인트 이벤트가 있다는 것을 알 수 있습니다. 하나는 레이어용이고 하나는 버튼 자체에 대한 이벤트입니다.

<ph type="x-smartling-placeholder">
</ph> 레이어를 다시 페인팅하는 개발자 도구 타임라인 스크린샷
레이어를 다시 페인팅하는 Dev Tools 타임라인의 스크린샷

Chrome은 항상 전체 레이어를 다시 페인트할 필요는 없으며, 무효화된 DOM 부분만 다시 페인팅하는 데 스마트하게 시도합니다. 이 경우 수정한 DOM 요소는 전체 레이어의 크기입니다. 그러나 다른 많은 경우 레이어에 많은 DOM 요소가 있습니다.

다음으로 분명한 질문은 무효화를 유발하고 다시 페인트를 강제로 적용하는 것입니다. 무효화를 강요할 수 있는 극단적인 사례가 많기 때문에 이 방법은 철저하게 답변하기가 까다롭습니다. 가장 일반적인 원인은 CSS 스타일을 조작하거나 재배치를 유발하여 DOM을 더티게 하는 것입니다. Tony Gentilcore는 재레이아웃의 원인에 대한 블로그 게시물을 게시하고 스토얀 스테파노프는 회화에 대한 자세한 내용을 다루고 있습니다. 마지막 부분은 그림 그리기가 아니라 그림 그리기로 끝납니다.

작업 중인 항목에 영향을 주는지 확인하는 가장 좋은 방법은 Dev Tools Timeline 및 Show Paint Rects 도구를 사용하여 다시 페인트하지 않았으면 하는 경우 다시 페인트하고 있는지 확인한 다음 다시 레이아웃/다시 페인트 직전에 DOM을 더 이상 지운 부분을 파악하는 것입니다. 페인트를 칠할 수밖에 없지만 너무 오래 걸리는 것 같다면 개발자 도구의 연속 페인팅 모드에 대한 에버하드 그레터의 도움말을 확인하세요.

종합: DOM에서 화면으로

그렇다면 Chrome은 어떻게 DOM을 화면 이미지로 변환할까요? 개념적으로는 다음과 같습니다.

  1. DOM을 가져와 레이어로 분할합니다.
  2. 이러한 각 레이어를 소프트웨어 비트맵에 독립적으로 페인트합니다.
  3. 텍스처로 GPU에 업로드합니다.
  4. 다양한 레이어를 최종 화면 이미지로 합성합니다.

이 모든 작업은 Chrome이 웹페이지의 프레임을 처음으로 생성할 때 이루어져야 합니다. 그러나 향후 프레임에는 몇 가지 지름길이 필요할 수 있습니다.

  1. 특정 CSS 속성이 변경되면 다시 페인트할 필요가 없습니다. Chrome은 GPU에 이미 텍스처로 존재하지만 다른 합성 속성 (예: 위치, 불투명도 등)이 다른 기존 레이어를 다시 합성할 수 있습니다.
  2. 레이어의 일부가 무효화되면 다시 페인트하여 다시 업로드됩니다. 콘텐츠는 동일하게 유지되지만 합성된 속성이 변경되는 경우 (예: 변환되거나 불투명도가 변경됨) Chrome은 GPU에 그대로 두고 다시 합성하여 새 프레임을 만들 수 있습니다.

계층 기반 합성 모델은 렌더링 성능에 큰 영향을 미칩니다. 아무것도 페인트할 필요가 없을 때는 합성 비용이 비교적 저렴하므로, 렌더링 성능을 디버그하려고 할 때 전반적으로 레이어 다시 페인트를 피하는 것이 좋은 목표입니다. 능숙한 개발자는 위의 합성 트리거 목록을 보고 이를 통해 손쉽게 레이어를 만들 수 있음을 알게 될 것입니다. 그러나 이러한 API는 무료가 아니므로 맹목적으로 생성하는 것에 주의하세요. 시스템 RAM과 GPU (특히 모바일 기기에서 제한됨)에서 메모리를 차지하며 많은 메모리를 사용하면 로직에 다른 오버헤드가 발생하여 어떤 항목이 표시되는지 추적할 수 있습니다. 또한 많은 레이어의 레이어가 크고 이전에 겹치지 않았는데 많은 경우 래스터화에 소요되는 시간을 실제로 증가시켜 '오버드로'라고도 합니다. 그러니 지식을 현명하게 사용하세요.

오늘 소식은 여기까지입니다. 계층 모델의 실질적인 의미에 대한 몇 가지 추가 기사를 기대해 주세요.

추가 리소스