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를 담당하는 여러 유형의 레이어가 있습니다. 여기서 후자가 가장 흥미롭습니다. GraphicsLayer가 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>
페이지 기본 레이어 주위의 합성 레이어 렌더링 테두리 스크린샷
페이지 기본 레이어 주변의 합성 레이어 렌더링 테두리 스크린샷

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

그림 2: 자체 레이어의 요소

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

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

레이어 생성 기준

자체 레이어를 가지는 다른 것은 무엇인가요? Chrome의 휴리스틱은 시간이 지남에 따라 계속 발전해 왔지만 현재 다음과 같은 트리거 레이어 생성이 가능합니다.

  • 3D 또는 원근법으로 CSS 속성을 변환합니다.
  • 가속 동영상 디코딩을 사용하는 요소 <video>
  • 3D (WebGL) 컨텍스트 또는 가속 2D 컨텍스트가 있는 <canvas> 요소
  • 복합 플러그인 (예: Flash)
  • 불투명도에 대한 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 타임라인 보기는 이 레이어가 앞뒤로 회전하는 동안 페인트 작업이 실행되지 않습니다.

애니메이션 도중의 개발자 도구 타임라인 스크린샷
애니메이션 도중의 개발자 도구 타임라인 스크린샷

잘못되었습니다. 다시 페인팅

하지만 레이어의 콘텐츠가 변경되면 다시 페인트해야 합니다.

그림 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 표시' 도구를 사용하는 것입니다. 사용 설정한 후 버튼을 클릭하면 애니메이션 요소와 버튼이 모두 빨간색으로 깜박입니다.

페인트 직사각형 표시 체크박스 스크린샷
페인트 직사각형 표시 체크박스의 스크린샷

페인트 이벤트는 개발자 도구 타임라인에도 표시됩니다. 예리한 눈을 가진 독자라면 레이어에 대한 페인트 이벤트와 버튼 자체에 대한 페인트 이벤트가 두 개 있을 수 있습니다. 버튼이 눌린 상태로 변경될 때 다시 페인트되는 이벤트가 있습니다.

레이어를 다시 칠하는 개발자 도구 타임라인의 스크린샷
레이어를 다시 칠하는 개발자 도구 타임라인의 스크린샷

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

다음 문제는 무엇이 무효화를 유발하고 리페인트를 강제하는가입니다. 무효화를 강제할 수 있는 특이 사례가 많기 때문에 정확한 답변을 제시하기가 어렵습니다. 가장 일반적인 원인은 CSS 스타일을 조작하거나 레이아웃을 재조정하여 DOM을 더티(더티)시키는 것입니다. 토니 젠틸코어는 레이아웃 재배치 원인에 관한 블로그 게시물을 읽어보세요. 또한 스토얀 스테파노프는 회화에 대해 자세히 다루는 기사도 확인할 수 있습니다. 하지만 화려한 합성 자료가 아닌 그림으로 마무리됩니다.

작업 중인 항목에 영향을 미치는지 파악하는 가장 좋은 방법은 Dev Tools 타임라인과 Show Paint Rects(페인트 직사각형 표시) 도구를 사용하여 원하는 작업을 하고 있지 않을 때 다시 페인트를 하고 있는지 확인한 다음, 재레이아웃/재페인트 직전에 DOM을 더러운 부분을 식별하는 것입니다. 페인팅이 불가피한 경우 지나치게 오래 걸리는 것 같다면 개발자 도구의 연속 페인팅 모드에 관한 Eberhard Gräther의 도움말을 확인하세요.

종합하기: DOM을 스크린으로

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

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

이 모든 작업은 Chrome이 웹페이지 프레임을 처음으로 생성할 때 이루어져야 합니다. 그러나 이후 프레임에 몇 가지 단축키를 사용할 수 있습니다.

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

이제 명확하게 알 수 있듯이 레이어 기반 합성 모델은 렌더링 성능에 큰 영향을 미칩니다. 아무것도 그릴 필요가 없는 경우 합성은 비교적 저렴하므로, 렌더링 성능을 디버그하려고 할 때 레이어의 다시 페인트를 방지하는 것이 전반적인 목표가 좋습니다. 숙련된 개발자는 위의 합성 트리거 목록을 살펴보고 쉽게 레이어 생성을 만들 수 있음을 깨닫게 됩니다. 그러나 이러한 객체는 무료가 아니기 때문에 맹목적으로 생성하는 것에 주의해야 합니다. 메모리는 시스템 RAM과 GPU (특히 휴대기기에서 제한됨)에서 메모리를 차지하며, 메모리가 많으면 보이는 것을 추적하는 로직에 다른 오버헤드가 발생할 수 있습니다. 또한 많은 레이어는 레이어가 크고 이전에 하지 않았던 많은 부분과 겹치는 경우 실제로 래스터화에 소요되는 시간을 증가시킬 수 있습니다. 이로 인해 때때로 '오버드로'라고도 하는 문제가 발생합니다. 따라서 지식을 현명하게 사용하세요!

오늘은 여기까지입니다 레이어 모델의 실질적인 함의에 관한 추가 도움말을 기대해 주세요.

추가 리소스