Support for WebGPU backend in Android SDK (#4097)

This commit is contained in:
Birk Skyum
2026-02-28 16:24:14 +01:00
committed by GitHub
parent 166593f0fb
commit 57a14cf110
15 changed files with 1242 additions and 11 deletions

View File

@@ -311,6 +311,124 @@ jobs:
with:
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:
runs-on: ubuntu-latest
permissions:
@@ -322,9 +440,11 @@ jobs:
- android-build
- android-build-cpp-test
- android-build-render-test
- android-build-webgpu-dawn
- android-build-webgpu-wgpu
steps:
- 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"
- name: Mark result as failed

View File

@@ -185,6 +185,36 @@
"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",
"inherits": "linux",

View File

@@ -8,6 +8,8 @@
- [Documentation](./platforms/android/android-documentation.md)
- [Benchmark](./platforms/android/benchmark.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)
- [Tests](./platforms/ios/ios-tests.md)
- [Documentation](./platforms/ios/ios-documentation.md)

View File

@@ -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.

View 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.

View File

@@ -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 {
getByName("opengl") {
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

View File

@@ -217,6 +217,22 @@ if(MLN_WITH_VULKAN)
)
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(
maplibre
PRIVATE ${PROJECT_SOURCE_DIR}

View File

@@ -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, &copySize);
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

View File

@@ -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

View File

@@ -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 {

View File

@@ -36,7 +36,7 @@ class HeatmapLayerActivity : AppCompatActivity() {
Style.Builder()
.fromUri(TestStyles.getPredefinedStyleWithFallback("Pastel"))
.withSource(createEarthquakeSource())
.withLayerAbove(createHeatmapLayer(), "country_label")
.withLayer(createHeatmapLayer())
.withLayerBelow(createCircleLayer(), HEATMAP_LAYER_ID)
)
} catch (exception: URISyntaxException) {

View File

@@ -91,6 +91,14 @@ if(MLN_WITH_VULKAN)
)
endif()
if(MLN_WITH_WEBGPU)
target_sources(
mbgl-core
PRIVATE
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/headless_backend.cpp
)
endif()
target_include_directories(
mbgl-core
PRIVATE ${PROJECT_SOURCE_DIR}/platform/default/include

View File

@@ -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"

29
vendor/wgpu.android.cmake vendored Normal file
View 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()

16
vendor/wgpu.cmake vendored
View File

@@ -31,7 +31,8 @@ if(APPLE)
set(_wgpu_lib_arch "x86_64-apple-darwin")
endif()
set(_wgpu_lib_name "libwgpu_native.a")
set(_wgpu_lib_suffix ".dylib")
elseif(ANDROID)
include("${CMAKE_CURRENT_LIST_DIR}/wgpu.android.cmake")
elseif(WIN32)
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
set(_wgpu_lib_arch "x86_64-pc-windows-msvc")
@@ -39,7 +40,6 @@ elseif(WIN32)
set(_wgpu_lib_arch "i686-pc-windows-msvc")
endif()
set(_wgpu_lib_name "wgpu_native.lib")
set(_wgpu_lib_suffix ".dll")
else() # Linux
if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64")
set(_wgpu_lib_arch "aarch64-unknown-linux-gnu")
@@ -47,22 +47,28 @@ else() # Linux
set(_wgpu_lib_arch "x86_64-unknown-linux-gnu")
endif()
set(_wgpu_lib_name "libwgpu_native.a")
set(_wgpu_lib_suffix ".so")
endif()
# Look for prebuilt wgpu-native library
# Search platform-specific path first to prefer cross-compiled libraries
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/release"
"${_mln_wgpu_source_dir}/lib"
"${_mln_wgpu_source_dir}/build/release"
)
# On Android, prefer the static library to avoid needing to ship a separate .so
if(ANDROID)
mln_wgpu_android_find_library()
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 WGPU_LIBRARY)
@@ -235,6 +241,8 @@ elseif(WIN32)
bcrypt
ntdll
)
elseif(ANDROID)
mln_wgpu_android_link_library(mbgl-vendor-wgpu)
else() # Linux
# wgpu-native on Linux might need these depending on the build configuration
find_package(Threads REQUIRED)