Trang này trình bày chi tiết toàn bộ quy trình đồ hoạ của trình kết xuất có tính sẵn sàng cao (HAR), theo dõi luồng dữ liệu từ tài liệu thiết kế Figma đến các pixel cuối cùng hiển thị trên màn hình.
Tổng quan
Quy trình này chuyển đổi các định nghĩa giao diện người dùng cấp cao thành các lệnh đồ hoạ cấp thấp và trình bày hiệu quả các lệnh đó trên màn hình phần cứng. Quy trình này được thiết kế cho các ứng dụng quan trọng về độ an toàn cho ô tô, nhấn mạnh khả năng kết xuất có tính xác định, khả năng quản lý trạng thái hiệu quả và khả năng tương tác mạnh mẽ với các hệ thống con đồ hoạ nền tảng, chẳng hạn như Trình quản lý kết xuất trực tiếp (DRM) và Trình quản lý vùng đệm chung (GBM).
Quy trình này có thể được chia thành 4 giai đoạn chính:
- Kết xuất trước: Xử lý biểu đồ cảnh, áp dụng các chế độ tuỳ chỉnh và phân giải bố cục.
- Tạo lệnh: Chuyển đổi biểu đồ cảnh đã phân giải thành danh sách hiển thị độc lập với phần phụ trợ.
- Kết xuất: Thực thi các lệnh vẽ bằng công cụ đồ hoạ Impeller.
- Trình bày: Quản lý vùng đệm khung và đồng bộ hoá với phần cứng hiển thị.
Hình 1. Luồng đồ hoạ HAR.
Giai đoạn 1: Kết xuất trước
Giai đoạn này chuyển đổi thiết kế tĩnh của Figma và trạng thái ứng dụng động thành một cây giao diện người dùng hoàn toàn được phân giải trong bộ nhớ, sẵn sàng để kết xuất. Giai đoạn này chạy trên một luồng giảm chuyên dụng, tách biệt với vòng lặp hiển thị chính.
1.1 Nền tảng DesignCompose
Quy trình HAR được xây dựng dựa trên hệ sinh thái DesignCompose.
- Nguồn: Giao diện người dùng được thiết kế trong Figma và xuất bằng trình bổ trợ DesignCompose.
- Định nghĩa: Đầu ra là một thực thể của
DesignComposeDefinition, một bản trình bày được chuyển đổi tuần tự của thiết kế (các nút, kiểu, biến thể). - Liên kết dữ liệu: Mô hình giao diện người dùng của ứng dụng sử dụng các macro theo quy trình (ví dụ:
#[Design(node = "#speed")]) để liên kết rõ ràng các trường cấu trúc Rust với các nút được đặt tên cụ thể trong tài liệu Figma. Điều này cho phép trạng thái ứng dụng tự động điều khiển các thuộc tính của phần tử trực quan.
Các thành phần chính của nền tảng này là:
- Reducer: Đóng vai trò là vòng lặp sự kiện trung tâm, xử lý các thao tác và cập nhật trạng thái hiện tại. Khung này cung cấp
DefaultReducer, nhưng bạn có thể cung cấp một cách triển khai trình giảm tuỳ chỉnh nếu cần. - Presenter: Kết nối trạng thái hiện tại với mô hình giao diện người dùng. Đặc điểm
Presenterđược chỉ định bằng thùng khungharryvà một cách triển khai tham chiếu (UIModelPresenter) được cung cấp trong thùngharry-app-core. - Mô hình giao diện người dùng: Tạo các chế độ tuỳ chỉnh dựa trên trạng thái hiện tại. Mã mô hình giao diện người dùng được tạo bằng cách sử dụng macro
DesignDocumentdo thùngderive_customizationscung cấp. Cấu trúcUIModeltrong thùngharry-app-corecung cấp một ví dụ về điều này. - Squoosh: Cung cấp cấu trúc dữ liệu
SquooshViewvà kho lưu trữ biến thể, dùng để kết xuất giao diện người dùng theo thiết kế. Một tài liệu thiết kế được chuyển đổi tuần tự sẽ được vùng chứadc_bundletải từ thư viện DesignCompose và chuyển đổi thành một cây gồm các cấu trúcSquooshViewđể có hiệu suất thời gian chạy hiệu quả.
1.2 Vòng lặp giảm tốc
Quy trình này được điều khiển bằng các thao tác. Khung này chỉ định kiểu liệt kê Actions xác định các thao tác nội bộ do chính khung này sử dụng, nhưng cũng bao gồm một biến thể CustomAction cho phép người dùng xác định các thao tác bổ sung dành riêng cho ứng dụng (ví dụ: UpdateVehicleSpeed hoặc ButtonPress).
Khung này cũng cung cấp đặc điểm StateAction giúp đơn giản hoá việc triển khai các thao tác ảnh hưởng đến trạng thái ứng dụng và tuỳ ý tạo ra các hiệu ứng phụ, sau đó được truyền trở lại ứng dụng từ bộ giảm để xử lý. Enum CustomActions trong thùng harry-app-core cung cấp một ví dụ chi tiết về điều này.
Đây là dàn ý cơ bản của vòng lặp giảm:
- Xử lý hành động:
Reducernhận một hành động và cập nhật trạng thái hiện tại. Đây là dữ liệu thô, chẳng hạn như tốc độ hiện tại hoặc đèn báo (đèn cảnh báo) nào đang bật. Điều này cũng có thể tạo ra các hiệu ứng phụ (ví dụ: tín hiệu phát ra tiếng chuông khi đèn báo thắt dây an toàn nhấp nháy). - Trình bày:
Presenteránh xạ trạng thái mới vàoUIModel.UIModellà một mô hình khung hiển thị, lưu giữ dữ liệu được định dạng riêng cho giao diện người dùng (ví dụ: định dạng tốc độ "120" thành chuỗi "65 dặm/giờ"). - Tạo chế độ tuỳ chỉnh: Phương thức
applycủa mô hình giao diện người dùng được gọi để tạo một tập hợp các thực thểRenderCustomization. Đây là những hướng dẫn rõ ràng để sửa đổi thiết kế Figma (ví dụ: "Đặt văn bản của nút #speed thành "65 mph""). UpdatePolicyđể tối ưu hoá: Sau mỗi lần truyền trước, một giá trịUpdatePolicysẽ được trả về, cho biết thời điểm cần có bản cập nhật kết xuất tiếp theo. Nếu không có thay đổi trạng thái nào đang chờ xử lý và không có ảnh động nào đang chạy, thìUpdatePolicysẽ báo hiệu rằng không cần cập nhật thêm ngay lập tức. Trong những trường hợp như vậy, Reducer sẽ ngừng tạo danh sách hiển thị mới, ngăn chặn các chu kỳ kết xuất không cần thiết và tiết kiệm tài nguyên cho đến khi một thao tác hoặc sự kiện mới kích hoạt thay đổi.
1.3 Xem quá trình khởi tạo kho lưu trữ và nhập
Quy trình bắt đầu bằng một thực thể DesignComposeDefinition. Đây là tài liệu thiết kế Figma được DesignCompose chuyển đổi tuần tự thành một cấu trúc vùng đệm giao thức.
Tải ban đầu: Khi khởi động, thiết kế chính (do nút gốc chỉ định) sẽ được chuyển đổi từ
DesignComposeDefinitionthành câySquooshViewban đầu. Đây là quy trình chỉ cần thực hiện một lần.Kho lưu trữ:
SquooshVariantRepositoryquản lý các biến thể của thành phần có thể dùng lại và các khung hiển thị được tải ban đầu.Tải từng phần: Để giảm thiểu thời gian khởi động và mức sử dụng bộ nhớ, các khung hiển thị bổ sung (những khung hiển thị không thuộc cây nút gốc ban đầu) sẽ được tải từng phần từ tài liệu chỉ khi chúng được tham chiếu rõ ràng và cần thiết cho logic kết xuất (ví dụ: trong quá trình tuỳ chỉnh danh sách).
1.4 Thẻ tuỳ chỉnh
Cây SquooshView được duyệt qua để áp dụng trạng thái ứng dụng động:
Hoán đổi biến thể: Các thực thể thành phần được hoán đổi với các biến thể cụ thể (ví dụ: thay đổi biểu tượng đại diện cho chế độ lái hiện tại từ chế độ thể thao sang chế độ tiết kiệm) dựa trên logic thời gian chạy.
Mở rộng danh sách: Một mục mẫu duy nhất trong Figma được thay thế bằng một danh sách động gồm các phần tử con. Các mã nhận dạng duy nhất mới được tạo cho những thành phần con này để xác minh danh tính ổn định cho các ảnh động.
Văn bản và kiểu ghi đè: Nội dung văn bản (ví dụ: giá trị tốc độ) và kiểu (ví dụ: độ mờ, màu sắc) được cập nhật từ trạng thái hiện tại.
1.5 Độ phân giải biến thiên
Các mã thông báo và biến thiết kế được xác định trong Figma hoặc cục bộ trong ứng dụng sẽ được phân giải.
- Liên kết: Các thuộc tính
SquooshViewtham chiếu đến các biến (chẳng hạn như màu sắc hoặc kích thước) sẽ được thay thế bằng các giá trị cụ thể cho khung hình hiện tại.
1.6 Tính toán bố cục
Bố cục động:
DynamicLayouttính toán vị trí và kích thước cuối cùng (ranh giới) của mọi nút trong câySquooshView.Bố cục văn bản:
TextHelpersử dụng một cách triển khai đặc điểmLayoutHelperđể tính toán các chỉ số, cách xuống dòng và hình dạng của văn bản. Điều này giúp xác minh rằng văn bản hiển thị chính xác trong các ràng buộc của văn bản trước khi kết xuất.
1.7 Mặt số và đồng hồ đo
Đây là một bước chuyên biệt dành cho giao diện người dùng ô tô.
MeterData: Nếu một nút có dữ liệu đồng hồ đo (được xác định trong Figma), thì hình học của nút đó sẽ được thay đổi linh hoạt dựa trênmeter_value(ví dụ: tốc độ xe).- Cung: Góc quét được điều chỉnh.
- Xoay: Biến đổi xoay được tính dựa trên góc bắt đầu và góc kết thúc.
- Thanh tiến trình: Chiều rộng hoặc chiều cao của một hình chữ nhật được điều chỉnh tỷ lệ.
- Vectơ tiến trình: Độ dài của đường dẫn vectơ được điều chỉnh.
1.8 Ảnh động
So sánh:
SquooshViewhiện tại được so sánh vớiprevious_squoosh_viewtừPreRenderCache.Nội suy: Nếu các thuộc tính đã thay đổi,
Squooshsẽ tạo các bộ nội suy để chuyển đổi các giá trị (ví dụ: độ mờ hoặc biến đổi) một cách mượt mà theo thời gian.
Giai đoạn 2: Tạo lệnh
Sau khi cây SquooshView được phân giải và tạo hiệu ứng động hoàn toàn, cây này sẽ được chuyển đổi thành một chuỗi lệnh vẽ tuyến tính.
Thành phần chính của giai đoạn này là thùng DisplayList:
generate_dl: Hàm này đệ quy duyệt qua câySquooshView.Bản dịch:
- Hình dạng và đường dẫn: Được chuyển đổi thành
DisplayListEntrybằng biến thểDisplayListAppearancethích hợp (ví dụ:RecthoặcPath) - Văn bản: Đã chuyển đổi bằng
TextHelperthành các mục nhập vẽ văn bản. - Biến đổi và đoạn cắt: Chuyển đổi thành các cặp
PushTransform3DvàPopTransform3DhoặcPushClipRegionvàPopClipRegionđể quản lý ngăn xếp trạng thái vẽ. - Tạo mặt nạ: Chuyển đổi thành các cặp
PushMaskLayervàPopMaskLayerđể tạo và kết hợp các lớp một cách chính xác.
- Hình dạng và đường dẫn: Được chuyển đổi thành
Kết quả cuối cùng là một thực thể của Vec<DisplayListEntry> mô tả what (cần vẽ), độc lập với how (cách vẽ).
2.1 Chuyển sang hiệu ứng lặp âm
Sau khi DisplayList được tạo, Trình giảm sẽ bao bọc DisplayList trong một phiên bản của ViewDescriptor và gửi phiên bản đó qua kênh MPSC của Rust (LooperMessage) đến luồng looper. Looper chịu trách nhiệm về các giai đoạn kết xuất và hiển thị, giúp ngăn luồng Reducer chặn quy trình đồ hoạ.
Giai đoạn 3: Kết xuất
DisplayList độc lập với nền tảng được chuyển giao cho phần phụ trợ kết xuất, trong đó các lệnh trừu tượng được dịch thành hướng dẫn của GPU.
HAR sử dụng Impeller, một công cụ kết xuất ban đầu được xây dựng cho Flutter. Impeller được thiết kế để giải quyết vấn đề về lỗi tốc độ khung hình do quá trình biên dịch chương trình đổ bóng bằng cách biên dịch trước một nhóm nhỏ chương trình đổ bóng hiệu quả tại thời điểm tạo bản dựng. Phương pháp này, kết hợp với tính năng phân lô hiệu quả và một phần phụ trợ được tối ưu hoá cao, mang lại:
- Hiệu suất xác định: Gần như loại bỏ các trục trặc khi biên dịch chương trình đổ bóng trong thời gian chạy.
- Khởi động nhanh: Giảm chi phí khởi động.
- Kích thước nhỏ: Tạo ra kích thước nhị phân nhỏ gọn.
Để biết thông tin giới thiệu đầy đủ về cấu trúc của Impeller, hãy xem video Giới thiệu Impeller – Công cụ kết xuất mới của Flutter. Mặc dù video này thảo luận về Flutter, nhưng những lợi ích cốt lõi này trực tiếp hỗ trợ ngăn xếp ô tô HAR.
Các thành phần chính của giai đoạn kết xuất là:
ImpellerRenderer: Chuyển đổi danh sách hiển thị từ giai đoạn kết xuất trước thành các lệnh kết xuất Impeller.API Rust của Impeller: Bao bọc thư viện Impeller để sử dụng trong Rust (các crate
impellervàimpeller-rs-bindgen).TypographyContext: Quản lý việc đăng ký phông chữ và tạo hình văn bản.
3.1 Khởi tạo và quản lý bề mặt
Tạo ngữ cảnh: Trình kết xuất khởi tạo một phiên bản
impeller::Contextbằng một phần phụ trợ OpenGL ES, truyền một lệnh gọi lại để phân giải con trỏ hàm OpenGL ES từ ngữ cảnh GL của nền tảng.Bề mặt FBO được bao bọc: Thay vì tạo cửa sổ riêng, Impeller sẽ kết xuất vào một đối tượng framebuffer OpenGL (FBO) hiện có do Giai đoạn 4 cung cấp. Bạn có thể thực hiện việc này bằng cách gọi
Surface::create_wrapped_fbo.
3.2 Quản lý tài nguyên
Hình ảnh: Hỗ trợ các định dạng chuẩn và kết cấu nén KTX2. Các đối tượng này được tải lên hoạ tiết GPU và do một cấu trúc
Resourcesnội bộ quản lý.Phông chữ: Phông chữ TrueType và OpenType được tải và đăng ký bằng
TypographyContextđể hiển thị văn bản.Hình ảnh bên ngoài: Việc xử lý chuyên biệt cho các kết cấu bên ngoài (ví dụ: nguồn cấp dữ liệu camera và trình kết xuất 3D bên ngoài) liên quan đến việc liên kết các thực thể
EGLImagehoặc các kết cấu OpenGL bên ngoài với các đối tượngTexturecủa Impeller để kết xuất không sao chép.
3.3 Lượt kết xuất
Vòng lặp render tạo một thực thể DisplayList Impeller (không nhầm lẫn với Vec<DisplayListEntry> do giai đoạn kết xuất trước tạo) bằng cách sử dụng DisplayListBuilder:
Xoá vùng đệm và áp dụng các phép biến đổi chung để điều chỉnh tỷ lệ DPI và xoay màn hình.
Lặp lại các mục
DisplayListEntryđầu vào:- Trạng thái:
save()vàrestore()được dùng để đẩy và bật các biến đổi cũng như vùng cắt. - Nguyên hàm:
RectvàRoundedRectđược vẽ bằng các thao tác vẽ chuẩn. - Đường dẫn: Các đường dẫn vectơ phức tạp (bao gồm cả các thực thể
Arcđộng) được tạo và vẽ. - Văn bản:
TextvàStyledTextđược kết xuất bằngTypographyContext. - Hình ảnh: Hình ảnh chuẩn và hình ảnh bên ngoài được vẽ bằng
draw_texture_rect.
- Trạng thái:
Gửi danh sách hiển thị Impeller đã tạo đến nền tảng bằng cách sử dụng
surface.draw_display_list(), tạo các lệnh GL cơ bản.Gọi
swap_buffers()trên ngữ cảnh cơ bản để kích hoạt Giai đoạn 4.
Giai đoạn 4: Trình bày
Giai đoạn cuối cùng này xử lý hoạt động tương tác với phần cứng hiển thị để trình chiếu khung hình đã kết xuất. HAR sử dụng một đường dẫn kết xuất trực tiếp mạnh mẽ trên Xe được xác định bằng phần mềm (SDV) Android Automotive OS (AAOS).
Thành phần chính của giai đoạn này là HarDirectRenderingContext (trong thùng har-gl-context).
4.1 Kiến trúc
Lớp trình bày sử dụng phương pháp đệm đôi với mục tiêu vẽ ngoài màn hình:
Vùng đệm vẽ: FBO ngoài màn hình nơi Impeller kết xuất cảnh.
Giải quyết vùng đệm (không bắt buộc): Vùng đệm phụ không bắt buộc để hỗ trợ chế độ khử răng cưa nhiều mẫu (MSAA)
- Bạn có thể bật chế độ này khi cần bằng cách triển khai hoặc định cấu hình OpenGL ES cơ bản. Trong những trường hợp như vậy, nó đóng vai trò là mục tiêu trung gian để phân giải vùng đệm vẽ được lấy mẫu nhiều lần trước khi blitting (truyền khối bit) đến vùng đệm kết xuất.
Vùng đệm kết xuất: Vùng đệm chung được hỗ trợ bởi một đối tượng GBM, tương ứng với vùng đệm sau trong chuỗi hoán đổi đồ hoạ thông thường.
Vùng đệm trước: Vùng đệm GBM được quét ra màn hình.
4.2 Chuỗi hoán đổi
Khi swap_buffers được gọi, HAR sẽ làm theo các bước sau:
Blit nội dung của vùng đệm vẽ vào vùng đệm kết xuất (với một blit trung gian vào vùng đệm phân giải, nếu cần theo cách triển khai).
Gọi
glFlush()trên bối cảnh GL và tạo một phiên bản củaEGL_SYNC_NATIVE_FENCE_ANDROIDđể theo dõi quá trình hoàn tất của GPU.Tạo một yêu cầu nguyên tử DRM để hoán đổi vùng đệm kết xuất sang màn hình. Yêu cầu này chứa FD hàng rào GPU (gọi là hàng rào đầu vào) để ngăn bộ điều khiển màn hình hiển thị vùng đệm kết xuất trước khi GPU hoàn tất quá trình vẽ.
Đồng thời yêu cầu một hàng rào mới từ DRM (gọi là hàng rào ngoài), để báo hiệu thời điểm bộ đệm trước (bộ đệm trước cho khung hình trước) không còn trên màn hình.
Xác nhận yêu cầu không thể phân chia bằng cờ không chặn, để cho phép luồng chính tiếp tục trong khi các hệ thống con đồ hoạ vẫn được đồng bộ hoá.
Lưu hàng rào mới trong bối cảnh để HAR có thể đợi hàng rào này được báo hiệu khi bắt đầu quy trình
swap_bufferstrên khung hình tiếp theo. Điều này ngăn GPU vẽ vào một vùng đệm vẫn đang được hiển thị.
4.3 Chế độ cài đặt chế độ trực tiếp
HAR tương tác trực tiếp với nhân bằng cách sử dụng các hệ thống con DRM và Kernel Mode Setting (KMS) để định cấu hình độ phân giải màn hình AAOS SDV, bỏ qua các tương tác với trình quản lý cửa sổ như SurfaceFlinger (trong các cấu hình cụ thể), cho phép kiểm soát độc quyền và có mức độ ưu tiên cao đối với phần cứng màn hình.
4.4 Kết xuất bên ngoài
HAR hỗ trợ uỷ quyền việc kết xuất các phần tử giao diện người dùng cụ thể (được xác định bằng thẻ trong Figma) cho các quy trình hoặc luồng bên ngoài. Điều này rất hữu ích khi tích hợp các cảnh 3D phức tạp (ví dụ: hình ảnh trực quan về ô tô từ các công cụ như Kanzi hoặc Unity) hoặc nội dung khác yêu cầu một ngữ cảnh OpenGL chuyên dụng.
4.4.1 Các thành phần chính
HarExternalRenderContext: Ngữ cảnh EGL chuyên dụng ngoài màn hình cho dịch vụ bên ngoài.SurfacePool: Quản lý một tập hợp các vùng đệmLocalSurface(Texturecộng vớiEGLImage) để đệm gấp đôi hoặc gấp ba.SharedSurfaceExternalImage: Một trình bao bọc an toàn cho luồng để truyền cácEGLImagexử lý giữa dịch vụ bên ngoài và trình kết xuất chính.
4.4.2 Quy trình công việc
Quy trình công việc diễn ra theo trình tự sau:
Dịch vụ bên ngoài sẽ khởi động và tự đăng ký với trình vòng lặp chính, xác định thẻ Figma nào (ví dụ:
#cluster/3d-car) mà dịch vụ đó hiển thị.Dịch vụ này đợi tín hiệu
RenderStarttừ trình lặp để căn chỉnh hoạt động kết xuất với tín hiệu VSYNC của màn hình.Ngoài màn hình, dịch vụ sẽ kết xuất nội dung vào một vùng đệm khung hình do
SurfacePoolcung cấp.Dịch vụ này gọi
swap_bufferstrên ngữ cảnh của dịch vụ, thao tác này sẽ xoay vùng chứa và cung cấp khung hình đã hoàn tất dưới dạng một thực thể củaSharedSurface.SharedSurfaceđược bao bọc trongExternalImagevà gửi qua kênh MPSC Rust đến looper.Trình kết xuất Impeller chính (Giai đoạn 3) nhận hình ảnh bên ngoài. Thay vì sao chép dữ liệu pixel, nó liên kết
EGLImagecơ bản trực tiếp với một hoạ tiết và vẽ hoạ tiết đó như một phần của cảnh chính, đạt được thành phần không sao chép.
4.5 Nền tảng phát triển và kiểm thử (har-platform-linux)
Đối với mục đích phát triển và kiểm thử, các ứng dụng HAR có thể nhắm đến môi trường máy tính Linux tiêu chuẩn và các chế độ thiết lập không cần màn hình. Các nền tảng này được triển khai trong thùng crates/reference/platforms/har-platform-linux.
Không giống như mục tiêu SDV AAOS sản xuất, các nền tảng này không sử dụng hệ thống con direct-rendering của har-gl-context cho đầu ra hiển thị. Thay vào đó, chúng dựa vào các thùng OpenGL Rust tiêu chuẩn:
Chế độ cửa sổ: Sử dụng
winitđể quản lý cửa sổ và vòng lặp sự kiện, đồng thời sử dụngglutinđể tạo ngữ cảnh OpenGL ES và tích hợp với hệ thống tạo cửa sổ.Chế độ không đầu: Sử dụng thùng
har-gl-contextđể tạo một bối cảnh pbuffer ngoài màn hình bằng màn hình EGL mặc định. Điều này cho phép kết xuất vào một vùng đệm ngoài màn hình mà không cần cửa sổ hiển thị hoặc quyền truy cập trực tiếp vào phần cứng hiển thị, chủ yếu được dùng cho kiểm thử tự động hoặc xử lý phụ trợ.