wayland, weston, kms, egl, eglfs, drm, dri, mesa, fb

markyang92·2022년 7월 11일
0

yocto

목록 보기
40/54
post-thumbnail

QT_QPA_PLATFORM

  • QT의 QPA Backend
  • Embedded Linux System에선 보통 EGLFS, LinuxFB, DriectFB, Wayland를 사용한다.

EGLFS

  • EGLFS는 Window System(e.g., X11 or Wayland)없이 EGL, OpenGL ES 2.0 위에서 Qt 어플리케이션을 실행하기 위한 Platform plugin.
  • Qt Quick2, Native OpenGL App 외에도, Software-rendered windowQWidget도 지원한다.
    • QWidget의 경우 위젯의 콘텐츠는 CPU를 사용하여 이미지로 렌더링되며, 이미지들은 텍스처로 업로드되고 플러그인에 의해 합성된다.
  • GPU를 포함하는 현대 Embedded Linux devices에 추천되는 플러그인이다.
  • EGLFS는 first top-level 윈도우(QWidget, QQuickView)를 강제로 전체 화면으로 만든다.
    • 이 Window는 다른 모든 최상위 위젯(e.g., dialogs, pop-up menu, combo box drop downs)이 composited 되는 root widget창으로 선택된다.
    • 이는 EGLFS를 사용하는 경우, 항상 하나의 native window와 EGL window surface이 있으며, 이 창은 먼저 만들어진 위젯이나 창에 속하기 때문에 필요하다.
    • 이 접근 방식은 응용 프로그램의 전체 수명 동안 존재하는 기본 창이 있고 다른 모든 위젯이 최상위 수준이 아니거나 기본 창이 표시된 후 생성된 경우 잘 작동한다.
  • no window system으로, EGLFS를 빌드하고 싶다면 yocto에서
DISTRO_FEATURES:remove = "wayland"
DISTRO_FEATURES:remove = "x11"
  • X11을 window system으로 사용 시, EGLFS를 빌드하고 싶다면 yocto에서
DISTRO_FEATURES:remove = "wayland"
  • Wayland Compositor를 사용하면서(X11 Client는 사용), EGLFS를 빌드하고 싶다면 yocto에서
DISTRO_FEATURES:remove = "x11"
  • qt app을 사용 시, -platform 옵션과 아규먼트를 통해 EGLFS로 사용케 할 수 있다.
    • x11 package가 설치되지 않은 환경에서
      qtglviddemo -platform eglfs
  • DISTRO_FEATURES에서 remove하면, 그 값을 필요로하는 패키지들은(REQUIRED_DISTRO_FEATURES가 명시된) skipped된다.
    • x11을 remove하면, gateworks-image-x11을 사용할 수 없다.
    • weston을 remove하면, core-image-weston을 사용할 수 없다.

출처: https://prographics.tistory.com/15?category=957088


weston

  • wayland: Graphic System에 있어, 서버 <-> 클라이언트 간의 '프로토콜'
    • X11을 대체하는 새로운 window manager이며, 클라이언트 및 서버에서 사용할 수 있는 프로토콜, 라이브러리 제공
    • window server(compositor)와 클라이언트가 통신하기 위한 프로토콜 정의(wayland.xml)
  • weston: wayland의 레퍼런스 compositor
    • 기존 X server + compositor => wayland 사용 => weston이라고 생각하자.
  • 해당 프로젝트가 시작된 주요 이유는 X window에서 수행되던 수많은 하부 기능들이 커널이나, 라이브러리(cairo, pixman, freetype, fontconfig, pango...)로 옮겨져, 사용 되지 않음에도 불구하고, user는 잔존해 있는 X protocol을 지원해야만 했기 때문이다.

x windowwayland
독립적인 프로세스로서 동작라이브러리 형태로 구현되어, OS의 오버헤드 줄어 듬
서버와 컴포지터가 분리서버 + 컴포지터가 하나로 합쳐져, 불필요한 통신이 줄어 듬
렌더링을 서버가 담당렌더링을 클라이언트가 담당하여 서버와의 복잡도 감소
X서버가 클라이언트, 컴포지터, 커널간의 모든 동작을 중개서버가 클라이언트와 커널만 중개
  • wayland를 사용하는 대표적인 이유
    • 기존(X11) 대비 경량화, 체계화된 프레임워크
      • 기존의 X11이 사용하지 않는 부분을 제거하고 필요한 부분만을 구현
      • 표준 GEM/DRM 스택을 이용하여 최대한 단순하게 구현
        • 커널에서 DRM/GEM을 지원하여 더이상 user framework이 복잡해 질 필요가 없음
  • Genivi, AGL, Tizen 플랫폼에서 사용
  • X11의 1/10 크기

