מסגרת סנכרון

מסגרת הסנכרון מתארת באופן מפורש יחסי תלות בין פעולות אסינכרוניות שונות במערכת הגרפיקה של Android. ה-framework מספק API שמאפשר לרכיבים לציין מתי שוחררים מאגרי נתונים זמניים. המסגרת מאפשרת גם להעביר פרימיטיבים של סנכרון בין מנהלי התקנים מהליבה למרחב המשתמש, ובין תהליכים במרחב המשתמש עצמם.

לדוגמה, אפליקציה עשויה להוסיף משימות לתור לביצוע ב-GPU. המעבד הגרפי מתחיל לצייר את התמונה הזו. התמונה עדיין לא נוצרה בזיכרון, אבל מצביע המאגר מועבר למרכיב המרכזי של החלון יחד עם פס שמציין מתי העבודה של ה-GPU תסתיים. רכיב ה-window compositor מתחיל את העיבוד מראש ומעביר את העבודה לבקר המסך. באופן דומה, העבודה של המעבד מתבצעת מראש. כאשר ה-GPU מסתיים, בקר התצוגה מציג את התמונה באופן מיידי.

מסגרת הסנכרון מאפשרת גם לספקי הטמעה להשתמש במשאבי סנכרון ברכיבי החומרה שלהם. לבסוף, המסגרת מספקת שקיפות בצינור עיבוד הנתונים של הגרפיקה כדי לעזור בניפוי באגים.

סנכרון מפורש

סנכרון מפורש מאפשר למפיקים ולצרכנים של מאגרי הנתונים הגרפיים לסמן שהם מסיימים להשתמש במאגר הנתונים הזמני. הסנכרון המפורש מיושם במרחב הליבה.

היתרונות של סנכרון מפורש כוללים:

  • פחות הבדלים בהתנהגות בין מכשירים
  • תמיכה טובה יותר בניפוי באגים
  • מדדי בדיקה משופרים

יש שלושה סוגים של אובייקטים במסגרת הסנכרון:

  • sync_timeline
  • sync_pt
  • sync_fence

sync_timeline

sync_timeline הוא ציר זמן שגדל באופן מונוטוני, שספקים צריכים להטמיע לכל מופע של הנהג, כמו הקשר GL, בקר תצוגה או 2D blitter. sync_timeline מונה את המשימות שנשלחו לליבה עבור חומרה מסוימת. sync_timeline מספק אחריות לגבי סדר הפעולות ומאפשר הטמעות ספציפיות לחומרה.

כשמטמיעים את sync_timeline, צריך לפעול לפי ההנחיות הבאות:

  • כדאי לתת שמות שימושיים לכל הנהגים, צירי הזמן והגדרות כדי לפשט את ניפוי הבאגים.
  • מטמיעים את האופרטורים timeline_value_str ו-pt_value_str בזמני פעילות כדי להקל על הקריאה של הפלט של ניפוי הבאגים.
  • מטמיעים את המילוי driver_data כדי לתת לספריות מרחב המשתמשים, כמו ספריית GL, גישה לנתונים פרטיים בציר הזמן, אם רוצים. data_driver מאפשר לספקים להעביר מידע על sync_fence ו-sync_pts שלא ניתנים לשינוי כדי ליצור על סמך שורות הפקודה.
  • אסור לאפשר למרחב המשתמשים ליצור גדר או לסמן אותה באופן מפורש. יצירה מפורשת של אותות או גדרות מובילה להתקפת התקפת מניעת שירות (DoS) שנעצרת את הפונקציונליות של צינור עיבוד הנתונים.
  • אין לגשת לרכיבים sync_timeline, sync_pt או sync_fence באופן מפורש. ה-API מספק את כל הפונקציות הנדרשות.

sync_pt

sync_pt הוא ערך יחיד או נקודה יחידה ב-sync_timeline. לנקודה יש שלושה מצבים: פעיל, מסומן ושגיאה. הנקודות מתחילות במצב פעיל ומעברות למצבים 'התקבל אות' או 'שגיאה'. לדוגמה, כשצרכן התמונות כבר לא זקוק למאגר נתונים זמני, מסמנים את התיבה sync_pt כדי שמפיק התמונה ידע שמותר לכתוב למאגר הנתונים הזמני.

sync_fence

sync_fence הוא אוסף של ערכים של sync_pt, שלרוב יש להם הורים שונים של sync_timeline (למשל, בקר המסך ו-GPU). sync_fence,‏ sync_pt ו-sync_timeline הם הפרימיטיבים העיקריים שבהם משתמשים מנהלי ההתקנים ומרחב המשתמש כדי להעביר את יחסי התלות שלהם. כשמתקבל אות לגדר, מובטח שכל הפקודות שהופקו לפני הגדר הושלמו, כי מנהל הליבה או בלוק החומרה מבצעים את הפקודות לפי הסדר.

