این صفحه جزئیات کامل خط لوله گرافیکی رندرکننده با قابلیت دسترسی بالا (HAR) را شرح میدهد و جریان دادهها را از یک سند طراحی فیگما تا پیکسلهای نهایی نمایش داده شده روی صفحه نمایش میدهد.
نمای کلی
این خط لوله، تعاریف رابط کاربری سطح بالا را به دستورات گرافیکی سطح پایین تبدیل کرده و آنها را به طور کارآمد روی نمایشگرهای سختافزاری ارائه میدهد. این خط لوله برای برنامههای کاربردی حیاتی برای ایمنی خودرو طراحی شده است و بر رندر قطعی، مدیریت کارآمد وضعیت و تعامل قوی با زیرسیستمهای گرافیکی پلتفرم، مانند مدیر رندر مستقیم (DRM) و مدیریت بافر عمومی (GBM) تأکید دارد.
خط لوله را میتوان به چهار مرحله اصلی تقسیم کرد:
- پیشرندر: پردازش نمودار صحنه، اعمال سفارشیسازیها و حل طرحبندی.
- تولید فرمان: تبدیل نمودار صحنهی حلشده به یک لیست نمایش بدون وابستگی به بکاند.
- رندرینگ: اجرای دستورات ترسیم با استفاده از موتور گرافیکی ایمپرلِر.
- ارائه: مدیریت فریمبافرها و همگامسازی با سختافزار نمایشگر.

