Frame Pacing Library   חלק מ-Android Game Development Kit.

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

רקע

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

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

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

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

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

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

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

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

שימוש ב-Android Choreographer לבד

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

היתרונות של ספריית קצב הפריימים

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

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

איך זה עובד

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

תזמון פריימים תקין ב-30Hz

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

קצב פריימים אידיאלי של 30Hz במכשיר 60Hz

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

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

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

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

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

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

חותמות זמן של מצגות

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

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

כשזמן הטיפול בעומס העבודה של התצוגה ארוך יותר מעומס העבודה של האפליקציה, נוספים לתור עוד פריימים. גם במקרה הזה, התוצאה היא גמגום, ויכול להיות שתהיה גם השהיה נוספת של פריים בגלל שתילת נתונים במאגר (buffer-stuffing) (ראו איור 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);

מצב צינור עיבוד נתונים

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

מצב צינור עיבוד נתונים

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

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

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

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

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

מצב אוטומטי

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

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

מספר ערכים של קצב רענון

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

  • במכשירים עם תדר רענון של 60Hz: 60FPS‏ / 30FPS‏ / 20FPS
  • במכשירים עם תצוגה של 60 הרץ ו-90 הרץ: 90FPS‏ / 60FPS‏ / 45FPS‏ / 30FPS
  • במכשירים עם תצוגה של 60Hz,‏ 90Hz ו-120Hz: 120FPS‏ / 90FPS‏ / 60FPS‏ / 45FPS‏ / 40FPS‏ / 30FPS

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

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

נתונים סטטיסטיים של פריימים

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

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

השלב הבא

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

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