Aby korzystać z Jetpack WebGPU, Twój projekt musi spełniać te minimalne wymagania:
- Minimalny poziom interfejsu API: wymagany jest Android API 24 (Nougat) lub nowszy.
- Sprzęt: preferowane są urządzenia obsługujące interfejs Vulkan w wersji 1.1 lub nowszej.
- Tryb zgodności i obsługa OpenGL ES: korzystanie z WebGPU w trybie zgodności jest możliwe po ustawieniu standardowej opcji
featureLevelnacompatibilitypodczas wysyłania żądaniaGPUAdapter.
// Example of requesting an adapter with "compatibility" mode enabled:
val adapter = instance.requestAdapter(
GPURequestAdapterOptions(featureLevel = FeatureLevel.Compatibility))
Instalacja i konfiguracja
Wymagania wstępne:
Android Studio: pobierz najnowszą wersję Androida Studio z oficjalnej strony i postępuj zgodnie z instrukcjami podanymi w przewodniku instalacji Androida Studio.
Tworzenie nowego projektu
Po zainstalowaniu Androida Studio wykonaj te czynności, aby skonfigurować projekt WebGPU:
- Rozpocznij nowy projekt: otwórz Android Studio i kliknij Nowy projekt.
Wybierz szablon: w Android Studio wybierz szablon Empty Activity i kliknij Next (Dalej).
Rysunek 1. Tworzenie nowego projektu w Android Studio Skonfiguruj projekt:
- Nazwa: nadaj projektowi nazwę (np. „JetpackWebGPUSample”).
- Nazwa pakietu: sprawdź, czy nazwa pakietu jest zgodna z wybraną przestrzenią nazw (np. com.example.webgpuapp).
- Język: wybierz Kotlin.
- Minimalny pakiet SDK: wybierz API 24: Android 7.0 (Nougat) lub nowszy, zgodnie z zaleceniami dotyczącymi tej biblioteki.
- Język konfiguracji kompilacji: do nowoczesnego zarządzania zależnościami zalecamy używanie Kotlin DSL (build.gradle.kts).
Rysunek 2. Rozpoczynanie od pustej aktywności Zakończ: kliknij Zakończ i poczekaj, aż Android Studio zsynchronizuje pliki projektu.
Dodawanie biblioteki Jetpack WebGPU
- Dodaj repozytorium
googledosettings.gradlezgodnie z opisem w artykule Używanie biblioteki Jetpack w aplikacji. - Dodaj zależności dla potrzebnych artefaktów w pliku build.gradle aplikacji lub modułu:
- Uwaga: najnowszą wersję biblioteki znajdziesz na stronie webgpu | Jetpack | Android Developers.
Biblioteka androidx.webgpu zawiera pliki biblioteki WebGPU NDK .so oraz interfejsy kodu zarządzanego.
Wersję biblioteki możesz zaktualizować, modyfikując plik build.gradle i synchronizując projekt z plikami Gradle za pomocą przycisku „Synchronizuj projekt” w Android Studio.
Architektura wysokiego poziomu
Renderowanie WebGPU w aplikacji na Androida odbywa się w dedykowanym wątku renderowania, aby zachować responsywność interfejsu.
- Warstwa interfejsu: interfejs jest zbudowany za pomocą Jetpack Compose. Powierzchnia rysowania WebGPU jest zintegrowana z hierarchią Compose za pomocą funkcji
AndroidExternalSurface. - Logika renderowania: wyspecjalizowana klasa (np. WebGpuRenderer) odpowiada za zarządzanie wszystkimi obiektami WebGPU i koordynowanie pętli renderowania.
- Warstwa shadera: kod shadera WGSL przechowywany w stałych wartościach res lub string.
Krok po kroku: przykładowa aplikacja
W tej sekcji znajdziesz podstawowe kroki wymagane do wyrenderowania kolorowego trójkąta na ekranie, co pokazuje podstawowy przepływ pracy WebGPU.
Główne działanie
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
WebGpuSurface()
}
}
}
Kompozycja powierzchni zewnętrznej
Utwórz nowy plik o nazwie WebgpuSurface.kt. Ten komponent kompozycyjny otacza AndroidExternalSurface, aby zapewnić połączenie między Compose a renderem.
@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()
}
}
}
}
}
Konfigurowanie renderera
Utwórz zajęcia WebGpuRenderer w WebGpuRenderer.kt. Ta klasa będzie odpowiadać za komunikację z procesorem graficznym.
Najpierw zdefiniuj strukturę klasy i zmienne:
class WebGpuRenderer() {
private lateinit var webGpu: WebGpu
private lateinit var renderPipeline: GPURenderPipeline
}
Inicjowanie: następnie zaimplementuj funkcję init, aby utworzyć instancję WebGPU i skonfigurować powierzchnię. Ta funkcja jest wywoływana przez zakres AndroidExternalSurface w kompozycyjnym elemencie zewnętrznym, który został utworzony wcześniej.
Uwaga: funkcja init używa
createWebGpu,
metody pomocniczej (części
androidx.webgpu.helper), aby uprościć konfigurację. To narzędzie tworzy instancję WebGPU, wybiera adapter i wysyła żądanie urządzenia.
// 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 Biblioteka zawiera pliki JNI i .so, które są automatycznie łączone i zarządzane przez system kompilacji. Metoda pomocnicza
createWebGpu odpowiada za wczytywanie pakietu libwebgpu_c_bundled.so.
Konfiguracja potoku
Skoro mamy już urządzenie, musimy poinformować procesor graficzny, jak narysować trójkąt. W tym celu tworzymy „potok” zawierający kod shadera (napisany w języku WGSL).
Dodaj tę prywatną funkcję pomocniczą do klasy WebGpuRenderer, aby skompilować shadery i utworzyć potok renderowania.
// 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)
)
)
}
Rysowanie ramki
Gdy potok będzie gotowy, możemy wdrożyć funkcję renderowania. Ta funkcja pobiera następną dostępną teksturę z ekranu, rejestruje polecenia rysowania i przesyła je do procesora graficznego.
Dodaj tę metodę do klasy 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()
}
Czyszczenie zasobów
Zaimplementuj funkcję czyszczenia, która jest wywoływana przez WebGpuSurface, gdy powierzchnia zostanie zniszczona.
// Inside WebGpuRenderer class
fun cleanup() {
if (::webGpu.isInitialized) {
webGpu.close()
}
}
Wyrenderowane dane wyjściowe
Struktura przykładowej aplikacji
Warto oddzielić implementację renderowania od logiki interfejsu, tak jak w strukturze używanej przez przykładową aplikację:
app/src/main/
├── java/com/example/app/
│ ├── MainActivity.kt // Entry point
│ ├── WebGpuSurface.kt // Composable Surface
│ └── WebGpuRenderer.kt // Pure WebGPU logic
- MainActivity.kt: punkt wejścia aplikacji. Ustawia treść na
WebGpuSurfaceComposable. - WebGpuSurface.kt: definiuje komponent interfejsu za pomocą
[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)). ZarządzaSurfacezakresem cyklu życia, inicjując moduł renderujący, gdy powierzchnia jest gotowa, i czyszcząc go, gdy zostanie zniszczony. - WebGpuRenderer.kt: zawiera całą logikę związaną z WebGPU (tworzenie urządzenia, konfiguracja potoku). Jest on odłączony od interfejsu i otrzymuje tylko
[Surface](/reference/android/view/Surface.html)i wymiary potrzebne do rysowania.
Zarządzanie cyklem życia i zasobami
Zarządzanie cyklem życia jest obsługiwane przez zakres Kotlin Coroutine udostępniany przez [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)) w Jetpack Compose.
- Tworzenie powierzchni: zainicjuj konfigurację
DeviceiSurfacena początku bloku lambdaonSurface. Ten kod zostanie uruchomiony natychmiast po udostępnieniuSurface. - Zniszczenie powierzchni: gdy użytkownik przejdzie na inną stronę lub
Surfacezostanie zniszczony przez system, blok lambda zostanie anulowany. Wykonuje się blokfinally, wywołującrenderer.cleanup(), aby zapobiec wyciekom pamięci. - Zmiana rozmiaru: jeśli zmienią się wymiary powierzchni,
AndroidExternalSurfacemoże ponownie uruchomić blok lub bezpośrednio obsłużyć aktualizacje w zależności od konfiguracji, dzięki czemu moduł renderujący zawsze zapisuje dane w prawidłowym buforze.
Debugowanie i weryfikacja
WebGPU ma mechanizmy zaprojektowane do weryfikowania struktur wejściowych i wykrywania błędów w czasie działania.
- Logcat: błędy weryfikacji są drukowane w Android Logcat.
- Zakresy błędów: możesz przechwytywać konkretne błędy, umieszczając polecenia GPU w blokach
[device.pushErrorScope()](/reference/kotlin/androidx/webgpu/GPUDevice#pushErrorScope(kotlin.Int))i 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")
}
}
Wskazówki dotyczące skuteczności
Podczas programowania w WebGPU pamiętaj o tych kwestiach, aby uniknąć wąskich gardeł wydajności:
- Unikaj tworzenia obiektów w każdej klatce: instancje potoków (
GPURenderPipeline), układy grup wiązań i moduły cieniowania twórz tylko raz podczas konfiguracji aplikacji, aby zmaksymalizować ich ponowne wykorzystanie. - Optymalizacja wykorzystania bufora: aktualizuj zawartość istniejących buforów za pomocą funkcji
GPUQueue.writeBufferzamiast tworzyć nowe bufory w każdej klatce.GPUBuffers - Minimalizuj zmiany stanu: grupuj wywołania rysowania, które korzystają z tego samego potoku i grup wiązań, aby zminimalizować obciążenie sterownika i zwiększyć wydajność renderowania.