מסגרת הסנכרון מאפשרת למספר צרכנים או יצרנים לסמן שהם סיימו באמצעות מאגר, ולשלוח את פרטי התלות באמצעות פרמטר פונקציה אחד. מגדלי הגדרה נתמכים על ידי מתאר קובץ, והם מועברים ממרחב הליבה למרחב המשתמש. לדוגמה, גדר יכולה להכיל שני ערכים של sync_pt שמציינים מתי שני צרכני תמונות נפרדים סיימו לקרוא מאגר. כשהמערכת מקבלת אות מהגדר הגבול, יוצרי התמונות יודעים ששני הצרכנים סיימו לצרוך את התמונה.

גדרות, כמו ערכים של sync_pt, מתחילות להיות פעילות ומחליפות את המצב שלהן בהתאם למצב של הנקודות שלהן. אם כל הערכים של sync_pt יתקבלו אות, sync_fence יקבל אות. אם אחד מ-sync_pt נכנס למצב שגיאה, כל ה-sync_fence נכנס למצב שגיאה.

אי אפשר לשנות את החברות ב-sync_fence אחרי יצירת המחסום. כדי לקבל יותר מנקודה אחת בגדר, מתבצע מיזוג שבו נקודות משתי גדרות נפרדות מתווספות לגדר שלישית. אם אחת מהנקודות האלה קיבלה אות בגדר המקורית והשנייה לא, גם הגדר השלישית לא תהיה במצב איתות.

כדי להטמיע סנכרון מפורש, צריך לספק את הפרטים הבאים:

  • מערכת משנה במרחב הליבה שמטמיעה את מסגרת הסנכרון למנהל חומרה מסוים. מנהלי התקנים שצריכים להיות מודעים למחסום הם בדרך כלל כל מה שיש לו גישה ל-Hardware Composer או מתקשר איתו. קבצים חשובים כוללים:
    • הטמעה ליבה:
      • kernel/common/include/linux/sync.h
      • kernel/common/drivers/base/sync.c
    • מסמכי התיעוד ב-kernel/common/Documentation/sync.txt
    • ספרייה לתקשורת עם מרחב הליבה ב-platform/system/core/libsync
  • הספק צריך לספק את גדרות הסנכרון המתאימות כפרמטרים לפונקציות validateDisplay() ו-presentDisplay() ב-HAL.
  • שני תוספים של GL שקשורים למחסום (EGL_ANDROID_native_fence_sync ו-EGL_ANDROID_wait_sync) ותמיכה במחסום במנהל הגרפיקה.

מקרה לדוגמה: הטמעת מנהל תצוגה

כדי להשתמש ב-API שתומך בפונקציית הסנכרון, צריך לפתח מנהל תצוגה עם פונקציית מאגר תצוגה. לפני שהיה מסגרת לסנכרון, הפונקציה הזו הייתה מקבלת אובייקטים מסוג dma-buf, מעבירה את המאגרים האלה לתצוגה וחוסמת בזמן שהמאגר גלוי. לדוגמה:

/*
 * assumes buffer is ready to be displayed.  returns when buffer is no longer on
 * screen.
 */
void display_buffer(struct dma_buf *buffer);

במסגרת הסנכרון, הפונקציה display_buffer מורכבת יותר. כשמציגים מאגר, הוא משויך למחסום שמציין מתי המאגר יהיה מוכן. תוכלו להוסיף את העבודה לתור ולהתחיל אותה אחרי שהמצב ישתפר.

הוספה של עבודות לרשימת 'הבאים בתור' והתחלת עבודה אחרי שמחליטים סתים את המסגרת לא חוסמת כלום. אתם מחזירים מיד את הגדרת הגדרת הגבול שלכם, וכך מוודאים מתי המאגר יוסר מהתצוגה. כשאתם מוסיפים מאגרי נתונים זמניים לתור, הליבה מציגה את יחסי התלות במסגרת הסנכרון:

/*
 * displays buffer when fence is signaled.  returns immediately with a fence
 * that signals when buffer is no longer displayed.
 */
struct sync_fence* display_buffer(struct dma_buf *buffer, struct sync_fence
*fence);

שילוב סנכרון

כאן מוסבר איך לשלב את מסגרת הסנכרון של הליבה-מרחב עם חלקי מרחב המשתמש ב-framework של Android ועם מנהלי ההתקנים שחייבים לתקשר זה עם זה. אובייקטים במרחב הליבה מיוצגים כמתארי קבצים במרחב המשתמש.

מוסכמות שילוב

פועלים לפי המוסכמות של ממשק HAL ב-Android:

  • אם ה-API מספק מתאר קובץ שמתייחס ל-sync_pt, הנהג של הספק או ה-HAL שמשתמש ב-API חייבים לסגור את מתאר הקובץ.
  • אם מנהל ההתקן של הספק או ה-HAL מעבירים תיאור קובץ שמכיל sync_pt לפונקציית API, מנהל ההתקן של הספק או ה-HAL לא יכולים לסגור את תיאור הקובץ.
  • כדי להמשיך להשתמש בתיאור הקובץ של המחסום, מנהל ההתקן של הספק או ה-HAL צריכים להעתיק את התיאורים.