Wayland 동작

wayland 렌더링 모델

  • wayland 프로토콜은 렌더링 API가 없다.
  • 클라이언트가 윈도우 컨텐츠를 렌더링해야하며, 이 컨텐츠는 컴포지터와 공유할 수 있는 버퍼여야한다.
  • 클라이언트가 스스로 렌더링 하기 위해선, 렌더링 라이브러리(cairo, opengl)나 wayland가 지원하는 high-level widget 라이브러리를 사용해야한다.
  • 렌더링된 결과 버퍼는 wl_buffer object에 저장되어 있다.
  • 만약 클라이언트가 소프트웨어 렌더링을 사용하고, 결과가 system 메모리에 저장되어 있다면, 버퍼 커뮤니케이션을 구현하기 위해 클라이언트와 컴포지터는 shared memory를 사용할 수 있다. (특별한 복사 동작 없이)
  • wayland 프로토콜은 wl_shm, wl_shm_pool 인터페이스를 통해 Shared Memory 버퍼를 지원한다.
  • 전형적인 방법은 클라이언트가 GPU API(opengl, opengl es, vulkan)을 사용하여 '비디오 메모리' 버퍼에 직접 렌더링하는 것
  • 클라이언트와 컴포지터는 특별한 핸들러를 이용해 비디오 버퍼를 공유할 수 있다.

wayland 서버의 동작 모델

  • App launcher, task switcher와 같은 코어 UI
  • KMS를 동작시킬 수 있음
  • 어플리케이션들의 최종 화면을 합성 할 수 있음(H/W overlay 포함)

  • 이미지 링크: https://t1.daumcdn.net/cfile/tistory/23129A44593BEEA402?download
  • wayland를 사용하는 어플리케이션을 이해하는 것 뿐만 아니라, wayland compositor의 동작을 이해하기 위해서는 리눅스 시스템의 그래픽 시스템을 두루 알고 있어야 가능하다.
    • 특히, opengl, mesa, drm에 대한 개념없이는 wayland의 구현을 이해할 수 없다.

  • 그래서, yocto에서 virtual/egl = "mesa"를 사용한다.


eglfs 기반에서 입력장치

  • Window 시스템이 없는 경우, 마우스 및 키보드 터치 입력은 evdev를 통해 직접 읽거나, libinput 또는 tslib와 같은 helper 라이브러리를 사용하여 직접 읽을 수 있다.
  • 이 경우, 장치 노드 /dev/input/event*를 사용자가 읽을 수 있어야만 하지만! eglfs, linuxfb 는 컴파일 된 모든 입력 처리 코드를 가지고 있다.
  • libinput 사용
    • libinput은 입력 장치를 처리하는 라이브러리이다.
    • libinput을 사용 가능하게 하려면, Qt를 구성하고 컴파일 할 때, libudevlibinput 용 개발 패키지(헤더파일, 라이브러리등)을 사용할 수 있어야한다.

http://doc.qt.io/qt-5/embedded-linux.html
https://wiki.qt.io/Qt_Platform_Abstraction
https://wiki.qt.io/DirectFBAndQt
http://doc.qt.io/qt-5/qpa.html

출처: https://makersweb.net/qt/998


weston

  • wayland를 사용하는 compositor 중 하나
  • wayland에서 제공하는 wayland server의 레퍼런스 구현체
    • wayland 프로토콜을 구현한 레퍼런스 compositor
    • shell plugin 지원 (desktop-shell, xdg-shell, tablet-shell, ... ivi-shell)
  • qt를 빌드 시켜보면 나오는 정보들을 살펴보자(weston과 관계없음)
  • shell plugin 지원 (desktop-shell, xdg-shell)
  • weston은 윈도우들을 여러 계층으로(레이어) 구분해서 관리한다.
    • 마우스 커서 뷰, app1 뷰, app2 뷰
  • weston은 shell을 이용하여 출력되는 각 윈도우(뷰) 순서를 결정한다.
  • weston은 윈도우를 화면에 나타나게 하거나 위치를 옮기거나 크기를 변경하는 기능을 지원한다.
    • get_shell_surface()wl_surface를 얻은 후

