במאמר הזה מוסבר איך לשפר את ביצועי המשחק באמצעות כלים לזיהוי צווארי בקבוק ב-CPU וב-GPU ולפתרון שלהם.
אופטימיזציה של המעבד
אם הניתוח מראה שהמשחק מוגבל על ידי המעבד, חשוב לבצע בדיקה נוספת. כדי לעשות את זה, צריך לזהות את ה-Threads או ממשקי ה-API הספציפיים שגורמים לצווארי בקבוק ומפחיתים את ה-FPS.
בדרך כלל, פתרון אוניברסלי לא יעיל לאופטימיזציה של ה-CPU. במקום זאת, צריך לזהות את עומס העבודה הכי תובעני על סמך המשחק או הסצנה, ואז לבצע אופטימיזציה של הלוגיקה והפונקציות הרלוונטיות.
כלים למעקב אחר התזמון של מנוע המשחק
הכלים הבאים יכולים לעזור בניתוח הזה:
תובנות מ-Unreal
בפרויקטים של Unreal Engine, הכלי Unreal Insight מאפשר לנתח מידע על תזמון של שרשורים נפרדים שמרכיבים פריים.
לדוגמה, בדרך כלל GameThread משתמש בחלק הכי גדול מזמן המעבד, בעיקר בגלל Tick Time. בנוסף, חלק ניכר מזמן הטיק מוקדש למשימות שמשויכות ל-FActorComponentTickFunction.
כדי לבצע אופטימיזציה של FActorComponentTick, חשוב להחריג חישובים וליישם בחירה של תווים ואובייקטים שממוקמים מחוץ לשדה הראייה של המצלמה. בנוסף, שימוש באנימציות מבוססות-LOD (רמת פירוט) יכול להניב שיפורים נוספים בביצועים.
Unity Profiler (Unity)
ניתוח באמצעות כלי לניתוח ביצועים (profiler) של Unity מגלה שה-Thread הראשי צורך יותר מ-45ms, כאשר PostLateUpdate.FinishFrameRendering תופס 16.23ms, ולכן הוא הפעולה הכי אינטנסיבית מבחינת זמן. בתוך זה, נצפו כמה הפעלות של Inl_RenderCameraStack. מומלץ לוודא שהמצלמות מופעלות לפי הצורך ולבצע אופטימיזציה בהתאם.
כלי פרופיל ברמת המערכת
אפשר להשתמש בכלי הפרופיל הבאים:
Perfetto
באמצעות Perfetto trace, אפשר לקבוע את הקצאות ליבות ה-CPU ואת פרטי הביצוע של כל שרשור במכשיר עם Android. כך תוכלו לזהות צווארי בקבוק בביצועים על ידי ניתוח נתוני הביצוע של השרשור.
CPU overhead case
המעקב מצביע על כך שהעומס על GameThread ו-RenderThread גורם לעיכובים ב-QueuePresent של RHI Thread, מה שמוביל לתרחיש שמוגבל על ידי המעבד (CPU), על סמך VSync.
מקרה של תקורה ב-GPU
הנתונים במעקב מצביעים על כך שהשלמת ה-GPU עצמה חורגת מ-25 אלפיות השנייה, מה שמצביע על תרחיש שמוגבל על ידי ה-GPU.
Simpleperf
כדי לזהות את הפונקציות עם השימוש הכי גבוה במעבד, אפשר להשתמש ב-simpleperf. כדי להשיג תוצאות אופטימליות, מומלץ למיין את הפונקציות האלה כדי לתת עדיפות לפונקציות עם השימוש הכי גבוה ולטפל בהן קודם.
בעזרת Simpleperf אפשר לבדוק נתונים על פונקציות שצורכות הכי הרבה זמן מעבד. כדי לייעל את השימוש במעבד, כדאי להתחיל עם הפונקציות שמשתמשות בהכי הרבה מעבד. בדוגמה הזו, USkeletalMeshComponent, שמשויך לאנימציה ב-ActorComponentTickFunctions, צורך הכי הרבה CPU.
אופטימיזציה של GPU
אם הניתוח מראה שהמשחק מוגבל על ידי ה-GPU, חשוב לבצע בדיקה נוספת. לשם כך צריך להשתמש במגוון כלים וטכניקות לאופטימיזציה ולניתוח של GPU.
כדי לבצע אופטימיזציה של ה-GPU, משתמשים בכלי לניפוי באגים של פריימים כדי לנתח את צינור העיבוד ואת קריאות הציור של כל סצנה. בנוסף, כדי לזהות פעולות מיותרות או אזורים שצריך לבצע בהם אופטימיזציה, צריך להבין היטב את ארכיטקטורת ה-GPU ואת התנהגות הפייפליין.
בקטעים הבאים מוסבר על שיטות וכלים לאופטימיזציה של GPU.
הסרה של RenderPasses מיותרים
כדי לשפר את ביצועי הרינדור ולהפחית את העומס על ה-GPU, כדאי לבטל מעברים מיותרים של רינדור. הם כוללים כל render pass שחסרים בו קריאות לציור או שהפלט שלו לא נמצא בשימוש בפריים הסופי.
אפשר להשתמש בכלי לניפוי באגים ב-GPU, כמו RenderDoc, כדי לנתח את צינור העיבוד ולזהות הזדמנויות לאופטימיזציה.
No Draw Calls: Check if the render pass includes any draw calls. אם אין לו קריאות לציור, מסירים את המעבר.
פלט שלא נעשה בו שימוש: בודקים אם מעברים עוקבים ניגשים לפלטים של מעבר העיבוד או מציגים אותם, למשל צבע או עומק. אם לא, מסירים את הכרטיס.
כרטיסים שאפשר למזג: זיהוי כרטיסים שאפשר למזג:
- אותו מאגר מסגרות או קבצים מצורפים
- פעולות תואמות של טעינה או אחסון
- אין מחסומי תלות ביניהם
צמצום העומס או פעולות האחסון
פעולות טעינה או אחסון דורשות הרבה משאבים כי הן משתמשות בהרבה זיכרון.
צריך לצמצם את פעולות הטעינה והשמירה המיותרות. הפעולות האלה מתבצעות רק כשנדרשים קבצים מצורפים בתוך RenderPass. אחרת, מחליפים אותן בפעולות Clear או Don't care כדי לצמצם את התקורה.
איך מבצעים אופטימיזציה
אפשר להשתמש בכלי לניפוי באגים ב-GPU, כמו RenderDoc, כדי לנתח את צינור הרינדור ולזהות את ההזדמנויות הבאות לאופטימיזציה:
טעינה: אם קובץ מצורף של שלב רינדור לא משתמש בנתונים משלב או מקובץ מצורף קודמים, אין צורך בפעולת טעינה. במקרים כאלה, שימוש ב-
Don't careאו ב-Clearיכול להפחית את התקורה.Store: אם לא נעשה שימוש בצירוף של מעבר רינדור אחרי מעבר הרינדור הנוכחי, פעולת ה-Store מיותרת. במקרים כאלה, צריך להשתמש ב-
Don't careאו ב-Clear.החלפה: בדיקה אם אפשר להחליף את הגדרות הטעינה או האחסון הנוכחיות ב-
Clearאו ב-Don't Careבלי להשפיע על הפריים הסופי.
כדי להפעיל את Early-Z, צריך להימנע מהשלכה
התכונה Early-Z משפרת את הביצועים בפלטפורמות לנייד. עם זאת, discard
הוראה בתוך shader משביתה באופן אוטומטי את Early-Z. אם discard
ההוראה לא חיונית, מסירים אותה.
האצת Z מוקדמת
האופטימיזציה הזו מפחיתה באופן משמעותי את הפעולות של הצללת השברים ומשפרת את הביצועים של ה-GPU.
איך מבצעים אופטימיזציה
אפשר להשתמש בכלי לניפוי באגים ב-GPU, כמו RenderDoc, כדי לנתח את צינור הרינדור ולזהות את ההזדמנויות הבאות לאופטימיזציה:
שימוש ב-
discardב-fragment shaders: מילת המפתחdiscardמונעת מה-GPU לבצע בדיקות עומק מוקדמות כי הנראות של הפרגמנט לא ידועה מראש.שינוי של
gl_FragDepth: שינוי דינמי שלgl_FragDepthמשנה את העומק של פרגמנט, ולכן השדרוג של Early-Z מושבת כי העומק הסופי לא ידוע לפני עיבוד הפרגמנט.ההגדרה 'אלפא לכיסוי' מופעלת: כשההגדרה 'אלפא לכיסוי' מופעלת (לרוב משתמשים בה בעת עיבוד MSAA), כיסוי הפרגמנט תלוי בערכי האלפא. הפעולה הזו עלולה לעכב את בדיקת העומק ולהשבית את Early-Z.
אופטימיזציה של פורמט הטקסטורה
בחירה אופטימלית של פורמט טקסטורה מפחיתה את צריכת הזיכרון, משפרת את יעילות רוחב הפס ומשפרת את ביצועי הרינדור. שימוש בפורמטים עם דיוק גבוה מדי עלול לבזבז משאבי GPU בלי לספק יתרונות ויזואליים.
איך מבצעים אופטימיזציה
אפשר להשתמש בכלי לניפוי באגים ב-GPU, כמו RenderDoc, כדי לנתח את צינור הרינדור ולזהות את ההזדמנויות הבאות לאופטימיזציה:
- שימוש ב-
D24S8במקום ב-D32S8למאגרי מידע של שבלונות עומק: שימוש ב-D24S8למאגרי מידע של שבלונות עומק מפחית את צריכת הזיכרון ב-20% בהשוואה ל-D32S8, עם הבדל קטן או ללא הבדל באיכות החזותית ברוב האפליקציות. - שימוש בדחיסת
ASTCלטקסטורות צבעוניות: דחיסתASTCמקטינה באופן משמעותי את השימוש בזיכרון של הטקסטורה – עד פי 8 בהשוואה לפורמטים לא דחוסים – תוך שמירה על איכות ויזואלית גבוהה. - שימוש בפורמטים של חצי-נקודה צפה במקום בנקודה צפה מלאה: אפשר להשתמש ב-
R16Fאו ב-RG16Fכדי לצמצם את רוחב הפס של הזיכרון ואת צריכת האחסון. הפורמטים האלה מתאימים מאוד למאגרי נתונים של עיבוד תמונה.
אופטימיזציה של מורכבות גיאומטרית
צמצום המורכבות הגיאומטרית משפר את ביצועי הרינדור, במיוחד במכשירים ניידים עם יכולות GPU מוגבלות. התהליך הזה כולל שימוש במספר מצומצם של קודקודים ומשולשים, איחוד אובייקטים כדי להקטין את מספר קריאות הציור וביטול גיאומטריה לא מעובדת או לא נחוצה. טכניקות כמו פישוט רשת, רמת פירוט (LOD) וסינון של פירמידת תצוגה או של הסתרה יכולות להפחית באופן משמעותי את עומס העבודה על ה-GPU ולהגדיל את קצב הפריימים.
איך מבצעים אופטימיזציה
כדי לזהות צווארי בקבוק בביצועים שקשורים לגיאומטריה, אפשר להשתמש בכלי פרופיילינג ובכלי ניפוי שגיאות של GPU, כמו RenderDoc, Android GPU Inspector או כלי ניתוח ביצועים אחרים.
הפחתת מספר המשולשים: צמצום השימוש במצולעים, במיוחד באובייקטים קטנים או רחוקים.
שימוש ברמת פירוט (LOD): בהתאם למרחק מהמצלמה, נעשה שימוש אוטומטי ברשתות פשוטות יותר.
מיזוג רשתות קטנות: איחוד של אובייקטים סטטיים כדי לצמצם את מספר קריאות הציור ואת העומס על המעבד.
Frustum and Occlusion Culling: הימנעו מרינדור של אובייקטים שנמצאים מחוץ לתצוגה או מוסתרים על ידי אלמנטים אחרים.
הסרת קבצים מצורפים מיותרים
קבצים מצורפים של שלבי עיבוד (לדוגמה: צבע, עומק, שבלונה) צורכים רוחב פס של זיכרון ומשאבי GPU, גם אם לא נעשה בהם שימוש. הסרה של קבצים מצורפים מיותרים או כפולים משפרת את הביצועים ומפחיתה את צריכת החשמל, במיוחד בפלטפורמות לנייד.
איך מבצעים אופטימיזציה
כדי לזהות צווארי בקבוק בביצועים שקשורים לגיאומטריה, אפשר להשתמש בכלי פרופיילינג ובכלי ניפוי באגים ב-GPU, כמו RenderDoc,
Android GPU Inspector או כלי ניתוח ביצועים אחרים.
- בדיקת השימוש בפועל: האם יש קריאות לציור או הצללות שכותבות לצירוף או קוראות ממנו?
- ניתוח הפלט של המסגרת: אפשר להשתמש ב-
RenderDocאו בכלי השוואה דומים כדי לבדוק אם הקובץ המצורף תורם לתמונה הסופית. - כדאי להשתמש בקבצים מצורפים זמניים או פיקטיביים: בקבצים מצורפים זמניים או בפעולת אחסון מסוג Don't Care (לא חשוב) צריך להשתמש לנתונים זמניים שלא דורשים אחסון מתמשך.
אופטימיזציה של דיוק ההצללה
שימוש ברמת דיוק גבוהה מדי (לדוגמה, highp במקום mediump או lowp) בתוך הצללות מגדיל את עומס העבודה של ה-GPU, את צריכת החשמל ואת העומס על הרישום, במיוחד ב-GPU לנייד. שימוש ברמת הדיוק הנמוכה ביותר שמתאימה למשתנים (לדוגמה, מיקומים, צבעים, ערכי UV) יכול לשפר את הביצועים בלי השפעה ויזואלית מורגשת.
איך מבצעים אופטימיזציה
כדי לזהות צווארי בקבוק בביצועים שקשורים לגיאומטריה, אפשר להשתמש בכלי פרופילים ובכלי ניפוי באגים של GPU כמו RenderDoc, Android GPU Inspector או כלי ניתוח ביצועים אחרים.
בדיקת קוד shader: בודקים את משתני ה-shader ומוודאים שנעשה שימוש בדיוק גבוה רק כשצריך, למשל לחישובים של עומק או של מרחב המסך. משתמשים ברמת דיוק בינונית או נמוכה לצבעים, לקואורדינטות UV או לערכים שלא מצריכים רמת דיוק גבוהה.
שימוש בכלי ניפוי באגים של GPU: כלי אבחון, כמו RenderDoc או פרופילים של GPU לנייד (לדוגמה, AGI, Mali/GPU Inspector), מזהים שימוש מוגבר ברגיסטר או השהיות של Shader שקשורות לבעיות דיוק.
הפעלת הסרת פאות אחוריות
לרוב אין צורך לעבד משולשים שפונים הרחק מהמצלמה (משולשים אחוריים) באובייקטים מוצקים.
איך מבצעים אופטימיזציה
שימוש ב-VK_CULL_MODE_NONE עלול להשפיע לרעה על הביצועים כי הוא מכריח את ה-GPU לעבד גם את הפנים הקדמיות וגם את הפנים האחוריות, מה שמגדיל את עומס העבודה של העיבוד.
מזעור הציור החוזר בסצנות של ממשק המשתמש
כדי לשפר את ביצועי הרינדור ולהפחית את העומס על ה-GPU, כדאי לבטל קריאות מיותרות לציור ומעברים בין רינדורים, במיוחד בסצנות של ממשק המשתמש. לדוגמה, בסצנת ממשק משתמש שבה כל העולם עובר רינדור לפני שממשק המשתמש מוצג על המסך, רינדור העולם הופך למיותר.
איך מבצעים אופטימיזציה
אפשר להשתמש בכלי לניפוי באגים ב-GPU, כמו RenderDoc, כדי לנתח את צינור הרינדור ולזהות את ההזדמנויות הבאות לאופטימיזציה:
- מוודאים שאין משיכות יתר מיותרות. בממשקי משתמש, שבהם יכול להיות שהמסך כולו יעבור רינדור, צריך לוודא שמעברי הרינדור הקודמים לא מצוירים מחדש שלא לצורך.
- כדי לשפר את הביצועים, מומלץ להפעיל בדיקת עומק והסרה של חלקים לא נראים.
- כדאי לשקול להציג את הסדר מהחלק הקדמי לחלק האחורי.