합성
고친 사람 github-actions[bot]
합성은 따로 그려 둔 여러 장의 그림을 겹쳐 화면 한 장을 만듭니다. 브라우저는 웹 페이지를 몇 장의 판으로 나눠 그린 뒤 마지막 단계에서 이 일을 합니다. 판 하나가 움직일 때 다시 그리지 않고 겹치기만 새로 하면 되므로 움직임이 가벼워집니다. 객체지향 설계에서 말하는 합성은 이름만 같은 다른 개념입니다.
쉽고 빠른 이해
합성은 여러 장의 그림을 뒤에서 앞 순서로 포개 한 화면으로 만드는 일입니다. 스크롤해도 위에 붙어 있는 머리글이 있는 페이지라면, 머리글 그림과 본문 그림을 따로 들고 있다가 매번 포개어 냅니다.
페이지 전체를 한 장에 그리면 조금만 움직여도 전부 다시 그려야 합니다. 그리는 일은 오래 걸려서 화면이 끊겨 보이기 쉽습니다. 움직이는 부분을 따로 그려 두면 그 그림을 옮겨 다시 포개기만 하면 됩니다.
- 페이지를 몇 장의 판으로 나눠 판마다 그림을 그려 둡니다
- 판들을 정해진 위치와 투명도로 뒤에서부터 포갭니다
- 판 하나가 움직이면 그 판을 다시 그리지 않고 2만 다시 합니다
대가는 메모리입니다. 판마다 그림을 한 장씩 따로 들고 있어야 합니다. 판이 너무 많으면 포개는 일도 무거워집니다.
상세
셀 애니메이션을 만드는 화실을 떠올려 봅시다. 배경은 종이 한 장에 한 번만 그려 둡니다. 자동차는 투명한 필름에 따로 그려 배경 위에 올립니다.
자동차가 달리는 장면에서는 배경 종이를 카메라 아래에서 옆으로 조금씩 밉니다. 밀 때마다 한 컷씩 찍습니다. 배경도 자동차도 다시 그리지 않습니다.
화면을 만드는 프로그램도 같은 방식을 씁니다. 화면의 부분부분을 따로 그린 그림 여러 장을 둡니다. 이것을 순서대로 겹쳐 최종 화면 한 장을 만듭니다. 이 겹치는 처리를 합성(compositing)이라고 부릅니다.
화면을 나눈 판 하나를 레이어라고 부릅니다. 레이어마다 제 그림을 한 장씩 가집니다.
렌더링 단계 속 합성의 몫
이 소절은 브라우저가 화면을 그리는 단계들 가운데 합성이 어디에 서는지 봅니다.
브라우저는 서버가 보낸 글을 읽어 화면을 그립니다. 내용과 구조는 HTML(HyperText Markup Language, 하이퍼텍스트 마크업 언어)로 적혀 옵니다. 색과 크기 같은 생김새는 CSS(Cascading Style Sheets, 캐스케이딩 스타일 시트)로 적혀 옵니다. 이 글을 화면의 그림으로 바꾸는 일을 렌더링이라고 부릅니다.
렌더링은 여러 단계로 나뉘어 돕니다. 위에서부터 차례로 돕니다. 앞 단계가 낸 것을 다음 단계가 받습니다.
| 단계 | 하는 일 |
|---|---|
| 스타일 계산 | 요소마다 어떤 CSS 규칙이 걸리는지 정한다 |
| 레이아웃 | 요소마다 화면의 어느 좌표에 얼마만 한 크기로 놓일지 계산한다 |
| 페인트 | 무엇을 어떤 순서로 칠할지 그리기 명령의 목록으로 적는다 |
| 래스터화 | 그리기 명령을 레이어마다 따로 픽셀 그림으로 바꾼다 |
| 합성 | 레이어 수만큼 생긴 그림을 겹쳐 화면 한 장으로 만든다 |
표에서 합성은 맨 끝에 있습니다. 끝에 있다는 것이 이 단계의 쓸모입니다. 앞 단계를 다시 돌리지 않고 합성만 다시 돌릴 수 있는 변경이 있기 때문입니다. 아래 「합성만 다시 도는 변경」 소절이 그것을 봅니다.
레이어로 나뉜 페이지
이 소절은 브라우저가 페이지를 어떤 판들로 나누는지 봅니다. 머리글이 고정된 페이지 하나를 예로 듭니다.
브라우저는 페이지 전체를 한 장에 그리지 않습니다. 따로 움직이거나 따로 바뀔 만한 부분을 떼어 다른 레이어에 그립니다. 어느 요소를 뗄지는 브라우저가 정합니다. 흔히 떼어지는 것은 움직임 효과가 걸린 요소, 동영상, 스크롤해도 제자리에 붙어 있는 요소입니다.
아래는 스크롤해도 위에 붙어 있는 머리글과, 그 위로 뜬 알림 창이 있는 페이지입니다. 판 셋이 따로 그려져 있고 합성이 이것을 겹쳐 화면 한 장을 냅니다.
flowchart TD
subgraph 레이어["따로 그려 둔 레이어 · 위가 앞"]
N["알림 창"]
H["고정된 머리글"]
B["스크롤되는 본문"]
end
레이어 --> C["합성 · 뒤에서 앞 순서로 겹친다"]
C --> V["화면 한 장"]
사용자가 스크롤하면 본문 레이어만 위로 밀립니다. 머리글과 알림 창 레이어는 그대로 둡니다. 세 장을 다시 겹치기만 하면 새 화면이 나옵니다. 이미 그려 둔 그림을 다시 칠하지 않습니다.
겹치는 순서는 페인트가 정한 칠하는 순서를 따릅니다. 나중에 칠할 요소가 든 레이어일수록 화면 앞쪽에 겹쳐집니다. CSS 의 z-index 속성이 이 순서를 바꿉니다.
두 픽셀을 겹치는 계산
이 소절은 레이어 두 장이 겹친 곳에서 픽셀 하나의 색이 어떻게 정해지는지 봅니다. 반투명한 빨간 판을 흰 바탕 위에 올린 경우로 계산해 봅니다.
픽셀의 색은 빨강·초록·파랑 세 값으로 적습니다. 각 값은 0에서 255 사이의 수입니다. 여기에 투명도를 나타내는 값이 하나 더 붙기도 합니다. 이 값을 알파라고 부릅니다. 알파가 1이면 완전히 불투명하고 0이면 완전히 투명합니다.
앞 판이 불투명하면 계산할 것이 없습니다. 앞 판의 색이 그대로 결과가 됩니다. 앞 판이 반투명하면 앞 색과 뒤 색을 섞습니다. 앞 색에는 알파만큼, 뒤 색에는 남은 몫만큼 무게를 줘서 더합니다.
아래 코드는 알파가 0.5인 빨강 픽셀을 흰 픽셀 위에 올립니다. a 가 앞 판의 알파입니다. 줄마다 오른쪽
주석이 그 줄의 결과입니다.
const a = 0.5; // 알파
const r = 255 * a + 255 * (1 - a); // 255
const g = 0 * a + 255 * (1 - a); // 127.5
const b = 0 * a + 255 * (1 - a); // 127.5
결과는 빨강 255, 초록과 파랑이 절반쯤인 연분홍입니다. 빨간 필름 너머로 흰 종이가 비쳐 보이는 색입니다. 이렇게 앞 색과 뒤 색을 알파로 나눠 섞는 계산을 알파 합성이라고 부릅니다.
화면의 픽셀 수만큼 이 계산을 되풀이하므로 양이 많습니다. 픽셀마다 같은 계산을 하므로 한꺼번에 나란히 돌리기 좋습니다. 그래서 이 일은 흔히 그림 처리에 특화된 장치인 GPU(Graphics Processing Unit, 그래픽 처리 장치)가 맡습니다.
합성만 다시 도는 변경
이 소절은 무엇을 바꾸면 어느 단계부터 다시 도는지 봅니다. 앞 단계를 건너뛰고 합성만 다시 도는 변경이 있다는 것이 실무에서 합성을 챙기는 가장 큰 까닭입니다.
화면의 요소를 바꾸면 브라우저는 영향받는 단계부터 다시 돌립니다. 앞 단계일수록 뒤 단계까지 전부 다시 돌아야 하므로 비쌉니다. 바꾼 것과 다시 도는 첫 단계를 표로 놓으면 이렇습니다.
| 바꾼 것 | 다시 도는 첫 단계 | 이름 |
|---|---|---|
| 너비 · 높이 · 위치 좌표 | 레이아웃 | 리플로우 |
| 글자 색 · 배경색 · 그림자 | 페인트 | 리페인트 |
| 제 레이어에 든 요소의 이동 · 회전 · 투명도 | 합성 | — |
표의 셋째 줄이 합성의 쓸모입니다. 제 레이어에 든 요소를 옮기거나 흐리게 할 때는 레이어 그림을 다시 그리지 않습니다. 그 그림을 다른 위치에, 다른 알파로 겹치기만 합니다.
아래 두 줄은 같은 메뉴를 오른쪽으로 200픽셀 옮기는 CSS 입니다. 주석은 다시 도는 첫 단계입니다.
left: 200px; /* 레이아웃 */
transform: translateX(200px); /* 합성 */
첫 줄은 요소의 좌표를 바꿉니다. 좌표가 바뀌면 이웃 요소의 위치가 달라질 수 있어 레이아웃부터 다시
돕니다. 둘째 줄의 transform 은 레이아웃이 정한 좌표를 두고 그림만 옮겨 보이게 합니다. 그 요소가 제
레이어에 있으면 합성만 다시 돕니다.
그래서 움직임 효과는 흔히 transform 과 opacity 두 속성으로만 만듭니다. transform 은 이동·회전·
크기를, opacity 는 투명도를 바꿉니다.
will-change 속성은 요소의 어떤 속성이 곧 바뀔지 브라우저에 미리 알립니다. 브라우저는 그것을 보고 그 요소를 미리 제 레이어로 떼어 둘 수 있습니다. 움직임이 시작되는 순간에 레이어를 떼고 그리느라 첫 화면이 늦어지는 일을 피하려고 씁니다.
메인 스레드와 따로 도는 합성
이 소절은 합성이 자바스크립트 코드와 다른 실행 흐름에서 돈다는 것을 봅니다. 코드가 오래 도는 동안에도 스크롤이 되는 까닭이 여기 있습니다.
스레드는 운영체제가 따로따로 돌려 주는 실행 흐름 하나입니다. 브라우저에서 자바스크립트 코드는 한 스레드에서 돕니다. 이 스레드를 메인 스레드라고 부릅니다. 스타일 계산, 레이아웃, 페인트도 대개 이 스레드에서 코드와 번갈아 돕니다.
크롬과 파이어폭스를 비롯한 많은 브라우저는 합성을 메인 스레드 밖의 스레드에 맡깁니다. 이 스레드를 컴포지터 스레드라고 부릅니다. 메인 스레드는 레이어와 그리기 명령을 넘기고 제 일로 돌아갑니다.
화면은 1초에 여러 번 새 그림으로 바뀝니다. 새로 바뀌는 그림 한 장을 프레임이라고 부릅니다. 아래 그림은 메인 스레드가 긴 코드를 도는 동안에도 컴포지터 스레드가 프레임을 계속 내는 모습입니다.
sequenceDiagram
participant 메인 as 메인 스레드
participant 컴포지터 as 컴포지터 스레드
participant 화면
메인->>컴포지터: 레이어와 그리기 명령을 넘긴다
Note over 메인: 오래 걸리는 코드를 돈다
loop 프레임마다
컴포지터->>컴포지터: 스크롤한 만큼 본문 레이어를 옮겨 겹친다
컴포지터->>화면: 새 프레임
end
그림에서 컴포지터 스레드는 메인 스레드를 기다리지 않습니다. 스크롤이나 transform 움직임처럼 합성만
다시 돌면 되는 변경은 코드가 바빠도 부드럽게 이어집니다. 반대로 레이아웃이나 페인트가 필요한 변경은
메인 스레드가 풀릴 때까지 화면에 안 나옵니다.
레이어가 많을 때의 대가
이 소절은 레이어를 떼어 둘 때 무엇을 잃는지 봅니다. 레이어 한 장이 차지하는 메모리를 셈해 봅니다.
레이어는 저마다 픽셀 그림을 한 장씩 따로 들고 있습니다. 픽셀 하나에 빨강·초록·파랑·알파 네 값을 한 바이트씩 담으면 4바이트입니다. 알파는 계산할 때 0~1 로 다루지만 담을 때는 다른 세 값처럼 0~255 로 적습니다. 가로 1920, 세로 1080 픽셀인 레이어 한 장이면 약 8메가바이트입니다.
레이어를 열 장 떼면 그만큼 메모리를 더 씁니다. 레이어 그림은 대개 GPU 가 쓰는 메모리에 올라갑니다. 휴대폰처럼 이 메모리가 작은 기기에서는 레이어 몇 장만으로도 메모리가 빠듯해집니다.
레이어는 저절로 늘어나기도 합니다. 레이어로 뗀 요소 위에 다른 요소가 겹쳐 보여야 할 때가 있습니다. 위에 떠야 할 그 요소를 바닥 판에 함께 그리면 순서가 뒤집힙니다. 떼어 낸 레이어가 그 위를 덮기 때문입니다.
그래서 그 요소도 따로 떼어 더 앞에 둡니다. 이렇게 뜻하지 않게 레이어가 불어나는 일을 레이어 폭발이라고 부릅니다.
겹칠 장수가 늘면 합성 자체도 무거워집니다. 그래서 레이어는 실제로 따로 움직이는 요소에만 떼는 것이
보통입니다. 모든 요소에 will-change 를 붙이는 것은 이 대가를 페이지 전체에 거는 셈입니다.
브라우저 밖의 합성
같은 일은 브라우저 밖에서도 합니다. 이 소절은 브라우저 밖에서 똑같이 그림을 겹치는 두 분야, 데스크톱 창 화면과 영상 편집을 봅니다. 이름만 같고 뜻이 다른 합성은 다음 소절이 다룹니다.
데스크톱 화면에는 창이 여러 개 떠 있습니다. 많은 운영체제는 창마다 제 그림을 따로 그리게 한 뒤, 창 그림들을 겹쳐 화면 한 장을 만듭니다. 이 일을 맡는 프로그램을 컴포지터라고 부릅니다. 브라우저의 컴포지터 스레드가 하는 일을 운영체제 수준에서 하는 프로그램입니다.
윈도우에서는 DWM(Desktop Window Manager, 데스크톱 창 관리자)이 이 일을 합니다. 리눅스에서 Wayland 방식으로 화면을 띄우는 데스크톱은 컴포지터가 이 일을 맡습니다. 창을 반투명하게 하거나 창에 그림자를 드리우는 효과가 이 구조 덕분에 됩니다.
영상 편집에서도 여러 장면을 한 화면에 겹치는 일을 합성이라고 부릅니다. 초록 배경 앞에서 찍은 사람을 다른 배경 영상 위에 얹는 크로마키가 대표적인 예입니다. 초록 부분의 알파를 0으로 만들고 두 영상을 겹칩니다.
이름이 같은 다른 합성
한국어 「합성」은 원어가 다른 여러 개념을 함께 부릅니다. 백엔드 개발자가 자주 만나는 것은 오히려 아래 표의 둘째 줄입니다.
| 부르는 곳 | 원어 | 뜻 |
|---|---|---|
| 화면 렌더링 | compositing | 따로 그린 그림을 겹쳐 한 장으로 만든다. 이 항목이다 |
| 객체지향 설계 | composition | 객체가 다른 객체를 필드로 품고 그 기능을 빌려 쓴다. 객체 합성 |
| 함수형 프로그래밍 | function composition | 한 함수의 결과를 다음 함수에 넣어 새 함수를 만든다. 함수 합성 |
| 모니터링 | synthetic monitoring | 가짜 사용자 요청을 만들어 서비스를 주기적으로 찔러 본다. 합성 모니터링 |
넷째 줄의 「합성」은 「인공으로 만든」이라는 뜻의 synthetic 을 옮긴 말입니다. 앞 세 줄과는 뿌리가 다릅니다.
관련 항목
합성 앞뒤로 도는 렌더링 단계
렌더링 · 렌더링 엔진 · 스타일 계산 · 레이아웃 · 페인트 · 래스터화 · 렌더 파이프라인 · 중요 렌더링 경로
합성이 겹치는 재료
합성 레이어 · 픽셀 · 비트맵 · 디스플레이 리스트 · 프레임버퍼 · 타일 래스터화
합성이 픽셀 색을 정하는 계산
알파 채널 · 알파 합성 · 블렌딩 · 프리멀티플라이드 알파 · 반투명 · 혼합 모드
합성만 다시 돌게 하는 CSS 속성
CSS 트랜스폼 · opacity · will-change · z-index · 쌓임 맥락 · CSS 애니메이션
합성을 피하지 못하고 앞 단계부터 다시 도는 변경
리플로우 · 리페인트 · 페인트 무효화 · 레이아웃 스래싱 · 강제 동기 레이아웃
합성을 돌리는 실행 구조
메인 스레드 · 컴포지터 스레드 · 스레드 · GPU · 하드웨어 가속 · 이벤트 루프 · requestAnimationFrame
합성이 늘리는 비용과 문제
레이어 폭발 · GPU 메모리 · 프레임 · 프레임 레이트 · 버벅임
브라우저 밖에서 합성을 하는 프로그램과 기법
컴포지터 · 창 관리자 · 크로마키 · 영상 편집
합성과 이름이 겹치는 개념
객체 합성 · 상속보다 합성 · 함수 합성 · 합성 모니터링 · 합성곱
다른 이름: compositing · 컴포지팅 · 레이어 합성