העברת סקריפטים ל-OpenGL ES 3.1

בעומסי עבודה שבהם מחשוב GPU הוא אידיאלי, העברת סקריפטים של RenderScript OpenGL ES (GLES) מאפשר אפליקציות שנכתבו ב-Kotlin וב-Java או באמצעות NDK כדי לנצל את היתרונות של חומרת GPU. סקירה כללית ברמה גבוהה תעזור לך להשתמש ב-OpenGL ES 3.1 תוכנות הצללה למחשוב כדי להחליף את הסקריפטים של RenderScript.

אתחול GLES

במקום ליצור אובייקט הקשר של RenderScript, מבצעים את השלבים הבאים כדי ליצור הקשר של GLES מחוץ למסך באמצעות EGL:

  1. איך להציג את תצוגת ברירת המחדל

  2. אתחול EGL באמצעות מסך ברירת המחדל, שמציין את גרסת ה-GLES.

  3. צריך לבחור הגדרת EGL עם סוג שטח של EGL_PBUFFER_BIT

  4. משתמשים בתצוגה ובהגדרות כדי ליצור הקשר EGL.

  5. יצירת המשטח מחוץ למסך באמצעות eglCreatePBufferSurface אם ההקשר תשמש רק למחשוב, היא יכולה להיות קטנה באופן טריוויאלי (1x1) פלטפורמה.

  6. יצירת השרשור והשיחה eglMakeCurrent בשרשור העיבוד עם המסך, המשטח והקשר EGL כדי לקשר בין ההקשר של GL לשרשור.

האפליקציה לדוגמה מדגימה איך לאתחל את ההקשר של GLES ב- GLSLImageProcessor.kt מידע נוסף זמין במאמר הבא: EGLSurfaces ו-OpenGL ES.

פלט ניפוי באגים של GLES

קבלת שגיאות שימושיות מ-OpenGL מתבססת על תוסף כדי להפעיל רישום ביומן של ניפוי באגים שמגדירה קריאה חוזרת (callback) לפלט ניפוי הבאגים. הדרך לעשות את זה מתוך ה-SDK. glDebugMessageCallbackKHR, מעולם לא יושם יוצא מן הכלל. האפליקציה לדוגמה כולל wrapper לקריאה חוזרת מקוד NDK.

הקצאת GLES

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

משאבי GLES מוקצים בתוך GLES. כדי למנוע העתקת זיכרון באינטראקציה עם רכיבי Android אחרים, ל-KHR Images שמאפשר לשתף של מערכים דו-ממדיים של נתוני תמונה. התוסף הזה נדרש למכשירי Android החל מ-Android 8.0. graphics-core ספריית Android Jetpack כוללת תמיכה ביצירת התמונות האלה בקוד מנוהל ובמיפוי להם HardwareBuffer שהוקצה:

val outputBuffers = Array(numberOfOutputImages) {
  HardwareBuffer.create(
    width, height, HardwareBuffer.RGBA_8888, 1,
    HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE
  )
}
val outputEGLImages = Array(numberOfOutputImages) { i ->
    androidx.opengl.EGLExt.eglCreateImageFromHardwareBuffer(
        display,
        outputBuffers[i]
    )!!
}

לצערנו, הפעולה הזו לא יוצרת את מרקם האחסון שלא ניתן לשינוי שנדרש עבור תוכנת הצללה למחשוב כדי לכתוב ישירות למאגר הנתונים הזמני. הדוגמה משתמשת glCopyTexSubImage2D להעתקה של מרקם האחסון שבו נעשה שימוש באמצעות תוכנת ההצללה (shader) המחשוב לתוך KHR Image. אם מנהל התקן OpenGL תומך התוסף EGL Image Storage (אחסון תמונות ב-EGL) ואז התוסף הזה יכול לשמש ליצירת מרקם אחסון משותף שאינו ניתן לשינוי, כדי למנוע את העתקה.

המרה לתוכנות הצללה למחשוב GLSL

הסקריפטים של RenderScript מומרים לתוכנות הצללה למחשוב GLSL.

כתיבת תוכנת הצללה למחשוב GLSL

ב-OpenGL ES,תוכנות הצללה (shader) מחשוב נכתבות שפת ההצללה של OpenGL (GLSL).

התאמה של סקריפט globals

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

  • מאגר נתונים זמני: מומלץ לסקריפט שהשתנה בתדירות גבוהה globals בגדלים גדולים יותר לדחוף גבול קבוע.

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

ביצוע חישובים

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

יצירה והפעלה של תוכנת המחשוב

ליצירה ולאתחול של תוכנת המחשוב יש הרבה במשותף עם כל תוכנת הצללה (shader) אחרת של GLES.

  1. יצירת התוכנית ואת תוכנת ההצללה (shader) המחשוב המשויכת אליה.

  2. חיבור המקור של תוכנת ההצללה, הרכבה של תוכנת ההצללה (shader) (ובדיקת התוצאות של האוסף).

  3. מחברים את תוכנת ההצללה, מקשרים בין התוכנה ומשתמשים בתוכנה.

  4. יצירה, אתחול וחיבור של מדים.

התחלת חישוב

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

private const val WORKGROUP_SIZE_X = 8
private const val WORKGROUP_SIZE_Y = 8
private const val ROTATION_MATRIX_SHADER =
    """#version 310 es
    layout (local_size_x = $WORKGROUP_SIZE_X, local_size_y = $WORKGROUP_SIZE_Y, local_size_z = 1) in;

קבוצות עבודה יכולות לשתף זיכרון, שמוגדר על ידי GL_MAX_COMPUTE_SHARED_MEMORY_SIZE, שהגודל שלו הוא 32KB לפחות, והוא יכול להשתמש ב-memoryBarrierShared() כדי לספק גישה לזיכרון קוהרנטי.

הגדרת הגודל של קבוצת העבודה

גם אם מרחב הבעיות שלכם מתאים לקבוצות עבודה בגודל 1, חשוב להתאים את קבוצת העבודה לגודל המתאים של תוכנת ההצללה (shader). אם הגודל קטן מדי, יכול להיות שמנהל התקן ה-GPU לא יבצע את החישוב במקביל. מספיק, למשל. באופן אידיאלי כדאי לכוונן את הגדלים האלה לכל GPU, במכשירים קיימים, כמו קבוצת העבודה, שהוגדרו כברירת מחדל מספיק טובים. בגודל 8x8 בקטע הקוד של תוכנת ההצללה.

יש GL_MAX_COMPUTE_WORK_GROUP_COUNT, אבל הוא משמעותי; חייב להיות לפחות 65535 בכל שלושת הצירים בהתאם למפרט.

הסרת ההצללה

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

GLES31.glDispatchCompute(
  roundUp(inputImage.width, WORKGROUP_SIZE_X),
  roundUp(inputImage.height, WORKGROUP_SIZE_Y),
  1 // Z workgroup size. 1 == only one z level, which indicates a 2D kernel
)

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

GLES31.glMemoryBarrier(GLES31.GL_SHADER_IMAGE_ACCESS_BARRIER_BIT)

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

האפליקציה לדוגמה בוחן שתי משימות מחשוב:

  • סיבוב HUE: משימת מחשוב עם תוכנת הצללה למחשוב אחת. צפייה GLSLImageProcessor::rotateHue לדוגמת הקוד.
  • טשטוש: משימת מחשוב מורכבת יותר שמבצעת ברציפות שני מחשוב תוכנות הצללה (shader). דוגמת הקוד מופיעה בכתובת GLSLImageProcessor::blur.

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