각종 용어

shell

  • 윈도우를 옮기거나 크기를 변경하고 패널이나 배경화면을 보여주는 기능
    • 모든 윈도우 화면 상에서의 위치와 크기를 결정(마우스 클릭, 드래그, 최대/최소)

compositor

  • 화면 위에 겹쳐있는 모든 윈도우를 하나의 스크린 위에 합쳐서 보여주는 역할(윈도우 우선순위 결과)

weston supported layer

  • fade_layer...
  • cursor_layer...
  • fullscreen_layer...
  • panel_layer...
  • workspace_layer...
  • background_layer...
  • lock_layer...
  • input_panel_layer...

Desktop-shell

  • 전통적인 데스크탑과 같은 윈도우 매니지와 키보드/마우스 인터페이스를 지원하는 plugin
  • 바탕화면, 패널, 스크린 잠금 지원

xdg-shell

  • 윈도우의 크기를 옮기고 크기를 조절하거나, 전체화면으로 전환하는 등의 기능을 지원하는 plugin

ivi-shell

  • wayland의 surface(wl_surface)를 ivi-application 프로토콜에 매핑하여, surface, layer, screen의 관리 기능을 지원하는 plugin

wayland IVI Extension

  • weston 밖에서 등록되어 있는 surface를 컨트롤하기위한 라이브러리

opengl

  • 2/3차원 그래픽스 표준 API로, 프로그래머에게 단일한 API를 제공하여, 서로다른 3차원 가속기(GPU)를 지원한다.

opengl es

  • 임베디드 단말을 위한 opengl

egl

  • eglOpenGL, OpenGL ES, OpenVG와 같은 크로노스 렌더링 API와 기본 네이티브 플랫폼 윈도우 시스템간의 인터페이스
  • wayland 디스플레이 서버 프로토콜은 EGL을 사용한다.
  • wayland 디스플레이 클라이언트가 EGL을 사용하여 fb(frame buffer)에 직접 그려주는 방식!!


glsl

  • opengl 상위 레벨 셰이딩 언어

mesa

  • OpenGL API를 구현한 오픈 소스
  • 그냥 그래픽카드 드라이버가 없을 때, 이걸로 적용해 특정 그래픽카드에 최적화된 드라이버는 아니지만, 범용적으로 쓰는 그래픽 카드 드라이버라고 생각하자!!!
  • 예전에는 userland를 사용하긴했음


DRI

  • Direct Rendering Infrastructure(DRI)
    • DRI는 X Window 시스템 환경에서 그래픽 하드웨어를 직접 접근하기 위한 framework
    • DRI는 X server의 변경과 여러가지의 클라이언트 라이브러리, 커널의 DRM(Direct Rendering Manager)을 포함한다.
    • DRI 사용의 가장 중요한 점은 mesa를 위한 H/W가속을 지원하는 빠른 OpenGL 프로그램을 만들기 위함이다.
  • DRI가 포함하는 3개의 컴포넌트
    • X server
    • The direct rendering client
    • Kernel-level device driver(DRM)
  • DRI 컴포넌트 구성 모듈
    • XFree86 extinsons
    • GLXExtension
    • GLcoreExtension
    • DriExtension
    • libGL
    • libGLDriver
    • MesaDriver
    • DRM
    • DRMTemplete

