‫Frame Pacing library   Part of Android Game Development Kit.

ספריית Android Frame Pacing, שנקראת גם Swappy, היא חלק מספריות AGDK. הוא עוזר למשחקי OpenGL ו-Vulkan להשיג רינדור חלק וקצב פריימים נכון ב-Android. במסמך הזה מוגדרת טכניקת קצב הפריימים, מתוארים מצבים שבהם נדרשת טכניקת קצב הפריימים ומוסבר איך הספרייה מטפלת במצבים האלה. אם אתם רוצים לדלג ישירות להטמעה של קצב פריימים במשחק, אפשר לעבור אל השלב הבא.

רקע

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

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

משחק מודיע ל-SurfaceFlinger, המרכיב האחראי על קומפוזיציה במערכת המשנה של התצוגה, שהוא שלח את כל קריאות הציור שנדרשות לפריים (על ידי קריאה ל-eglSwapBuffers או ל-vkQueuePresentKHR).‏ SurfaceFlinger מסמן את הזמינות של פריים לחומרת התצוגה באמצעות תפס. לאחר מכן, חומרת התצוגה מציגה את הפריים. החומרה של המסך פועלת בקצב קבוע, למשל 60 הרץ, ואם אין פריים חדש כשהחומרה צריכה אותו, החומרה מציגה שוב את הפריים הקודם.

זמני פריימים לא עקביים קורים בדרך כלל כשלולאת עיבוד של משחק מעבדת בקצב שונה מזה של חומרת התצוגה המקורית. אם משחק שפועל ב-30 FPS מנסה לבצע רינדור במכשיר שתומך באופן טבעי ב-60 FPS, לולאת הרינדור של המשחק לא מזהה שפריים חוזר נשאר על המסך למשך 16 מילישניות נוספות. הניתוק הזה בדרך כלל יוצר חוסר עקביות משמעותי בזמני הפריימים, למשל: 49 אלפיות שנייה, 16 אלפיות שנייה, 33 אלפיות שנייה. סצנות מורכבות מדי מחמירות את הבעיה הזו, כי הן גורמות לפספוס פריימים.

פתרונות לא אופטימליים

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

שליחת פריימים במהירות המקסימלית שממשק ה-API לעיבוד מאפשר

בגישה הזו, המשחק קשור לפעילות משתנה של SurfaceFlinger, ונוסף פריים אחד של זמן אחזור. צינור העיבוד של התצוגה מכיל תור של פריימים, בדרך כלל בגודל 2, שמתמלא אם המשחק מנסה להציג פריימים מהר מדי. אם אין יותר מקום בתור, לולאת המשחק (או לפחות השרשור של הרינדור) נחסמת על ידי קריאה ל-OpenGL או ל-Vulkan. המשחק נאלץ להמתין עד שחומרת התצוגה תציג פריים, והלחץ הזה מסנכרן בין שני הרכיבים. המצב הזה נקרא buffer-stuffing או queue-stuffing. תהליך הרינדור לא מבין מה קורה, ולכן חוסר העקביות בקצב הפריימים מחמיר. אם המשחק דוגם את הקלט לפני המסגרת, השהיית הקלט גדלה.

שימוש ב-Android Choreographer באופן עצמאי

משחקים משתמשים גם ב-Android Choreographer לצורך סנכרון. הרכיב הזה, שזמין ב-Java מגרסת API 16 וב-C++ מגרסת API 24, מספק טיקים רגילים באותה תדירות כמו מערכת המשנה של התצוגה. עדיין יש ניואנסים לגבי המועד שבו הסימון הזה מועבר ביחס ל-VSYNC של החומרה בפועל, וההיסטים האלה משתנים בהתאם למכשיר. יכול להיות שעדיין תתבצע אגירת נתונים עבור פריימים ארוכים.

היתרונות של ספריית Frame Pacing

הספרייה Frame Pacing משתמשת ב-Android Choreographer לסנכרון, ומטפלת בשונות של מסירת הטיקים בשבילכם. הוא משתמש בחותמות זמן של הצגת נתונים כדי לוודא שהפריימים מוצגים בזמן הנכון, ובגדרות סנכרון כדי למנוע מילוי יתר של המאגר. הספרייה משתמשת ב-NDK Choreographer אם הוא זמין, ואם לא, היא חוזרת ל-Java Choreographer.

הספרייה מטפלת בכמה קצבי רענון אם הם נתמכים על ידי המכשיר, מה שמעניק למשחק גמישות רבה יותר בהצגת פריימים. לדוגמה, במכשיר שתומך בקצב רענון של 60 הרץ וגם של 90 הרץ, משחק שלא יכול להפיק 60 פריימים לשנייה יכול לרדת ל-45 פריימים לשנייה במקום ל-30 פריימים לשנייה כדי לשמור על חלקות. הספרייה מזהה את קצב הפריימים הצפוי של המשחק ומתאימה אוטומטית את זמני הצגת הפריימים בהתאם. ספריית Frame Pacing גם משפרת את חיי הסוללה כי היא מונעת עדכוני תצוגה מיותרים. לדוגמה, אם משחק עובר רינדור בקצב של 60 פריימים לשנייה (FPS), אבל המסך מתעדכן בקצב של 120 הרץ, המסך מתעדכן פעמיים לכל פרים. ספריית Frame Pacing מונעת את הבעיה הזו על ידי הגדרת קצב הרענון לערך שנתמך על ידי המכשיר, שהוא הכי קרוב ליעד של קצב הפריימים.

