Начало работы с WebGPU

Для использования Jetpack WebGPU ваш проект должен соответствовать следующим минимальным требованиям:

  • Минимальный уровень API : требуется Android API 24 (Nougat) или выше.
  • Аппаратное обеспечение : Для бэкэнда предпочтительны устройства, поддерживающие Vulkan 1.1+.
  • Режим совместимости и поддержка OpenGL ES : Использование WebGPU в режиме совместимости возможно путем установки параметра standardized 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:

  1. Чтобы начать новый проект , откройте Android Studio и нажмите «Новый проект» .
  2. Выберите шаблон : выберите шаблон «Пустая активность» в Android Studio и нажмите «Далее» .

    Диалоговое окно «Новый проект» в Android Studio, отображающее встроенный список действий, которые Studio создаст от вашего имени.
    Рисунок 1. Создание нового проекта в Android Studio.
  3. Настройте свой проект :

    • Название : Дайте вашему проекту название (например, "JetpackWebGPUSample").
    • Имя пакета : Убедитесь, что имя пакета соответствует выбранному вами пространству имен (например, com.example.webgpuapp).
    • Язык : Выберите Kotlin .
    • Минимальные требования к SDK : выберите API 24: Android 7.0 (Nougat) или выше, как рекомендуется для этой библиотеки.
    • Язык конфигурации сборки : Для современного управления зависимостями рекомендуется использовать Kotlin DSL (build.gradle.kts) .
    Диалоговое окно «Пустая активность» в Android Studio, содержащее поля для заполнения новой пустой активности, такие как «Имя», «Имя пакета», «Место сохранения» и «Минимальный SDK».
    Рисунок 2. Начало с пустой активности.
  4. Завершение : Нажмите «Завершить» и дождитесь синхронизации файлов проекта в Android Studio.

Добавить библиотеку WebGPU Jetpack

Библиотека androidx.webgpu содержит файлы библиотеки WebGPU NDK .so, а также интерфейсы управляемого кода.

Вы можете обновить версию библиотеки, обновив файл build.gradle и синхронизировав проект с файлами gradle с помощью кнопки "Синхронизировать проект" в Android Studio.

Архитектура высокого уровня

В приложениях Android рендеринг с использованием WebGPU выполняется в выделенном потоке для обеспечения отзывчивости пользовательского интерфейса.

  • Слой пользовательского интерфейса : Пользовательский интерфейс создан с помощью Jetpack Compose. В иерархию Compose интегрирована поверхность для отрисовки WebGPU с использованием AndroidExternalSurface .
  • Логика рендеринга : специализированный класс (например, WebGpuRenderer) отвечает за управление всеми объектами WebGPU и координацию цикла рендеринга.
  • Шейдерный слой : код шейдера WGSL, хранящийся в константах типа res или string.
Схема архитектуры высокого уровня, демонстрирующая взаимодействие между потоком пользовательского интерфейса (UI Thread), выделенным потоком рендеринга (Rendering Thread) и аппаратным обеспечением графического процессора (GPU) в приложении WebGPU для Android.
Рисунок 3. Высокоуровневая архитектура WebGPU на Android.

Пошаговое руководство: пример приложения

В этом разделе подробно описаны основные шаги, необходимые для отображения цветного треугольника на экране, и продемонстрирован базовый рабочий процесс 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()
                }
            }
        }
    }
}

Настройте рендерер

Создайте класс WebGpuRenderer в WebGpuRenderer.kt . Этот класс возьмет на себя основную работу по взаимодействию с графическим процессором.

Сначала определите структуру класса и переменные:

class WebGpuRenderer() {
    private lateinit var webGpu: WebGpu
    private lateinit var renderPipeline: GPURenderPipeline
}

Инициализация: Далее реализуйте функцию init для создания экземпляра WebGPU и настройки поверхности. Эта функция вызывается из области видимости AndroidExternalSurface внутри созданного ранее внешнего составного объекта поверхности.

Примечание: Функция инициализации использует метод 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()
    }
  }

Отрендеренный результат

Скриншот экрана телефона Android, отображающий результат работы приложения WebGPU: сплошной красный треугольник по центру на темно-синем фоне.
Рисунок 4. Результат рендеринга тестового приложения WebGPU, на котором показан красный треугольник.

Пример структуры приложения

Рекомендуется разделять реализацию отрисовки от логики пользовательского интерфейса, как это показано в примере приложения:

app/src/main/
├── java/com/example/app/
│   ├── MainActivity.kt       // Entry point
│   ├── WebGpuSurface.kt      // Composable Surface
│   └── WebGpuRenderer.kt     // Pure WebGPU logic
  • MainActivity.kt : Точка входа в приложение. Она устанавливает содержимое для объекта WebGpuSurface Composable.
  • 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.

  • Создание Surface : Инициализируйте конфигурацию Device и Surface в начале блока лямбда-функции onSurface . Этот код выполняется немедленно, как только Surface становится доступным.
  • Уничтожение поверхности : Когда пользователь переходит на другую страницу или 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 ), привязывайте компоновку групп и модули шейдеров один раз во время настройки приложения, чтобы максимально увеличить повторное использование.
  • Оптимизация использования буферов : обновляйте содержимое существующих GPUBuffers с помощью GPUQueue.writeBuffer вместо создания новых буферов для каждого кадра.
  • Минимизация изменений состояния : группируйте вызовы отрисовки, использующие один и тот же конвейер, и связывайте группы, чтобы минимизировать накладные расходы драйвера и повысить эффективность рендеринга.