DRM

  • DRM은 FBDEV(fb 드라이버)를 대체하는 디스플레이 컨트롤러 드라이버이자 GPU 접근을 가능케하는 리눅스 커널의 서브시스템 (DRM 드라이버를 사용하여, 디스플레이 컨트롤과 GPU컨트롤을 함께 제어할 수 있다.)
  • 리눅스 커널의 서브시스템인 DRM(Direct Rendering Manager)은 GPU를 접근할 수 있는 Display H/W IP(통칭 그래픽 하드웨어, x86 desktop에서의 video cards)를 제어하기 위한 커널 드라이버 라고 할 수 있다.
  • 커널의 DRM드라이버는 다음과 같은 기능의 User API를 제공한다.
    • GPU Access API: User-space에서 GPU를 access(3D rendering, video decoding)하기 위한 API
    • DRM Security: master로 지정된 1개의 프로세스에 의해서만 호출되도록 제한하는 기능을 제공하는 API
    • KMS(kernel Mode Setting) API: 디스플레이 설정(resolution, refresh rate)등을 위한 API
    • GEM(Graphics Execution Manager) API: 그래픽 메모리(allocation, free, synchronization) 등 관리를 위한 API
  • DRM은 direct h/w access를 DRI 클라이언트에게 제공하는 커널 모듈이다.
  • DRM은 h/w lock을 이용한 graphics h/w에 대한 동기화된 액세스를 제공한다.
  • DRM은 그래픽 하드웨어에 액세스 하기 위한 DRI보안 정책을 적용한다.
  • DRM은 일반적인 DMA엔진을 제공한다.
  • DRM은 확장가능하다.
  • DRM security

    • DRM은 ioctls은 master로 지정된 1개의 프로세스에 의해서만 호출하도록 제한된다.
      일반적으로 wayland와 같은 display server 프로세스는 프로그램을 시작 할 때 master 프로세스가 되기 위해 SET_MASTER ioctl을 통해 DRM-Master상태를 얻어 전체 그래픽 세션에 대한 권한을 갖을 수 있으며, DROP_MASTER를 이용하여 master권한을 해제 할 수 있다. master권한이 없는 프로세스가 제한된 ioctl을 호출 할 경우 error가 반환될 것이다. DRM-Master권한을 갖지 못한 나머지 프로세스들은 DRM-Auth를 통해 일부 제한된 ioctl을 호출할 수 있는 권한을 얻을 수 있다
  • DRM DMA engine

    • 서버는 할당 된 버퍼를 지정할 수 있다
    • 클라이언트는 DRM API를 사용하여 지정된 버퍼를 가상 주소 공간에 매핑한다
    • 클라이언트는 DRM으로부터 이런 버퍼중 일부를 예약한다
    • 버퍼를 명령으로 채운다
    • DRM이 그래픽 하드웨어에 버퍼를 보내도록 요청한다.
    • DRM은 각 OpenGL GLX컨텍스트에 대한 DMA버퍼 규를 관리한다.
    • DRM은 각 DMA버퍼 요청의 간단한 스케쥴링을 수행한다.
  • DRM 인터페이스(지원 범위)
    • Memory mapping
    • Context management
    • DMA operations
    • AGP management
    • vblank control
    • fence management
    • memory management
    • output management

DRM 아키텍처

  • DRM 디바이스 파일: /dev/dri/cardX
  • DRM 커널 모듈: /kernel/drivers/gpu/drm 디렉토리
  • GPU H/W마다 DRM을 위한 커널 모듈이 존재하며, DRM을 지원하기 위한 코드가 구현되어 있다.
  • DRM은 DRM core, DRM driver 두 부분으로 구성된다.
    • DRM core는 DRM의 기본 freamework로서, 공통적인 API를 제공하여, DRM driver는 사용중인 특정 하드웨어를 위해 API를 확장해서 사용할 수 잇다.
      • 특정 H/W를 위해 확장된 DRM과 함께, libdrm또한 libdrm-driver를 확장하여 사용할 수 있다. (AP/GPU vendor에 의해 확장됨)
  • 아래는 리눅스 커널에서 DRM드라이버 디렉토리 구조이다.
  • 라즈베리파이4는 Broadcom사의 VC4를 이용하여, H/W 가속화를 지원한다.

