توضّح هذه الصفحة مسار الرسومات الكاملة في أداة العرض عالية التوفّر (HAR)، مع تتبُّع تدفق البيانات من مستند تصميم Figma إلى وحدات البكسل النهائية المعروضة على الشاشة.
نظرة عامة
يحوّل خط أنابيب العرض تعريفات واجهة المستخدم ذات المستوى العالي إلى أوامر رسومات ذات مستوى منخفض، ويعرضها بكفاءة على شاشات الأجهزة. تم تصميم مسار العرض هذا للتطبيقات المهمة جدًا للسلامة في السيارات، مع التركيز على العرض الحتمي، والإدارة الفعّالة للحالة، والتفاعل القوي مع الأنظمة الفرعية لرسومات المنصة، مثل Direct Rendering Manager (DRM) وGeneric Buffer Management (GBM).
يمكن تقسيم مسار الإحالة الناجحة إلى أربع مراحل رئيسية:
- العرض المسبق: معالجة الرسم البياني للمشهد وتطبيق التخصيصات وحلّ التنسيق
- إنشاء الأوامر: تحويل الرسم البياني للمشهد الذي تم تحليله إلى قائمة عرض مستقلة عن الخلفية.
- العرض: تنفيذ أوامر الرسم باستخدام محرّك الرسومات Impeller
- العرض التقديمي: إدارة مخازن مؤقتة للإطارات والمزامنة مع أجهزة العرض
الشكل 1: مخطط انسيابي لرسومات HAR
المرحلة 1: العرض المسبق
تحوّل هذه المرحلة تصميم Figma الثابت وحالة التطبيق الديناميكية إلى شجرة واجهة مستخدم محلّلة بالكامل ومخزّنة في الذاكرة وجاهزة للعرض. يتم تنفيذ هذه المرحلة على سلسلة مصغّرة مخصّصة، منفصلة عن حلقة العرض الرئيسية.
1.1 أساسيات DesignCompose
تم إنشاء مسار HAR استنادًا إلى منظومة DesignCompose المتكاملة.
- المصدر: تم تصميم واجهة المستخدم في Figma وتصديرها باستخدام المكوّن الإضافي DesignCompose.
- التعريف: الناتج هو مثيل من
DesignComposeDefinition، وهو تمثيل متسلسل للتصميم (العُقد والأنماط وخيارات المنتج). - ربط البيانات: يستخدم نموذج واجهة المستخدم للتطبيق وحدات ماكرو إجرائية (على سبيل المثال،
#[Design(node = "#speed")]) لربط حقول بنية Rust بشكل صريح بعُقد محدّدة الاسم في مستند Figma. يتيح ذلك لحالة التطبيق التحكّم تلقائيًا في خصائص العناصر المرئية.
تتضمّن هذه الأسس المكوّنات الرئيسية التالية:
- الدالة المخفّضة: تعمل كحلقة معالجة الأحداث مركزية، وتعالج الإجراءات وتعدّل الحالة الحالية. يوفّر إطار العمل
DefaultReducer، ولكن يمكن توفير عملية تنفيذ مخصّصة لبرنامج تقليل البيانات إذا لزم الأمر. - العنصر Presenter: يربط الحالة الحالية بنموذج واجهة المستخدم. يتم تحديد السمة
Presenterمن خلال حزمة إطار العملharry، ويتم توفير تنفيذ مرجعي (UIModelPresenter) في حزمةharry-app-core. - نموذج واجهة المستخدم: ينشئ عمليات تخصيص استنادًا إلى الحالة الحالية. يتم إنشاء رمز نموذج واجهة المستخدم باستخدام ماكرو
DesignDocumentالذي توفّره حزمةderive_customizations. يوفر البنيةUIModelفي الحزمةharry-app-coreمثالاً على ذلك. - Squoosh: يوفّر بنية بيانات
SquooshViewومستودعًا للمتغيرات، ويُستخدم لعرض واجهة المستخدم وفقًا للتصميم. يتم تحميل مستند تصميم مسلسَل من خلال حزمةdc_bundleمن مكتبة DesignCompose، ويتم تحويله إلى شجرة من بنىSquooshViewلتحقيق أداء فعال في وقت التشغيل.
1.2 حلقة التخفيض
يتم تشغيل سلسلة المعالجة من خلال الإجراءات. يحدّد إطار العمل Actionsالنوع المُعدَّد الذي يحدّد الإجراءات الداخلية التي يستخدمها إطار العمل نفسه، ولكنّه يتضمّن أيضًا صيغة CustomAction تتيح للمستخدمين تحديد إجراءات إضافية خاصة بالتطبيق (على سبيل المثال، UpdateVehicleSpeed أو ButtonPress).
يوفّر إطار العمل أيضًا السمة StateAction التي تسهّل تنفيذ الإجراءات التي تؤثّر في حالة التطبيق وتؤدي اختياريًا إلى آثار جانبية يتم إرجاعها إلى التطبيق من أداة الاختزال لمعالجتها. يقدّم النوع CustomActions enum في الحزمة harry-app-core مثالاً مفصّلاً على ذلك.
في ما يلي مخطط أساسي لحلقة المخفّض:
- معالجة الإجراء: يتلقّى
Reducerإجراءً ويعدّل الحالة الحالية. وهي البيانات الأولية، مثل السرعة الحالية أو مؤشرات التحذير النشطة. وقد يؤدي ذلك أيضًا إلى آثار جانبية (على سبيل المثال، تشغيل رنين عند وميض ضوء حزام الأمان). - العرض التقديمي: يربط
Presenterالحالة الجديدة بـUIModel. UIModelهو نموذج عرض يحتوي على بيانات منسَّقة خصيصًا لواجهة المستخدم (على سبيل المثال، تنسيق السرعة "120" كسلسلة "65 ميل في الساعة"). - إنشاء التخصيص: يتم استدعاء طريقة
applyفي نموذج واجهة المستخدم لإنشاء مجموعة من مثيلاتRenderCustomization. هذه تعليمات صريحة لتعديل تصميم Figma (مثلاً، "اضبط نص العقدة #speed على '65 ميل في الساعة'"). UpdatePolicyللتحسين: بعد كل عملية عرض مسبق، يتم عرض القيمةUpdatePolicy، ما يشير إلى الوقت الذي يجب فيه إجراء عملية تعديل العرض التالية. إذا لم تكن هناك أي تغييرات في الحالة في انتظار المراجعة ولم تكن أي رسوم متحركة قيد التشغيل، يشيرUpdatePolicyإلى أنّه ما مِن حاجة إلى إجراء أي تعديلات أخرى على الفور. في مثل هذه الحالات، يتوقف Reducer عن إنشاء قوائم عرض جديدة، ما يمنع دورات العرض غير الضرورية ويحافظ على الموارد إلى أن يؤدي إجراء أو حدث جديد إلى تغيير.
1.3 عرض عملية استيعاب البيانات وتهيئة المستودع
تبدأ سلسلة المعالجة بمثيل DesignComposeDefinition. هذه هي مستندات التصميم في Figma التي تم تسلسلها بواسطة DesignCompose إلى بنية Protocol Buffers.
التحميل الأولي: عند بدء التشغيل، يتم تحويل التصميم الرئيسي (المحدّد بواسطة عقدته الجذرية) من
DesignComposeDefinitionإلى شجرةSquooshViewأولية. هذه عملية تُجرى لمرة واحدة.المستودع: يدير
SquooshVariantRepositoryأشكال المكوّنات القابلة لإعادة الاستخدام وطرق العرض التي يتم تحميلها في البداية.التحميل الكسول: للحدّ من وقت بدء التشغيل واستخدام الذاكرة، يتم تحميل طرق العرض الإضافية (التي لا تشكّل جزءًا من شجرة عقدة الجذر الأولية) بشكل كسول من المستند فقط عندما تتم الإشارة إليها بشكل صريح وتكون مطلوبة من خلال منطق العرض (على سبيل المثال، أثناء تخصيص قائمة).
1.4 بطاقة التخصيص
يتم الانتقال إلى شجرة SquooshView لتطبيق حالة التطبيق الديناميكية:
تبديل الأشكال المختلفة: يتم تبديل مثيلات المكوّنات بأشكال مختلفة محدّدة (على سبيل المثال، تغيير رمز يمثّل وضع القيادة الحالي من الوضع الرياضي إلى الوضع الاقتصادي) استنادًا إلى منطق وقت التشغيل.
توسيع القائمة: يتم استبدال عنصر نموذج واحد في Figma بقائمة ديناميكية من العناصر الفرعية. يتم إنشاء معرّفات فريدة جديدة لهذه العناصر الفرعية للتحقّق من هوية ثابتة للرسوم المتحركة.
تجاهل النص والنمط: يتم تعديل محتوى النص (مثل قيمة السرعة) والأنماط (مثل مستوى التعتيم واللون) من الحالة الحالية.
1.5 دقة متغيرة
يتم حلّ رموز التصميم والمتغيرات المحدّدة في Figma أو محليًا في التطبيق.
- الربط: يتم استبدال خصائص
SquooshViewالتي تشير إلى متغيرات (مثل الألوان أو الأبعاد) بقيمها المحدّدة للإطار الحالي.
1.6 احتساب التنسيق
التصميم الديناميكي: تحسب
DynamicLayoutالموضع والحجم النهائيين (الحدود) لكل عقدة في شجرةSquooshView.تنسيق النص: تستخدم
TextHelperعملية تنفيذ السمةLayoutHelperلحساب مقاييس النص وتغليفه وتشكيله. يساعد ذلك في التأكّد من أنّ النص يتدفق بشكل صحيح ضمن قيوده قبل عرضه.
1.7 أدوات القياس
هذه خطوة متخصّصة لواجهات مستخدم السيارات.
-
MeterData: إذا كانت إحدى العُقد تتضمّن بيانات عدّاد (محدّدة في Figma)، يتم تغيير شكلها الهندسي بشكل ديناميكي استنادًا إلىmeter_value(مثل سرعة المركبة).- الأقواس: يتم ضبط زاوية المسح.
- عمليات التدوير: يتم احتساب عملية التدوير استنادًا إلى زاويتَي البدء والنهاية.
- أشرطة التقدم: يتم تغيير حجم عرض المستطيل أو ارتفاعه.
- متجهات التقدّم: يتم تعديل طول مسار المتجه.
1.8 Animation
المقارنة: تتم مقارنة
SquooshViewالحالي بـprevious_squoosh_viewمنPreRenderCache.الاستيفاء: إذا تغيّرت الخصائص، تنشئ
Squooshأدوات استيفاء للانتقال بسلاسة بين القيم (مثل التعتيم أو التحويل) بمرور الوقت.
المرحلة 2: إنشاء الأوامر
بعد اكتمال عملية تحليل شجرة SquooshView وتحريكها، يتم تحويلها إلى تسلسل خطي من أوامر الرسم.
المكوّن الرئيسي في هذه المرحلة هو حزمة DisplayList:
generate_dl: تتنقّل هذه الدالة بشكل متكرّر في شجرةSquooshView.الترجمة:
- الأشكال والمسارات: يتم تحويلها إلى
DisplayListEntryمع نوعDisplayListAppearanceالمناسب (على سبيل المثال،RectأوPath) - النص: تم تحويله باستخدام
TextHelperإلى إدخالات رسم نصية. - عمليات التحويل والمقاطع: يتم تحويلها إلى أزواج
PushTransform3DوPopTransform3DأوPushClipRegionوPopClipRegionلإدارة حزمة حالات الرسم. - الإخفاء: تم تحويلها إلى أزواج
PushMaskLayerوPopMaskLayerمن أجل إنشاء الطبقات ودمجها بشكلٍ صحيح.
- الأشكال والمسارات: يتم تحويلها إلى
النتيجة النهائية هي مثيل من Vec<DisplayListEntry> يصف ما
سيتم رسمه، بغض النظر عن كيفية رسمه.
2.1 النقل إلى مشّغل رسائل
بعد إنشاء DisplayList، يغلّفه Reducer في مثيل من ViewDescriptor ويرسله عبر قناة Rust MPSC (LooperMessage) إلى سلسلة looper. يكون Looper مسؤولاً عن مرحلتَي العرض والتنفيذ، ما يمنع سلسلة Reducer من حظر مسار الرسومات.
المرحلة 3: العرض
يتم تسليم DisplayList المستقل عن النظام الأساسي إلى الخلفية الخاصة بالعرض، حيث يتم تحويل الأوامر المجردة إلى تعليمات لوحدة معالجة الرسومات.
تستخدم أداة HAR محرك العرض Impeller الذي تم إنشاؤه في الأصل لـ Flutter. تم تصميم Impeller لحل مشكلة الأخطاء في عدد اللقطات في الثانية الناتجة عن تجميع برامج التظليل، وذلك من خلال تجميع مجموعة صغيرة وفعّالة من برامج التظليل مسبقًا في مدّة التصميم. يوفّر هذا النهج، بالإضافة إلى التجميع الفعّال ومعالجة الخلفية المحسّنة للغاية، ما يلي:
- الأداء الحتمي: يزيل هذا الخيار بشكل شبه كامل المشاكل التي تحدث أثناء تجميع برامج التظليل في وقت التشغيل.
- بدء التشغيل السريع: يقلّل من تكلفة الإعداد.
- حجم صغير: ينتج عنه حجم ثنائي مضغوط.
للحصول على مقدمة شاملة حول بنية Impeller، شاهِد [Introducing Impeller - Flutter's new rendering engine][impeller-video]. على الرغم من أنّ الفيديو يتناول موضوع Flutter، إلا أنّ هذه المزايا الأساسية تعزّز بشكل مباشر حزمة HAR للسيارات.
المكوّنات الرئيسية لمرحلة العرض هي:
ImpellerRenderer: تحويل قائمة العرض من مرحلة العرض المسبق إلى أوامر العرض في Impellerواجهة برمجة تطبيقات Impeller Rust: تعمل على تضمين مكتبة Impeller لاستخدامها في Rust (الحزمتان
impellerوimpeller-rs-bindgen).
TypographyContext: تدير تسجيل الخطوط وتشكيل النصوص.
3.1 الإعداد وإدارة مساحة العرض
إنشاء السياق: يبدأ برنامج العرض مثيلاً من
impeller::Contextباستخدام برنامج OpenGL ES الخلفي، مع تمرير دالة ردّ اتصال لحل مؤشرات دالة OpenGL ES من سياق GL للنظام الأساسي.سطح FBO المغلّف: بدلاً من إنشاء نافذته الخاصة، يعرض Impeller المحتوى في عنصر مخزن مؤقت للإطارات (FBO) حالي من OpenGL توفّره المرحلة 4. يتم ذلك من خلال استدعاء
Surface::create_wrapped_fbo.
3.2 إدارة الموارد
الصور: تتوافق مع التنسيقات العادية والأنسجة المضغوطة KTX2. ويتم تحميلها إلى مواد العرض في وحدة معالجة الرسومات وإدارتها من خلال بنية
Resourcesداخلية.الخطوط: يتم تحميل خطوط TrueType وOpenType وتسجيلها باستخدام
TypographyContextلعرض النصوص.الصور الخارجية: تتضمّن المعالجة المتخصّصة للنسيج الخارجي (على سبيل المثال، خلاصات الكاميرا وعمليات العرض ثلاثي الأبعاد الخارجية) ربط مثيلات
EGLImageأو نصوص OpenGL الخارجية بكائناتTextureفي Impeller من أجل العرض بدون نسخ.
3.3 بطاقة العرض
تنشئ حلقة render مثيلاً من DisplayList في Impeller (يجب عدم الخلط بينه وبين Vec<DisplayListEntry> الذي يتم إنشاؤه في مرحلة العرض المسبق) باستخدام DisplayListBuilder:
يمحو المخزن المؤقت ويطبّق عمليات التحويل العامة لتغيير حجم الشاشة حسب عدد النقاط في البوصة وتدوير الشاشة.
تكرِّر هذه الدالة عناصر الإدخال
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 Architecture
تستخدم طبقة العرض نهجًا مزدوجًا مع هدف رسم خارج الشاشة:
مخزن مؤقت للرسم: هو FBO خارج الشاشة يعرض فيه Impeller المشهد.
تحديد حجم المخزن المؤقت (اختياري): مخزن مؤقت إضافي اختياري لدعم ميزة "التنعيم المتعدد العينات" (MSAA)
- يمكن تفعيل هذا الخيار عند الحاجة من خلال تنفيذ OpenGL ES الأساسي أو الإعدادات. في مثل هذه الحالات، يعمل كهدف وسيط لحلّ مخزن الرسومات المتعددة العينات قبل النقل السريع للبيانات (نقل كتلة البتات) إلى مخزن العرض.
مخزن العرض المؤقت: مخزن مؤقت عام يستند إلى عنصر GBM، ويتوافق مع المخزن المؤقت الخلفي في سلسلة تبديل الرسومات النموذجية.
المخزن المؤقت الأمامي: هو مخزن مؤقت في GBM يتم عرضه على الشاشة.
4.2 سلسلة التبديل
عند استدعاء swap_buffers، يتّبع HAR الخطوات التالية:
يتم نقل محتويات مخزن مؤقت للرسم إلى مخزن مؤقت للعرض (مع نقل وسيط إلى مخزن مؤقت للحل، إذا كان ذلك مطلوبًا في التنفيذ).
يتم استدعاء
glFlush()في سياق GL، ويتم إنشاء مثيل منEGL_SYNC_NATIVE_FENCE_ANDROIDلتتبُّع اكتمال وحدة معالجة الرسومات.تنشئ هذه السمة طلبًا ذريًا لإدارة الحقوق الرقمية (DRM) من أجل تبديل المخزن المؤقت للعرض إلى الشاشة. يحتوي هذا الطلب على معرّف وصف ملف (FD) لحاجز وحدة معالجة الرسومات (يُسمى الحاجز الداخلي) لمنع وحدة التحكّم في العرض من عرض مخزن العرض المؤقت قبل أن تنتهي وحدة معالجة الرسومات من الرسم.
يطلب في الوقت نفسه سياجًا جديدًا من نظام إدارة الحقوق الرقمية (يُسمى السياج الخارجي)، وذلك للإشارة إلى أنّ المخزن المؤقت السابق (المخزن المؤقت الأمامي للإطار السابق) لم يعُد معروضًا على الشاشة.
تنفِّذ هذه السمة الطلب الذري باستخدام العلامة غير الحظرية، ما يتيح استمرار عمل سلسلة المحادثات الرئيسية مع الحفاظ على مزامنة الأنظمة الفرعية للرسومات.
يخزِّن هذا الحقل السياج الخارجي الجديد في السياق حتى يتمكّن HAR من انتظار الإشارة إليه في بداية عملية
swap_buffersفي الإطار اللاحق. يمنع ذلك وحدة معالجة الرسومات من الرسم في مخزن مؤقت لا يزال معروضًا.
4.3 إعداد الوضع المباشر
تتفاعل HAR مباشرةً مع النواة باستخدام أنظمة DRM وKernel Mode Setting (KMS) الفرعية لإعداد دقة العرض في AAOS SDV، مع تجاوز التفاعلات مع أدوات إدارة النوافذ، مثل SurfaceFlinger (في إعدادات معيّنة)، ما يتيح التحكّم الحصري والعالي الأولوية في أجهزة العرض.
4.4 العرض الخارجي
تتيح HAR تفويض عرض عناصر معيّنة في واجهة المستخدم (يتم تحديدها باستخدام علامات في Figma) إلى عمليات أو سلاسل خارجية. ويكون ذلك مفيدًا لدمج مشاهد ثلاثية الأبعاد معقّدة (على سبيل المثال، عرض مرئي لسيارة من محركات مثل 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 الإنتاجي، لا تستخدم هذه المنصات نظام direct-rendering الفرعي من har-gl-context لإخراج العرض. بدلاً من ذلك،
تعتمد على حِزم Rust OpenGL العادية:
وضع النافذة: يستخدم
winitلإدارة النوافذ وحلقات الأحداث، وglutinلإنشاء سياقات OpenGL ES والدمج مع نظام النوافذ.وضع التشغيل بدون واجهة مستخدم رسومية: يستخدم حزمة
har-gl-contextلإنشاء سياق pbuffer خارج الشاشة مع شاشة EGL التلقائية. يتيح ذلك عرض المحتوى في مخزن مؤقت خارج الشاشة بدون الحاجة إلى نافذة مرئية أو إذن مباشر بالوصول إلى أجهزة العرض، ويُستخدم بشكل أساسي في الاختبارات الآلية أو معالجة الخلفية.