شکل ۱. جریان گرافیکی HAR.
مرحله ۱: پیشرندر
این مرحله، طراحی استاتیک فیگما و حالت پویای برنامه را به یک درخت رابط کاربری کاملاً حلشده و درون حافظهای آماده برای رندر تبدیل میکند. این مرحله روی یک نخ کاهنده اختصاصی، جدا از حلقه نمایش اصلی، اجرا میشود.
۱.۱ بنیاد DesignCompose
خط لوله HAR بر روی اکوسیستم DesignCompose ساخته شده است.
- منبع: رابط کاربری در فیگما طراحی شده و با استفاده از افزونه DesignCompose خروجی گرفته شده است.
- تعریف: خروجی یک نمونه از
DesignComposeDefinitionاست، یک نمایش سریالی از طراحی (گرهها، سبکها، انواع). - اتصال داده: مدل رابط کاربری برنامه از ماکروهای رویهای (برای مثال،
#[Design(node = "#speed")]) برای اتصال صریح فیلدهای Rust struct به گرههای نامگذاری شده خاص در سند Figma استفاده میکند. این امر به حالت برنامه اجازه میدهد تا به طور خودکار ویژگیهای عناصر بصری را هدایت کند.
اجزای اصلی این بنیاد عبارتند از:
- Reducer: به عنوان حلقه رویداد مرکزی عمل میکند، اقدامات را پردازش کرده و وضعیت فعلی را بهروزرسانی میکند. این چارچوب
DefaultReducerارائه میدهد، اما در صورت نیاز میتوان پیادهسازی سفارشی reducer را نیز ارائه داد. - ارائهدهنده: وضعیت فعلی را به مدل رابط کاربری متصل میکند. ویژگی
Presenterتوسط جعبه چارچوبharryمشخص شده است و یک پیادهسازی مرجع (UIModelPresenter) در جعبهharry-app-coreارائه شده است. - مدل رابط کاربری: سفارشیسازیها را بر اساس وضعیت فعلی تولید میکند. کد مدل رابط کاربری با استفاده از ماکروی
DesignDocumentکه توسط کادرderive_customizationsارائه میشود، تولید میشود. structUIModelدر کادرharry-app-coreنمونهای از این مورد را ارائه میدهد. - Squoosh: ساختار داده
SquooshViewو مخزن گونهها را فراهم میکند که برای رندر رابط کاربری مطابق با طراحی استفاده میشود. یک سند طراحی سریالی شده توسط جعبهdc_bundleاز کتابخانه DesignCompose بارگذاری شده و برای عملکرد کارآمد در زمان اجرا به درختی از ساختارهایSquooshViewتبدیل میشود.
۱.۲ حلقه کاهنده
این خط لوله توسط اکشنها هدایت میشود. این چارچوب نوع شمارششدهی Actions را مشخص میکند که اکشنهای داخلی مورد استفاده توسط خود چارچوب را تعریف میکند، اما همچنین شامل یک نوع CustomAction است که به کاربران امکان میدهد اکشنهای خاص برنامهی اضافی (مثلاً UpdateVehicleSpeed یا ButtonPress ) را تعریف کنند.
این چارچوب همچنین ویژگی StateAction را ارائه میدهد که پیادهسازی اقداماتی را که بر وضعیت برنامه تأثیر میگذارند و به صورت اختیاری عوارض جانبی ایجاد میکنند، ساده میکند و سپس از reducer برای پردازش به برنامه بازگردانده میشوند. شمارش CustomActions در جعبه harry-app-core مثالی دقیق از این مورد ارائه میدهد.
این یک طرح کلی از حلقه کاهنده است:
- پردازش عمل:
Reducerیک عمل را دریافت میکند و وضعیت فعلی را بهروزرسانی میکند. این دادههای خام مانند سرعت فعلی یا اینکه کدام چراغهای هشدار دهنده فعال هستند، هستند. این ممکن است عوارض جانبی نیز ایجاد کند (برای مثال، وقتی چراغ کمربند ایمنی چشمک میزند، سیگنالی پخش شود). - ارائه:
Presenterحالت جدید را بهUIModelنگاشت میکند.UIModelیک مدل نمایش است که دادههایی را که بهطور خاص برای رابط کاربری قالببندی شدهاند، نگهداری میکند (برای مثال، قالببندی سرعت "120" به رشته "65 مایل در ساعت"). - تولید سفارشیسازی: متد
applyمدل رابط کاربری برای تولید مجموعهای از نمونههایRenderCustomizationفراخوانی میشود. اینها دستورالعملهای صریحی برای اصلاح طراحی فیگما هستند (برای مثال، "متن گره #سرعت را روی '65 مایل در ساعت' تنظیم کن"). -
UpdatePolicyبرای بهینهسازی: پس از هر بار پیش رندر، یک مقدارUpdatePolicyبرگردانده میشود که نشان میدهد چه زمانی بهروزرسانی رندر بعدی مورد نیاز است. اگر هیچ تغییر وضعیتی در انتظار نباشد و هیچ انیمیشنی در حال اجرا نباشد،UpdatePolicyسیگنال میدهد که هیچ بهروزرسانی بیشتری فوراً مورد نیاز نیست. در چنین مواردی، Reducer تولید لیستهای نمایش جدید را متوقف میکند، از چرخههای رندر غیرضروری جلوگیری میکند و منابع را تا زمانی که یک اقدام یا رویداد جدید باعث ایجاد تغییر شود، حفظ میکند.
۱.۳ مشاهدهی مصرف و مقداردهی اولیهی مخزن
این خط لوله با یک نمونه DesignComposeDefinition آغاز میشود. این سند طراحی Figma است که توسط DesignCompose در یک ساختار بافر پروتکل سریالی شده است.
بارگذاری اولیه: در هنگام راهاندازی، طرح اصلی (که توسط گره ریشه آن مشخص میشود) از
DesignComposeDefinitionبه یک درختSquooshViewاولیه تبدیل میشود. این فرآیند یکباره است.مخزن:
SquooshVariantRepositoryانواع کامپوننتهای قابل استفاده مجدد و نماهای بارگذاری شده اولیه را مدیریت میکند.بارگذاری تنبل: برای به حداقل رساندن زمان راهاندازی و استفاده از حافظه، نماهای اضافی (آنهایی که بخشی از درخت گره ریشه اولیه نیستند) فقط زمانی که صریحاً به آنها ارجاع داده شده و منطق رندر به آنها نیاز داشته باشد (مثلاً در طول سفارشیسازی لیست)، به صورت تنبل از سند بارگذاری میشوند.
۱.۴ مرحله شخصیسازی
درخت SquooshView پیمایش میشود تا حالت پویای برنامه اعمال شود:
تعویضهای متغیر: نمونههای کامپوننت با متغیرهای خاص (مثلاً تغییر آیکونی که نشاندهنده حالت رانندگی فعلی از اسپرت به اقتصادی است) بر اساس منطق زمان اجرا تعویض میشوند.
گسترش لیست: یک آیتم قالب واحد در فیگما با یک لیست پویا از فرزندان جایگزین میشود. شناسههای منحصر به فرد جدیدی برای این فرزندان ایجاد میشود تا هویت پایدار انیمیشنها تأیید شود.
لغو متن و سبک: محتوای متن (برای مثال، مقدار سرعت) و سبکها (برای مثال، شفافیت، رنگ) از حالت فعلی بهروزرسانی میشوند.
۱.۵ وضوح متغیر
توکنها و متغیرهای طراحی که در فیگما یا به صورت محلی در برنامه تعریف شدهاند، قابل حل هستند.
- اتصال: ویژگیهای
SquooshViewکه به متغیرهایی (مانند رنگها یا ابعاد) ارجاع میدهند، با مقادیر واقعی آنها برای فریم فعلی جایگزین میشوند.
۱.۶ محاسبه طرحبندی
طرحبندی پویا:
DynamicLayoutموقعیت و اندازه نهایی (مرزهای) هر گره در درختSquooshViewرا محاسبه میکند.طرحبندی متن:
TextHelperاز پیادهسازی ویژگیLayoutHelperبرای محاسبه معیارهای متن، پوشش و شکلدهی استفاده میکند. این امر به تأیید صحت جریان متن در محدودیتهای آن قبل از رندر شدن کمک میکند.
۱.۷ صفحه مدرج و گیجها
این یک مرحله تخصصی برای رابطهای کاربری خودرو است.
-
MeterData: اگر یک گره دارای دادههای کنتور (تعریفشده در فیگما) باشد، هندسه آن به صورت پویا بر اساسmeter_value(مثلاً سرعت خودرو) تغییر میکند.- کمانها: زاویه جاروب تنظیم میشود.
- چرخشها: تبدیل چرخش بر اساس زوایای شروع و پایان محاسبه میشود.
- نوارهای پیشرفت: عرض یا ارتفاع یک مستطیل مقیاسبندی میشود.
- بردارهای پیشرفت: طول یک مسیر برداری تنظیم میشود.
۱.۸ انیمیشن
تفاوت:
SquooshViewفعلی باprevious_squoosh_viewازPreRenderCacheمقایسه میشود.درونیابی: اگر ویژگیها تغییر کرده باشند،
Squooshدرونیابیهایی ایجاد میکند تا مقادیر (مثلاً شفافیت یا تبدیل) را به مرور زمان به طور روان تغییر دهد.
مرحله ۲: تولید فرمان
پس از اینکه درخت SquooshView به طور کامل حل و متحرکسازی شد، به یک توالی خطی از دستورات ترسیم تبدیل میشود.
جزء کلیدی این مرحله، جعبه DisplayList است:
generate_dl: این تابع به صورت بازگشتی درختSquooshViewرا پیمایش میکند.ترجمه:
- شکلها و مسیرها: با نوع مناسب
DisplayListAppearance(مثلاًRectیاPath) بهDisplayListEntryتبدیل میشوند. - متن: با
TextHelperبه ورودیهای طراحی متن تبدیل شده است. - تبدیلها و کلیپها: برای مدیریت پشته حالت ترسیم، به جفتهای
PushTransform3DوPopTransform3DیاPushClipRegionوPopClipRegionتبدیل میشوند. - ماسک کردن: برای ایجاد و ترکیب صحیح لایهها، به جفتهای
PushMaskLayerوPopMaskLayerتبدیل شده است.
- شکلها و مسیرها: با نوع مناسب
نتیجه نهایی یک نمونه از Vec<DisplayListEntry> است که مستقل از نحوه ترسیم، آنچه را که باید ترسیم شود، توصیف میکند.
۲.۱ انتقال به حلقهزن
پس از تولید DisplayList ، Reducer آن را در نمونهای از ViewDescriptor قرار میدهد و آن را از طریق کانال Rust MPSC ( LooperMessage ) به نخ looper ارسال میکند. Looper مسئول مراحل رندر و نمایش است که مانع از مسدود شدن خط لوله گرافیکی توسط نخ Reducer میشود.
مرحله ۳: رندرینگ
DisplayList مستقل از پلتفرم به بخش رندرینگ تحویل داده میشود، جایی که دستورات انتزاعی به دستورالعملهای GPU ترجمه میشوند.
HAR از Impeller، یک موتور رندر که در ابتدا برای Flutter ساخته شده بود، استفاده میکند. Impeller برای حل مشکل اشکالات نرخ فریم ناشی از کامپایل شیدرها طراحی شده است که این کار را با پیشکامپایل کردن مجموعهای کوچک و کارآمد از شیدرها در زمان ساخت انجام میدهد. این رویکرد، همراه با دستهبندی مؤثر و یک backend بسیار بهینه، موارد زیر را ارائه میدهد:
- عملکرد قطعی: عملاً اشکالات کامپایل سایهزن در زمان اجرا را از بین میبرد.
- راهاندازی سریع: سربار راهاندازی اولیه را کاهش میدهد.
- ردپای کوچک: اندازه دودویی فشردهای تولید میکند.
برای آشنایی کامل با معماری Impeller، [معرفی Impeller - موتور رندر جدید Flutter] [impeller-video] را تماشا کنید. اگرچه این ویدیو در مورد Flutter صحبت میکند، اما این مزایای اصلی مستقیماً به پشته خودروسازی HAR قدرت میدهند.
اجزای کلیدی مرحله رندرینگ عبارتند از:
ImpellerRenderer: لیست نمایش را از مرحله پیش رندر به دستورات رندر Impeller تبدیل میکند.رابط برنامهنویسی کاربردی Impeller Rust: کتابخانه Impeller را برای استفاده در Rust (جعبههای
impellerوimpeller-rs-bindgen) در بر میگیرد.TypographyContext: ثبت فونت و شکلدهی متن را مدیریت میکند.
۳.۱ مقداردهی اولیه و مدیریت سطح
ایجاد زمینه: رندرکننده یک نمونه از
impeller::Contextبا یک backend از OpenGL ES مقداردهی اولیه میکند و یک callback برای حل اشارهگرهای تابع OpenGL ES از زمینه GL پلتفرم ارسال میکند.سطح FBO پیچیده شده: به جای ایجاد پنجرهی خود، Impeller در یک شیء فریمبافر OpenGL (FBO) موجود که توسط فاز ۴ ارائه شده است، رندر میشود. این کار با فراخوانی
Surface::create_wrapped_fboانجام میشود.
۳.۲ مدیریت منابع
تصاویر: از فرمتهای استاندارد و بافتهای فشرده KTX2 پشتیبانی میکند. این تصاویر در بافتهای GPU آپلود شده و توسط یک ساختار
Resourcesداخلی مدیریت میشوند.فونتها: فونتهای TrueType و OpenType برای رندر متن، در
TypographyContextبارگذاری و ثبت میشوند.تصاویر خارجی: مدیریت تخصصی بافتهای خارجی (برای مثال، فیدهای دوربین و رندرکنندههای سهبعدی خارجی) شامل اتصال نمونههای
EGLImageیا بافتهای OpenGL خارجی به اشیاء ImpellerTextureبرای رندر بدون کپی است.
۳.۳ رندر پس
حلقه render با استفاده از DisplayListBuilder یک نمونه از Impeller DisplayList میسازد (نباید با Vec<DisplayListEntry> تولید شده توسط مرحله پیش رندر اشتباه گرفته شود):
بافر را پاک میکند و تبدیلهای سراسری را برای مقیاسبندی DPI و چرخش صفحه نمایش اعمال میکند.
روی آیتمهای ورودی
DisplayListEntryتکرار میکند:- حالت:
save()وrestore()برای ارسال و حذف تبدیلها و برش ناحیهها استفاده میشود. - اشکال هندسی اولیه:
RectوRoundedRectبا استفاده از عملیات استاندارد رنگآمیزی ترسیم میشوند. - مسیرها: مسیرهای برداری پیچیده (از جمله نمونههای
Arcپویا) ساخته و ترسیم میشوند. - متن:
TextوStyledTextبا استفاده ازTypographyContextرندر میشوند. - تصاویر: تصاویر استاندارد و خارجی با استفاده از
draw_texture_rectترسیم میشوند.
- حالت:
لیست نمایش پروانه ساخته شده را با استفاده از
surface.draw_display_list()به سطح ارسال میکند و دستورات GL زیرین را تولید میکند.تابع
swap_buffers()را روی context اصلی فراخوانی میکند تا فاز ۴ را آغاز کند.
مرحله ۴: ارائه
این مرحله نهایی، تعامل با سختافزار نمایشگر را برای نمایش فریم رندر شده مدیریت میکند. HAR از یک مسیر رندر مستقیم قوی در سیستم عامل اندروید اتوموتیو (AAOS) و سیستم عامل نرمافزاری تعریفشده برای خودرو (SDV) استفاده میکند.
مولفه کلیدی این مرحله HarDirectRenderingContext (در جعبه har-gl-context ) است.
۴.۱ معماری
لایه ارائه از یک رویکرد بافر دوگانه با یک هدف ترسیم خارج از صفحه استفاده میکند:
بافر ترسیم: FBO خارج از صفحه، جایی که ایمپلر صحنه را رندر میکند.
بافر Resolve (اختیاری): بافر کمکی اختیاری برای پشتیبانی از ضد الاسینگ چند نمونهای (MSAA)
- این میتواند در صورت نیاز توسط پیادهسازی یا پیکربندی اساسی OpenGL ES فعال شود. در چنین مواردی، به عنوان یک هدف میانی برای حل بافر ترسیم چند نمونهای قبل از blitting (انتقال بلوک بیت) به بافر رندر عمل میکند.
بافر رندر: بافر عمومی که توسط یک شیء GBM پشتیبانی میشود، که معادل بافر پشتی در یک زنجیرهی مبادلهی گرافیکی معمولی است.
بافر جلویی: بافر GBM که به صفحه نمایش اسکن میشود.
۴.۲ زنجیره سواپ
وقتی swap_buffers فراخوانی میشود، HAR این مراحل را دنبال میکند:
محتویات بافر رسم را به بافر رندر منتقل میکند (و در صورت نیاز پیادهسازی، یک انتقال میانی به بافر حل و فصل نیز انجام میدهد).
تابع
glFlush()را در زمینه GL فراخوانی میکند و یک نمونه ازEGL_SYNC_NATIVE_FENCE_ANDROIDبرای ردیابی تکمیل GPU ایجاد میکند.یک درخواست اتمی DRM برای جابجایی بافر رندر به صفحه نمایش ایجاد میکند. این درخواست شامل FD حصار GPU (که in fence نامیده میشود) است تا از نمایش بافر رندر توسط کنترلر نمایشگر قبل از اتمام ترسیم توسط GPU جلوگیری کند.
همزمان یک حصار جدید از DRM (به نام حصار خروجی) درخواست میکند تا زمانی که بافر قبلی (بافر جلویی برای فریم قبلی) دیگر روی صفحه نمایش نیست، سیگنال دهد.
درخواست اتمی را با استفاده از پرچم غیر مسدودکننده ثبت میکند تا نخ اصلی بتواند ادامه دهد در حالی که زیرسیستمهای گرافیکی همگامسازی میشوند.
حصار خروجی جدید را در متن ذخیره میکند تا HAR بتواند منتظر بماند تا در شروع فرآیند
swap_buffersدر فریم بعدی سیگنال آن ارسال شود. این کار مانع از آن میشود که GPU به بافری که هنوز نمایش داده میشود، متصل شود.
۴.۳ تنظیم حالت مستقیم
HAR با استفاده از زیرسیستمهای DRM و Kernel Mode Setting (KMS) مستقیماً با هسته تعامل میکند تا وضوح صفحه نمایش AAOS SDV را پیکربندی کند و تعاملات با مدیران پنجره مانند SurfaceFlinger (در پیکربندیهای خاص) را دور بزند و امکان کنترل انحصاری و با اولویت بالا بر سختافزار صفحه نمایش را فراهم کند.
۴.۴ رندر خارجی
HAR از واگذاری رندر عناصر خاص رابط کاربری (که با تگها در فیگما مشخص میشوند) به فرآیندها یا رشتههای خارجی پشتیبانی میکند. این امر برای ادغام صحنههای سهبعدی پیچیده (به عنوان مثال، تجسم ماشین ego از موتورهایی مانند Kanzi یا Unity) یا سایر محتواهایی که به یک زمینه OpenGL اختصاصی نیاز دارند، مفید است.
۴.۴.۱ اجزای کلیدی
-
HarExternalRenderContext: یک زمینه EGL خارج از صفحه اختصاصی برای سرویس خارجی. -
SurfacePool: مجموعهای از بافرهایLocalSurface(Textureبه علاوهEGLImage) را برای بافرینگ دوگانه یا سهگانه مدیریت میکند. -
SharedSurfaceExternalImage: یک پوشش thread-safe برای انتقال هندلهایEGLImageبین سرویس خارجی و رندرکننده اصلی.
۴.۴.۲ گردش کار
گردش کار از این ترتیب پیروی میکند:
سرویس خارجی شروع به کار میکند و خود را در حلقهساز اصلی ثبت میکند و مشخص میکند که کدام تگهای فیگما (مثلاً
#cluster/3d-car) را رندر میکند.این سرویس منتظر سیگنالهای
RenderStartاز looper میماند تا رندر خود را با سیگنال VSYNC نمایشگر هماهنگ کند.خارج از صفحه نمایش، این سرویس محتوای خود را در یک فریم بافر ارائه شده توسط
SurfacePoolرندر میکند.این سرویس
swap_buffersدر context خود فراخوانی میکند که pool را میچرخاند و فریم تکمیلشده را به عنوان نمونهای ازSharedSurfaceدر دسترس قرار میدهد.SharedSurfaceدرExternalImageقرار میگیرد و از طریق کانال Rust MPSC به looper ارسال میشود.رندرکنندهی اصلی Impeller (فاز ۳) تصویر خارجی را دریافت میکند. به جای کپی کردن دادههای پیکسلی،
EGLImageزیرین را مستقیماً به یک بافت متصل میکند و آن را به عنوان بخشی از صحنهی اصلی ترسیم میکند و به این ترتیب به ترکیببندی بدون کپی دست مییابد.
۴.۵ پلتفرمهای توسعه و آزمایش (har-platform-linux)
برای اهداف توسعه و آزمایش، برنامههای HAR میتوانند محیطهای دسکتاپ استاندارد لینوکس و تنظیمات بدون سر (headless) را هدف قرار دهند. این پلتفرمها در جعبهی crates/reference/platforms/har-platform-linux پیادهسازی شدهاند.
برخلاف هدف AAOS SDV تولیدی، این پلتفرمها از زیرسیستم direct-rendering har-gl-context برای خروجی نمایش استفاده نمیکنند. در عوض، آنها به جعبههای استاندارد Rust OpenGL متکی هستند:
حالت پنجرهای:
winitبرای مدیریت پنجره و حلقههای رویداد وglutinبرای ایجاد زمینههای OpenGL ES و ادغام با سیستم پنجرهای استفاده میکند.حالت بدون سر (Headless mode): از جعبه
har-gl-contextبرای ایجاد یک زمینه pbuffer خارج از صفحه با نمایش پیشفرض EGL استفاده میکند. این امکان رندر کردن در یک بافر خارج از صفحه را بدون نیاز به یک پنجره قابل مشاهده یا دسترسی مستقیم به سختافزار صفحه نمایش فراهم میکند، که در درجه اول برای تست خودکار یا پردازش backend استفاده میشود.