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

3. Weston 컴포지터 살펴보기 - 화면 갱신 (2/3)

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

Weston은 다양한 역할을 수행한다. Input handling, Keyboard shortcut, 대기상태 enter / exit 등등. 그 중 가장 중요한 역할은 Desktop GUI의 변경사항을 처리하고, 이를 표현하는 것이다. 이를 다른말로 하면, 데스크탑의 윈도우들을 컴포지팅하는 일이다. 여기서는 weston 컴포지터가 화면을 어떤 원리와 구조로 화면을 갱신하고, 컴포지팅하는지 살펴보려고 한다.

 

Weston 컴포지터 화면 갱신 구조

Weston 화면 갱신

weston-flower 예제를 실행하면 일정 주기마다 화면이 변한다. Desktop App(weston-flower)와 Weston 컴포지터는 화면 변화를 처리하기 위해 다음과 같은 동작을 수행한다.

 

Weston 화면 갱신 시 플로우

  1. 앱은 Wayland 서버에 프로토콜을 보낼 수 있는 handler를 받아온다. 이 과정은 일반적으로 앱 초기화 시점에 이뤄진다. (자세한 과정은 다음 링크 참조: jan.newmarch.name/Wayland/ProgrammingClient/)
  2. 앱에서 화면 변화가 발생한다. 위에서는 weston-flower 이미지가 변하면서 화면이 변경이 요구된다.
  3. 화면에서 변경된 이미지 데이터(buffer)는 surface attach 프로토콜로 전달된다. 현재까지는 Surface에 처리되지 않은 버퍼가 등록이 되었지만, 구체적으로 어떤 위치에 수정이 이뤄지는지는 알지 못한다. (만약 Surface의 일부만 변경되었는데 화면 전체를 갱신해야 한다면, 큰 자원낭비가 발생할 것이다.) 따라서 surface damage 프로토콜은 화면에서 수정되어야 하는 영역이 지정된다.
  4. surface commit 프로토콜을 통해, 갱신된 내용을 출력하라고 요청한다. commit이 없으면, 실제 화면 갱신은 repaint 이벤트가 timeout으로 호출되는 시점에나 발생한다. 일반적으로 timeout에 의한 화면 갱신은 60초마다 발생한다.
  5. 명시적으로 메인 이벤트 루프에서 repaint 이벤트를 처리할 것을 요구한다.
  6. repaint 이벤트가 실행되면, compositing이 발생한 후 (compositing 과정은 다음에 자세히 다룰 것이다.) Output에 화면 갱신 요청을 한다.
  7. Output(혹은 Backend)에서는 사전에 초기화한 output에 화면 갱신을 한다.
Note

화면에 변경이 발생할 할 때, 데스크탑 앱은 Wayland 프로토콜(surface attach, damage, commit)을 통해 버퍼를 전달하고, 갱신을 요청한다. 그리고 Weston은 해당 프로토콜이 올 때 적절한 동작을 수행한다. Gnome이나 Kde, wayfire, sway(i3의 wayland 구현체) 등은 모두 Wayland 프로토콜에 대한 동작을 정의한다. 이러한 설계를 통해서 Wayland는 데스크탑 환경에 따라 앱이 다양하게 반응하도록 하였다.

프로그래밍을 하다보면 중요한 컴포넌트를 너무 많은 곳에서 호출해서 관리가 안되는 때가 있다. 생각해보면 내가 회사에서 최근에 짠 프로그램도 여러 클래스에서 필요할 때마다 직접 엑셀파일을 수정하겠다고 접근하고 있다. 그러다 보니 엑셀에 이상한 값이 입력되어도 어디서 부터 디버깅을 시작해야 할 지 막막하다. 처음부터 엑셀에 접근하는 기능, 적어도 엑셀에 기록하는 기능만이라도 한 컴포넌트에서 담당하였다면 이런 문제는 없었을 텐데 하는 아쉬움이 남는다.

Weston은 변경사항을 우선 내부 surface 객체에 저장한다. 저장된 이미지(버퍼)는 그 자체로는 아무런 화면 변화를 만들지 못한다. 대신 repaint를 commit 프로토콜을 통해 명시적으로 요청하거나 timeout 등이 발생하면, repaint 이벤트 핸들러를 통해서 Output에 화면 변경사항을 기록한다. 이러한 구조는 많은 GUI 응용 프로그램들이 가지고 있는 이벤트 디스패치 스레드(Event Dispatch Thread) 디자인 패턴과 유사하다.
* 참조: en.wikipedia.org/wiki/Event_dispatching_thread

 

