כדי להשתמש ב-Jetpack WebGPU, הפרויקט שלכם צריך לעמוד בדרישות המינימום הבאות:
- רמת ה-API המינימלית: נדרשת רמת API 24 (Nougat) ואילך ב-Android.
- חומרה: מומלץ להשתמש בקצה העורפי במכשירים שתומכים ב-Vulkan 1.1 ומעלה.
- מצב תאימות ותמיכה ב-OpenGL ES: אפשר להשתמש ב-WebGPU עם מצב תאימות על ידי הגדרת האפשרות המתוקננת
featureLevelלערךcompatibilityבזמן שליחת הבקשהGPUAdapter.
// Example of requesting an adapter with "compatibility" mode enabled:
val adapter = instance.requestAdapter(
GPURequestAdapterOptions(featureLevel = FeatureLevel.Compatibility))
התקנה והגדרה
דרישות מוקדמות:
Android Studio: מורידים את הגרסה העדכנית של Android Studio מהאתר הרשמי ופועלים לפי ההוראות שמופיעות במדריך להתקנת Android Studio.
יצירת פרויקט חדש
אחרי שמתקינים את Android Studio, מבצעים את השלבים הבאים כדי להגדיר את פרויקט WebGPU:
- מתחילים פרויקט חדש: פותחים את Android Studio ולוחצים על New Project (פרויקט חדש).
בחירת תבנית: בוחרים את התבנית Empty Activity ב-Android Studio ולוחצים על Next (הבא).
איור 1. יצירת פרויקט חדש ב-Android Studio הגדרת הפרויקט:
- שם: נותנים שם לפרויקט (לדוגמה, 'JetpackWebGPUSample').
- שם החבילה: מוודאים ששם החבילה זהה למרחב השמות שבחרתם (לדוגמה, com.example.webgpuapp).
- שפה: בוחרים באפשרות Kotlin.
- גרסת ה-SDK המינימלית: בוחרים באפשרות API 24: Android 7.0 (Nougat) או בגרסה מתקדמת יותר, כמומלץ לספרייה הזו.
- שפת תצורת ה-build: מומלץ להשתמש ב-Kotlin DSL (build.gradle.kts) לניהול תלויות מודרני.
איור 2. התחלה עם פעילות ריקה סיום: לוחצים על סיום ומחכים ש-Android Studio יסנכרן את קובצי הפרויקט.
הוספת ספריית WebGPU Jetpack
- מוסיפים את מאגר
googleאלsettings.gradleכמו שמתואר במאמר שימוש בספריית Jetpack באפליקציה. - מוסיפים את יחסי התלות של הארטיפקטים שאתם צריכים לקובץ build.gradle של האפליקציה או המודול:
- הערה: כדי לראות את הגרסה האחרונה של הספרייה, אפשר לעיין במאמר webgpu | Jetpack | Android Developers
הספרייה androidx.webgpu מכילה את קובצי הספרייה WebGPU NDK .so וגם את הממשקים של הקוד המנוהל.
כדי לעדכן את גרסת הספרייה, צריך לעדכן את קובץ build.gradle ולסנכרן את הפרויקט עם קובצי Gradle באמצעות הלחצן Sync Project (סנכרון הפרויקט) ב-Android Studio.
ארכיטקטורה ברמה גבוהה
רינדור WebGPU באפליקציית Android מופעל בשרשור רינדור ייעודי כדי לשמור על היענות ממשק המשתמש.
- שכבת ממשק המשתמש: ממשק המשתמש בנוי באמצעות Jetpack פיתוח נייטיב. משטח ציור של WebGPU משולב בהיררכיית הפיתוח הנייטיב באמצעות
AndroidExternalSurface. - לוגיקת רינדור: מחלקה מיוחדת (למשל, WebGpuRenderer) אחראי לניהול כל אובייקטי WebGPU ולתיאום של לולאת הרינדור.
- שכבת ה-Shader: קוד Shader של WGSL שמאוחסן ב-res או בקבועי מחרוזת.
הוראות מפורטות: אפליקציה לדוגמה
בקטע הזה מפורטים השלבים החיוניים שנדרשים כדי לעבד משולש צבעוני על המסך, ומוצג בו תהליך העבודה הבסיסי של WebGPU.
הפעילות הראשית
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
WebGpuSurface()
}
}
}
הקומפוזיציה של המשטח החיצוני
יוצרים קובץ חדש בשם WebgpuSurface.kt. רכיב ה-Composable הזה עוטף את AndroidExternalSurface כדי לספק גשר בין Compose לבין רכיב הרינדור.
@Composable
fun WebGpuSurface(modifier: Modifier = Modifier) {
// Create and remember a WebGpuRenderer instance.
val renderer = remember { WebGpuRenderer() }
AndroidExternalSurface(
modifier = modifier.fillMaxSize(),
) {
// This block is called when the surface is created or resized.
onSurface { surface, width, height ->
// Run the rendering logic on a background thread.
withContext(Dispatchers.Default) {
try {
// Initialize the renderer with the surface
renderer.init(surface, width, height)
// Render a frame.
renderer.render()
} finally {
// Clean up resources when the surface is destroyed.
renderer.cleanup()
}
}
}
}
}
הגדרת רכיב ה-Renderer
יוצרים כיתה ב-WebGpuRendererWebGpuRenderer.kt. המחלקה הזו תטפל בעבודה המורכבת של התקשורת עם ה-GPU.
קודם כול, מגדירים את מבנה המחלקה ואת המשתנים:
class WebGpuRenderer() {
private lateinit var webGpu: WebGpu
private lateinit var renderPipeline: GPURenderPipeline
}
אתחול: לאחר מכן, מטמיעים את פונקציית האתחול כדי ליצור את מופע WebGPU ולהגדיר את המשטח. הפונקציה הזו נקראת על ידי היקף AndroidExternalSurface בתוך רכיב ה-Composable של הממשק החיצוני שיצרנו קודם.
הערה: הפונקציה init משתמשת ב-createWebGpu, שיטת עזר (חלק מ-androidx.webgpu.helper) כדי לייעל את ההגדרה. כלי השירות הזה יוצר את מופע WebGPU, בוחר מתאם ומבקש מכשיר.
// Inside WebGpuRenderer class
suspend fun init(surface: Surface, width: Int, height: Int) {
// 1. Create Instance & Device
webGpu = createWebGpu(surface)
val device = webGpu.device
// 2. Setup Pipeline (compile shaders)
initPipeline(device)
// 3. Configure the Surface
webGpu.webgpuSurface.configure(
GPUSurfaceConfiguration(
device,
width,
height,
TextureFormat.RGBA8Unorm,
)
)
}
ספריית androidx.webgpu כוללת קבצים מסוג JNI ו- .so, שמקושרים ומנוהלים באופן אוטומטי על ידי מערכת ה-build. שיטת העזר
createWebGpu מטפלת בטעינה של חבילת libwebgpu_c_bundled.so.
הגדרת צינור עיבוד הנתונים
עכשיו, אחרי שיש לנו מכשיר, אנחנו צריכים להגיד ל-GPU איך לצייר את המשולש. אנחנו עושים את זה על ידי יצירת 'צינור' שמכיל את קוד ה-shader שלנו (שנכתב ב-WGSL).
מוסיפים את פונקציית העזר הפרטית הזו למחלקה WebGpuRenderer כדי לקמפל את ה-shaders וליצור את צינור העיבוד.
// Inside WebGpuRenderer class
private fun initPipeline(device: GPUDevice) {
val shaderCode = """
@vertex fn vs_main(@builtin(vertex_index) vertexIndex : u32) ->
@builtin(position) vec4f {
const pos = array(vec2f(0.0, 0.5), vec2f(-0.5, -0.5), vec2f(0.5, -0.5));
return vec4f(pos[vertexIndex], 0, 1);
}
@fragment fn fs_main() -> @location(0) vec4f {
return vec4f(1, 0, 0, 1);
}
"""
// Create Shader Module
val shaderModule = device.createShaderModule(
GPUShaderModuleDescriptor(shaderSourceWGSL = GPUShaderSourceWGSL(shaderCode))
)
// Create Render Pipeline
renderPipeline = device.createRenderPipeline(
GPURenderPipelineDescriptor(
vertex = GPUVertexState(
shaderModule,
), fragment = GPUFragmentState(
shaderModule, targets = arrayOf(GPUColorTargetState(TextureFormat.RGBA8Unorm))
), primitive = GPUPrimitiveState(PrimitiveTopology.TriangleList)
)
)
}
ציור מסגרת
אחרי שהצינור מוכן, אפשר להטמיע את פונקציית הרינדור. הפונקציה הזו מקבלת את הטקסטורה הזמינה הבאה מהמסך, מתעדת פקודות ציור ושולחת אותן ל-GPU.
מוסיפים את השיטה הזו לכיתה WebGpuRenderer:
// Inside WebGpuRenderer class
fun render() {
if (!::webGpu.isInitialized) {
return
}
val gpu = webGpu
// 1. Get the next available texture from the screen
val surfaceTexture = gpu.webgpuSurface.getCurrentTexture()
// 2. Create a command encoder
val commandEncoder = gpu.device.createCommandEncoder()
// 3. Begin a render pass (clearing the screen to blue)
val renderPass = commandEncoder.beginRenderPass(
GPURenderPassDescriptor(
colorAttachments = arrayOf(
GPURenderPassColorAttachment(
GPUColor(0.0, 0.0, 0.5, 1.0),
surfaceTexture.texture.createView(),
loadOp = LoadOp.Clear,
storeOp = StoreOp.Store,
)
)
)
)
// 4. Draw
renderPass.setPipeline(renderPipeline)
renderPass.draw(3) // Draw 3 vertices
renderPass.end()
// 5. Submit and Present
gpu.device.queue.submit(arrayOf(commandEncoder.finish()))
gpu.webgpuSurface.present()
}
ניקוי משאבים
מטמיעים את פונקציית הניקוי, שמופעלת על ידי WebGpuSurface כשהמשטח נהרס.
// Inside WebGpuRenderer class
fun cleanup() {
if (::webGpu.isInitialized) {
webGpu.close()
}
}
פלט שעבר רינדור
דוגמה למבנה של אפליקציה
מומלץ להפריד את ההטמעה של הרינדור מהלוגיקה של ממשק המשתמש, כמו במבנה שבו נעשה שימוש באפליקציה לדוגמה:
app/src/main/
├── java/com/example/app/
│ ├── MainActivity.kt // Entry point
│ ├── WebGpuSurface.kt // Composable Surface
│ └── WebGpuRenderer.kt // Pure WebGPU logic
- MainActivity.kt: נקודת הכניסה לאפליקציה. הוא מגדיר את התוכן כ
WebGpuSurfaceניתן להרכבה. - WebGpuSurface.kt: הגדרת רכיב ממשק המשתמש באמצעות
[AndroidExternalSurface](/reference/kotlin/androidx/compose/foundation/package-summary#AndroidExternalSurface(androidx.compose.ui.Modifier,kotlin.Boolean,androidx.compose.ui.unit.IntSize,androidx.compose.foundation.AndroidExternalSurfaceZOrder,kotlin.Boolean,kotlin.Function1)). הוא מנהל את היקף מחזור החיים שלSurface, ומפעיל את רכיב ה-Renderer כשהמשטח מוכן ומנקה אותו כשהוא נהרס. - WebGpuRenderer.kt: מכיל את כל הלוגיקה הספציפית ל-WebGPU (יצירת מכשיר, הגדרת צינור). הוא מופרד מממשק המשתמש ומקבל רק את
[Surface](/reference/android/view/Surface.html)והממדים שהוא צריך כדי לצייר.
ניהול מחזור החיים והמשאבים
ניהול מחזור החיים מתבצע על ידי היקף שגרות ההמשך של Kotlin שסופק על ידי [AndroidExternalSurface](/reference/kotlin/androidx/compose/foundation/package-summary#AndroidExternalSurface(androidx.compose.ui.Modifier,kotlin.Boolean,androidx.compose.ui.unit.IntSize,androidx.compose.foundation.AndroidExternalSurfaceZOrder,kotlin.Boolean,kotlin.Function1)) ב-Jetpack Compose.
- יצירת משטח: מפעילים את ההגדרה של
DeviceושלSurfaceבתחילת בלוק ה-lambda שלonSurface. הקוד הזה מופעל באופן מיידי כשהרכיבSurfaceהופך לזמין. - השמדה של Surface: כשהמשתמש יוצא מהדף או כשבלוק ה-lambda של
Surfaceמושמד על ידי המערכת, הפעולה מבוטלת. מתבצעת חסימה שלfinally, ומופעלת קריאה ל-renderer.cleanup()כדי למנוע דליפות זיכרון. - שינוי גודל: אם המידות של משטח התצוגה משתנות, יכול להיות ש-
AndroidExternalSurfaceיפעיל מחדש את הבלוק או יטפל ישירות בעדכונים, בהתאם להגדרה, כך שהרכיב לעיבוד תמיד יכתוב למאגר תקף.
ניפוי באגים ואימות
ל-WebGPU יש מנגנונים שנועדו לאמת את מבני הקלט ולתעד שגיאות בזמן ריצה.
- Logcat: שגיאות האימות מודפסות ב-Android Logcat.
- היקפי שגיאה: אפשר לתעד שגיאות ספציפיות על ידי הוספת פקודות GPU בתוך בלוקים של
[device.pushErrorScope()](/reference/kotlin/androidx/webgpu/GPUDevice#pushErrorScope(kotlin.Int))ושל device.popErrorScope().
device.pushErrorScope(ErrorFilter.Validation)
// ... potentially incorrect code ...
device.popErrorScope { status, type, message ->
if (status == PopErrorScopeStatus.Success && type != ErrorType.NoError) {
Log.e("WebGPU", "Validation Error: $message")
}
}
טיפים לשיפור הביצועים
כשמבצעים תכנות ב-WebGPU, חשוב להביא בחשבון את הנקודות הבאות כדי להימנע מנקודות צוואר בקבוק בביצועים:
- מומלץ להימנע מיצירת אובייקטים לכל פריים: כדאי ליצור מופעים של צינורות עיבוד נתונים (
GPURenderPipeline), פריסות של קבוצות כבילה ומודולים של Shader פעם אחת במהלך הגדרת האפליקציה כדי למקסם את השימוש החוזר. - אופטימיזציה של השימוש ב-Buffer: עדכון התוכן של
GPUBuffersקיים באמצעותGPUQueue.writeBufferבמקום ליצור מאגרי Buffer חדשים בכל פריים. - צמצום שינויי המצב: קיבוץ קריאות לציור שמשתפות את אותו צינור וקשירת קבוצות כדי לצמצם את התקורה של מנהל ההתקן ולשפר את יעילות העיבוד.