본문 바로가기
오픈소스 읽기 (OLD)/데스크탑환경 - weston (wayland)

4. Weston 컴포지터 살펴보기 - 컴포지팅 (3/3)

by 커널패닉 2020. 8. 21.
반응형

스티브 잡스는 제록스(Xerox)의 팔로알토연구센터(PARC)에서 GUI에 대한 통찰을 얻고, 최초로 PC에 GUI를 도입하게 된다. 스티브잡스 전기에 따르면 잡스와 연구원들은 GUI를 개발하면서, 창들이 겹치는 기능을 구현하느라 꽤나 애를 먹었다고 한다. 당시 컴퓨터의 처리능력을 생각해보면, 실시간으로 여러 창들을 띄우고 컴포지팅하기 위해 얼마나 많은 최적화가 들어갔을지 채 짐작도 되지 않는다. 오늘날에는 컴퓨터의 처리능력이 스티브잡스 당시와 비교할 수 없이 좋아졌다. 그럼에도 여전히 (굳이 자원을 낭비할 필요는 없기 때문에) 컴포지팅을 효율적으로 할 수 있도록 weston은 레어이와 뷰를 사용한다.

 

weston (terminal + epiphany + gedit)

레이어

위에 있는 weston 화면을 보자. 맨 위에는 시간정보를 제공하는 패널이 보이고, 아래에 세 앱이 실행되고 있다. 그리고 화면 가장 뒤에는 회색의 배경화면이 있다. weston은 각각의 영역을 레이어로 구분하여, 순서대로 쌓는다. 따라서 어플리케이션은 항상 백그라운드를 덮어쓰고, 패널은 항상 어플리케이션을 덮어쓴다. (weston desktop-shell에서는 어플리케이션이 패널 영역으로 이동할 수 없도록 되어 있다.)

weston layer

위 그림에서는 간단하게 세 가지 레이어를 보여주었지만, 실제 weston에서는 훨씬 더 많은 레이어가 있다. weston 소스코드를 보면 구체적으로 어떤 레이어가 있는지 주석으로 설명해 주고 있다.

// https://gitlab.freedesktop.org/wayland/weston/blob/11f91bbd36e7ebdc01fb6c2b29bcab53aec7209e/desktop-shell/shell.c

/*
 * Surface stacking and ordering.
 *
 * This is handled using several linked lists of surfaces, organised into
 * ‘layers’. The layers are ordered, and each of the surfaces in one layer are
 * above all of the surfaces in the layer below. The set of layers is static and
 * in the following order (top-most first):
 *  • Lock layer (only ever displayed on its own)
 *  • Cursor layer
 *  • Input panel layer
 *  • Fullscreen layer
 *  • Panel layer
 *  • Workspace layers
 *  • Background layer
 *
 * The list of layers may be manipulated to remove whole layers of surfaces from
 * display. For example, when locking the screen, all layers except the lock
 * layer are removed.
 *
 * A surface’s layer is modified on configuring the surface, in
 * set_surface_type() (which is only called when the surface’s type change is
 * _committed_). If a surface’s type changes (e.g. when making a window
 * fullscreen) its layer changes too.
 *
 * In order to allow popup and transient surfaces to be correctly stacked above
 * their parent surfaces, each surface tracks both its parent surface, and a
 * linked list of its children. When a surface’s layer is updated, so are the
 * layers of its children. Note that child surfaces are *not* the same as
 * subsurfaces — child/parent surfaces are purely for maintaining stacking
 * order.
 *
 * The children_link list of siblings of a surface (i.e. those surfaces which
 * have the same parent) only contains weston_surfaces which have a
 * shell_surface. Stacking is not implemented for non-shell_surface
 * weston_surfaces. This means that the following implication does *not* hold:
 *     (shsurf->parent != NULL) ⇒ !wl_list_is_empty(shsurf->children_link)
 */

 

weston layer with view

뷰는 어플리케이션의 화면, 패널 등 실제 surface buffer를 저장하는 단위이다. 하나의 레이어 안에 여러개의 뷰가 있다. 예를 들어 어플리케이션의 view들은 위 그림과 같이 어플리케이션 레이어에 위치하게 된다. 그렇다면 레이어 안의 뷰들은 어떤 식으로 컴포지팅이 될까? 좀 더 구체적으로, 뷰들의 순서는 정해져 있다고 할 때, 어떻게 각각의 뷰들을 컴포지팅하는 것이 효과적일까?

예를 들기 위해 맨 위의 weston 화면 이미지를 보자. 맨 앞에 브라우저(epiphany)가 있고, 다음에 터미널(weston-terminal), 맨 뒤에 메모장(gedit)이 있다. 이 세 창을 효과적으로 컴포지팅하는 방법은 무엇이 있을까?

 