איך זה עובד

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

קצב פריימים נכון ב-30 הרץ

כשמבצעים רינדור בקצב של 30 הרץ במכשיר עם קצב רענון של 60 הרץ, המצב האידיאלי ב-Android מוצג באיור 1. ‫SurfaceFlinger נועל מאגרי גרפיקה חדשים, אם יש כאלה (הערה: בדיאגרמה, NB מציין שאין מאגר, והמאגר הקודם חוזר על עצמו).

קצב פריימים אידיאלי של 30 הרץ במכשיר עם 60 הרץ

איור 1. קצב פריימים אידיאלי של 30 הרץ במכשיר עם 60 הרץ.

מסגרות משחק קצרות מובילות לגמגום

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

פריימים של משחקים קצרים

איור 2. פרים קצר של משחק C גורם לפרים B להציג רק פרים אחד, ואחריו כמה פריימים של C.

ספריית Frame Pacing פותרת את הבעיה הזו באמצעות חותמות זמן של הצגה. הספרייה משתמשת בתוספים של חותמות זמן להצגה EGL_ANDROID_presentation_time ו-VK_GOOGLE_display_timing כדי שהפריים לא יוצג מוקדם מדי, כמו שרואים באיור 3.

חותמות זמן במצגת

איור 3. פריימים של משחק B מוצגים פעמיים כדי שהתצוגה תהיה חלקה יותר.

פריימים ארוכים מובילים לגמגום ולחביון

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

מסגרות ארוכות של משחקים

איור 4. פריימים ארוכים מסוג B גורמים לקצב שגוי של 2 פריימים – A ו-B

הספרייה פותרת את הבעיה הזו באמצעות גדרות סנכרון (EGL_KHR_fence_sync ו-VkFence) כדי להוסיף השהיות לאפליקציה, שמאפשרות לצינור התצוגה להתעדכן במקום לאפשר ללחץ האחורי להצטבר. פריים א' עדיין מציג פריים נוסף, אבל פריים ב' מוצג עכשיו בצורה נכונה, כפי שניתן לראות באיור 5.

הוספת מצבי המתנה לשכבת האפליקציה

איור 5. מסגרות C ו-D ממתינות להצגה.

מצבי הפעולה הנתמכים

אפשר להגדיר את ספריית Frame Pacing לפעול באחד משלושת המצבים הבאים:

  • מצב אוטומטי מושבת + צינור
  • מצב אוטומטי מופעל + צינור
  • מצב אוטומטי מופעל + מצב צינור אוטומטי (צינור/לא צינור)

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

  swappyAutoSwapInterval(false);
  swappyAutoPipelineMode(false);
  swappyEnableStats(false);
  swappySwapIntervalNS(1000000000L/yourPreferredFrameRateInHz);

מצב צינור

כדי לתאם בין עומסי העבודה של המנוע, הספרייה בדרך כלל משתמשת במודל של צינורות (pipelining) שמפריד בין עומסי העבודה של ה-CPU ושל ה-GPU בגבולות של VSYNC.

מצב צינור

איור 6. מצב צינור עיבוד נתונים.

מצב ללא צינור

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

מצב ללא צינור

איור 7. מצב ללא צינור.

מצב אוטומטי

ברוב המשחקים אין אפשרות לבחור את מרווח ההחלפה, שהוא משך הזמן שבו כל פריים מוצג (לדוגמה, 33.3 אלפיות השנייה ל-30 הרץ). במכשירים מסוימים, אפשר להציג משחק ב-60 FPS, אבל במכשירים אחרים יכול להיות שיהיה צורך להוריד את הערך. במצב אוטומטי, המערכת מודדת את זמני המעבד וה-GPU כדי לבצע את הפעולות הבאות:

  • בחירה אוטומטית של מרווחי החלפה: במשחקים שמוצגים בקצב רענון של 30 הרץ בסצנות מסוימות וב-60 הרץ בסצנות אחרות, אפשר לאפשר לספרייה להתאים את המרווח הזה באופן דינמי.
  • השבתה של pipelining לקבלת פריימים מהירים במיוחד: מאפשרת השהיה אופטימלית בין הקלט למסך בכל המקרים.

מספר קצבי רענון

מכשירים שתומכים בכמה קצבי רענון מאפשרים גמישות רבה יותר בבחירת מרווח החלפה שנראה חלק:

  • במכשירים עם 60 הרץ: 60 FPS / 30 FPS / 20FPS
  • במכשירים עם ‎60 Hz + 90 Hz: ‎90 FPS / 60 FPS / 45 FPS / 30 FPS
  • במכשירים עם ‎60 Hz‏, ‎90 Hz‏ ו-‎120 Hz: ‎120 FPS / 90 FPS / 60 FPS / 45 FPS / ‎40 FPS / 30 FPS

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

מידע נוסף על קצב רענון מרובה של קצב פריימים זמין בפוסט בבלוג בנושא עיבוד בקצב רענון גבוה ב-Android.

נתוני פריימים

ספריית Frame Pacing מציעה את הנתונים הסטטיסטיים הבאים למטרות ניפוי באגים ופרופילים:

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

השלב הבא

כדי לשלב את ספריית Android Frame Pacing במשחק, אפשר לעיין באחד מהמדריכים הבאים:

.

מקורות מידע נוספים