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에서 레이어를 연구할 때 매우 유용한 도구는 Dev Tools의 '렌더링' 제목 아래에 있는 설정 (작은 톱니바퀴 아이콘)의 '합성된 레이어 테두리 표시' 플래그입니다. 레이어가 화면의 어디에 있는지 매우 간단하게 강조 표시합니다. 사용 설정해 보겠습니다. 이 스크린샷과 예시는 모두 이 글을 작성할 당시의 최신 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>
회전된 레이어의 렌더링 테두리 스크린샷
회전된 레이어의 렌더링 테두리 스크린샷

회전하는 <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 타임라인의 이 뷰를 살펴보세요. 이 레이어가 앞뒤로 회전하는 동안에는 페인트 작업이 없습니다.

애니메이션 중 DevTools 타임라인의 스크린샷
애니메이션 중 DevTools 타임라인 스크린샷

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

하지만 레이어의 콘텐츠가 변경되면 다시 그려야 합니다.

그림 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픽셀 더 넓어집니다. 이렇게 하면 전체 요소(이 경우 전체 레이어)가 다시 레이아웃되고 다시 그려집니다.

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

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

페인트 이벤트도 Dev Tools 타임라인에 표시됩니다. 눈이 밝은 독자는 레이어용 페인트 이벤트와 버튼 자체용 페인트 이벤트가 두 개 있음을 알아차릴 수 있습니다. 버튼 자체용 페인트 이벤트는 버튼이 눌린 상태와 눌리지 않은 상태 간에 변경될 때 다시 그려집니다.

레이어를 다시 칠하는 Dev Tools 타임라인의 스크린샷
레이어를 다시 칠하는 DevTools 타임라인 스크린샷

Chrome은 항상 전체 레이어를 다시 그릴 필요는 없으며, 무효화된 DOM 부분만 다시 그리는 것이 좋습니다. 이 경우 수정된 DOM 요소는 전체 레이어의 크기입니다. 하지만 대부분의 경우 레이어에 DOM 요소가 많이 있습니다.

다음으로 알아야 할 것은 무효화의 원인과 강제 재색상 지정입니다. 무효화를 강제할 수 있는 특이 사례가 많기 때문에 이 질문에 완전히 답변하기는 어렵습니다. 가장 일반적인 원인은 CSS 스타일을 조작하거나 레이아웃 재조정을 일으켜 DOM을 더럽히는 것입니다. Tony Gentilcore의 재레이아웃의 원인에 관한 블로그 게시물과 Stoyan Stefanov의 페인팅을 다루는 도움말을 참고하세요. 이 도움말은 페인팅에 관해 자세히 설명하지만 이 멋진 합성 작업은 다루지 않습니다.

이 문제가 작업 중인 항목에 영향을 미치는지 확인하는 가장 좋은 방법은 Dev Tools 타임라인 및 Show Paint Rects 도구를 사용하여 원치 않는 리페인팅이 발생하는지 확인한 다음 리레이아웃/리페인팅 직전에 DOM을 더럽힌 위치를 파악하는 것입니다. 페인팅이 불가피하지만 시간이 너무 오래 걸리는 것 같다면 Dev Tools의 연속 페인팅 모드에 관한 Eberhard Gräther의 도움말을 확인하세요.

종합: DOM에서 화면으로

Chrome은 DOM을 화면 이미지로 어떻게 변환하나요? 개념적으로 다음과 같은 역할을 합니다.

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

이 모든 작업은 Chrome에서 웹페이지의 프레임을 처음 생성할 때 이루어져야 합니다. 하지만 이후에는 향후 프레임에 몇 가지 단축키를 사용할 수 있습니다.

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

이제 레이어 기반 합성 모델이 렌더링 성능에 큰 영향을 미친다는 것을 알 수 있습니다. 페인팅할 항목이 없으면 합성은 비교적 저렴하므로 렌더링 성능을 디버그하려고 할 때는 레이어의 다시 페인트 작업을 피하는 것이 좋습니다. 현명한 개발자는 위의 합성 트리거 목록을 살펴보고 레이어 생성을 쉽게 강제할 수 있다는 것을 알 수 있습니다. 하지만 무작위로 생성하지는 마세요. 무료가 아니기 때문입니다. 시스템 RAM과 GPU의 메모리를 차지하며 (특히 휴대기기에서는 제한적) 뷰어블 노드를 추적하는 로직에 다른 오버헤드가 발생할 수 있습니다. 또한 레이어가 크고 이전에는 겹치지 않았던 부분에서 많이 겹치면 래스터링하는 데 걸리는 시간이 실제로 늘어날 수 있으며, 이로 인해 '오버드로'라고도 하는 문제가 발생할 수 있습니다. 따라서 지식을 현명하게 사용하세요.

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

추가 리소스