1. 가장 뒤에 있는 뷰부터 그리기

우선 떠오르는 방법은 가장 아래에 깔린 뷰부터 그리는 것이다. 이렇게 하면 우선 메모장을 그리고, 다음에 터미널, 마지막으로 브라우저를 그리면 된다. 이를 그림으로 표현하면 아래와 같다.

마치 벽돌을 쌓듯이 가장 뒤에 있는 뷰부터 쌓아가기 때문에 직관적이다. 하지만 벽돌쌓기 비유는 실제세계와 완전히 같지는 않다. 실 세계는 3D로 모든 벽돌이 온전히 있어야 하지만, weston 데스크탑 화면의 세계는 2D로 뒤에 있는 뷰들은 전부 다 그려질 필요가 없다는 것이다. 따라서 에디터와 터미널 앱에서 보이지 않는 영역은 그릴 필요가 없다.

 

2. 가장 앞에 있는 뷰부터 그리기

 

가장 앞에 있는 뷰부터 그리는 방법은 직관적이지 않고, 관리하는데 약간 복잡하다. 먼저 브라우저의 뷰를 그린다음, 브라우저가 차지한 영역을 opaque(투명한) 영역이라고 지정한다. 다음 터미널을 그릴때는 opaque 영역을 뺀 나머지 영역만 그린다. 그리고 opaque 영역에 터미널 영역을 추가한다.

'뭐 그렇게 복잡하지는 않네'라는 생각이 든다. 하지만 문제는 weston 안에 있는 영역들은 모두 사각형으로 관리되기 때문에, 창이 많아지면 많아질 수록 weston에서 opaque 영역을 관리하는 것은 점점 복잡해지고, 해당 영역을 제외하고 그리는 것 자체가 큰 부담이 된다.

 

3. 변경점이 있는 영역에 한정해서 가장 뒤에 있는 뷰부터 그리기

1번 '가장 뒤에 있는 뷰부터 그리기'는 직관적이지만, 그리지 않아도 되는 영역을 그려야 하기 때문에 비효율적이다. 2번 '가장 앞에 있는 뷰부터 그리기'는 효율적이지만, 뷰의 개수가 많아질 수록 관리가 어렵고 오히려 비효율적이 될 수 있다. 따라서 weston은두 방법의 절충안을 택한다.

우선 '가장 앞에 있는 뷰부터 그리기' 방식처럼, 가장 앞에 있는 뷰부터 확인하면서 opaque 영역과  변경점이 있는 영역(이하 damage 영역)을 구한다. 이때 opaque 영역은 아주 상세하게 설정하는 것이 아니라, 대충 겹쳐지는 영역을 구해서 복잡한 영역을 다루는 일을 피한다. 그리고 각 뷰에서 opaque 영역을 제외한 damage 영역을 구한다. (그림 1~3) 이렇게 구한 damage 영역을 바탕으로 '가장 뒤에 있는 뷰부터 그리기' 방식처럼 합쳐서 전체 화면을 구성한다. (그림 4~6)

 

소스코드

opaque 영역을 통해 각 뷰의 damage 영역을 계산하는 소스코드

// libweston/compositor.c

static void
compositor_accumulate_damage(struct weston_compositor *ec)
{
	struct weston_plane *plane;
	struct weston_view *ev;
	pixman_region32_t opaque;

	wl_list_for_each(plane, &ec->plane_list, link) {
		pixman_region32_init(&opaque);

		// View 리스트를 앞에있는 view부터 뒤에있는 view 순서로 순회한다.
		// 앞에있는 view에서 opaque 영역을 설정해서 뒤에있는 view로 넘겨준다.
		wl_list_for_each(ev, &ec->view_list, link) {
			view_accumulate_damage(ev, &opaque);
		}

		pixman_region32_fini(&opaque);
	}

	// (하략)
}

static void
view_accumulate_damage(struct weston_view *view,
		       pixman_region32_t *opaque)
{
	pixman_region32_t damage;

	pixman_region32_init(&damage);
	
	// pixman api는 사각형 영역을 다루는 API로 이름을 보면 대략 동작을 유추할 수 있다.
	// 일반적으로 pixman_region32_[function](dest, src1, src2)의 형태로 사용된다.
	// ex. pixman_region32_intersect(dest, src1, src2)
	//     src1과 src2 영역에서 교차되는 영역을 dest로 가져온다.
	
	// damage 변수에 view의 damage 영역 복사
	pixman_region32_copy(&damage, &view->surface->damage);
	
	// damage 영역에서 opaque 영역 제외
	pixman_region32_subtract(&damage, &damage, opaque);

	// 각 뷰의 opaque 영역 합산
	pixman_region32_union(opaque, opaque, &view->transform.opaque);
}

 

반응형