// libweston/compositor.c

static void
surface_attach(struct wl_client *client,
	       struct wl_resource *resource,
	       struct wl_resource *buffer_resource, int32_t sx, int32_t sy)
{
	struct weston_surface *surface = wl_resource_get_user_data(resource);
	struct weston_buffer *buffer = NULL;
	weston_log("%s\n", __func__);

	if (buffer_resource) {
		buffer = weston_buffer_from_resource(buffer_resource);
		if (buffer == NULL) {
			wl_client_post_no_memory(client);
			return;
		}
	}

	/* Attach, attach, without commit in between does not send
	 * wl_buffer.release. */
	// Attach와 attach 프로토콜 사이에 (surface) commit이 호출되지 않으면 
	// 버퍼를 화면에 출력하지 않는다. Attach에서는 이미지 버퍼를 pending에 
	// 등록하기만 한다.
	weston_surface_state_set_buffer(&surface->pending, buffer);

	surface->pending.sx = sx;
	surface->pending.sy = sy;
	surface->pending.newly_attached = 1;
}

static void
surface_damage(struct wl_client *client,
	       struct wl_resource *resource,
	       int32_t x, int32_t y, int32_t width, int32_t height)
{
	struct weston_surface *surface = wl_resource_get_user_data(resource);
	weston_log("%s\n", __func__);

	if (width <= 0 || height <= 0)
		return;

	// Damage 프로토콜은 화면에서 어떤 영역에 변경이 발생했는지 전달한다.
	pixman_region32_union_rect(&surface->pending.damage_surface,
				   &surface->pending.damage_surface,
				   x, y, width, height);
}

// ...

static void
surface_commit(struct wl_client *client, struct wl_resource *resource)
{
	struct weston_surface *surface = wl_resource_get_user_data(resource);
	struct weston_subsurface *sub = weston_surface_to_subsurface(surface);
	weston_log("%s\n", __func__);

	// (생략)
	// Commit 프로토콜을 처리할 수 있는지 확인하는 코드

	// 실제로 Commit 프로토콜을 처리하는 함수를 호출한다.
	weston_surface_commit(surface);
	
	// (생략)
}

// ...

static void
weston_surface_commit(struct weston_surface *surface)
{
	// (생략)
	// Commit 준비

	// weston_output_schedule_repaint를 호출한다.
	weston_surface_schedule_repaint(surface);
}

// ...

WL_EXPORT void
weston_surface_schedule_repaint(struct weston_surface *surface)
{
	struct weston_output *output;

	wl_list_for_each(output, &surface->compositor->output_list, link)
		if (surface->output_mask & (1u << output->id))
			// 유효한 output을 weston_output_schedule_repaint에 전달한다.
			weston_output_schedule_repaint(output);
}

// ...

WL_EXPORT void
weston_output_schedule_repaint(struct weston_output *output)
{
	struct weston_compositor *compositor = output->compositor;
	struct wl_event_loop *loop;

	// 메인 이벤트 루프 객체를 가져온다.
	loop = wl_display_get_event_loop(compositor->wl_display);
	
	// (생략)

	// 메인 이벤트 루프에 idle_repaint 함수를 등록한다. 등록된 함수는 event dispatcher가 
	// 다른 event를 처리하지 않을 때(시작 / 마무리할 때), 호출된다. 예를 들어서 
	// event dispatcher가 대기중에 idle_repaint와 키보드 입력이 들어온다면, 키보드 입력을
	// 먼저 처리하고, idle_repaint를 처리하게 된다. 만약 아무런 이벤트가 들어오지 않는다면,
	// 일정시간 후에 idle_repaint를 처리한다. 물론 event dispatcher는 최대 16.6ms(60fps 기준)
	// 안에 idle_repaint를 처리할 것이기 때문에, 사용성에 지장이 있지는 않을 것이다.
	output->idle_repaint_source = wl_event_loop_add_idle(loop, idle_repaint,
							     output);
}

 

반응형