DRM Device model

  • DRM의 KMS는 디스플레이 컨트롤러의 출력 파이프라인을 추상 하드웨어 블록으로 모델링하고 관리한다.
  • CRTC
    • 스캔아웃 엔진을 나타내며 현재 스캔아웃 버퍼에 있는 픽셀 데이터를 읽고 비디오모드 타이밍 신호를 생성한다. 사용 가능한 CRTC의 수는 하드웨어가 동시에 처리 할 수 있는 독립 출력 장치의 수를 결정 하므로 디스플레이 장치 당 하나 이상의 CRTC가 필요하다
  • Connectors
    • 디스플레이 컨트롤러가 스캔 아웃 작업(CRTC에 의한)에서 비디오 신호를 전송하여 표시 할 위치를 나타낸다
    • 일반적으로 출력장치(모니터, 패널)가 있는 하드웨어의 물리적 커넥터(VGA, DVI, HDMI)를 나타내며 연결상태, EDID데이터, DPMS상태 또는 지원되는 비디오 모드와 같은 현재 물리적으로 연결 된 출력 장치의 정보도 커넥터에 의해 관리된다
  • Encoders
    • 디스플레이 컨트롤러는 커넥터에 적합한 포맷을 사용하여 CRTC로부터 비디오 모드 타이밍 신호를 인코딩 해야 한다. 인코더는 이러한 인코딩 중 하나를 수행 할 수 있는 하드웨어 블록을 나타낸다. 디지털 출력을 위한 인코딩의 예로 TMDS 및 LVDS 있음. 아날로그 출력일 경우 일반적으로 특정 DAC블록이 사용 된다. 커넥터는 한 번에 한 인코더의 신호만 수신 가능하다. 모든 CRTC가 모든 사용 가능한 인코더에 연결되는 것은 아니며 CRTC- 인코더 - 커넥터의 가능한 조합을 제한하는 추가 물리적 제한이있을 수 있다
  • Planes
    • plane은 하드웨어 블록이 아니라 스캔아웃 엔진(CRTC)이 공급되는 버퍼가 있는 메모리 개체이다(한개의 이미지 레이어) 프레임 버퍼를 보유하고있는 plane을 primary plane이라고 부르며 CRTC가 비디오 모드(display resolution, pixel size, pixel format, refresh rate )를 결정하는 소스이기 때문에 각 CRTC에는 연결된 하나의 plane이 반드시 있어야한다.
    • 디스플레이 컨트롤러가 하드웨어 커서 오버레이를 지원하거나 추가 하드웨어 오버레이에서 스캔하여 출력 장치로 전송 된 최종 이미지를 혼합 할 수있는 경우 CRTC는 커서 플레인과 연관 될 수 있다
  • 위 그림은 FBDEV와 DRM이 최종 display를 위한 device종단까지 연결되는 과정을 보여주고 있다. 기존 FBDEV는 graphic memory를 바로 화면에 출력하였지만 DRM은 scaling, rotate, composit등 다양한 기능을 지원하기 위해 CRTC와 Encoder, Connector블락을 포함하고있다

FBDEV, DRM 비교

  • 아래 표와 같이 DRM은 최근 그래픽 시스템의 복잡한 요구사항을 만족시키기 위해 FBDEV에 비해 많은 기능을 지원한다
  • 최근 AP는 Display controller H/W IP에서 분리되어 존재하고 있던 rotation, scaling등을 위한 H/W IP를 통합 시키고 있다.
  • 이와 함께 DRM드라이버는 Display controller H/W IP기반에 display, overlay, rotation, scaling, cropping, CSC등의 기능을 하나로 통합하여 user application이 쉽게 사용할 수 있는 간단한 사용 path를 제공한다(기존 FB드라이버가 framebuffer에서 DMA로 바로 출력만 하는 기능을 갖고 있다). 또한 디스플레이에 고해상도가 필요해지고 복잡한 디스플레이 사용 시나리오에 대한 지원을 가능케 하기위해 DRM은 GEM(그래픽 메모리 관리를 위한 메니져)을 포함하고 있다. GEM은 user application에서의 유연한 buffer allocation과 read/write를 지원한다.
  • 과거 FBDEV에서는 멀티 어플리케이션이 하나뿐인 GPU사용을 위해 비디오 메모리를 사용하고 free하는 작업을 진행 했어야 했다. 이 작업이 멀티 어플리케이션에 의해 다중으로 수행되면 GPU를 사용함에 있어 충돌하는 상황이 발생할 수 밖에 없었다. DRM은 이러한 상황을 중간에서 관리하여 멀티 어플리케이션이 충돌없이 GPU를 사용할 수 있게 해준다.
  • 초창기 DRM은 다중 프로그램이 GPU를 충돌없이 사용하게 하는 중재자 역할의 목적으로만 만들어 졌으나, 이후에 user가 Framebuffer를 관리하거나 mode setting(KMS), memory sharing, memory 동기화(GEM)을 위한 목적으로 확장 되었다. 위 그림과 같이 DRM은 video card에(Embedded인 경우 display H/W IP + GPU) 대한 멀티 application의 동시 접근을 가능케 한다
  • 위 그림은 어플리케이션이 DRM을 이용하여 video card를 사용하는 과정을 간단히 나타내고 있다.
    • libdrm은 Kernel의 DRM드라이버를 사용하기 위한 user space 라이브러리(wrapper) 이다. libdrm을 사용하면 커널 인터페이스가 사용자 공간에 직접 노출되는 것을 피할 수 있을 뿐만 아니라 프로그램간에 코드를 재사용하고 공유할 수 있는 이점이 있다. 또한 예제에서 libdrm을 사용하는 것과 사용하지 않을 때를 설명 할 것이다.
  • 위 그림은 어플리케이션이 DRM을 이용하여 video card를 사용하는 과정을 좀더 자세히 나타내고 있다. 어플리케이션은 libdrm라이브러리를 사용하여 ioctl을 통해 커널의 DRM드라이버에 명령을 내린다. DRM드라이버는 GEM을 이용하여 메모리 버퍼를 관리를 하며 KMS를 이용하여 framebuffer에 mode setting(screen resolution, color depth, refresh rate)을 수행 한다

