이 페이지에서는 Figma 디자인 문서에서 화면에 표시되는 최종 픽셀까지 데이터 흐름을 추적하여 고가용성 렌더러 (HAR)의 전체 그래픽 파이프라인을 자세히 설명합니다.
개요
이 파이프라인은 상위 수준 UI 정의를 하위 수준 그래픽 명령어로 변환하고 하드웨어 디스플레이에 효율적으로 표시합니다. 이 파이프라인은 자동차 안전에 중요한 앱을 위해 설계되었으며 결정적 렌더링, 효율적인 상태 관리, DRM (Direct Rendering Manager) 및 GBM (Generic Buffer Management)과 같은 플랫폼 그래픽 하위 시스템과의 강력한 상호작용을 강조합니다.
이 파이프라인은 다음과 같은 네 가지 주요 단계로 나눌 수 있습니다.
- 사전 렌더링: 장면 그래프 처리, 맞춤설정 적용, 레이아웃 확인
- 명령어 생성: 확인된 장면 그래프를 백엔드에 구애받지 않는 표시 목록으로 변환
- 렌더링: Impeller 그래픽 엔진을 사용하여 그리기 명령어 실행
- 프레젠테이션: 프레임 버퍼 관리 및 디스플레이 하드웨어와 동기화
그림 1. HAR 그래픽 흐름
1단계: 사전 렌더링
이 단계에서는 정적 Figma 디자인과 동적 앱 상태를 렌더링 준비가 완료된 완전히 확인된 인메모리 UI 트리로 변환합니다. 이 단계는 기본 디스플레이 루프와 별도로 전용 감소기 스레드에서 실행됩니다.
1.1 DesignCompose 기초
HAR 파이프라인은 DesignCompose 생태계를 기반으로 합니다.
- 소스: UI는 Figma에서 디자인되고 DesignCompose 플러그인을 사용하여 내보내집니다.
- 정의: 출력은 디자인 (노드, 스타일, 변형)의 직렬화된 표현인
DesignComposeDefinition의 인스턴스입니다. - 데이터 결합: 앱의 UI 모델은 절차적 매크로 (예:
#[Design(node = "#speed")])를 사용하여 Rust 구조체 필드를 Figma 문서의 특정 명명된 노드에 명시적으로 결합합니다. 이렇게 하면 앱 상태가 시각적 요소의 속성을 자동으로 구동할 수 있습니다.
이 기초의 주요 구성요소는 다음과 같습니다.
- 감소기: 중앙 이벤트 루프 역할을 하며 작업을 처리하고 현재 상태를 업데이트합니다. 프레임워크는
DefaultReducer를 제공하지만 필요한 경우 맞춤 감소기 구현을 제공할 수 있습니다. - 프레젠터: 현재 상태를 UI 모델에 연결합니다.
Presenter트레이트는harry프레임워크 크레이트에 의해 지정되며 참조 구현 (UIModelPresenter)은harry-app-core크레이트에 제공됩니다. - UI 모델: 현재 상태를 기반으로 맞춤설정을 생성합니다. UI 모델 코드는
derive_customizations크레이트에서 제공하는DesignDocument매크로를 사용하여 생성됩니다.harry-app-core크레이트의UIModel구조체는 이 예시를 제공합니다. - Squoosh: 디자인에 따라 UI를 렌더링하는 데 사용되는
SquooshView데이터 구조 및 변형 저장소를 제공합니다. 직렬화된 디자인 문서는 DesignCompose 라이브러리의dc_bundle크레이트에 의해 로드되고 효율적인 런타임 성능을 위해SquooshView구조체 트리로 변환됩니다.
1.2 감소기 루프
파이프라인은 작업에 의해 구동됩니다. 프레임워크는 프레임워크 자체에서 사용하는 내부 작업을 정의하는 Actions 열거형 유형을 지정하지만 사용자가 추가 앱별 작업 (예: UpdateVehicleSpeed 또는 ButtonPress)을 정의할 수 있는 CustomAction 변형도 포함합니다.
프레임워크는 앱 상태에 영향을 미치고 선택적으로 처리할 감소기에서 앱으로 다시 전달되는 부작용을 생성하는 작업의 구현을 간소화하는 StateAction 트레이트도 제공합니다. harry-app-core 크레이트의 CustomActions enum은 이 예시를 자세히 제공합니다.
다음은 감소기 루프의 기본 개요입니다.
- 작업 처리:
Reducer는 작업을 수신하고 현재 상태를 업데이트합니다. 이는 현재 속도 또는 활성 상태인 경고등과 같은 원시 데이터입니다. 또한 부작용(예: 안전벨트 표시등이 깜박일 때 차임벨을 재생하는 신호)을 생성할 수도 있습니다. - 프레젠테이션:
Presenter는 새 상태를UIModel에 매핑합니다.UIModel은 UI용으로 특별히 형식이 지정된 데이터(예: '120' 속도를 '65mph' 문자열로 형식 지정)를 보유하는 뷰 모델입니다. - 맞춤설정 생성: UI 모델의
apply메서드가 호출되어RenderCustomization인스턴스 집합을 생성합니다. 이는 Figma 디자인을 수정하기 위한 명시적 안내입니다 (예: '노드 #speed의 텍스트를 '65mph'로 설정'). - 최적화를 위한
UpdatePolicy: 각 사전 렌더링 패스 후 다음 렌더링 업데이트가 필요한 시점을 나타내는UpdatePolicy값이 반환됩니다. 대기 중인 상태 변경사항이 없고 애니메이션이 실행되고 있지 않으면UpdatePolicy는 추가 업데이트가 즉시 필요하지 않음을 알립니다. 이러한 경우 감소기는 새 표시 목록 생성을 중지하여 새 작업 또는 이벤트가 변경을 트리거할 때까지 불필요한 렌더링 주기를 방지하고 리소스를 보존합니다.
1.3 뷰 수집 및 저장소 초기화
파이프라인은 DesignComposeDefinition 인스턴스로 시작됩니다. 이는 DesignCompose에 의해 프로토콜 버퍼 구조로 직렬화된 Figma 디자인 문서입니다.
초기 로드: 시작 시 기본 디자인 (루트 노드로 지정)은
DesignComposeDefinition에서 초기SquooshView트리로 변환됩니다. 이는 일회성 프로세스입니다.저장소:
SquooshVariantRepository는 재사용 가능한 구성요소 변형과 처음에 로드된 뷰를 관리합니다.지연 로드: 시작 시간과 메모리 사용량을 최소화하기 위해 추가 뷰(초기 루트 노드 트리의 일부가 아닌 뷰)는 렌더링 로직에서 명시적으로 참조되고 필요할 때만 문서에서 지연 로드됩니다 (예: 목록 맞춤설정 중).
1.4 맞춤설정 패스
SquooshView 트리는 동적 앱 상태를 적용하기 위해 트래버스됩니다.
변형 스왑: 구성요소 인스턴스는 런타임 로직을 기반으로 특정 변형으로 스왑됩니다(예: 현재 운전 모드를 나타내는 아이콘을 스포츠에서 에코로 변경).
목록 확장: Figma의 단일 템플릿 항목이 하위 요소의 동적 목록으로 대체됩니다. 이러한 하위 요소에 새 고유 ID가 생성되어 애니메이션의 안정적인 ID를 확인합니다.
텍스트 및 스타일 재정의: 텍스트 콘텐츠 (예: 속도 값) 및 스타일 (예: 불투명도, 색상)이 현재 상태에서 업데이트됩니다.
1.5 변수 확인
Figma 또는 앱에서 로컬로 정의된 디자인 토큰과 변수가 확인됩니다.
- 결합: 변수 (예: 색상 또는 측정기준)를 참조하는
SquooshView속성은 현재 프레임의 구체적인 값으로 대체됩니다.
1.6 레이아웃 계산
동적 레이아웃:
DynamicLayout은SquooshView트리에 있는 모든 노드의 최종 위치와 크기(경계)를 계산합니다.텍스트 레이아웃:
TextHelper는LayoutHelper트레이트의 구현을 사용하여 텍스트 측정항목, 줄바꿈, 모양을 계산합니다. 이렇게 하면 렌더링 전에 텍스트가 제약 조건 내에서 올바르게 흐르는지 확인할 수 있습니다.
1.7 다이얼 및 게이지
이는 자동차 UI를 위한 전문 단계입니다.
MeterData: 노드에 계기 데이터 (Figma에서 정의)가 있는 경우 기하 도형은meter_value(예: 차량 속도)를 기반으로 동적으로 변경됩니다.- 호: 스윕 각도가 조정됩니다.
- 회전: 회전 변환은 시작 각도와 종료 각도를 기반으로 계산됩니다.
- 진행률 표시줄: 직사각형의 너비 또는 높이가 조정됩니다.
- 진행률 벡터: 벡터 경로의 길이가 조정됩니다.
1.8 애니메이션
차이점: 현재
SquooshView가PreRenderCache의previous_squoosh_view와 비교됩니다.보간: 속성이 변경된 경우
Squoosh는 시간이 지남에 따라 값을 원활하게 전환하는 보간기 (예: 불투명도 또는 변환)를 만듭니다.
2단계: 명령어 생성
SquooshView 트리가 완전히 확인되고 애니메이션된 후에는 그리기 명령어의 선형 시퀀스로 변환됩니다.
이 단계의 주요 구성요소는 DisplayList 크레이트입니다.
generate_dl: 이 함수는SquooshView트리를 재귀적으로 트래버스합니다.번역:
- 도형 및 경로: 적절한
DisplayListAppearance변형 (예:Rect또는Path)으로DisplayListEntry로 변환됩니다. - 텍스트:
TextHelper를 사용하여 텍스트 그리기 항목으로 변환됩니다. - 변환 및 클립:
PushTransform3D및PopTransform3D또는PushClipRegion및PopClipRegion쌍으로 변환되어 그리기 상태 스택을 관리합니다. - 마스크:
PushMaskLayer및PopMaskLayer쌍으로 변환되어 레이어를 올바르게 만들고 혼합합니다.
- 도형 및 경로: 적절한
최종 결과는 그리는 방법과 관계없이 그릴 내용
을 설명하는 Vec<DisplayListEntry>의 인스턴스입니다.
2.1 루퍼에 핸드오프
DisplayList가 생성되면 감소기는 ViewDescriptor의 인스턴스로 래핑하고 Rust MPSC 채널 (LooperMessage)을 통해 루퍼 스레드로 전송합니다. Looper는 렌더링 및 표시 단계를 담당하므로 감소기 스레드가 그래픽 파이프라인을 차단하지 않습니다.
3단계: 렌더링
플랫폼에 구애받지 않는 DisplayList는 추상 명령어가 GPU 안내로 변환되는 렌더링 백엔드로 전달됩니다.
HAR는 원래 Flutter용으로 빌드된 렌더링 엔진인 Impeller를 사용합니다. Impeller는 빌드 시 작고 효율적인 셰이더 집합을 사전 컴파일하여 셰이더 컴파일로 인한 프레임 속도 결함 문제를 해결하도록 설계되었습니다. 이 접근 방식은 효과적인 일괄 처리 및 고도로 최적화된 백엔드와 결합되어 다음을 제공합니다.
- 결정적 성능: 런타임 셰이더 컴파일 결함을 거의 없앱니다.
- 빠른 시작: 초기화 오버헤드를 줄입니다.
- 작은 공간: 컴팩트한 바이너리 크기를 생성합니다.
Impeller의 아키텍처에 관한 자세한 소개는 [Impeller 소개 - Flutter의 새로운 렌더링 엔진][impeller-video]을 시청하세요. 동영상에서는 Flutter에 관해 설명하지만 이러한 핵심 이점은 HAR 자동차 스택을 직접 지원합니다.
렌더링 단계의 주요 구성요소는 다음과 같습니다.
ImpellerRenderer: 사전 렌더링 단계의 표시 목록을 Impeller 렌더링 명령어로 변환합니다.Impeller Rust API: Rust에서 사용할 Impeller 라이브러리를 래핑합니다 (
impeller및impeller-rs-bindgen크레이트).TypographyContext: 글꼴 등록 및 텍스트 모양을 관리합니다.
3.1 초기화 및 노출 영역 관리
컨텍스트 생성: 렌더러는 OpenGL ES 백엔드로
impeller::Context의 인스턴스를 초기화하고 플랫폼의 GL 컨텍스트에서 OpenGL ES 함수 포인터를 확인하는 콜백을 전달합니다.래핑된 FBO 노출 영역: Impeller는 자체 창을 만드는 대신 4단계에서 제공하는 기존 OpenGL 프레임 버퍼 객체 (FBO)로 렌더링합니다. 이는
Surface::create_wrapped_fbo를 호출하여 실행됩니다.
3.2 리소스 관리
이미지: 표준 형식 및 KTX2 압축 텍스처를 지원합니다. 이러한 항목은 GPU 텍스처에 업로드되고 내부
Resources구조체에 의해 관리됩니다.글꼴: TrueType 및 OpenType 글꼴이 로드되고 텍스트 렌더링을 위해
TypographyContext에 등록됩니다.외부 이미지: 외부 텍스처 (예: 카메라 피드 및 외부 3D 렌더러)의 특수 처리는
EGLImage인스턴스 또는 외부 OpenGL 텍스처를 ImpellerTexture객체에 결합하여 무사본 렌더링을 실행하는 것과 관련이 있습니다.
3.3 렌더링 패스
render 루프는 Impeller DisplayList 인스턴스 (사전 렌더링 단계에서 생성된 Vec<DisplayListEntry>와 혼동하지 말 것)
을 DisplayListBuilder를 사용하여 구성합니다.
버퍼를 지우고 DPI 조정 및 디스플레이 회전을 위한 전역 변환을 적용합니다.
입력
DisplayListEntry항목을 반복합니다.- 상태:
save()및restore()는 변환 및 클립 영역을 푸시하고 팝하는 데 사용됩니다. - 기본 요소:
Rect및RoundedRect는 표준 페인트 작업을 사용하여 그려집니다. - 경로: 동적
Arc인스턴스를 포함한 복잡한 벡터 경로가 빌드되고 그려집니다. - 텍스트:
Text및StyledText는TypographyContext를 사용하여 렌더링됩니다. - 이미지: 표준 및 외부 이미지는
draw_texture_rect를 사용하여 그려집니다.
- 상태:
빌드된 Impeller 표시 목록을
surface.draw_display_list()를 사용하여 노출 영역에 제출하고 기본 GL 명령어를 생성합니다.기본 컨텍스트에서
swap_buffers()를 호출하여 4단계를 트리거합니다.
4단계: 프레젠테이션
이 최종 단계에서는 렌더링된 프레임을 표시하기 위해 디스플레이 하드웨어와의 상호작용을 처리합니다. HAR는 Android Automotive OS (AAOS) 소프트웨어 정의 차량 (SDV)에서 강력한 직접 렌더링 경로를 사용합니다.
이 단계의 주요 구성요소는 HarDirectRenderingContext (har-gl-context 크레이트)입니다.
4.1 아키텍처
프레젠테이션 레이어는 화면 외부 그리기 타겟이 있는 이중 버퍼링 접근 방식을 사용합니다.
그리기 버퍼: Impeller가 장면을 렌더링하는 화면 외부 FBO입니다.
버퍼 확인 (선택사항): 다중 샘플링 앤티앨리어싱 (MSAA)을 지원하는 선택적 보조 버퍼
- 이는 기본 OpenGL ES 구현 또는 구성에 의해 필요할 때 사용 설정할 수 있습니다. 이러한 경우 렌더링 버퍼로 블리팅 (비트 블록 전송)하기 전에 다중 샘플링된 그리기 버퍼를 확인하는 중간 타겟 역할을 합니다.
렌더링 버퍼: 일반적인 그래픽 스왑 체인의 백 버퍼에 해당하는 GBM 객체로 지원되는 일반 버퍼입니다.
전면 버퍼: 디스플레이로 스캔되는 GBM 버퍼입니다.
4.2 스왑 체인
swap_buffers가 호출되면 HAR는 다음 단계를 따릅니다.
그리기 버퍼의 콘텐츠를 렌더링 버퍼로 블리팅합니다 (구현에 필요한 경우 확인 버퍼로 중간 블리팅).
GL 컨텍스트에서
glFlush()를 호출하고 GPU 완료를 추적하기 위해EGL_SYNC_NATIVE_FENCE_ANDROID의 인스턴스를 만듭니다.렌더링 버퍼를 화면으로 스왑하는 DRM 원자 요청을 빌드합니다. 이 요청에는 GPU가 그리기 완료되기 전에 디스플레이 컨트롤러가 렌더링 버퍼를 표시하지 못하도록 하는 GPU 펜스 FD (인 펜스라고 함)가 포함되어 있습니다.
이전 버퍼 (이전 프레임의 전면 버퍼)가 더 이상 화면에 표시되지 않는 시점을 알리기 위해 DRM에서 새 펜스 (아웃 펜스라고 함)를 동시에 요청합니다.
원자 요청을 차단하지 않는 플래그를 사용하여 커밋하여 그래픽 하위 시스템이 동기화된 상태로 유지되는 동안 기본 스레드가 계속 실행되도록 합니다.
HAR가 후속 프레임의
swap_buffers프로세스 시작 시 신호가 전송될 때까지 기다릴 수 있도록 새 아웃 펜스를 컨텍스트에 저장합니다. 이렇게 하면 GPU가 아직 표시 중인 버퍼에 그리지 못합니다.
4.3 직접 모드 설정
HAR는 DRM 및 커널 모드 설정(KMS) 하위 시스템을 사용하여 커널과 직접 상호작용하여 디스플레이 해상도 AAOS SDV를 구성하고 (특정 구성에서) SurfaceFlinger와 같은 창 관리자와의 상호작용을 우회하여 디스플레이 하드웨어를 독점적이고 우선순위가 높은 방식으로 제어할 수 있습니다.
4.4 외부 렌더링
HAR는 특정 UI 요소 (Figma의 태그로 식별)의 렌더링을 외부 프로세스 또는 스레드에 위임하는 것을 지원합니다. 이는 복잡한 3D 장면 (예: Kanzi 또는 Unity와 같은 엔진의 에고 자동차 시각화) 또는 전용 OpenGL 컨텍스트가 필요한 기타 콘텐츠를 통합하는 데 유용합니다.
4.4.1 주요 구성요소
HarExternalRenderContext: 외부 서비스를 위한 전용 화면 외부 EGL 컨텍스트입니다.SurfacePool: 이중 또는 삼중 버퍼링을 위한LocalSurface(Texture+EGLImage) 버퍼 집합을 관리합니다.SharedSurfaceExternalImage: 외부 서비스와 기본 렌더러 간에EGLImage핸들을 전달하기 위한 스레드로부터 안전한 래퍼입니다.
4.4.2 워크플로
워크플로는 다음 순서를 따릅니다.
외부 서비스가 시작되고 기본 루퍼에 등록되어 렌더링하는 Figma 태그 (예:
#cluster/3d-car)를 식별합니다.서비스는 루퍼의
RenderStart신호를 기다려 렌더링을 디스플레이의 VSYNC 신호와 정렬합니다.화면 외부에서 서비스는
SurfacePool에서 제공하는 프레임 버퍼로 콘텐츠를 렌더링합니다.서비스는 컨텍스트에서
swap_buffers를 호출하여 풀을 회전하고 완료된 프레임을SharedSurface의 인스턴스로 사용할 수 있도록 합니다.SharedSurface는ExternalImage로 래핑되고 Rust MPSC 채널을 통해 루퍼로 전송됩니다.기본 Impeller 렌더러 (3단계)가 외부 이미지를 수신합니다. 픽셀 데이터를 복사하는 대신 기본
EGLImage를 텍스처에 직접 결합하고 기본 장면의 일부로 그려 무사본 컴포지션을 구현합니다.
4.5 개발 및 테스트 플랫폼 (har-platform-linux)
개발 및 테스트 목적으로 HAR 앱은 표준 Linux 데스크톱 환경과 헤드리스 설정을 타겟팅할 수 있습니다. 이러한 플랫폼은 crates/reference/platforms/har-platform-linux 크레이트에 구현됩니다.
프로덕션 AAOS SDV 타겟과 달리 이러한 플랫폼은 디스플레이 출력을 위해 har-gl-context의 direct-rendering 하위 시스템을 사용하지 않습니다. 대신 표준 Rust OpenGL 크레이트를 사용합니다.
창 모드: 창 관리 및 이벤트 루프에
winit를 사용하고 OpenGL ES 컨텍스트를 만들고 창 시스템과 통합하는 데glutin을 사용합니다.헤드리스 모드:
har-gl-context크레이트를 사용하여 기본 EGL 디스플레이로 화면 외부 pbuffer 컨텍스트를 만듭니다. 이렇게 하면 주로 자동화된 테스트 또는 백엔드 처리에 사용되는 표시되는 창 또는 직접 디스플레이 하드웨어 액세스 없이 화면 외부 버퍼로 렌더링할 수 있습니다.