Trang này trình bày chi tiết quy trình đồ hoạ hoàn chỉnh của trình kết xuất có độ khả dụ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 các lệnh đó một cách hiệu quả 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 vào việc kết xuất mang tính xác định, quản lý trạng thái hiệu quả và tương tác mạnh mẽ với các hệ thống con đồ hoạ của 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 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ị không phụ thuộc vào 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ý các khung đệm 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ế Figma tĩnh và trạng thái ứng dụng động thành một cây giao diện người dùng đã phân giải hoàn toàn trong bộ nhớ, sẵn sàng cho quá trình kết xuất. Giai đoạn này chạy trên một luồng bộ giảm tải 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: Kết quả là một thực thể của
DesignComposeDefinition, một biểu diễn được tuần tự hoá 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 các phần tử trực quan.
Các thành phần chính của nền tảng này là:
- Bộ giảm tải: Hoạt động như vòng lặp sự kiện trung tâm, xử lý các hành động 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 bộ giảm tải tuỳ chỉnh nếu cần. - Trình trình bày: 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ởi 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 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 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 tuần tự hoá sẽ được thùngdc_bundletải từ thư viện DesignCompose và chuyển đổi thành một cây cấu trúcSquooshViewđể có hiệu suất thời gian chạy hiệu quả.
1.2 Vòng lặp bộ giảm tải
Quy trình này được điều khiển bởi các hành động. Khung này chỉ định loại được liệt kê Actions xác định các hành động 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 hành động 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 hành động ảnh hưởng đến trạng thái ứng dụng và tuỳ ý tạo các tác dụng phụ, sau đó được chuyển lại cho ứng dụng từ bộ giảm tải để 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à phác thảo cơ bản của vòng lặp bộ giảm tải:
- 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 các chỉ báo (đèn cảnh báo) đang hoạt động. Điều này cũng có thể tạo ra các tác dụng phụ (ví dụ: một tín hiệu phát ra tiếng chuông khi đèn 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 xem, lưu giữ dữ liệu được định dạng cụ thể 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 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à các 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 dặm/giờ'"). UpdatePolicyđể tối ưu hoá: Sau mỗi lần kết xuất trước, một giá trịUpdatePolicysẽ được trả về, cho biết thời điểm cầ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ó hoạt ảnh nào đang chạy,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, Bộ giảm tải 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 hành động hoặc sự kiện mới kích hoạt thay đổi.
1.3 Nhập chế độ xem và khởi chạy kho lưu trữ
Quy trình này 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 cấu trúc vùng đệm giao thức.
Tải ban đầu: Khi khởi động, thiết kế chính (được chỉ định bởi nút gốc) sẽ được chuyển đổi từ
DesignComposeDefinitionthành câySquooshViewban đầu. Đây là quy trình một lần.Kho lưu trữ:
SquooshVariantRepositoryquản lý các biến thể thành phần có thể dùng lại và các chế độ xem đượ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 chế độ xem bổ sung (những chế độ xem 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 Tuỳ chỉnh
Cây SquooshView được duyệt để á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ừ thể thao sang 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 danh sách con động. Mã nhận dạng duy nhất mới được tạo cho các phần tử con này để xác minh danh tính ổn định cho hoạt ảnh.
Ghi đè văn bản và kiểu: 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 (như màu sắc hoặc kích thước) được thay thế bằng các giá trị cụ thể cho khung 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 cách triển khai đặc điểmLayoutHelperđể tính toán các chỉ số văn bản, ngắt dòng và định hình. Điều này giúp xác minh rằng văn bản được hiển thị đúng trong các ràng buộc trước khi kết xuất.
1.7 Mặt số và đồng hồ đo
Đây là bước chuyên biệt 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).- Vòng 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 hình chữ nhật được điều chỉnh theo tỷ lệ.
- Véc tơ tiến trình: Chiều dài của đường dẫn véc tơ được điều chỉnh.
1.8 Hoạt ảnh
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 hoạt ảnh 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 duyệt đệ quy câySquooshView.Bản dịch:
- Hình dạng và đường dẫn: Chuyển đổi thành
DisplayListEntryvới 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 vẽ văn bản. - Biến đổi và 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: Chuyển đổi thành
Kết quả cuối cùng là một thực thể của Vec<DisplayListEntry> mô tả nội dung
cần vẽ, độc lập với cách vẽ.
2.1 Chuyển giao cho trình lặp
Sau khi DisplayList được tạo, Bộ giảm tải sẽ gói danh sách này trong một thực thể của ViewDescriptor và gửi danh sách đó qua kênh Rust MPSC (LooperMessage) đến luồng trình lặp. Looper chịu trách nhiệm về các giai đoạn kết xuất và hiển thị, ngăn luồng Bộ giảm tải chặn quy trình đồ hoạ.
Giai đoạn 3: Kết xuất
DisplayList không phụ thuộc vào 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 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 tập hợp nhỏ và hiệu quả các chương trình đổ bóng tại thời gian xây dựng. Phương pháp này, kết hợp với việc phân lô hiệu quả và phần phụ trợ được tối ưu hoá cao, mang lại:
- Hiệu suất mang tính xác định: Hầu như loại bỏ các lỗi 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 chạy.
- Dung lượng nhỏ: Tạo ra kích thước tệp nhị phân nhỏ gọn.
Để biết thông tin chi tiết về kiến trúc của Impeller, hãy xem [Giới thiệu về Impeller – công cụ kết xuất mới của Flutter][impeller-video]. 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 sẽ 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.Impeller Rust API: Gói thư viện Impeller để sử dụng trong Rust (các thùng
impellervàimpeller-rs-bindgen).TypographyContext: Quản lý việc đăng ký phông chữ và định hình văn bản.
3.1 Khởi chạy và quản lý bề mặt
Tạo ngữ cảnh: Trình kết xuất khởi chạy một thực thể của
impeller::Contextvới phần phụ trợ OpenGL ES, chuyển một lệnh gọi lại để phân giải các con trỏ hàm OpenGL ES từ ngữ cảnh GL của nền tảng.Bề mặt FBO được gói: Thay vì tạo cửa sổ riêng, Impeller sẽ kết xuất vào một đối tượng khung đệm OpenGL (FBO) hiện có do Giai đoạn 4 cung cấp. Điều này được thực hiện 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 tiêu chuẩn và hoạ tiết nén KTX2. Các định dạng này được tải lên hoạ tiết GPU và được quản lý bởi cấu trúc
Resourcesnội bộ.Phông chữ: Phông chữ TrueType và OpenType được tải và đăng ký bằng
TypographyContextđể kết xuất văn bản.Hình ảnh bên ngoài: Việc xử lý chuyên biệt cho các hoạ tiết 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 hoạ tiết 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 Kết xuất
Vòng lặp render tạo một thực thể DisplayList của 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 DisplayListBuilder:
Xoá bộ đệm và áp dụng các biến đổi chung cho việc đ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 và vùng cắt. - Nguyên thuỷ:
RectvàRoundedRectđược vẽ bằng các thao tác vẽ tiêu chuẩn. - Đường dẫn: Các đường dẫn véc tơ phức tạp (bao gồm cả các thực thể
Arcđộng) được xây dựng và vẽ. - Văn bản:
TextvàStyledTextđược kết xuất bằngTypographyContext. - Hình ảnh: Hình ảnh tiêu 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 bề mặt bằ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ý tương tác với phần cứng hiển thị để hiển thị khung đã kết xuất. HAR sử dụng đườ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) của Hệ điều hành ô tô Android (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 bộ đệm kép với mục tiêu vẽ ngoài màn hình:
Bộ đệm vẽ: FBO ngoài màn hình nơi Impeller kết xuất cảnh.
Bộ đệm phân giải (không bắt buộc): Bộ đệm phụ trợ không bắt buộc để hỗ trợ tính năng khử răng cưa nhiều mẫu (MSAA)
- Bạn có thể bật tính năng 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, tính năng này đóng vai trò là mục tiêu trung gian để phân giải bộ đệm vẽ nhiều mẫu trước khi blitting (chuyển khối bit) sang bộ đệm kết xuất.
Bộ đệm kết xuất: Bộ đệm chung được hỗ trợ bởi đối tượng GBM, tương ứng với bộ đệm sau trong chuỗi hoán đổi đồ hoạ thông thường.
Bộ đệm trước: Bộ đệ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 bộ đệm vẽ vào bộ đệm kết xuất (với một blit trung gian vào bộ đệm phân giải, nếu cần theo cách triển khai).
Gọi
glFlush()trên ngữ cảnh GL và tạo một thực thể củaEGL_SYNC_NATIVE_FENCE_ANDROIDđể theo dõi quá trình hoàn tất của GPU.Tạo yêu cầu nguyên tử DRM để hoán đổi bộ đệm kết xuất sang màn hình. Yêu cầu này chứa FD hàng rào GPU (được gọi là hàng rào trong) để ngăn bộ điều khiển màn hình hiển thị bộ đệm kết xuất trước khi GPU vẽ xong.
Đồng thời yêu cầu một hàng rào mới từ DRM (được 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 trước) không còn trên màn hình.
Cam kết yêu cầu nguyên tử bằng cách sử dụ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 trữ hàng rào ngoài mới trong ngữ 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 tiếp theo. Điều này ngăn GPU vẽ vào bộ đệm vẫn đang được hiển thị.
4.3 Cài đặt chế độ trực tiếp
HAR tương tác trực tiếp với kernel bằng cách sử dụng các hệ thống con DRM và Cài đặt chế độ kernel (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 hiển thị.
4.4 Kết xuất bên ngoài
HAR hỗ trợ uỷ quyền 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 hữu ích cho việc tích hợp các cảnh 3D phức tạp (ví dụ: hình ảnh trực quan về xe tự lái từ các công cụ như Kanzi hoặc Unity) hoặc nội dung khác yêu cầu ngữ cảnh OpenGL chuyên dụng.
4.4.1 Những hành động chính
HarExternalRenderContext: Ngữ cảnh EGL ngoài màn hình chuyên dụng cho dịch vụ bên ngoài.SurfacePool: Quản lý một tập hợp các bộ đệmLocalSurface(Texturecộng vớiEGLImage) để tạo bộ đệm kép hoặc bộ đệm ba.SharedSurfaceExternalImage: Trình bao bọc an toàn cho luồng để chuyển các trình xử lýEGLImagegiữa dịch vụ bên ngoài và trình kết xuất chính.
4.4.2 Quy trình làm việc
Quy trình làm việc tuân theo trình tự sau:
Dịch vụ bên ngoài bắt đầu và tự đăng ký với trình lặp chính, xác định các thẻ Figma (ví dụ:
#cluster/3d-car) mà dịch vụ này kết xuất.Dịch vụ này đợi các tín hiệu
RenderStarttừ trình lặp để căn chỉnh quá trình 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ụ này kết xuất nội dung vào một khung đệm do
SurfacePoolcung cấp.Dịch vụ này gọi
swap_bufferstrên ngữ cảnh của dịch vụ, xoay vùng chứa và cung cấp khung hoàn chỉnh dưới dạng một thực thể củaSharedSurface.SharedSurfaceđược gói trongExternalImagevà gửi qua kênh Rust MPSC đến vòng lặp.Trình kết xuất Impeller chính (Giai đoạn 3) nhận hình ảnh từ bên ngoài. Thay vì sao chép dữ liệu pixel, trình kết xuất này sẽ liên kết trực tiếp
EGLImagecơ bản 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)
Với mục đích phát triển và kiểm thử, các ứng dụng HAR có thể nhắm đến các môi trường máy tính tiêu chuẩn của Linux và các thiết lập không có 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 chính thức, 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 Rust OpenGL 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 cửa sổ.Chế độ không có màn hình: Sử dụng thùng
har-gl-contextđể tạo ngữ cảnh pbuffer ngoài màn hình với màn hình EGL mặc định. Điều này cho phép kết xuất vào bộ đệ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 để kiểm thử tự động hoặc xử lý phụ trợ.