لاستخدام Jetpack WebGPU، يجب أن يستوفي مشروعك الحد الأدنى من المتطلبات التالية:
- الحد الأدنى لمستوى واجهة برمجة التطبيقات: يجب أن يكون الإصدار 24 من واجهة برمجة تطبيقات Android (Nougat) أو إصدارًا أحدث.
- الأجهزة: يُفضّل استخدام الأجهزة المتوافقة مع الإصدار 1.1 من Vulkan أو الإصدارات الأحدث في الخلفية.
- وضع التوافق ودعم OpenGL ES: يمكن استخدام WebGPU مع وضع التوافق من خلال ضبط الخيار
featureLevelالمعياري علىcompatibilityعند طلبGPUAdapter.
// Example of requesting an adapter with "compatibility" mode enabled:
val adapter = instance.requestAdapter(
GPURequestAdapterOptions(featureLevel = FeatureLevel.Compatibility))
التثبيت والإعداد
المتطلَّبات الأساسية:
استوديو Android: نزِّل أحدث إصدار من "استوديو Android" من الموقع الإلكتروني الرسمي واتّبِع التعليمات الواردة في دليل تثبيت "استوديو Android".
خطوات إنشاء مشروع جديد
بعد تثبيت Android Studio، اتّبِع الخطوات التالية لإعداد مشروع WebGPU:
- بدء مشروع جديد: افتح "استوديو Android" وانقر على مشروع جديد.
اختيار نموذج: اختَر نموذج نشاط فارغ في Android Studio وانقر على التالي.
الشكل 1.إنشاء مشروع جديد في "استوديو Android" إعداد مشروعك:
- الاسم: أدخِل اسمًا لمشروعك (مثلاً "JetpackWebGPUSample").
- اسم الحزمة: تأكَّد من أنّ اسم الحزمة يتطابق مع مساحة الاسم التي اخترتها (مثل com.example.webgpuapp).
- اللغة: اختَر Kotlin.
- الحد الأدنى من حزمة تطوير البرامج (SDK): اختَر المستوى 24 من واجهة برمجة التطبيقات: الإصدار 7.0 من نظام التشغيل Android (Nougat) أو إصدارًا أحدث، كما يُنصح بذلك لهذه المكتبة.
- لغة إعدادات الإصدار: يُنصح باستخدام Kotlin DSL (build.gradle.kts) لإدارة الاعتماديات الحديثة.
الشكل 2.البدء بنشاط فارغ إنهاء: انقر على إنهاء وانتظِر إلى أن ينهي "استوديو Android" مزامنة ملفات مشروعك.
إضافة مكتبة WebGPU Jetpack
- أضِف مستودع
googleإلىsettings.gradleكما هو موضّح في استخدام مكتبة Jetpack في تطبيقك - أضِف الاعتماديات الخاصة بالعناصر التي تحتاج إليها في ملف build.gradle لتطبيقك أو وحدتك:
- ملاحظة: يمكنك الاطّلاع على webgpu | Jetpack | مطوّرو تطبيقات Android لمعرفة أحدث إصدار من المكتبة.
تحتوي مكتبة
androidx.webgpu
على ملفات مكتبة WebGPU NDK بتنسيق .so بالإضافة إلى واجهات الرمز المُدار.
يمكنك تعديل إصدار المكتبة من خلال تعديل ملف build.gradle ومزامنة مشروعك مع ملفات Gradle باستخدام الزر مزامنة المشروع في"استوديو Android".
التصميم العام
يتم تشغيل عملية عرض WebGPU داخل تطبيق Android على سلسلة محادثات عرض مخصّصة للحفاظ على سرعة استجابة واجهة المستخدم.
- طبقة واجهة المستخدم: تم إنشاء واجهة المستخدم باستخدام Jetpack Compose. يتم دمج مساحة رسم WebGPU
في التسلسل الهرمي لـ Compose باستخدام
AndroidExternalSurface. - منطق العرض: فئة متخصّصة (مثل WebGpuRenderer) هو المسؤول عن إدارة جميع عناصر WebGPU وتنسيق حلقة العرض.
- طبقة Shader: رمز Shader WGSL المخزَّن في ثوابت res أو السلسلة.
الخطوات بالتفصيل: نموذج التطبيق
يوضّح هذا القسم الخطوات الأساسية المطلوبة لعرض مثلث ملوّن على الشاشة، ما يوضّح سير عمل WebGPU الأساسي.
النشاط الرئيسي
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
WebGpuSurface()
}
}
}
The external surface Composable
أنشئ ملفًا جديدًا باسم WebgpuSurface.kt. يغلّف هذا العنصر القابل للإنشاء
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()
}
}
}
}
}
إعداد أداة العرض
أنشئ WebGpuRenderer صفًا في WebGpuRenderer.kt. ستتولّى هذه الفئة مهمة التواصل مع وحدة معالجة الرسومات.
أولاً، حدِّد بنية الفئة والمتغيرات:
class WebGpuRenderer() {
private lateinit var webGpu: WebGpu
private lateinit var renderPipeline: GPURenderPipeline
}
الإعداد: بعد ذلك، نفِّذ الدالة init لإنشاء مثيل WebGPU
وإعداد السطح. يتم استدعاء هذه الدالة من خلال النطاق AndroidExternalSurface داخل العنصر القابل للإنشاء الخارجي الذي أنشأناه سابقًا.
ملاحظة: تستخدم الدالة 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 التي يتم ربطها وإدارتها تلقائيًا من خلال نظام التصميم. تتولّى طريقة المساعدة
createWebGpu تحميل libwebgpu_c_bundled.so المجمَّع.
إعداد مسار الإجراءات
بعد أن أصبح لدينا جهاز، علينا أن نوضّح لوحدة معالجة الرسومات كيفية رسم المثلث. ونفعل ذلك من خلال إنشاء "مسار" يحتوي على رمز التظليل (المكتوب بلغة WGSL).
أضِف دالة المساعد الخاص هذه إلى فئة WebGpuRenderer لتجميع برامج التظليل وإنشاء مسار العرض.
// 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)
)
)
}
رسم إطار
بعد أن أصبح خط أنابيب العرض جاهزًا، يمكننا الآن تنفيذ دالة العرض. تسترد هذه الدالة النسيج التالي المتاح من الشاشة، وتسجّل أوامر الرسم، ثم ترسلها إلى وحدة معالجة الرسومات.
أضِف الطريقة التالية إلى صف 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: نقطة دخول التطبيق يتم ضبط المحتوى على
WebGpuSurfaceComposable. - 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، ويتم تهيئة أداة العرض عند توفّر مساحة العرض، ويتم تنظيفها عند إيقافها. - WebGpuRenderer.kt: يحتوي على جميع منطق WebGPU (إنشاء الجهاز، وإعداد خطوط الأنابيب). وهي منفصلة عن واجهة المستخدم، ولا تتلقّى سوى
[Surface](/reference/android/view/Surface.html)والسمات التي تحتاج إليها للرسم.
إدارة مراحل النشاط والموارد
تتم إدارة مراحل النشاط من خلال نطاق Kotlin Coroutine الذي يوفّره
[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في بداية كتلة lambdaonSurface. يتم تنفيذ هذا الرمز فور توفّرSurface. - إيقاف العرض: عند خروج المستخدم من الصفحة أو إيقاف النظام
Surface، يتم إلغاء رمز lambda البرمجي. يتم تنفيذ عملية حظر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) وتنسيقات مجموعات الربط ووحدات تظليل مرة واحدة أثناء إعداد التطبيق لتحقيق أقصى استفادة من إعادة الاستخدام. - تحسين استخدام المخزن المؤقت: عدِّل محتوى
GPUBuffersالحالي باستخدامGPUQueue.writeBufferبدلاً من إنشاء مخازن مؤقتة جديدة لكل إطار. - تقليل تغييرات الحالة: يمكنك تجميع طلبات الرسم التي تتشارك في خط الأنابيب ومجموعات الربط نفسها لتقليل الحمل الزائد على برنامج التشغيل وتحسين كفاءة العرض.