KMS

  • 커널에서 그래픽 하드웨어 출력에 대한 설정/변경을 담당한다. (resolution, color depths, memory layouts and refresh rate)
    • 과거에 그래픽 하드웨어 출력에 대한 설정은 X server의 역할이었다.
    • User space의 X server가 mode-setting 수행하면서 발생했던, slow user switching, flickering 이슈를 해결할 수 있었다.
    • KMS를 사용하게 됨으로서 많은 작업을 단순화 할 수 있고, 보안을 향상 시킬 수 있었다.
    • 커널과 X server에서 중복으로 존재하던, mode-setting 코드가 시스템에서 제거 되고, 오직 커널에만 존재할 수 있게되었다.
    • 커널 부팅시에 mode-setting이 가능해졌다.

GEM

  • DRM 드라이버GEM을 사용하여, 메모리 버퍼를 관리한다.
    • GPU 메모리 할당/해제 한다.
    • GEM을 통해 user space 프로그램은 gpu 비디오 메모리에 있는 메모리 객체를 create, handle, destroy 할 수 있다.
    • user space 프로그램이 비디오 메모리를 필요로 할 경우 GEM API를 사용하여, DRM driver에게 할당을 요청하면 된다.
    • GEM API는 buffer를 populate하고, 필요하지 않을 때는 release 하는 동작을 제공한다.
    • GEM은 동일한 DRM device를 사용하는 두개 이상의 user space 프로그램이 GEM 개체를 공유할 수 있게한다.

  • 위 그림은 DRM을 사용하는 리눅스 시스템에서 OpenGL을 사용하는 어플리케이션의 동작을 나타낸 그림이다.
    기존 OpenGL 어플리케이션과 같이 wayland 어플리케이션도 egl을 사용하여, direct rendering을 사용한다.
    본문의 예제는 어플리케이션이 wayland 없이 DRM을 이용하는 것과 wayland 위에서 DRM을 이용하는 케이스를 포함한다.


위 그림은 어플리케이션이 wayland 없이 OpenGL과 DRM 드라이버를 사용하는 구조를 보이고 있다.
위 상황에서는 어플리케이션 스스로가 OpenGL과 커널 DRM interface를 이용하여 최종 이미지를 원하는 영역에 출력해야 한다.


위 그림은 어플리케이션이 wayland를 이용하여 OpenGL과 DRM 드라이버를 사용하는 구조를 보여주고 있다.
윈도우 매니저 기능을하는 wayland가 OpenGL 명령과 KMS 제어에 관여하여 어플리케이션의 최종 이미지를 합성하거나 렌더링한다.
임베디드에서는 mesa가 특정 부분만 구현되어 사용되고 있는 경우가 많으나 전체적인 동작 구조를 좀더 쉽게 이해하고자 위와같이 표현하였다.


출처: https://prographics.tistory.com/2
그림: https://wayland.freedesktop.org/architecture.html

profile
pllpokko@alumni.kaist.ac.kr

2개의 댓글

comment-user-thumbnail
2022년 10월 25일

엄청 감사합니다.
관련 내용을 검토 중인데, 덕분에 쉽게 이해 되었습니다

답글 달기
comment-user-thumbnail
2022년 12월 22일

이 Window는 다른 모든 최상위 위젯 fireboy and watergirl

답글 달기