שם של אובייקט גדר משתנה בכל פעם שהוא עובר דרך BufferQueue. תמיכה בגדר החשבון באמצעות ליבה מאפשרת לגבולות לקבל מחרוזות לשמות, כך שמסגרת הסנכרון משתמשת בשם החלון ובאינדקס מאגר הנתונים הזמני שממתינים בתור לזיהוי הגדר, למשל SurfaceView:0. האפשרות הזו מועילה לניפוי באגים כדי לזהות את המקור של נעילה מרומזת, כי השמות מופיעים בפלט של /d/sync ובדוחות באגים.

שילוב עם ANativeWindow

ANativeWindow מודע למחיצות. ל-dequeueBuffer,‏ queueBuffer ו-cancelBuffer יש פרמטרים של גדרות.

שילוב עם OpenGL ES

השילוב של OpenGL ES לסנכרון מסתמך על שני תוספי EGL:

  • באמצעות EGL_ANDROID_native_fence_sync יש דרך לכווץ או ליצור מתארי קבצים מקוריים של Android באובייקטים של EGLSyncKHR.
  • EGL_ANDROID_wait_sync מאפשר דוכנים בצד ה-GPU במקום בצד ה-CPU, וגורם ל-GPU להמתין ל-EGLSyncKHR. התוסף EGL_ANDROID_wait_sync זהה לתוסף EGL_KHR_wait_sync.

כדי להשתמש בתוספים האלה בנפרד, צריך להטמיע את התוסף EGL_ANDROID_native_fence_sync יחד עם תמיכת הליבה המשויכת. בשלב הבא, מפעילים את התוסף EGL_ANDROID_wait_sync ב-driver. התוסף EGL_ANDROID_native_fence_sync מורכב מסוג אובייקט EGLSyncKHR נפרד של גדר מקומית. כתוצאה מכך, תוספים שחלים על סוגי אובייקטים קיימים מסוג EGLSyncKHR לא חלים בהכרח על אובייקטים מסוג EGL_ANDROID_native_fence, וכך נמנעות אינטראקציות לא רצויות.

התוסף EGL_ANDROID_native_fence_sync כולל מאפיין תואם של מתאר קובץ גבולות, שאפשר להגדיר רק בזמן היצירה, ואי אפשר לשלוח שאילתות לגביו ישירות מאובייקט סנכרון קיים. אפשר להגדיר למאפיין הזה אחד משני המצבים הבאים:

  • מתאר קובץ גדר תקין כולל מתאר קיים של קובץ גידור קיים ב-Android באובייקט EGLSyncKHR.
  • -1 יוצרת מתאר קובץ של גדר מקורית ל-Android מאובייקט EGLSyncKHR.

משתמשים בקריאה לפונקציה DupNativeFenceFD() כדי לחלץ את האובייקט EGLSyncKHR מתיאורי הקובץ של הגדרת המחיצה המקורית של Android. התוצאה זהה לשאילתה על המאפיין שהוגדר, אבל היא תואמת למוסכמה שלפיה הנמען סוגר את הגדרת הגבולות (לכן הפעולה כפולה). לבסוף, השמדת האובייקט EGLSyncKHR סוגרת את מאפיין הגדרת ההיקף הפנימי.

שילוב עם Hardware Composer

הכלי ליצירת חומרה מטפל בשלושה סוגים של גדרות סנכרון:

  • Acquire fences מועברים יחד עם מאגרי הקלט לשיחות setLayerBuffer ו-setClientTarget. הם מייצגים כתיבה בהמתנה למאגר, וצריך לסמן אותם לפני ש-SurfaceFlinger או ה-HWC מנסים לקרוא מהמאגר המשויך כדי לבצע קומפוזיציה.
  • גדרות שחרור מאוחזרות אחרי הקריאה ל-presentDisplay באמצעות הקריאה getReleaseFences. אלה מייצגים קריאה בהמתנה מהמאגר הקודם באותה שכבה. ‎release fence מאותת כאשר ה-HWC כבר לא משתמש במאגר הקודם כי המאגר הנוכחי החליף את המאגר הקודם במסך. גדרות השחרור מועברות חזרה לאפליקציה יחד עם מאגרי הנתונים הקודמים, שיוחלפו במהלך היצירה הנוכחית. האפליקציה צריכה להמתין עד שתקבל אות מ-release fence כדי לכתוב תוכן חדש במאגר שקיבלתם בחזרה.
  • Present fences מוחזרים, אחד לכל פריים, כחלק מהקריאה ל-presentDisplay. גדרות נוכחיות מייצגות את הזמן שבו ההרכבה של המסגרת הזו הושלמה, או לחלופין, את הזמן שבו אין יותר צורך בתוצאת ההרכבה של המסגרת הקודמת. במסכים פיזיים, הפונקציה presentDisplay מחזירה את המחיצות הנוכחיות כשהפריים הנוכחי מופיע במסך. אחרי שההגדרות הקיימות מוחזרות, אפשר לכתוב שוב למאגר הנתונים הזמני של SurfaceFlinger, אם רלוונטי. במסכים וירטואליים, גדרות נוכחיות מוחזרות כשבטוח לקרוא מאגר הפלט.