Support for WebGPU backend in Android SDK (#4097)
This commit is contained in:
122
.github/workflows/android-ci.yml
vendored
122
.github/workflows/android-ci.yml
vendored
@@ -311,6 +311,124 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
flavor: ${{ matrix.flavor }}
|
flavor: ${{ matrix.flavor }}
|
||||||
|
|
||||||
|
android-build-webgpu-dawn:
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
needs:
|
||||||
|
- pre_job
|
||||||
|
if: needs.pre_job.outputs.should_skip != 'true'
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: platform/android
|
||||||
|
env:
|
||||||
|
IS_LOCAL_DEVELOPMENT: false
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- uses: ./.github/actions/setup-android-ci
|
||||||
|
|
||||||
|
- name: Configure AWS Credentials
|
||||||
|
if: vars.OIDC_AWS_ROLE_TO_ASSUME
|
||||||
|
uses: aws-actions/configure-aws-credentials@61815dcd50bd041e203e49132bacad1fd04d2708 # v4
|
||||||
|
with:
|
||||||
|
aws-region: us-west-2
|
||||||
|
role-to-assume: ${{ vars.OIDC_AWS_ROLE_TO_ASSUME }}
|
||||||
|
role-session-name: ${{ github.run_id }}
|
||||||
|
|
||||||
|
- name: Configure sccache
|
||||||
|
run: |
|
||||||
|
echo SCCACHE_BUCKET=maplibre-native-sccache >> "$GITHUB_ENV"
|
||||||
|
echo SCCACHE_REGION=eu-central-1 >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
if [ -z "${AWS_SECRET_ACCESS_KEY}" ]; then
|
||||||
|
echo "AWS_SECRET_ACCESS_KEY not set; not uploading sccache cache to S3"
|
||||||
|
echo SCCACHE_S3_NO_CREDENTIALS=1 >> "$GITHUB_ENV"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Build WebGPU Dawn library for arm64-v8a
|
||||||
|
run: ./gradlew :MapLibreAndroid:assembleWebgpuDawnDebug -Pmaplibre.abis=arm64-v8a
|
||||||
|
|
||||||
|
- name: Show sccache stats
|
||||||
|
run: sccache --show-stats
|
||||||
|
|
||||||
|
android-build-webgpu-wgpu:
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
needs:
|
||||||
|
- pre_job
|
||||||
|
if: needs.pre_job.outputs.should_skip != 'true'
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: platform/android
|
||||||
|
env:
|
||||||
|
IS_LOCAL_DEVELOPMENT: false
|
||||||
|
ANDROID_ARCH: arm64-v8a
|
||||||
|
RUST_TARGET: aarch64-linux-android
|
||||||
|
C_COMPILER: aarch64-linux-android26-clang
|
||||||
|
BINDGEN_TARGET: aarch64-linux-android26
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- uses: ./.github/actions/setup-android-ci
|
||||||
|
|
||||||
|
- name: Install Rust toolchain
|
||||||
|
uses: dtolnay/rust-toolchain@5d458579430fc14a04a08a1e7d3694f545e91ce6 # stable
|
||||||
|
with:
|
||||||
|
toolchain: stable
|
||||||
|
|
||||||
|
- name: Add Android Rust target
|
||||||
|
run: rustup target add ${{ env.RUST_TARGET }}
|
||||||
|
|
||||||
|
- name: Configure cargo for Android NDK linker
|
||||||
|
run: |
|
||||||
|
LATEST_NDK=$(find "$ANDROID_SDK_ROOT/ndk" -mindepth 1 -maxdepth 1 -type d | sort -V | tail -n 1)
|
||||||
|
NDK_BIN="$LATEST_NDK/toolchains/llvm/prebuilt/linux-x86_64/bin"
|
||||||
|
mkdir -p ../../vendor/wgpu-native/.cargo
|
||||||
|
cat > ../../vendor/wgpu-native/.cargo/config.toml << EOF
|
||||||
|
[target.${{ env.RUST_TARGET }}]
|
||||||
|
linker = "$NDK_BIN/${{ env.C_COMPILER }}"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
- name: Pre-build wgpu-native for Android
|
||||||
|
working-directory: vendor/wgpu-native
|
||||||
|
run: |
|
||||||
|
LATEST_NDK=$(find "$ANDROID_SDK_ROOT/ndk" -mindepth 1 -maxdepth 1 -type d | sort -V | tail -n 1)
|
||||||
|
NDK_PREBUILT="$LATEST_NDK/toolchains/llvm/prebuilt/linux-x86_64"
|
||||||
|
RESOURCE_DIR=$("$NDK_PREBUILT/bin/clang" --print-resource-dir)
|
||||||
|
BINDGEN_EXTRA_CLANG_ARGS=$(echo "${{ env.RUST_TARGET }}" | tr '-' '_')
|
||||||
|
export BINDGEN_EXTRA_CLANG_ARGS
|
||||||
|
ENV_VAR="BINDGEN_EXTRA_CLANG_ARGS_$BINDGEN_EXTRA_CLANG_ARGS"
|
||||||
|
export "${ENV_VAR}=--sysroot=$NDK_PREBUILT/sysroot --target=${{ env.BINDGEN_TARGET }} -isystem $RESOURCE_DIR/include"
|
||||||
|
cargo build --release --target ${{ env.RUST_TARGET }} --no-default-features --features vulkan,wgsl,spirv,glsl
|
||||||
|
|
||||||
|
- name: Configure AWS Credentials
|
||||||
|
if: vars.OIDC_AWS_ROLE_TO_ASSUME
|
||||||
|
uses: aws-actions/configure-aws-credentials@61815dcd50bd041e203e49132bacad1fd04d2708 # v4
|
||||||
|
with:
|
||||||
|
aws-region: us-west-2
|
||||||
|
role-to-assume: ${{ vars.OIDC_AWS_ROLE_TO_ASSUME }}
|
||||||
|
role-session-name: ${{ github.run_id }}
|
||||||
|
|
||||||
|
- name: Configure sccache
|
||||||
|
run: |
|
||||||
|
echo SCCACHE_BUCKET=maplibre-native-sccache >> "$GITHUB_ENV"
|
||||||
|
echo SCCACHE_REGION=eu-central-1 >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
if [ -z "${AWS_SECRET_ACCESS_KEY}" ]; then
|
||||||
|
echo "AWS_SECRET_ACCESS_KEY not set; not uploading sccache cache to S3"
|
||||||
|
echo SCCACHE_S3_NO_CREDENTIALS=1 >> "$GITHUB_ENV"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Build WebGPU wgpu-native library for Android
|
||||||
|
run: ./gradlew :MapLibreAndroid:assembleWebgpuWgpuDebug -Pmaplibre.abis=${{ env.ANDROID_ARCH }}
|
||||||
|
|
||||||
|
- name: Show sccache stats
|
||||||
|
run: sccache --show-stats
|
||||||
|
|
||||||
android-ci-result:
|
android-ci-result:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
@@ -322,9 +440,11 @@ jobs:
|
|||||||
- android-build
|
- android-build
|
||||||
- android-build-cpp-test
|
- android-build-cpp-test
|
||||||
- android-build-render-test
|
- android-build-render-test
|
||||||
|
- android-build-webgpu-dawn
|
||||||
|
- android-build-webgpu-wgpu
|
||||||
steps:
|
steps:
|
||||||
- name: Set success=true
|
- name: Set success=true
|
||||||
if: needs.android-build.result == 'success' && needs.android-build-cpp-test.result == 'success' && needs.android-build-render-test.result == 'success'
|
if: needs.android-build.result == 'success' && needs.android-build-cpp-test.result == 'success' && needs.android-build-render-test.result == 'success' && needs.android-build-webgpu-dawn.result == 'success' && needs.android-build-webgpu-wgpu.result == 'success'
|
||||||
run: echo success=true > "$GITHUB_ENV"
|
run: echo success=true > "$GITHUB_ENV"
|
||||||
|
|
||||||
- name: Mark result as failed
|
- name: Mark result as failed
|
||||||
|
|||||||
@@ -185,6 +185,36 @@
|
|||||||
"MLN_WITH_X11": "ON"
|
"MLN_WITH_X11": "ON"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "android-webgpu-dawn",
|
||||||
|
"displayName": "Android WebGPU (Dawn)",
|
||||||
|
"generator": "Ninja",
|
||||||
|
"binaryDir": "${sourceDir}/build-android-webgpu-dawn",
|
||||||
|
"cacheVariables": {
|
||||||
|
"CMAKE_TOOLCHAIN_FILE": "$env{ANDROID_NDK}/build/cmake/android.toolchain.cmake",
|
||||||
|
"CMAKE_ANDROID_NDK": "$env{ANDROID_NDK}",
|
||||||
|
"ANDROID_ABI": "arm64-v8a",
|
||||||
|
"ANDROID_PLATFORM": "android-26",
|
||||||
|
"CMAKE_BUILD_TYPE": "RelWithDebInfo",
|
||||||
|
"MLN_WITH_WEBGPU": "ON",
|
||||||
|
"MLN_WEBGPU_IMPL_DAWN": "ON"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "android-webgpu-wgpu",
|
||||||
|
"displayName": "Android WebGPU (wgpu-native)",
|
||||||
|
"generator": "Ninja",
|
||||||
|
"binaryDir": "${sourceDir}/build-android-webgpu-wgpu",
|
||||||
|
"cacheVariables": {
|
||||||
|
"CMAKE_TOOLCHAIN_FILE": "$env{ANDROID_NDK}/build/cmake/android.toolchain.cmake",
|
||||||
|
"CMAKE_ANDROID_NDK": "$env{ANDROID_NDK}",
|
||||||
|
"ANDROID_ABI": "arm64-v8a",
|
||||||
|
"ANDROID_PLATFORM": "android-26",
|
||||||
|
"CMAKE_BUILD_TYPE": "RelWithDebInfo",
|
||||||
|
"MLN_WITH_WEBGPU": "ON",
|
||||||
|
"MLN_WEBGPU_IMPL_WGPU": "ON"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "linux-opengl-node",
|
"name": "linux-opengl-node",
|
||||||
"inherits": "linux",
|
"inherits": "linux",
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
- [Documentation](./platforms/android/android-documentation.md)
|
- [Documentation](./platforms/android/android-documentation.md)
|
||||||
- [Benchmark](./platforms/android/benchmark.md)
|
- [Benchmark](./platforms/android/benchmark.md)
|
||||||
- [Release](./platforms/android/release.md)
|
- [Release](./platforms/android/release.md)
|
||||||
|
- [Rendering Backends](./platforms/android/android-rendering-backends.md)
|
||||||
|
- [WebGPU](./platforms/android/android-webgpu.md)
|
||||||
- [iOS](./platforms/ios/README.md)
|
- [iOS](./platforms/ios/README.md)
|
||||||
- [Tests](./platforms/ios/ios-tests.md)
|
- [Tests](./platforms/ios/ios-tests.md)
|
||||||
- [Documentation](./platforms/ios/ios-documentation.md)
|
- [Documentation](./platforms/ios/ios-documentation.md)
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
# Rendering Backends
|
||||||
|
|
||||||
|
The rendering backend is selected at build time through Gradle product flavors.
|
||||||
|
|
||||||
|
## Available backends
|
||||||
|
|
||||||
|
| Flavor | Backend | Status | Notes |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `opengl` | OpenGL ES | Stable | Default, widest device compatibility |
|
||||||
|
| `vulkan` | Vulkan | Stable | Better performance on modern devices |
|
||||||
|
| `webgpuDawn` | WebGPU (Dawn) | Experimental | Uses Vulkan internally, see [WebGPU](./android-webgpu.md) |
|
||||||
|
| `webgpuWgpu` | WebGPU (wgpu-native) | Experimental | Uses Vulkan internally, arm64-v8a only, see [WebGPU](./android-webgpu.md) |
|
||||||
|
|
||||||
|
## Building with a specific backend
|
||||||
|
|
||||||
|
The flavor name is part of the Gradle task. For the test app:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd platform/android
|
||||||
|
|
||||||
|
# OpenGL ES
|
||||||
|
./gradlew :MapLibreAndroidTestApp:installOpenglDebug
|
||||||
|
|
||||||
|
# Vulkan
|
||||||
|
./gradlew :MapLibreAndroidTestApp:installVulkanDebug
|
||||||
|
|
||||||
|
# WebGPU (Dawn)
|
||||||
|
./gradlew :MapLibreAndroidTestApp:installWebgpuDawnDebug
|
||||||
|
|
||||||
|
# WebGPU (wgpu-native)
|
||||||
|
./gradlew :MapLibreAndroidTestApp:installWebgpuWgpuDebug
|
||||||
|
```
|
||||||
|
|
||||||
|
The same pattern applies to the library module (`MapLibreAndroid`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./gradlew :MapLibreAndroid:assembleVulkanRelease
|
||||||
|
```
|
||||||
|
|
||||||
|
## Selecting in Android Studio
|
||||||
|
|
||||||
|
In Android Studio, use the **Build Variants** panel (View > Tool Windows > Build Variants) to pick the renderer flavor. Each module will show a dropdown combining the flavor and build type, e.g. `vulkanDebug` or `openglRelease`.
|
||||||
|
|
||||||
|
## How it works
|
||||||
|
|
||||||
|
Each flavor sets different CMake arguments:
|
||||||
|
|
||||||
|
- `opengl`: `-DMLN_WITH_OPENGL=ON`
|
||||||
|
- `vulkan`: `-DMLN_WITH_VULKAN=ON`
|
||||||
|
- `webgpuDawn`: `-DMLN_WITH_WEBGPU=ON`, `-DMLN_WEBGPU_IMPL_DAWN=ON`
|
||||||
|
- `webgpuWgpu`: `-DMLN_WITH_WEBGPU=ON`, `-DMLN_WEBGPU_IMPL_WGPU=ON`
|
||||||
|
|
||||||
|
Only one backend is active per build. Renderer-specific Java code lives in `src/opengl/`, `src/vulkan/`, or `src/webgpu/` source sets.
|
||||||
73
docs/mdbook/src/platforms/android/android-webgpu.md
Normal file
73
docs/mdbook/src/platforms/android/android-webgpu.md
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
# WebGPU on Android
|
||||||
|
|
||||||
|
Two WebGPU implementations are supported: **Dawn** (Google, C++) and **wgpu-native** (Rust). Both use Vulkan internally on Android. The Gradle product flavors are `webgpuDawn` and `webgpuWgpu`, sharing the `src/webgpu/` Java source set.
|
||||||
|
|
||||||
|
## Building with Dawn
|
||||||
|
|
||||||
|
Dawn is fetched and built from source via CMake `FetchContent`, so no extra setup is needed. First build is slow (~20 min) because of Dawn's dependencies (abseil, SPIRV-Tools, Tint, etc.).
|
||||||
|
|
||||||
|
**Android Studio:** Open the **Build Variants** panel (View > Tool Windows > Build Variants) and select `webgpuDawnDebug` for both `:MapLibreAndroid` and `:MapLibreAndroidTestApp`, then run normally.
|
||||||
|
|
||||||
|
**Terminal:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd platform/android
|
||||||
|
./gradlew :MapLibreAndroidTestApp:installWebgpuDawnDebug
|
||||||
|
```
|
||||||
|
|
||||||
|
## Building with wgpu-native
|
||||||
|
|
||||||
|
wgpu-native requires a pre-built static library for the target ABI. The `webgpuWgpu` flavor is currently limited to `arm64-v8a`.
|
||||||
|
|
||||||
|
### 1. Cross-compile wgpu-native for Android
|
||||||
|
|
||||||
|
Install the Rust Android target:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rustup target add aarch64-linux-android
|
||||||
|
```
|
||||||
|
|
||||||
|
Point cargo at the NDK linker. Create or edit `~/.cargo/config.toml`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[target.aarch64-linux-android]
|
||||||
|
linker = "<NDK_PATH>/toolchains/llvm/prebuilt/<host>/bin/aarch64-linux-android26-clang"
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace `<NDK_PATH>` with the path to your Android NDK and `<host>` with your platform (e.g. `darwin-x86_64` on macOS).
|
||||||
|
|
||||||
|
Then build:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd vendor/wgpu-native
|
||||||
|
cargo build --release --target aarch64-linux-android
|
||||||
|
```
|
||||||
|
|
||||||
|
This produces `vendor/wgpu-native/target/aarch64-linux-android/release/libwgpu_native.a`, which the CMake build picks up automatically.
|
||||||
|
|
||||||
|
### 2. Build and install
|
||||||
|
|
||||||
|
**Android Studio:** Select `webgpuWgpuDebug` in the Build Variants panel and run.
|
||||||
|
|
||||||
|
**Terminal:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd platform/android
|
||||||
|
./gradlew :MapLibreAndroidTestApp:installWebgpuWgpuDebug
|
||||||
|
```
|
||||||
|
|
||||||
|
## Debugging wgpu-native
|
||||||
|
|
||||||
|
wgpu-native logs are routed to Android logcat under the `wgpu-native` tag (warnings and errors by default). Rust panic messages appear under `wgpu-native-stderr`. Filter with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
adb logcat -s wgpu-native:W wgpu-native-stderr:E
|
||||||
|
```
|
||||||
|
|
||||||
|
To increase verbosity, change `WGPULogLevel_Warn` to `WGPULogLevel_Info` (or `Debug` / `Trace`) in `android_webgpu_renderer_backend.cpp`.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
The native backend is in `MapLibreAndroid/src/cpp/android_webgpu_renderer_backend.cpp`. It uses `#if MLN_WEBGPU_IMPL_DAWN` / `#elif MLN_WEBGPU_IMPL_WGPU` for the API differences, same pattern as the desktop backends.
|
||||||
|
|
||||||
|
wgpu-native's adapter request forces `backendType = Vulkan` — without this it may pick GL on some emulators, which can't present to an ANativeWindow surface.
|
||||||
@@ -90,12 +90,37 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
create("webgpuDawn") {
|
||||||
|
dimension = "renderer"
|
||||||
|
externalNativeBuild {
|
||||||
|
cmake {
|
||||||
|
arguments("-DMLN_WITH_WEBGPU=ON", "-DMLN_WEBGPU_IMPL_DAWN=ON")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
create("webgpuWgpu") {
|
||||||
|
dimension = "renderer"
|
||||||
|
ndk {
|
||||||
|
abiFilters += "arm64-v8a"
|
||||||
|
}
|
||||||
|
externalNativeBuild {
|
||||||
|
cmake {
|
||||||
|
arguments("-DMLN_WITH_WEBGPU=ON", "-DMLN_WEBGPU_IMPL_WGPU=ON")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
getByName("opengl") {
|
getByName("opengl") {
|
||||||
java.srcDirs("src/opengl/java/")
|
java.srcDirs("src/opengl/java/")
|
||||||
}
|
}
|
||||||
|
listOf("webgpuDawn", "webgpuWgpu").forEach {
|
||||||
|
getByName(it) {
|
||||||
|
java.srcDirs("src/vulkan/java")
|
||||||
|
manifest.srcFile("src/vulkan/AndroidManifest.xml")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build native libraries
|
// Build native libraries
|
||||||
|
|||||||
@@ -217,6 +217,22 @@ if(MLN_WITH_VULKAN)
|
|||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(MLN_WITH_WEBGPU)
|
||||||
|
target_sources(
|
||||||
|
maplibre PRIVATE
|
||||||
|
${PROJECT_SOURCE_DIR}/android_webgpu_renderer_backend.cpp
|
||||||
|
${PROJECT_SOURCE_DIR}/android_webgpu_renderer_backend.hpp
|
||||||
|
)
|
||||||
|
target_compile_definitions(maplibre PRIVATE MLN_RENDER_BACKEND_WEBGPU=1)
|
||||||
|
if(MLN_WEBGPU_IMPL_DAWN AND TARGET mbgl-vendor-dawn)
|
||||||
|
target_compile_definitions(maplibre PRIVATE MLN_WEBGPU_IMPL_DAWN=1)
|
||||||
|
target_link_libraries(maplibre PRIVATE mbgl-vendor-dawn)
|
||||||
|
elseif(MLN_WEBGPU_IMPL_WGPU AND TARGET mbgl-vendor-wgpu)
|
||||||
|
target_compile_definitions(maplibre PRIVATE MLN_WEBGPU_IMPL_WGPU=1)
|
||||||
|
target_link_libraries(maplibre PRIVATE mbgl-vendor-wgpu)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
target_include_directories(
|
target_include_directories(
|
||||||
maplibre
|
maplibre
|
||||||
PRIVATE ${PROJECT_SOURCE_DIR}
|
PRIVATE ${PROJECT_SOURCE_DIR}
|
||||||
|
|||||||
@@ -0,0 +1,776 @@
|
|||||||
|
#include "android_webgpu_renderer_backend.hpp"
|
||||||
|
|
||||||
|
#include <mbgl/gfx/backend_scope.hpp>
|
||||||
|
#include <mbgl/util/logging.hpp>
|
||||||
|
#include <mbgl/util/scoped.hpp>
|
||||||
|
#include <mbgl/webgpu/context.hpp>
|
||||||
|
|
||||||
|
#include <webgpu/webgpu.h>
|
||||||
|
|
||||||
|
#if MLN_WEBGPU_IMPL_DAWN
|
||||||
|
#include <dawn/native/DawnNative.h>
|
||||||
|
#elif MLN_WEBGPU_IMPL_WGPU
|
||||||
|
#include <wgpu.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#if MLN_WEBGPU_IMPL_WGPU
|
||||||
|
#include <android/log.h>
|
||||||
|
#include <chrono>
|
||||||
|
#include <mutex>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
static void wgpuLogCallback(WGPULogLevel level, WGPUStringView message, void* /*userdata*/) {
|
||||||
|
const int androidLevel = [&]() {
|
||||||
|
switch (level) {
|
||||||
|
case WGPULogLevel_Error:
|
||||||
|
return ANDROID_LOG_ERROR;
|
||||||
|
case WGPULogLevel_Warn:
|
||||||
|
return ANDROID_LOG_WARN;
|
||||||
|
case WGPULogLevel_Info:
|
||||||
|
return ANDROID_LOG_INFO;
|
||||||
|
case WGPULogLevel_Debug:
|
||||||
|
return ANDROID_LOG_DEBUG;
|
||||||
|
case WGPULogLevel_Trace:
|
||||||
|
return ANDROID_LOG_VERBOSE;
|
||||||
|
default:
|
||||||
|
return ANDROID_LOG_INFO;
|
||||||
|
}
|
||||||
|
}();
|
||||||
|
if (message.data && message.length > 0) {
|
||||||
|
__android_log_print(androidLevel, "wgpu-native", "%.*s", static_cast<int>(message.length), message.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redirect stderr to logcat so Rust panic messages are visible
|
||||||
|
static void startStderrToLogcat() {
|
||||||
|
static std::once_flag flag;
|
||||||
|
std::call_once(flag, [] {
|
||||||
|
int pipefd[2];
|
||||||
|
if (pipe(pipefd) == -1) return;
|
||||||
|
dup2(pipefd[1], STDERR_FILENO);
|
||||||
|
close(pipefd[1]);
|
||||||
|
|
||||||
|
std::thread([fd = pipefd[0]]() {
|
||||||
|
char buf[1024];
|
||||||
|
ssize_t n;
|
||||||
|
while ((n = read(fd, buf, sizeof(buf) - 1)) > 0) {
|
||||||
|
buf[n] = '\0';
|
||||||
|
__android_log_print(ANDROID_LOG_ERROR, "wgpu-native-stderr", "%s", buf);
|
||||||
|
}
|
||||||
|
close(fd);
|
||||||
|
}).detach();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace mbgl {
|
||||||
|
namespace android {
|
||||||
|
|
||||||
|
#if MLN_WEBGPU_IMPL_DAWN
|
||||||
|
static std::string errorTypeToString(wgpu::ErrorType type) {
|
||||||
|
switch (type) {
|
||||||
|
case wgpu::ErrorType::NoError:
|
||||||
|
return "NoError";
|
||||||
|
case wgpu::ErrorType::Validation:
|
||||||
|
return "Validation";
|
||||||
|
case wgpu::ErrorType::OutOfMemory:
|
||||||
|
return "OutOfMemory";
|
||||||
|
case wgpu::ErrorType::Internal:
|
||||||
|
return "Internal";
|
||||||
|
case wgpu::ErrorType::Unknown:
|
||||||
|
return "Unknown";
|
||||||
|
default:
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string deviceLostReasonToString(wgpu::DeviceLostReason reason) {
|
||||||
|
switch (reason) {
|
||||||
|
case wgpu::DeviceLostReason::Unknown:
|
||||||
|
return "Unknown";
|
||||||
|
case wgpu::DeviceLostReason::Destroyed:
|
||||||
|
return "Destroyed";
|
||||||
|
case wgpu::DeviceLostReason::FailedCreation:
|
||||||
|
return "FailedCreation";
|
||||||
|
case wgpu::DeviceLostReason::CallbackCancelled:
|
||||||
|
return "CallbackCancelled";
|
||||||
|
default:
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class AndroidWebGPURenderableResource final : public webgpu::RenderableResource {
|
||||||
|
public:
|
||||||
|
AndroidWebGPURenderableResource(AndroidWebGPURendererBackend& backend_)
|
||||||
|
: backend(backend_) {}
|
||||||
|
|
||||||
|
void bind() override {}
|
||||||
|
|
||||||
|
void swap() override { backend.markNeedsPresent(); }
|
||||||
|
|
||||||
|
const mbgl::webgpu::RendererBackend& getBackend() const override { return backend; }
|
||||||
|
|
||||||
|
const WGPUCommandEncoder& getCommandEncoder() const override {
|
||||||
|
assert(false);
|
||||||
|
static WGPUCommandEncoder dummy = nullptr;
|
||||||
|
return dummy;
|
||||||
|
}
|
||||||
|
|
||||||
|
WGPURenderPassEncoder getRenderPassEncoder() const override {
|
||||||
|
assert(false);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
WGPUTextureView getColorTextureView() override {
|
||||||
|
return static_cast<WGPUTextureView>(backend.getCurrentTextureView());
|
||||||
|
}
|
||||||
|
|
||||||
|
WGPUTextureView getDepthStencilTextureView() override {
|
||||||
|
return static_cast<WGPUTextureView>(backend.getDepthStencilView());
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
AndroidWebGPURendererBackend& backend;
|
||||||
|
};
|
||||||
|
|
||||||
|
class AndroidWebGPURendererBackend::Impl {
|
||||||
|
public:
|
||||||
|
#if MLN_WEBGPU_IMPL_DAWN
|
||||||
|
std::unique_ptr<dawn::native::Instance> instance;
|
||||||
|
WGPUAdapter adapter = nullptr;
|
||||||
|
#elif MLN_WEBGPU_IMPL_WGPU
|
||||||
|
wgpu::Instance instance;
|
||||||
|
wgpu::Adapter adapter;
|
||||||
|
#endif
|
||||||
|
wgpu::Device device;
|
||||||
|
wgpu::Queue queue;
|
||||||
|
|
||||||
|
WGPUSurface surface = nullptr;
|
||||||
|
WGPUTextureFormat surfaceFormat = WGPUTextureFormat_BGRA8Unorm;
|
||||||
|
bool surfaceConfigured = false;
|
||||||
|
|
||||||
|
WGPUTexture depthStencilTexture = nullptr;
|
||||||
|
WGPUTextureView depthStencilView = nullptr;
|
||||||
|
|
||||||
|
WGPUTexture currentTexture = nullptr;
|
||||||
|
WGPUTextureView currentTextureView = nullptr;
|
||||||
|
bool needsPresent = false;
|
||||||
|
|
||||||
|
Size framebufferSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
AndroidWebGPURendererBackend::AndroidWebGPURendererBackend(ANativeWindow* window_)
|
||||||
|
: webgpu::RendererBackend(gfx::ContextMode::Unique),
|
||||||
|
gfx::Renderable({64, 64}, std::make_unique<AndroidWebGPURenderableResource>(*this)),
|
||||||
|
impl(std::make_unique<Impl>()),
|
||||||
|
window(window_) {
|
||||||
|
#if MLN_WEBGPU_IMPL_WGPU
|
||||||
|
startStderrToLogcat();
|
||||||
|
wgpuSetLogLevel(WGPULogLevel_Warn);
|
||||||
|
wgpuSetLogCallback(wgpuLogCallback, nullptr);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if MLN_WEBGPU_IMPL_DAWN
|
||||||
|
WGPUInstanceFeatureName instanceFeatures[] = {WGPUInstanceFeatureName_TimedWaitAny};
|
||||||
|
WGPUInstanceLimits instanceLimits = WGPU_INSTANCE_LIMITS_INIT;
|
||||||
|
instanceLimits.timedWaitAnyMaxCount = 64;
|
||||||
|
|
||||||
|
WGPUInstanceDescriptor instanceDesc = WGPU_INSTANCE_DESCRIPTOR_INIT;
|
||||||
|
instanceDesc.requiredFeatureCount = 1;
|
||||||
|
instanceDesc.requiredFeatures = instanceFeatures;
|
||||||
|
instanceDesc.requiredLimits = &instanceLimits;
|
||||||
|
impl->instance = std::make_unique<dawn::native::Instance>(&instanceDesc);
|
||||||
|
|
||||||
|
std::vector<dawn::native::Adapter> adapters = impl->instance->EnumerateAdapters();
|
||||||
|
if (adapters.empty()) {
|
||||||
|
Log::Error(Event::Render, "WebGPU: no adapters found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto findVulkanAdapter = [&]() -> dawn::native::Adapter* {
|
||||||
|
for (auto& a : adapters) {
|
||||||
|
WGPUAdapterInfo info = WGPU_ADAPTER_INFO_INIT;
|
||||||
|
if (wgpuAdapterGetInfo(a.Get(), &info) == WGPUStatus_Success) {
|
||||||
|
const auto backendType = info.backendType;
|
||||||
|
wgpuAdapterInfoFreeMembers(info);
|
||||||
|
if (backendType == WGPUBackendType_Vulkan) {
|
||||||
|
return &a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &adapters.front();
|
||||||
|
};
|
||||||
|
|
||||||
|
dawn::native::Adapter& selectedAdapter = *findVulkanAdapter();
|
||||||
|
|
||||||
|
WGPUSupportedFeatures features = {};
|
||||||
|
wgpuAdapterGetFeatures(selectedAdapter.Get(), &features);
|
||||||
|
#elif MLN_WEBGPU_IMPL_WGPU
|
||||||
|
wgpu::InstanceDescriptor instanceDesc = {};
|
||||||
|
impl->instance = wgpu::createInstance(instanceDesc);
|
||||||
|
|
||||||
|
if (!impl->instance) {
|
||||||
|
Log::Error(Event::Render, "WebGPU: failed to create instance");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force Vulkan — the GL backend can't present to an ANativeWindow surface.
|
||||||
|
wgpu::RequestAdapterOptions adapterOpts = {};
|
||||||
|
adapterOpts.powerPreference = wgpu::PowerPreference::HighPerformance;
|
||||||
|
adapterOpts.backendType = wgpu::BackendType::Vulkan;
|
||||||
|
|
||||||
|
impl->adapter = impl->instance.requestAdapter(adapterOpts);
|
||||||
|
if (!impl->adapter) {
|
||||||
|
Log::Error(Event::Render, "WebGPU: no adapter found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
WGPUSupportedFeatures features = {};
|
||||||
|
wgpuAdapterGetFeatures(static_cast<WGPUAdapter>(impl->adapter), &features);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
std::vector<wgpu::FeatureName> requiredFeatures;
|
||||||
|
wgpu::TextureFormat depthStencilFormat = wgpu::TextureFormat::Depth24PlusStencil8;
|
||||||
|
if (features.featureCount > 0 && features.features) {
|
||||||
|
for (size_t i = 0; i < features.featureCount; ++i) {
|
||||||
|
if (static_cast<wgpu::FeatureName>(features.features[i]) == wgpu::FeatureName::Depth32FloatStencil8) {
|
||||||
|
requiredFeatures.push_back(wgpu::FeatureName::Depth32FloatStencil8);
|
||||||
|
depthStencilFormat = wgpu::TextureFormat::Depth32FloatStencil8;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wgpuSupportedFeaturesFreeMembers(features);
|
||||||
|
}
|
||||||
|
|
||||||
|
wgpu::DeviceDescriptor deviceDesc = {};
|
||||||
|
#if MLN_WEBGPU_IMPL_DAWN
|
||||||
|
deviceDesc.label = "MapLibre Android WebGPU Device";
|
||||||
|
deviceDesc.SetUncapturedErrorCallback([](const wgpu::Device&, wgpu::ErrorType type, wgpu::StringView message) {
|
||||||
|
Log::Error(Event::Render,
|
||||||
|
std::string("Dawn error [") + errorTypeToString(type) + "] " +
|
||||||
|
(message.data ? std::string(message.data, message.length) : std::string()));
|
||||||
|
});
|
||||||
|
deviceDesc.SetDeviceLostCallback(
|
||||||
|
wgpu::CallbackMode::AllowSpontaneous,
|
||||||
|
[](const wgpu::Device&, wgpu::DeviceLostReason reason, wgpu::StringView message) {
|
||||||
|
Log::Error(Event::Render,
|
||||||
|
std::string("Dawn device lost [") + deviceLostReasonToString(reason) + "] " +
|
||||||
|
(message.data ? std::string(message.data, message.length) : std::string()));
|
||||||
|
});
|
||||||
|
deviceDesc.requiredFeatures = requiredFeatures.data();
|
||||||
|
#elif MLN_WEBGPU_IMPL_WGPU
|
||||||
|
deviceDesc.requiredFeatures = reinterpret_cast<const WGPUFeatureName*>(requiredFeatures.data());
|
||||||
|
#endif
|
||||||
|
deviceDesc.requiredFeatureCount = requiredFeatures.size();
|
||||||
|
|
||||||
|
#if MLN_WEBGPU_IMPL_DAWN
|
||||||
|
WGPUDevice rawDevice = selectedAdapter.CreateDevice(&deviceDesc);
|
||||||
|
if (!rawDevice) {
|
||||||
|
Log::Error(Event::Render, "WebGPU: failed to create device");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl->device = wgpu::Device::Acquire(rawDevice);
|
||||||
|
impl->queue = impl->device.GetQueue();
|
||||||
|
impl->adapter = selectedAdapter.Get();
|
||||||
|
wgpuAdapterAddRef(impl->adapter);
|
||||||
|
|
||||||
|
setDevice(impl->device.Get());
|
||||||
|
setQueue(impl->queue.Get());
|
||||||
|
setInstance(impl->instance->Get());
|
||||||
|
#elif MLN_WEBGPU_IMPL_WGPU
|
||||||
|
impl->device = impl->adapter.requestDevice(deviceDesc);
|
||||||
|
if (!impl->device) {
|
||||||
|
Log::Error(Event::Render, "WebGPU: failed to create device");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl->queue = impl->device.getQueue();
|
||||||
|
|
||||||
|
setDevice(static_cast<WGPUDevice>(impl->device));
|
||||||
|
setQueue(static_cast<WGPUQueue>(impl->queue));
|
||||||
|
setInstance(static_cast<WGPUInstance>(impl->instance));
|
||||||
|
#endif
|
||||||
|
setDepthStencilFormat(depthStencilFormat);
|
||||||
|
|
||||||
|
createSurface();
|
||||||
|
|
||||||
|
const int w = ANativeWindow_getWidth(window);
|
||||||
|
const int h = ANativeWindow_getHeight(window);
|
||||||
|
if (w > 0 && h > 0) {
|
||||||
|
impl->framebufferSize = {static_cast<uint32_t>(w), static_cast<uint32_t>(h)};
|
||||||
|
size = impl->framebufferSize;
|
||||||
|
configureSurface(impl->framebufferSize.width, impl->framebufferSize.height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AndroidWebGPURendererBackend::~AndroidWebGPURendererBackend() {
|
||||||
|
if (impl) {
|
||||||
|
if (impl->currentTextureView) {
|
||||||
|
wgpuTextureViewRelease(impl->currentTextureView);
|
||||||
|
impl->currentTextureView = nullptr;
|
||||||
|
}
|
||||||
|
if (impl->currentTexture) {
|
||||||
|
wgpuTextureRelease(impl->currentTexture);
|
||||||
|
impl->currentTexture = nullptr;
|
||||||
|
}
|
||||||
|
if (impl->depthStencilView) {
|
||||||
|
wgpuTextureViewRelease(impl->depthStencilView);
|
||||||
|
impl->depthStencilView = nullptr;
|
||||||
|
}
|
||||||
|
if (impl->depthStencilTexture) {
|
||||||
|
wgpuTextureDestroy(impl->depthStencilTexture);
|
||||||
|
wgpuTextureRelease(impl->depthStencilTexture);
|
||||||
|
impl->depthStencilTexture = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (impl->surface) {
|
||||||
|
if (impl->surfaceConfigured) {
|
||||||
|
wgpuSurfaceUnconfigure(impl->surface);
|
||||||
|
}
|
||||||
|
wgpuSurfaceRelease(impl->surface);
|
||||||
|
impl->surface = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl->queue = nullptr;
|
||||||
|
impl->device = nullptr;
|
||||||
|
#if MLN_WEBGPU_IMPL_DAWN
|
||||||
|
if (impl->adapter) {
|
||||||
|
wgpuAdapterRelease(impl->adapter);
|
||||||
|
impl->adapter = nullptr;
|
||||||
|
}
|
||||||
|
impl->instance.reset();
|
||||||
|
#elif MLN_WEBGPU_IMPL_WGPU
|
||||||
|
impl->adapter = nullptr;
|
||||||
|
impl->instance = nullptr;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidWebGPURendererBackend::createSurface() {
|
||||||
|
#if MLN_WEBGPU_IMPL_DAWN
|
||||||
|
if (!impl->instance || !window) return;
|
||||||
|
|
||||||
|
WGPUSurfaceSourceAndroidNativeWindow androidDesc = WGPU_SURFACE_SOURCE_ANDROID_NATIVE_WINDOW_INIT;
|
||||||
|
androidDesc.window = window;
|
||||||
|
|
||||||
|
WGPUSurfaceDescriptor surfaceDesc = WGPU_SURFACE_DESCRIPTOR_INIT;
|
||||||
|
surfaceDesc.nextInChain = &androidDesc.chain;
|
||||||
|
|
||||||
|
WGPUInstance rawInstance = impl->instance->Get();
|
||||||
|
impl->surface = wgpuInstanceCreateSurface(rawInstance, &surfaceDesc);
|
||||||
|
#elif MLN_WEBGPU_IMPL_WGPU
|
||||||
|
if (!impl->instance || !window) return;
|
||||||
|
|
||||||
|
WGPUSurfaceSourceAndroidNativeWindow androidDesc = {};
|
||||||
|
androidDesc.chain.sType = WGPUSType_SurfaceSourceAndroidNativeWindow;
|
||||||
|
androidDesc.chain.next = nullptr;
|
||||||
|
androidDesc.window = window;
|
||||||
|
|
||||||
|
WGPUSurfaceDescriptor surfaceDesc = {};
|
||||||
|
surfaceDesc.nextInChain = &androidDesc.chain;
|
||||||
|
|
||||||
|
impl->surface = wgpuInstanceCreateSurface(static_cast<WGPUInstance>(impl->instance), &surfaceDesc);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!impl->surface) {
|
||||||
|
Log::Error(Event::Render, "WebGPU: failed to create surface");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidWebGPURendererBackend::configureSurface(uint32_t width, uint32_t height) {
|
||||||
|
if (!impl->surface || !impl->device || width == 0 || height == 0) return;
|
||||||
|
|
||||||
|
WGPUSurfaceCapabilities capabilities = {};
|
||||||
|
#if MLN_WEBGPU_IMPL_DAWN
|
||||||
|
WGPUStatus capStatus = wgpuSurfaceGetCapabilities(impl->surface, impl->adapter, &capabilities);
|
||||||
|
#elif MLN_WEBGPU_IMPL_WGPU
|
||||||
|
WGPUStatus capStatus = wgpuSurfaceGetCapabilities(
|
||||||
|
impl->surface, static_cast<WGPUAdapter>(impl->adapter), &capabilities);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
WGPUCompositeAlphaMode selectedAlphaMode = WGPUCompositeAlphaMode_Opaque;
|
||||||
|
WGPUPresentMode selectedPresentMode = WGPUPresentMode_Fifo;
|
||||||
|
|
||||||
|
if (capStatus == WGPUStatus_Success) {
|
||||||
|
const WGPUTextureFormat preferredFormats[] = {
|
||||||
|
WGPUTextureFormat_BGRA8Unorm,
|
||||||
|
WGPUTextureFormat_RGBA8Unorm,
|
||||||
|
};
|
||||||
|
for (auto preferred : preferredFormats) {
|
||||||
|
for (size_t i = 0; i < capabilities.formatCount; ++i) {
|
||||||
|
if (capabilities.formats[i] == preferred) {
|
||||||
|
impl->surfaceFormat = preferred;
|
||||||
|
goto formatFound;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (capabilities.formatCount > 0) {
|
||||||
|
impl->surfaceFormat = capabilities.formats[0];
|
||||||
|
}
|
||||||
|
formatFound:
|
||||||
|
|
||||||
|
if (capabilities.alphaModeCount > 0) {
|
||||||
|
selectedAlphaMode = capabilities.alphaModes[0];
|
||||||
|
}
|
||||||
|
if (capabilities.presentModeCount > 0) {
|
||||||
|
selectedPresentMode = capabilities.presentModes[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
wgpuSurfaceCapabilitiesFreeMembers(capabilities);
|
||||||
|
}
|
||||||
|
|
||||||
|
setColorFormat(static_cast<wgpu::TextureFormat>(impl->surfaceFormat));
|
||||||
|
|
||||||
|
#if MLN_WEBGPU_IMPL_DAWN
|
||||||
|
WGPUSurfaceConfiguration config = WGPU_SURFACE_CONFIGURATION_INIT;
|
||||||
|
config.device = impl->device.Get();
|
||||||
|
#elif MLN_WEBGPU_IMPL_WGPU
|
||||||
|
WGPUSurfaceConfiguration config = {};
|
||||||
|
config.device = static_cast<WGPUDevice>(impl->device);
|
||||||
|
#endif
|
||||||
|
config.format = impl->surfaceFormat;
|
||||||
|
config.usage = WGPUTextureUsage_RenderAttachment | WGPUTextureUsage_CopySrc;
|
||||||
|
config.alphaMode = selectedAlphaMode;
|
||||||
|
config.width = width;
|
||||||
|
config.height = height;
|
||||||
|
config.presentMode = selectedPresentMode;
|
||||||
|
|
||||||
|
wgpuSurfaceConfigure(impl->surface, &config);
|
||||||
|
impl->surfaceConfigured = true;
|
||||||
|
|
||||||
|
createDepthStencilTexture(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidWebGPURendererBackend::createDepthStencilTexture(uint32_t width, uint32_t height) {
|
||||||
|
if (impl->depthStencilView) {
|
||||||
|
wgpuTextureViewRelease(impl->depthStencilView);
|
||||||
|
impl->depthStencilView = nullptr;
|
||||||
|
}
|
||||||
|
if (impl->depthStencilTexture) {
|
||||||
|
wgpuTextureDestroy(impl->depthStencilTexture);
|
||||||
|
wgpuTextureRelease(impl->depthStencilTexture);
|
||||||
|
impl->depthStencilTexture = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!impl->device || width == 0 || height == 0) return;
|
||||||
|
|
||||||
|
WGPUTextureFormat dsFormat = static_cast<WGPUTextureFormat>(getDepthStencilFormat());
|
||||||
|
|
||||||
|
#if MLN_WEBGPU_IMPL_DAWN
|
||||||
|
WGPUTextureDescriptor depthDesc = WGPU_TEXTURE_DESCRIPTOR_INIT;
|
||||||
|
#elif MLN_WEBGPU_IMPL_WGPU
|
||||||
|
WGPUTextureDescriptor depthDesc = {};
|
||||||
|
#endif
|
||||||
|
depthDesc.usage = WGPUTextureUsage_RenderAttachment;
|
||||||
|
depthDesc.dimension = WGPUTextureDimension_2D;
|
||||||
|
depthDesc.size = {width, height, 1};
|
||||||
|
depthDesc.format = dsFormat;
|
||||||
|
depthDesc.mipLevelCount = 1;
|
||||||
|
depthDesc.sampleCount = 1;
|
||||||
|
|
||||||
|
#if MLN_WEBGPU_IMPL_DAWN
|
||||||
|
impl->depthStencilTexture = wgpuDeviceCreateTexture(impl->device.Get(), &depthDesc);
|
||||||
|
#elif MLN_WEBGPU_IMPL_WGPU
|
||||||
|
impl->depthStencilTexture = wgpuDeviceCreateTexture(static_cast<WGPUDevice>(impl->device), &depthDesc);
|
||||||
|
#endif
|
||||||
|
if (!impl->depthStencilTexture) {
|
||||||
|
Log::Warning(Event::Render, "WebGPU: failed to create depth/stencil texture");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if MLN_WEBGPU_IMPL_DAWN
|
||||||
|
WGPUTextureViewDescriptor viewDesc = WGPU_TEXTURE_VIEW_DESCRIPTOR_INIT;
|
||||||
|
#elif MLN_WEBGPU_IMPL_WGPU
|
||||||
|
WGPUTextureViewDescriptor viewDesc = {};
|
||||||
|
#endif
|
||||||
|
viewDesc.format = dsFormat;
|
||||||
|
viewDesc.dimension = WGPUTextureViewDimension_2D;
|
||||||
|
viewDesc.baseMipLevel = 0;
|
||||||
|
viewDesc.mipLevelCount = 1;
|
||||||
|
viewDesc.baseArrayLayer = 0;
|
||||||
|
viewDesc.arrayLayerCount = 1;
|
||||||
|
viewDesc.aspect = WGPUTextureAspect_All;
|
||||||
|
|
||||||
|
impl->depthStencilView = wgpuTextureCreateView(impl->depthStencilTexture, &viewDesc);
|
||||||
|
if (!impl->depthStencilView) {
|
||||||
|
Log::Warning(Event::Render, "WebGPU: failed to create depth/stencil view");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidWebGPURendererBackend::resizeFramebuffer(int width, int height) {
|
||||||
|
if (width <= 0 || height <= 0) return;
|
||||||
|
|
||||||
|
impl->framebufferSize = {static_cast<uint32_t>(width), static_cast<uint32_t>(height)};
|
||||||
|
size = impl->framebufferSize;
|
||||||
|
|
||||||
|
if (impl->currentTextureView) {
|
||||||
|
wgpuTextureViewRelease(impl->currentTextureView);
|
||||||
|
impl->currentTextureView = nullptr;
|
||||||
|
}
|
||||||
|
if (impl->currentTexture) {
|
||||||
|
wgpuTextureRelease(impl->currentTexture);
|
||||||
|
impl->currentTexture = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
configureSurface(impl->framebufferSize.width, impl->framebufferSize.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
PremultipliedImage AndroidWebGPURendererBackend::readFramebuffer() {
|
||||||
|
if (!impl->currentTexture || !impl->device || !impl->queue || impl->framebufferSize.width == 0 ||
|
||||||
|
impl->framebufferSize.height == 0) {
|
||||||
|
return PremultipliedImage(impl->framebufferSize.isEmpty() ? Size(2, 2) : impl->framebufferSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& fbSize = impl->framebufferSize;
|
||||||
|
constexpr uint32_t bytesPerPixel = 4;
|
||||||
|
constexpr uint32_t bytesPerRowAlignment = 256u;
|
||||||
|
const uint32_t rowStride = fbSize.width * bytesPerPixel;
|
||||||
|
const uint32_t alignedRowStride = ((rowStride + bytesPerRowAlignment - 1) / bytesPerRowAlignment) *
|
||||||
|
bytesPerRowAlignment;
|
||||||
|
const uint64_t alignedDataSize = static_cast<uint64_t>(alignedRowStride) * fbSize.height;
|
||||||
|
|
||||||
|
auto data = std::make_unique<uint8_t[]>(static_cast<size_t>(rowStride) * fbSize.height);
|
||||||
|
|
||||||
|
#if MLN_WEBGPU_IMPL_DAWN
|
||||||
|
auto device = impl->device.Get();
|
||||||
|
auto queue = impl->queue.Get();
|
||||||
|
#elif MLN_WEBGPU_IMPL_WGPU
|
||||||
|
auto device = static_cast<WGPUDevice>(impl->device);
|
||||||
|
auto queue = static_cast<WGPUQueue>(impl->queue);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if MLN_WEBGPU_IMPL_DAWN
|
||||||
|
wgpuDeviceTick(device);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
WGPUBufferDescriptor bufferDesc = {};
|
||||||
|
#if MLN_WEBGPU_IMPL_DAWN
|
||||||
|
WGPUStringView bufferLabel = {"Readback Buffer", strlen("Readback Buffer")};
|
||||||
|
bufferDesc.label = bufferLabel;
|
||||||
|
#endif
|
||||||
|
bufferDesc.size = alignedDataSize;
|
||||||
|
bufferDesc.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_MapRead;
|
||||||
|
|
||||||
|
WGPUBuffer stagingBuffer = wgpuDeviceCreateBuffer(device, &bufferDesc);
|
||||||
|
if (!stagingBuffer) {
|
||||||
|
Log::Error(Event::Render, "WebGPU readFramebuffer: failed to create staging buffer");
|
||||||
|
return {fbSize, std::move(data)};
|
||||||
|
}
|
||||||
|
|
||||||
|
mbgl::Scoped scopedCleanup([stagingBuffer]() {
|
||||||
|
wgpuBufferUnmap(stagingBuffer);
|
||||||
|
wgpuBufferRelease(stagingBuffer);
|
||||||
|
});
|
||||||
|
|
||||||
|
WGPUCommandEncoder encoder = wgpuDeviceCreateCommandEncoder(device, nullptr);
|
||||||
|
if (encoder) {
|
||||||
|
WGPUTexelCopyTextureInfo src = {};
|
||||||
|
src.texture = impl->currentTexture;
|
||||||
|
src.mipLevel = 0;
|
||||||
|
src.origin = {0, 0, 0};
|
||||||
|
src.aspect = WGPUTextureAspect_All;
|
||||||
|
|
||||||
|
WGPUTexelCopyBufferInfo dst = {};
|
||||||
|
dst.buffer = stagingBuffer;
|
||||||
|
dst.layout.offset = 0;
|
||||||
|
dst.layout.bytesPerRow = alignedRowStride;
|
||||||
|
dst.layout.rowsPerImage = fbSize.height;
|
||||||
|
|
||||||
|
WGPUExtent3D copySize = {fbSize.width, fbSize.height, 1};
|
||||||
|
wgpuCommandEncoderCopyTextureToBuffer(encoder, &src, &dst, ©Size);
|
||||||
|
|
||||||
|
WGPUCommandBuffer commands = wgpuCommandEncoderFinish(encoder, nullptr);
|
||||||
|
wgpuQueueSubmit(queue, 1, &commands);
|
||||||
|
wgpuCommandBufferRelease(commands);
|
||||||
|
wgpuCommandEncoderRelease(encoder);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MapContext {
|
||||||
|
WGPUMapAsyncStatus status = WGPUMapAsyncStatus_Error;
|
||||||
|
bool completed = false;
|
||||||
|
};
|
||||||
|
MapContext mapContext;
|
||||||
|
|
||||||
|
WGPUBufferMapCallbackInfo callbackInfo = {};
|
||||||
|
callbackInfo.callback = [](WGPUMapAsyncStatus status, WGPUStringView, void* userdata1, void*) {
|
||||||
|
auto* ctx = static_cast<MapContext*>(userdata1);
|
||||||
|
ctx->status = status;
|
||||||
|
ctx->completed = true;
|
||||||
|
};
|
||||||
|
callbackInfo.userdata1 = &mapContext;
|
||||||
|
|
||||||
|
#if MLN_WEBGPU_IMPL_DAWN
|
||||||
|
callbackInfo.mode = WGPUCallbackMode_WaitAnyOnly;
|
||||||
|
WGPUFuture future = wgpuBufferMapAsync(stagingBuffer, WGPUMapMode_Read, 0, alignedDataSize, callbackInfo);
|
||||||
|
|
||||||
|
WGPUInstance instance = impl->instance->Get();
|
||||||
|
if (instance) {
|
||||||
|
WGPUFutureWaitInfo waitInfo = {};
|
||||||
|
waitInfo.future = future;
|
||||||
|
waitInfo.completed = false;
|
||||||
|
|
||||||
|
WGPUWaitStatus waitStatus = wgpuInstanceWaitAny(instance, 1, &waitInfo, UINT64_MAX);
|
||||||
|
if (waitStatus != WGPUWaitStatus_Success || !waitInfo.completed) {
|
||||||
|
Log::Error(Event::Render, "WebGPU readFramebuffer: WaitAny failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#elif MLN_WEBGPU_IMPL_WGPU
|
||||||
|
callbackInfo.mode = WGPUCallbackMode_AllowProcessEvents;
|
||||||
|
wgpuBufferMapAsync(stagingBuffer, WGPUMapMode_Read, 0, alignedDataSize, callbackInfo);
|
||||||
|
|
||||||
|
for (int i = 0; i < 1000 && !mapContext.completed; ++i) {
|
||||||
|
wgpuDevicePoll(device, true, nullptr);
|
||||||
|
if (!mapContext.completed) {
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!mapContext.completed) {
|
||||||
|
Log::Error(Event::Render, "WebGPU readFramebuffer: buffer mapping timeout after polling");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (mapContext.status == WGPUMapAsyncStatus_Success) {
|
||||||
|
const auto* mappedData = static_cast<const uint8_t*>(
|
||||||
|
wgpuBufferGetConstMappedRange(stagingBuffer, 0, alignedDataSize));
|
||||||
|
if (mappedData) {
|
||||||
|
const bool swizzle = (impl->surfaceFormat == WGPUTextureFormat_BGRA8Unorm);
|
||||||
|
for (uint32_t row = 0; row < fbSize.height; ++row) {
|
||||||
|
const auto* src = mappedData + static_cast<size_t>(row) * alignedRowStride;
|
||||||
|
auto* dst = data.get() + static_cast<size_t>(row) * rowStride;
|
||||||
|
if (swizzle) {
|
||||||
|
for (uint32_t x = 0; x < fbSize.width; ++x) {
|
||||||
|
dst[x * 4 + 0] = src[x * 4 + 2];
|
||||||
|
dst[x * 4 + 1] = src[x * 4 + 1];
|
||||||
|
dst[x * 4 + 2] = src[x * 4 + 0];
|
||||||
|
dst[x * 4 + 3] = src[x * 4 + 3];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
std::memcpy(dst, src, rowStride);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log::Error(Event::Render, "WebGPU readFramebuffer: buffer mapping failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
presentSurface();
|
||||||
|
|
||||||
|
return {fbSize, std::move(data)};
|
||||||
|
}
|
||||||
|
|
||||||
|
void* AndroidWebGPURendererBackend::getCurrentTextureView() {
|
||||||
|
if (!impl->surface || !impl->surfaceConfigured) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
presentSurface();
|
||||||
|
|
||||||
|
WGPUSurfaceTexture surfaceTexture = {};
|
||||||
|
wgpuSurfaceGetCurrentTexture(impl->surface, &surfaceTexture);
|
||||||
|
|
||||||
|
if (!surfaceTexture.texture) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto status = static_cast<WGPUSurfaceGetCurrentTextureStatus>(surfaceTexture.status);
|
||||||
|
if (status == WGPUSurfaceGetCurrentTextureStatus_Outdated || status == WGPUSurfaceGetCurrentTextureStatus_Lost) {
|
||||||
|
wgpuTextureRelease(surfaceTexture.texture);
|
||||||
|
configureSurface(impl->framebufferSize.width, impl->framebufferSize.height);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status != WGPUSurfaceGetCurrentTextureStatus_SuccessOptimal &&
|
||||||
|
status != WGPUSurfaceGetCurrentTextureStatus_SuccessSuboptimal) {
|
||||||
|
wgpuTextureRelease(surfaceTexture.texture);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl->currentTexture = surfaceTexture.texture;
|
||||||
|
|
||||||
|
// Actual texture size may differ from configured size (Vulkan currentExtent clamping)
|
||||||
|
const uint32_t texW = wgpuTextureGetWidth(surfaceTexture.texture);
|
||||||
|
const uint32_t texH = wgpuTextureGetHeight(surfaceTexture.texture);
|
||||||
|
if (texW != size.width || texH != size.height) {
|
||||||
|
if (texW > 0 && texH > 0) {
|
||||||
|
size = {texW, texH};
|
||||||
|
impl->framebufferSize = size;
|
||||||
|
createDepthStencilTexture(texW, texH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if MLN_WEBGPU_IMPL_DAWN
|
||||||
|
WGPUTextureViewDescriptor viewDesc = WGPU_TEXTURE_VIEW_DESCRIPTOR_INIT;
|
||||||
|
#elif MLN_WEBGPU_IMPL_WGPU
|
||||||
|
WGPUTextureViewDescriptor viewDesc = {};
|
||||||
|
#endif
|
||||||
|
viewDesc.format = impl->surfaceFormat;
|
||||||
|
viewDesc.dimension = WGPUTextureViewDimension_2D;
|
||||||
|
viewDesc.baseMipLevel = 0;
|
||||||
|
viewDesc.mipLevelCount = 1;
|
||||||
|
viewDesc.baseArrayLayer = 0;
|
||||||
|
viewDesc.arrayLayerCount = 1;
|
||||||
|
viewDesc.aspect = WGPUTextureAspect_All;
|
||||||
|
|
||||||
|
impl->currentTextureView = wgpuTextureCreateView(impl->currentTexture, &viewDesc);
|
||||||
|
|
||||||
|
return impl->currentTextureView;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidWebGPURendererBackend::markNeedsPresent() {
|
||||||
|
impl->needsPresent = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidWebGPURendererBackend::presentSurface() {
|
||||||
|
if (!impl->surface || !impl->surfaceConfigured || !impl->needsPresent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl->needsPresent = false;
|
||||||
|
|
||||||
|
if (impl->currentTextureView) {
|
||||||
|
wgpuSurfacePresent(impl->surface);
|
||||||
|
wgpuTextureViewRelease(impl->currentTextureView);
|
||||||
|
impl->currentTextureView = nullptr;
|
||||||
|
}
|
||||||
|
if (impl->currentTexture) {
|
||||||
|
wgpuTextureRelease(impl->currentTexture);
|
||||||
|
impl->currentTexture = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (impl->device) {
|
||||||
|
#if MLN_WEBGPU_IMPL_DAWN
|
||||||
|
wgpuDeviceTick(impl->device.Get());
|
||||||
|
#elif MLN_WEBGPU_IMPL_WGPU
|
||||||
|
wgpuDevicePoll(static_cast<WGPUDevice>(impl->device), false, nullptr);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void* AndroidWebGPURendererBackend::getDepthStencilView() {
|
||||||
|
return impl->depthStencilView;
|
||||||
|
}
|
||||||
|
|
||||||
|
mbgl::Size AndroidWebGPURendererBackend::getFramebufferSize() const {
|
||||||
|
return impl->framebufferSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace android
|
||||||
|
} // namespace mbgl
|
||||||
|
|
||||||
|
namespace mbgl {
|
||||||
|
namespace gfx {
|
||||||
|
|
||||||
|
template <>
|
||||||
|
std::unique_ptr<android::AndroidRendererBackend> Backend::Create<mbgl::gfx::Backend::Type::WebGPU>(
|
||||||
|
ANativeWindow* window) {
|
||||||
|
return std::make_unique<android::AndroidWebGPURendererBackend>(window);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace gfx
|
||||||
|
} // namespace mbgl
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <mbgl/webgpu/renderer_backend.hpp>
|
||||||
|
#include <mbgl/webgpu/renderable_resource.hpp>
|
||||||
|
#include "android_renderer_backend.hpp"
|
||||||
|
#include <android/native_window.h>
|
||||||
|
|
||||||
|
#if MLN_WEBGPU_IMPL_WGPU
|
||||||
|
#include <wgpu.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace mbgl {
|
||||||
|
namespace android {
|
||||||
|
|
||||||
|
class AndroidWebGPURendererBackend : public AndroidRendererBackend,
|
||||||
|
public webgpu::RendererBackend,
|
||||||
|
public gfx::Renderable {
|
||||||
|
public:
|
||||||
|
AndroidWebGPURendererBackend(ANativeWindow*);
|
||||||
|
~AndroidWebGPURendererBackend() override;
|
||||||
|
|
||||||
|
ANativeWindow* getWindow() { return window; }
|
||||||
|
mbgl::gfx::RendererBackend& getImpl() override { return *this; }
|
||||||
|
|
||||||
|
void resizeFramebuffer(int width, int height) override;
|
||||||
|
PremultipliedImage readFramebuffer() override;
|
||||||
|
|
||||||
|
public:
|
||||||
|
mbgl::gfx::Renderable& getDefaultRenderable() override { return *this; }
|
||||||
|
|
||||||
|
void* getCurrentTextureView() override;
|
||||||
|
void* getDepthStencilView() override;
|
||||||
|
mbgl::Size getFramebufferSize() const override;
|
||||||
|
|
||||||
|
void presentSurface();
|
||||||
|
void markNeedsPresent();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void activate() override {}
|
||||||
|
void deactivate() override {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void createSurface();
|
||||||
|
void configureSurface(uint32_t width, uint32_t height);
|
||||||
|
void createDepthStencilTexture(uint32_t width, uint32_t height);
|
||||||
|
|
||||||
|
class Impl;
|
||||||
|
std::unique_ptr<Impl> impl;
|
||||||
|
ANativeWindow* window;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace android
|
||||||
|
} // namespace mbgl
|
||||||
@@ -93,6 +93,25 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
create("webgpuDawn") {
|
||||||
|
dimension = "renderer"
|
||||||
|
externalNativeBuild {
|
||||||
|
cmake {
|
||||||
|
arguments("-DMLN_WITH_WEBGPU=ON", "-DMLN_WEBGPU_IMPL_DAWN=ON")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
create("webgpuWgpu") {
|
||||||
|
dimension = "renderer"
|
||||||
|
ndk {
|
||||||
|
abiFilters += "arm64-v8a"
|
||||||
|
}
|
||||||
|
externalNativeBuild {
|
||||||
|
cmake {
|
||||||
|
arguments("-DMLN_WITH_WEBGPU=ON", "-DMLN_WEBGPU_IMPL_WGPU=ON")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class HeatmapLayerActivity : AppCompatActivity() {
|
|||||||
Style.Builder()
|
Style.Builder()
|
||||||
.fromUri(TestStyles.getPredefinedStyleWithFallback("Pastel"))
|
.fromUri(TestStyles.getPredefinedStyleWithFallback("Pastel"))
|
||||||
.withSource(createEarthquakeSource())
|
.withSource(createEarthquakeSource())
|
||||||
.withLayerAbove(createHeatmapLayer(), "country_label")
|
.withLayer(createHeatmapLayer())
|
||||||
.withLayerBelow(createCircleLayer(), HEATMAP_LAYER_ID)
|
.withLayerBelow(createCircleLayer(), HEATMAP_LAYER_ID)
|
||||||
)
|
)
|
||||||
} catch (exception: URISyntaxException) {
|
} catch (exception: URISyntaxException) {
|
||||||
|
|||||||
@@ -91,6 +91,14 @@ if(MLN_WITH_VULKAN)
|
|||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(MLN_WITH_WEBGPU)
|
||||||
|
target_sources(
|
||||||
|
mbgl-core
|
||||||
|
PRIVATE
|
||||||
|
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/headless_backend.cpp
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
target_include_directories(
|
target_include_directories(
|
||||||
mbgl-core
|
mbgl-core
|
||||||
PRIVATE ${PROJECT_SOURCE_DIR}/platform/default/include
|
PRIVATE ${PROJECT_SOURCE_DIR}/platform/default/include
|
||||||
|
|||||||
@@ -74,6 +74,25 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
create("webgpuDawn") {
|
||||||
|
dimension = "renderer"
|
||||||
|
externalNativeBuild {
|
||||||
|
cmake {
|
||||||
|
arguments("-DMLN_WITH_WEBGPU=ON", "-DMLN_WEBGPU_IMPL_DAWN=ON")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
create("webgpuWgpu") {
|
||||||
|
dimension = "renderer"
|
||||||
|
ndk {
|
||||||
|
abiFilters += "arm64-v8a"
|
||||||
|
}
|
||||||
|
externalNativeBuild {
|
||||||
|
cmake {
|
||||||
|
arguments("-DMLN_WITH_WEBGPU=ON", "-DMLN_WEBGPU_IMPL_WGPU=ON")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace = "android.app"
|
namespace = "android.app"
|
||||||
|
|||||||
29
vendor/wgpu.android.cmake
vendored
Normal file
29
vendor/wgpu.android.cmake
vendored
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
if(CMAKE_ANDROID_ARCH_ABI STREQUAL "arm64-v8a")
|
||||||
|
set(_wgpu_lib_arch "aarch64-linux-android")
|
||||||
|
elseif(CMAKE_ANDROID_ARCH_ABI STREQUAL "armeabi-v7a")
|
||||||
|
set(_wgpu_lib_arch "armv7-linux-androideabi")
|
||||||
|
elseif(CMAKE_ANDROID_ARCH_ABI STREQUAL "x86_64")
|
||||||
|
set(_wgpu_lib_arch "x86_64-linux-android")
|
||||||
|
elseif(CMAKE_ANDROID_ARCH_ABI STREQUAL "x86")
|
||||||
|
set(_wgpu_lib_arch "i686-linux-android")
|
||||||
|
endif()
|
||||||
|
set(_wgpu_lib_name "libwgpu_native.a")
|
||||||
|
|
||||||
|
macro(mln_wgpu_android_find_library)
|
||||||
|
foreach(_search_path ${_wgpu_lib_search_paths})
|
||||||
|
if(EXISTS "${_search_path}/${_wgpu_lib_name}")
|
||||||
|
set(WGPU_LIBRARY "${_search_path}/${_wgpu_lib_name}" CACHE FILEPATH "wgpu-native library")
|
||||||
|
break()
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
endmacro()
|
||||||
|
|
||||||
|
macro(mln_wgpu_android_link_library target)
|
||||||
|
find_package(Threads REQUIRED)
|
||||||
|
target_link_libraries(${target} INTERFACE
|
||||||
|
Threads::Threads
|
||||||
|
${CMAKE_DL_LIBS}
|
||||||
|
log
|
||||||
|
android
|
||||||
|
)
|
||||||
|
endmacro()
|
||||||
26
vendor/wgpu.cmake
vendored
26
vendor/wgpu.cmake
vendored
@@ -31,7 +31,8 @@ if(APPLE)
|
|||||||
set(_wgpu_lib_arch "x86_64-apple-darwin")
|
set(_wgpu_lib_arch "x86_64-apple-darwin")
|
||||||
endif()
|
endif()
|
||||||
set(_wgpu_lib_name "libwgpu_native.a")
|
set(_wgpu_lib_name "libwgpu_native.a")
|
||||||
set(_wgpu_lib_suffix ".dylib")
|
elseif(ANDROID)
|
||||||
|
include("${CMAKE_CURRENT_LIST_DIR}/wgpu.android.cmake")
|
||||||
elseif(WIN32)
|
elseif(WIN32)
|
||||||
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
|
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||||
set(_wgpu_lib_arch "x86_64-pc-windows-msvc")
|
set(_wgpu_lib_arch "x86_64-pc-windows-msvc")
|
||||||
@@ -39,7 +40,6 @@ elseif(WIN32)
|
|||||||
set(_wgpu_lib_arch "i686-pc-windows-msvc")
|
set(_wgpu_lib_arch "i686-pc-windows-msvc")
|
||||||
endif()
|
endif()
|
||||||
set(_wgpu_lib_name "wgpu_native.lib")
|
set(_wgpu_lib_name "wgpu_native.lib")
|
||||||
set(_wgpu_lib_suffix ".dll")
|
|
||||||
else() # Linux
|
else() # Linux
|
||||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64")
|
if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64")
|
||||||
set(_wgpu_lib_arch "aarch64-unknown-linux-gnu")
|
set(_wgpu_lib_arch "aarch64-unknown-linux-gnu")
|
||||||
@@ -47,22 +47,28 @@ else() # Linux
|
|||||||
set(_wgpu_lib_arch "x86_64-unknown-linux-gnu")
|
set(_wgpu_lib_arch "x86_64-unknown-linux-gnu")
|
||||||
endif()
|
endif()
|
||||||
set(_wgpu_lib_name "libwgpu_native.a")
|
set(_wgpu_lib_name "libwgpu_native.a")
|
||||||
set(_wgpu_lib_suffix ".so")
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Look for prebuilt wgpu-native library
|
# Look for prebuilt wgpu-native library
|
||||||
|
# Search platform-specific path first to prefer cross-compiled libraries
|
||||||
set(_wgpu_lib_search_paths
|
set(_wgpu_lib_search_paths
|
||||||
"${_mln_wgpu_source_dir}/target/release"
|
|
||||||
"${_mln_wgpu_source_dir}/target/${_wgpu_lib_arch}/release"
|
"${_mln_wgpu_source_dir}/target/${_wgpu_lib_arch}/release"
|
||||||
|
"${_mln_wgpu_source_dir}/target/release"
|
||||||
"${_mln_wgpu_source_dir}/lib"
|
"${_mln_wgpu_source_dir}/lib"
|
||||||
"${_mln_wgpu_source_dir}/build/release"
|
"${_mln_wgpu_source_dir}/build/release"
|
||||||
)
|
)
|
||||||
|
|
||||||
find_library(WGPU_LIBRARY
|
# On Android, prefer the static library to avoid needing to ship a separate .so
|
||||||
NAMES wgpu_native libwgpu_native
|
if(ANDROID)
|
||||||
PATHS ${_wgpu_lib_search_paths}
|
mln_wgpu_android_find_library()
|
||||||
NO_DEFAULT_PATH
|
else()
|
||||||
)
|
find_library(WGPU_LIBRARY
|
||||||
|
NAMES wgpu_native libwgpu_native
|
||||||
|
PATHS ${_wgpu_lib_search_paths}
|
||||||
|
NO_DEFAULT_PATH
|
||||||
|
NO_CMAKE_FIND_ROOT_PATH
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
# If not found, try to build it using cargo
|
# If not found, try to build it using cargo
|
||||||
if(NOT WGPU_LIBRARY)
|
if(NOT WGPU_LIBRARY)
|
||||||
@@ -235,6 +241,8 @@ elseif(WIN32)
|
|||||||
bcrypt
|
bcrypt
|
||||||
ntdll
|
ntdll
|
||||||
)
|
)
|
||||||
|
elseif(ANDROID)
|
||||||
|
mln_wgpu_android_link_library(mbgl-vendor-wgpu)
|
||||||
else() # Linux
|
else() # Linux
|
||||||
# wgpu-native on Linux might need these depending on the build configuration
|
# wgpu-native on Linux might need these depending on the build configuration
|
||||||
find_package(Threads REQUIRED)
|
find_package(Threads REQUIRED)
|
||||||
|
|||||||
Reference in New Issue
Block a user