Support for WebGPU backend in iOS SDK, core (#4103)
This commit is contained in:
44
.github/workflows/ios-ci.yml
vendored
44
.github/workflows/ios-ci.yml
vendored
@@ -243,6 +243,48 @@ jobs:
|
||||
- name: Build mbgl-core
|
||||
run: cmake --build build-ios --target mbgl-core ios-sdk-static app
|
||||
|
||||
ios-build-webgpu-dawn:
|
||||
needs: pre_job
|
||||
if: needs.pre_job.outputs.should_skip != 'true'
|
||||
runs-on: macos-15
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4
|
||||
with:
|
||||
submodules: recursive
|
||||
persist-credentials: false
|
||||
|
||||
- name: Configure build with CMake
|
||||
run: cmake --preset ios-webgpu-dawn
|
||||
|
||||
- name: Build mbgl-core and iOS SDK
|
||||
continue-on-error: true
|
||||
run: cmake --build build-ios-webgpu-dawn --target mbgl-core ios-sdk-static
|
||||
|
||||
ios-build-webgpu-wgpu:
|
||||
needs: pre_job
|
||||
if: needs.pre_job.outputs.should_skip != 'true'
|
||||
runs-on: macos-15
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4
|
||||
with:
|
||||
submodules: recursive
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@5d458579430fc14a04a08a1e7d3694f545e91ce6 # stable
|
||||
with:
|
||||
toolchain: stable
|
||||
|
||||
- name: Add iOS Rust target
|
||||
run: rustup target add aarch64-apple-ios
|
||||
|
||||
- name: Configure build with CMake
|
||||
run: cmake --preset ios-webgpu-wgpu
|
||||
|
||||
- name: Build mbgl-core and iOS SDK
|
||||
continue-on-error: true
|
||||
run: cmake --build build-ios-webgpu-wgpu --target mbgl-core ios-sdk-static
|
||||
|
||||
ios-ci-result:
|
||||
runs-on: ubuntu-latest
|
||||
if: needs.pre_job.outputs.should_skip != 'true' && always()
|
||||
@@ -250,6 +292,8 @@ jobs:
|
||||
- pre_job
|
||||
- ios-build
|
||||
- ios-build-cmake
|
||||
- ios-build-webgpu-dawn
|
||||
- ios-build-webgpu-wgpu
|
||||
steps:
|
||||
- name: Mark result as failed
|
||||
if: needs.ios-build.result != 'success' || needs.ios-build-cmake.result != 'success'
|
||||
|
||||
@@ -1206,7 +1206,15 @@ if(TARGET mbgl-vendor-wgpu)
|
||||
list(APPEND EXPORT_TARGETS mbgl-vendor-wgpu)
|
||||
endif()
|
||||
|
||||
export(TARGETS ${EXPORT_TARGETS} FILE MapboxCoreTargets.cmake)
|
||||
# The Xcode generator with Dawn component targets (no monolithic library) breaks export()
|
||||
# because dawn_native/dawn_proc aren't in an export set. Skip export in that case.
|
||||
set(_can_export TRUE)
|
||||
if(TARGET mbgl-vendor-dawn AND NOT TARGET dawn::webgpu_dawn)
|
||||
set(_can_export FALSE)
|
||||
endif()
|
||||
if(_can_export)
|
||||
export(TARGETS ${EXPORT_TARGETS} FILE MapboxCoreTargets.cmake)
|
||||
endif()
|
||||
|
||||
if(MLN_WITH_VULKAN)
|
||||
include(${PROJECT_SOURCE_DIR}/vendor/vulkan.cmake)
|
||||
|
||||
@@ -32,6 +32,26 @@
|
||||
"MLN_WITH_METAL": "ON"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ios-webgpu-dawn",
|
||||
"displayName": "iOS WebGPU (Dawn)",
|
||||
"inherits": "ios",
|
||||
"binaryDir": "${sourceDir}/build-ios-webgpu-dawn",
|
||||
"cacheVariables": {
|
||||
"MLN_WITH_WEBGPU": "ON",
|
||||
"MLN_WEBGPU_IMPL_DAWN": "ON"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ios-webgpu-wgpu",
|
||||
"displayName": "iOS WebGPU (wgpu-native)",
|
||||
"inherits": "ios",
|
||||
"binaryDir": "${sourceDir}/build-ios-webgpu-wgpu",
|
||||
"cacheVariables": {
|
||||
"MLN_WITH_WEBGPU": "ON",
|
||||
"MLN_WEBGPU_IMPL_WGPU": "ON"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "macos",
|
||||
"hidden": true,
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
- [Documentation](./platforms/ios/ios-documentation.md)
|
||||
- [Release](./platforms/ios/release.md)
|
||||
- [Development Apps](./platforms/ios/dev-apps.md)
|
||||
- [Rendering Backends](./platforms/ios/ios-rendering-backends.md)
|
||||
- [WebGPU](./platforms/ios/ios-webgpu.md)
|
||||
- [macOS](./platforms/macos/README.md)
|
||||
- [Linux](./platforms/linux/README.md)
|
||||
- [Using Docker](./platforms/linux/using-docker.md)
|
||||
|
||||
@@ -24,15 +24,15 @@ You can find an app that uses GLFW in [`platform/glfw`](https://github.com/mapli
|
||||
|
||||
## Rendering Backends
|
||||
|
||||
Originally the project only supported OpenGL 2.0. In 2023, the [renderer was modularized](https://github.com/maplibre/maplibre-native/blob/main/design-proposals/2022-10-27-rendering-modularization.md) allowing for the implementation of alternate rendering backends. The first alternate rendering backend that was implemented was [Metal](https://maplibre.org/news/2024-01-19-metal-support-for-maplibre-native-ios-is-here/), followed by [Vulkan](https://maplibre.org/news/2024-12-12-maplibre-android-vulkan/). In the future other rendering backends could be implemented such as WebGPU.
|
||||
Originally the project only supported OpenGL 2.0. In 2023, the [renderer was modularized](https://github.com/maplibre/maplibre-native/blob/main/design-proposals/2022-10-27-rendering-modularization.md) allowing for the implementation of alternate rendering backends. The first alternate rendering backend that was implemented was [Metal](https://maplibre.org/news/2024-01-19-metal-support-for-maplibre-native-ios-is-here/), followed by [Vulkan](https://maplibre.org/news/2024-12-12-maplibre-android-vulkan/), and recently [WebGPU](https://maplibre.org/news/2025-10-04-maplibre-newsletter-september-2025/).
|
||||
|
||||
What platforms support which rendering backend can be found below.
|
||||
|
||||
|
||||
| Platform | OpenGL ES 3.0 | Vulkan 1.0 | Metal | WebGPU |
|
||||
| -------- | -------------- | ---------- | ------ | ------- |
|
||||
| Android | ✅ | ✅ | ❌ | ❌ |
|
||||
| iOS | ❌ | ❌ | ✅ | ❌ |
|
||||
| Android | ✅ | ✅ | ❌ | ✅ |
|
||||
| iOS | ❌ | ❌ | ✅ | ✅ |
|
||||
| Linux | ✅ | ✅ | ❌ | ✅ |
|
||||
| Windows | ✅ | ❌ | ❌ | ❓ |
|
||||
| macOS | ❌ | ✅ | ✅[^1] | ✅ |
|
||||
|
||||
45
docs/mdbook/src/platforms/ios/ios-rendering-backends.md
Normal file
45
docs/mdbook/src/platforms/ios/ios-rendering-backends.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# Rendering Backends
|
||||
|
||||
MapLibre Native for iOS supports multiple rendering backends. The backend is selected at build time through CMake presets.
|
||||
|
||||
## Available backends
|
||||
|
||||
| Preset | Backend | Status | Notes |
|
||||
|---|---|---|---|
|
||||
| `ios-metal` | Metal | Stable | Default, recommended for production |
|
||||
| `ios-webgpu-dawn` | WebGPU (Dawn) | Experimental | Uses Metal internally, see [WebGPU](./ios-webgpu.md) |
|
||||
| `ios-webgpu-wgpu` | WebGPU (wgpu-native) | Experimental | Uses Metal internally, see [WebGPU](./ios-webgpu.md) |
|
||||
|
||||
## Building with a specific backend
|
||||
|
||||
Each preset generates an Xcode project in its own build directory:
|
||||
|
||||
```bash
|
||||
# Metal (default)
|
||||
cmake --preset ios-metal -DDEVELOPMENT_TEAM_ID=YOUR_TEAM_ID
|
||||
xed build-ios/MapLibre\ Native.xcodeproj
|
||||
|
||||
# WebGPU (Dawn)
|
||||
cmake --preset ios-webgpu-dawn -DDEVELOPMENT_TEAM_ID=YOUR_TEAM_ID
|
||||
xed build-ios-webgpu-dawn/MapLibre\ Native.xcodeproj
|
||||
|
||||
# WebGPU (wgpu-native)
|
||||
cmake --preset ios-webgpu-wgpu -DDEVELOPMENT_TEAM_ID=YOUR_TEAM_ID
|
||||
xed build-ios-webgpu-wgpu/MapLibre\ Native.xcodeproj
|
||||
```
|
||||
|
||||
Then build the `app` target in Xcode, or from the command line:
|
||||
|
||||
```bash
|
||||
cmake --build build-ios-webgpu-wgpu -- -sdk iphonesimulator -arch arm64
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
Each preset passes different CMake variables that control which native rendering code gets compiled:
|
||||
|
||||
- `ios-metal`: `-DMLN_WITH_METAL=ON`
|
||||
- `ios-webgpu-dawn`: `-DMLN_WITH_WEBGPU=ON -DMLN_WEBGPU_IMPL_DAWN=ON`
|
||||
- `ios-webgpu-wgpu`: `-DMLN_WITH_WEBGPU=ON -DMLN_WEBGPU_IMPL_WGPU=ON`
|
||||
|
||||
Only one backend is active per build. The Objective-C layer is shared across backends, with renderer-specific code selected at compile time via `#if MLN_WEBGPU_IMPL_DAWN` / `#elif MLN_WEBGPU_IMPL_WGPU` conditionals in `MLNMapView+WebGPU.mm`.
|
||||
65
docs/mdbook/src/platforms/ios/ios-webgpu.md
Normal file
65
docs/mdbook/src/platforms/ios/ios-webgpu.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# WebGPU on iOS
|
||||
|
||||
The iOS platform supports WebGPU as a rendering backend alongside Metal. There are two WebGPU implementations: **Dawn** (Google) and **wgpu-native** (Rust). Both use Metal as the GPU backend on iOS.
|
||||
|
||||
## Building with Dawn
|
||||
|
||||
Dawn is fetched and built from source via CMake `FetchContent`, so no extra setup is needed. The first build takes a while because Dawn pulls in abseil, SPIRV-Tools, Tint, etc. Subsequent builds are incremental.
|
||||
|
||||
```bash
|
||||
cmake --preset ios-webgpu-dawn
|
||||
cmake --build build-ios-webgpu-dawn -- -sdk iphonesimulator -arch arm64
|
||||
```
|
||||
|
||||
## Building with wgpu-native
|
||||
|
||||
wgpu-native requires a pre-built static library for the target architecture. The CMake build will attempt to build it from source if cargo is available, but cross-compilation for iOS needs some setup.
|
||||
|
||||
### 1. Install the Rust iOS targets
|
||||
|
||||
```bash
|
||||
# For simulator (Apple Silicon Mac)
|
||||
rustup target add aarch64-apple-ios-sim
|
||||
|
||||
# For device
|
||||
rustup target add aarch64-apple-ios
|
||||
```
|
||||
|
||||
### 2. Cross-compile wgpu-native
|
||||
|
||||
For the iOS Simulator:
|
||||
|
||||
```bash
|
||||
cd vendor/wgpu-native
|
||||
BINDGEN_EXTRA_CLANG_ARGS="--target=arm64-apple-ios -isysroot $(xcrun --sdk iphoneos --show-sdk-path)" \
|
||||
cargo build --release --target aarch64-apple-ios-sim \
|
||||
--no-default-features --features metal,wgsl,spirv,glsl
|
||||
```
|
||||
|
||||
For a physical device:
|
||||
|
||||
```bash
|
||||
cd vendor/wgpu-native
|
||||
BINDGEN_EXTRA_CLANG_ARGS="--target=arm64-apple-ios -isysroot $(xcrun --sdk iphoneos --show-sdk-path)" \
|
||||
cargo build --release --target aarch64-apple-ios \
|
||||
--no-default-features --features metal,wgsl,spirv,glsl
|
||||
```
|
||||
|
||||
The `BINDGEN_EXTRA_CLANG_ARGS` environment variable is required so that bindgen's macro fallback can find iOS system headers when generating FFI bindings.
|
||||
|
||||
This produces `vendor/wgpu-native/target/<target>/release/libwgpu_native.a`, which the CMake build picks up automatically.
|
||||
|
||||
### 3. Configure and build
|
||||
|
||||
```bash
|
||||
cmake --preset ios-webgpu-wgpu
|
||||
cmake --build build-ios-webgpu-wgpu -- -sdk iphonesimulator -arch arm64
|
||||
```
|
||||
|
||||
If the pre-built library is not found and cargo is available, CMake will attempt to build wgpu-native automatically during configuration.
|
||||
|
||||
## Implementation notes
|
||||
|
||||
The native backend lives in `platform/ios/src/MLNMapView+WebGPU.mm`. It uses `#if MLN_WEBGPU_IMPL_DAWN` / `#elif MLN_WEBGPU_IMPL_WGPU` conditionals for the API differences between the two implementations, following the same pattern as the Android and desktop backends.
|
||||
|
||||
Both backends use a plain `UIView` with a `CAMetalLayer` (not `MTKView`) for the WebGPU surface. The rendering view has `userInteractionEnabled = NO` so touch events pass through to the parent `MLNMapView`.
|
||||
@@ -172,25 +172,35 @@ add_custom_target(mbgl-darwin-style-code
|
||||
DEPENDS ${MLN_GENERATED_DARWIN_STYLE_SOURCE} ${MLN_GENERATED_DARWIN_STYLE_HEADERS}
|
||||
)
|
||||
|
||||
add_library(
|
||||
custom-layer-examples
|
||||
EXCLUDE_FROM_ALL
|
||||
"${CMAKE_CURRENT_LIST_DIR}/app/ExampleCustomDrawableStyleLayer.mm"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/app/CustomStyleLayerExample.m"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/app/PluginLayerExample.mm"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/app/PluginLayerExampleMetalRendering.mm"
|
||||
)
|
||||
# Custom layer examples use OpenGL ES / Metal APIs directly and are not
|
||||
# available for WebGPU builds.
|
||||
if(NOT MLN_WITH_WEBGPU)
|
||||
set(_custom_layer_sources
|
||||
"${CMAKE_CURRENT_LIST_DIR}/app/ExampleCustomDrawableStyleLayer.mm"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/app/CustomStyleLayerExample.m"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/app/PluginLayerExample.mm"
|
||||
)
|
||||
if(MLN_WITH_METAL)
|
||||
list(APPEND _custom_layer_sources "${CMAKE_CURRENT_LIST_DIR}/app/PluginLayerExampleMetalRendering.mm")
|
||||
endif()
|
||||
|
||||
target_link_libraries(
|
||||
custom-layer-examples
|
||||
PUBLIC ios-sdk-static
|
||||
PRIVATE mbgl-compiler-options mbgl-core
|
||||
)
|
||||
add_library(
|
||||
custom-layer-examples
|
||||
EXCLUDE_FROM_ALL
|
||||
${_custom_layer_sources}
|
||||
)
|
||||
|
||||
target_include_directories(
|
||||
custom-layer-examples
|
||||
PUBLIC
|
||||
"${CMAKE_CURRENT_LIST_DIR}/app"
|
||||
PRIVATE
|
||||
"${PROJECT_SOURCE_DIR}/src" # FIXME: should not use private headers
|
||||
)
|
||||
target_link_libraries(
|
||||
custom-layer-examples
|
||||
PUBLIC ios-sdk-static
|
||||
PRIVATE mbgl-compiler-options mbgl-core
|
||||
)
|
||||
|
||||
target_include_directories(
|
||||
custom-layer-examples
|
||||
PUBLIC
|
||||
"${CMAKE_CURRENT_LIST_DIR}/app"
|
||||
PRIVATE
|
||||
"${PROJECT_SOURCE_DIR}/src" # FIXME: should not use private headers
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -48,10 +48,17 @@ add_custom_command(TARGET app POST_BUILD
|
||||
COMMAND ls "$<TARGET_BUNDLE_DIR:app>"
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
app
|
||||
custom-layer-examples # ios-sdk-static transitive dependency
|
||||
)
|
||||
if(TARGET custom-layer-examples)
|
||||
target_link_libraries(
|
||||
app
|
||||
custom-layer-examples # ios-sdk-static transitive dependency
|
||||
)
|
||||
else()
|
||||
target_link_libraries(
|
||||
app
|
||||
ios-sdk-static
|
||||
)
|
||||
endif()
|
||||
|
||||
foreach(resource_file ${APP_RESOURCES})
|
||||
set_source_files_properties(
|
||||
|
||||
9
platform/ios/app/MBXSnapshotTestViewController.h
Normal file
9
platform/ios/app/MBXSnapshotTestViewController.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@class MLNMapView;
|
||||
|
||||
@interface MBXSnapshotTestViewController : UIViewController
|
||||
|
||||
@property (nonatomic, weak) MLNMapView *sourceMapView;
|
||||
|
||||
@end
|
||||
96
platform/ios/app/MBXSnapshotTestViewController.mm
Normal file
96
platform/ios/app/MBXSnapshotTestViewController.mm
Normal file
@@ -0,0 +1,96 @@
|
||||
#import "MBXSnapshotTestViewController.h"
|
||||
#import "MLNMapView+Impl.h"
|
||||
#import "MLNMapView_Private.h"
|
||||
#import "Mapbox.h"
|
||||
|
||||
@interface MBXSnapshotTestViewController () <MLNMapViewDelegate>
|
||||
@property (nonatomic) UIImageView *snapshotImageView;
|
||||
@property (nonatomic, weak) id<MLNMapViewDelegate> originalDelegate;
|
||||
@property (nonatomic, weak) UIView *originalSuperview;
|
||||
@property (nonatomic) NSArray<NSLayoutConstraint *> *mapConstraints;
|
||||
@end
|
||||
|
||||
@implementation MBXSnapshotTestViewController
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
self.title = @"Live Snapshot";
|
||||
self.view.backgroundColor = [UIColor blackColor];
|
||||
|
||||
_snapshotImageView = [[UIImageView alloc] init];
|
||||
_snapshotImageView.contentMode = UIViewContentModeScaleAspectFit;
|
||||
_snapshotImageView.backgroundColor = [UIColor darkGrayColor];
|
||||
_snapshotImageView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[self.view addSubview:_snapshotImageView];
|
||||
|
||||
UILayoutGuide *safe = self.view.safeAreaLayoutGuide;
|
||||
|
||||
if (_sourceMapView) {
|
||||
_originalDelegate = _sourceMapView.delegate;
|
||||
_originalSuperview = _sourceMapView.superview;
|
||||
|
||||
_sourceMapView.delegate = self;
|
||||
_sourceMapView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[self.view addSubview:_sourceMapView];
|
||||
|
||||
_mapConstraints = @[
|
||||
[_snapshotImageView.topAnchor constraintEqualToAnchor:safe.topAnchor],
|
||||
[_snapshotImageView.leadingAnchor constraintEqualToAnchor:safe.leadingAnchor],
|
||||
[_snapshotImageView.trailingAnchor constraintEqualToAnchor:safe.trailingAnchor],
|
||||
[_snapshotImageView.heightAnchor constraintEqualToAnchor:safe.heightAnchor multiplier:0.5],
|
||||
|
||||
[_sourceMapView.topAnchor constraintEqualToAnchor:_snapshotImageView.bottomAnchor],
|
||||
[_sourceMapView.leadingAnchor constraintEqualToAnchor:safe.leadingAnchor],
|
||||
[_sourceMapView.trailingAnchor constraintEqualToAnchor:safe.trailingAnchor],
|
||||
[_sourceMapView.bottomAnchor constraintEqualToAnchor:safe.bottomAnchor],
|
||||
];
|
||||
[NSLayoutConstraint activateConstraints:_mapConstraints];
|
||||
} else {
|
||||
[NSLayoutConstraint activateConstraints:@[
|
||||
[_snapshotImageView.topAnchor constraintEqualToAnchor:safe.topAnchor],
|
||||
[_snapshotImageView.leadingAnchor constraintEqualToAnchor:safe.leadingAnchor],
|
||||
[_snapshotImageView.trailingAnchor constraintEqualToAnchor:safe.trailingAnchor],
|
||||
[_snapshotImageView.bottomAnchor constraintEqualToAnchor:safe.bottomAnchor],
|
||||
]];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)viewWillDisappear:(BOOL)animated {
|
||||
[super viewWillDisappear:animated];
|
||||
|
||||
if (self.isMovingFromParentViewController && _sourceMapView && _originalSuperview) {
|
||||
_sourceMapView.delegate = _originalDelegate;
|
||||
|
||||
if (_mapConstraints) {
|
||||
[NSLayoutConstraint deactivateConstraints:_mapConstraints];
|
||||
_mapConstraints = nil;
|
||||
}
|
||||
|
||||
// Re-pin to all edges; original storyboard constraints were
|
||||
// removed when the map view was reparented into this VC.
|
||||
[_originalSuperview addSubview:_sourceMapView];
|
||||
_sourceMapView.translatesAutoresizingMaskIntoConstraints = NO;
|
||||
[NSLayoutConstraint activateConstraints:@[
|
||||
[_sourceMapView.topAnchor constraintEqualToAnchor:_originalSuperview.topAnchor],
|
||||
[_sourceMapView.leadingAnchor constraintEqualToAnchor:_originalSuperview.leadingAnchor],
|
||||
[_sourceMapView.trailingAnchor constraintEqualToAnchor:_originalSuperview.trailingAnchor],
|
||||
[_sourceMapView.bottomAnchor constraintEqualToAnchor:_originalSuperview.bottomAnchor],
|
||||
]];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)takeSnapshot {
|
||||
if (!_sourceMapView) return;
|
||||
UIImage *image = [_sourceMapView viewImpl]->snapshot();
|
||||
if (image) {
|
||||
self.snapshotImageView.image = image;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - MLNMapViewDelegate
|
||||
|
||||
- (void)mapViewDidFinishRenderingFrame:(MLNMapView *)mapView fullyRendered:(BOOL)fullyRendered {
|
||||
[self takeSnapshot];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -46,8 +46,8 @@
|
||||
MLNMapCamera* mapCamera = [[MLNMapCamera alloc] init];
|
||||
mapCamera.pitch = 20;
|
||||
mapCamera.centerCoordinate = coordinates;
|
||||
MLNMapSnapshotOptions* options = [[MLNMapSnapshotOptions alloc] initWithStyleURL:[NSURL URLWithString:@"https://tiles.openfreemap.org/styles/liberty"] camera:mapCamera size:CGSizeMake(imageView.frame.size.width, imageView.frame.size.height)];
|
||||
options.zoomLevel = 10;
|
||||
MLNMapSnapshotOptions* options = [[MLNMapSnapshotOptions alloc] initWithStyleURL:[NSURL URLWithString:@"https://demotiles.maplibre.org/style.json"] camera:mapCamera size:CGSizeMake(imageView.frame.size.width, imageView.frame.size.height)];
|
||||
options.zoomLevel = 5;
|
||||
options.showsAttribution = !hideAttribution;
|
||||
|
||||
// Create and start the snapshotter
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#import "MBXEmbeddedMapViewController.h"
|
||||
#import "MBXOfflinePacksTableViewController.h"
|
||||
#import "MBXOrnamentsViewController.h"
|
||||
#import "MBXSnapshotTestViewController.h"
|
||||
#import "MBXState.h"
|
||||
#import "MBXStateManager.h"
|
||||
#import "MBXUserLocationAnnotationView.h"
|
||||
@@ -24,9 +25,13 @@
|
||||
#import "MLNMapView_Experimental.h"
|
||||
|
||||
// Plug In Examples
|
||||
#if MLN_RENDER_BACKEND_METAL || MLN_RENDER_BACKEND_OPENGL
|
||||
#import "MLNPluginStyleLayer.h"
|
||||
#import "PluginLayerExample.h"
|
||||
#if MLN_RENDER_BACKEND_METAL
|
||||
#import "PluginLayerExampleMetalRendering.h"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
static const CLLocationCoordinate2D WorldTourDestinations[] = {
|
||||
{.latitude = 38.8999418, .longitude = -77.033996},
|
||||
@@ -129,6 +134,7 @@ typedef NS_ENUM(NSInteger, MBXSettingsMiscellaneousRows) {
|
||||
MBXSettingsMiscellaneousOrnamentsPlacement,
|
||||
MBXSettingsMiscellaneousLatLngBoundsWithPadding,
|
||||
MBXSettingsMiscellaneousCycleTileLOD,
|
||||
MBXSettingsMiscellaneousLiveSnapshot,
|
||||
MBXSettingsMiscellaneousPrintLogFile,
|
||||
MBXSettingsMiscellaneousDeleteLogFile
|
||||
};
|
||||
@@ -273,8 +279,12 @@ CLLocationCoordinate2D randomWorldCoordinate(void) {
|
||||
// This will add the plug-in layers. This is a demo of how
|
||||
// extensible layers for the style can be added to the map view
|
||||
- (void)addPluginLayers {
|
||||
#if MLN_RENDER_BACKEND_METAL || MLN_RENDER_BACKEND_OPENGL
|
||||
[self.mapView addPluginLayerType:[PluginLayerExample class]];
|
||||
#if MLN_RENDER_BACKEND_METAL
|
||||
[self.mapView addPluginLayerType:[PluginLayerExampleMetalRendering class]];
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
@@ -513,7 +523,8 @@ CLLocationCoordinate2D randomWorldCoordinate(void) {
|
||||
[NSString
|
||||
stringWithFormat:@"Turn %@ Content Insets", (_contentInsetsEnabled ? @"Off" : @"On")],
|
||||
@"View Route Simulation", @"Ornaments Placement", @"Lat Long bounds with padding",
|
||||
[NSString stringWithFormat:@"Cycle tile LOD mode: %@", [self getTileLodModeName]]
|
||||
[NSString stringWithFormat:@"Cycle tile LOD mode: %@", [self getTileLodModeName]],
|
||||
@"Live Snapshot"
|
||||
]];
|
||||
|
||||
break;
|
||||
@@ -862,6 +873,12 @@ CLLocationCoordinate2D randomWorldCoordinate(void) {
|
||||
case MBXSettingsMiscellaneousCycleTileLOD:
|
||||
[self cycleTileLodMode];
|
||||
break;
|
||||
case MBXSettingsMiscellaneousLiveSnapshot: {
|
||||
MBXSnapshotTestViewController *vc = [[MBXSnapshotTestViewController alloc] init];
|
||||
vc.sourceMapView = self.mapView;
|
||||
[self.navigationController pushViewController:vc animated:YES];
|
||||
break;
|
||||
}
|
||||
default:
|
||||
NSAssert(NO, @"All miscellaneous setting rows should be implemented");
|
||||
break;
|
||||
@@ -1863,6 +1880,7 @@ CLLocationCoordinate2D randomWorldCoordinate(void) {
|
||||
}
|
||||
|
||||
- (void)addCustomDrawableLayer {
|
||||
#if MLN_RENDER_BACKEND_METAL || MLN_RENDER_BACKEND_OPENGL
|
||||
// Create a CustomLayer that uses the Drawable/Builder toolkit to generate and render geometry
|
||||
ExampleCustomDrawableStyleLayer *layer =
|
||||
[[ExampleCustomDrawableStyleLayer alloc] initWithIdentifier:@"custom-drawable-layer"];
|
||||
@@ -1870,6 +1888,7 @@ CLLocationCoordinate2D randomWorldCoordinate(void) {
|
||||
if (layer) {
|
||||
[self.mapView.style addLayer:layer];
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)removeSource:(NSString *)ident {
|
||||
@@ -1925,10 +1944,12 @@ CLLocationCoordinate2D randomWorldCoordinate(void) {
|
||||
}
|
||||
|
||||
- (void)styleAddCustomTriangleLayer {
|
||||
#if MLN_RENDER_BACKEND_METAL || MLN_RENDER_BACKEND_OPENGL
|
||||
if (CustomStyleLayerExample *layer =
|
||||
[[CustomStyleLayerExample alloc] initWithIdentifier:@"mbx-custom"]) {
|
||||
[self.mapView.style addLayer:layer];
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)stylePolygonWithDDS {
|
||||
@@ -2505,6 +2526,9 @@ CLLocationCoordinate2D randomWorldCoordinate(void) {
|
||||
self.styleURLs = [NSMutableArray array];
|
||||
|
||||
/// Style that does not require an `apiKey` nor any further configuration
|
||||
[self.styleNames addObject:@"OpenFreeMap Liberty"];
|
||||
[self.styleURLs addObject:[NSURL URLWithString:@"https://tiles.openfreemap.org/styles/liberty"]];
|
||||
|
||||
[self.styleNames addObject:@"MapLibre Basic"];
|
||||
[self.styleURLs addObject:[NSURL URLWithString:@"https://demotiles.maplibre.org/style.json"]];
|
||||
|
||||
|
||||
@@ -24,10 +24,12 @@ file(GLOB_RECURSE IOS_SDK_RESOURCE_FILES
|
||||
)
|
||||
|
||||
|
||||
if(NOT MLN_WITH_OPENGL)
|
||||
list(FILTER IOS_SDK_SOURCE_FILES EXCLUDE REGEX ".*(OpenGL|gl).*")
|
||||
elseif(NOT MLN_WITH_METAL)
|
||||
list(FILTER IOS_SDK_SOURCE_FILES EXCLUDE REGEX ".*Metal.*")
|
||||
if(MLN_WITH_METAL)
|
||||
list(FILTER IOS_SDK_SOURCE_FILES EXCLUDE REGEX ".*(OpenGL|gl|WebGPU).*")
|
||||
elseif(MLN_WITH_WEBGPU)
|
||||
list(FILTER IOS_SDK_SOURCE_FILES EXCLUDE REGEX ".*(OpenGL|gl|Metal).*")
|
||||
else()
|
||||
list(FILTER IOS_SDK_SOURCE_FILES EXCLUDE REGEX ".*(Metal|WebGPU).*")
|
||||
endif()
|
||||
|
||||
set(FRAMEWORK_BUNDLE_DIR ${CMAKE_CURRENT_BINARY_DIR}/Mapbox.bundle)
|
||||
@@ -92,6 +94,8 @@ set_target_properties(ios-sdk-static PROPERTIES
|
||||
|
||||
if(MLN_WITH_METAL)
|
||||
message(STATUS "Configuring Metal renderer backend")
|
||||
elseif(MLN_WITH_WEBGPU)
|
||||
message(STATUS "Configuring WebGPU renderer backend")
|
||||
endif()
|
||||
|
||||
target_include_directories(
|
||||
@@ -105,16 +109,28 @@ include("${CMAKE_CURRENT_LIST_DIR}/vendor/calloutview.cmake")
|
||||
target_link_libraries(
|
||||
ios-sdk-static
|
||||
PUBLIC mbgl-core
|
||||
PRIVATE mbgl-compiler-options mbgl-vendor-calloutview mbgl-vendor-metal-cpp mbgl-vendor-polylabel
|
||||
PRIVATE mbgl-compiler-options mbgl-vendor-calloutview mbgl-vendor-polylabel
|
||||
"-framework CoreText"
|
||||
"-framework CoreImage"
|
||||
"-framework CoreGraphics"
|
||||
"-framework QuartzCore"
|
||||
"-framework UIKit"
|
||||
"-framework MetalKit"
|
||||
"-framework ImageIO"
|
||||
)
|
||||
|
||||
if(MLN_WITH_METAL)
|
||||
target_link_libraries(ios-sdk-static PRIVATE mbgl-vendor-metal-cpp "-framework MetalKit")
|
||||
elseif(MLN_WITH_WEBGPU)
|
||||
target_link_libraries(ios-sdk-static PRIVATE "-framework Metal")
|
||||
if(MLN_WEBGPU_IMPL_DAWN AND TARGET mbgl-vendor-dawn)
|
||||
target_compile_definitions(ios-sdk-static PRIVATE MLN_WEBGPU_IMPL_DAWN=1)
|
||||
target_link_libraries(ios-sdk-static PRIVATE mbgl-vendor-dawn)
|
||||
elseif(MLN_WEBGPU_IMPL_WGPU AND TARGET mbgl-vendor-wgpu)
|
||||
target_compile_definitions(ios-sdk-static PRIVATE MLN_WEBGPU_IMPL_WGPU=1)
|
||||
target_link_libraries(ios-sdk-static PRIVATE mbgl-vendor-wgpu)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
target_link_options(ios-sdk-static INTERFACE -ObjC)
|
||||
|
||||
add_dependencies(ios-sdk-static mbgl-darwin-style-code)
|
||||
|
||||
@@ -5,15 +5,24 @@
|
||||
|
||||
#if MLN_RENDER_BACKEND_METAL
|
||||
#import "MLNMapView+Metal.h"
|
||||
#else // MLN_RENDER_BACKEND_OPENGL
|
||||
#elif MLN_RENDER_BACKEND_WEBGPU
|
||||
#import "MLNMapView+WebGPU.h"
|
||||
#elif MLN_RENDER_BACKEND_OPENGL
|
||||
#import "MLNMapView+OpenGL.h"
|
||||
#else
|
||||
#error "No render backend defined"
|
||||
#endif
|
||||
|
||||
std::unique_ptr<MLNMapViewImpl> MLNMapViewImpl::Create(MLNMapView* nativeView) {
|
||||
#if MLN_RENDER_BACKEND_METAL
|
||||
return std::make_unique<MLNMapViewMetalImpl>(nativeView);
|
||||
#else // MLN_RENDER_BACKEND_OPENGL
|
||||
#elif MLN_RENDER_BACKEND_WEBGPU
|
||||
return std::make_unique<MLNMapViewWebGPUImpl>(nativeView);
|
||||
#elif MLN_RENDER_BACKEND_OPENGL
|
||||
return std::make_unique<MLNMapViewOpenGLImpl>(nativeView);
|
||||
#else
|
||||
#error "No render backend defined"
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
50
platform/ios/src/MLNMapView+WebGPU.h
Normal file
50
platform/ios/src/MLNMapView+WebGPU.h
Normal file
@@ -0,0 +1,50 @@
|
||||
#import "MLNMapView+Impl.h"
|
||||
#import "MLNMapView_Private.h"
|
||||
|
||||
#include <mbgl/gfx/renderable.hpp>
|
||||
#include <mbgl/webgpu/renderer_backend.hpp>
|
||||
|
||||
/// Adapter responsible for bridging calls from mbgl to MLNMapView and Cocoa (WebGPU backend).
|
||||
class MLNMapViewWebGPUImpl final : public MLNMapViewImpl,
|
||||
public mbgl::webgpu::RendererBackend,
|
||||
public mbgl::gfx::Renderable {
|
||||
public:
|
||||
MLNMapViewWebGPUImpl(MLNMapView*);
|
||||
~MLNMapViewWebGPUImpl() override;
|
||||
|
||||
// Implementation of mbgl::gfx::RendererBackend
|
||||
public:
|
||||
mbgl::gfx::Renderable& getDefaultRenderable() override { return *this; }
|
||||
|
||||
// webgpu::RendererBackend overrides
|
||||
void* getCurrentTextureView() override;
|
||||
void* getDepthStencilView() override;
|
||||
mbgl::Size getFramebufferSize() const override;
|
||||
void presentSurface();
|
||||
void markNeedsPresent();
|
||||
|
||||
// Implementation of MLNMapViewImpl
|
||||
public:
|
||||
mbgl::gfx::RendererBackend& getRendererBackend() override { return *this; }
|
||||
|
||||
void setOpaque(bool) override;
|
||||
void display() override;
|
||||
void setPresentsWithTransaction(bool) override;
|
||||
void createView() override;
|
||||
UIView* getView() override;
|
||||
void deleteView() override;
|
||||
UIImage* snapshot() override;
|
||||
void layoutChanged() override;
|
||||
MLNBackendResource* getObject() override;
|
||||
|
||||
private:
|
||||
void activate() override;
|
||||
void deactivate() override;
|
||||
|
||||
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;
|
||||
};
|
||||
802
platform/ios/src/MLNMapView+WebGPU.mm
Normal file
802
platform/ios/src/MLNMapView+WebGPU.mm
Normal file
@@ -0,0 +1,802 @@
|
||||
#import "MLNDisplayUtils.h"
|
||||
#import "MLNFoundation_Private.h"
|
||||
#import "MLNLoggingConfiguration_Private.h"
|
||||
#import "MLNMapView+WebGPU.h"
|
||||
|
||||
#import <mbgl/util/logging.hpp>
|
||||
#import <mbgl/webgpu/renderable_resource.hpp>
|
||||
|
||||
#import <Metal/Metal.h>
|
||||
#import <QuartzCore/CAMetalLayer.h>
|
||||
|
||||
#include <webgpu/webgpu.h>
|
||||
|
||||
#if MLN_WEBGPU_IMPL_DAWN
|
||||
#include <dawn/dawn_proc.h>
|
||||
#include <dawn/native/DawnNative.h>
|
||||
#include <dawn/native/MetalBackend.h>
|
||||
#include <webgpu/webgpu_cpp.h>
|
||||
#elif MLN_WEBGPU_IMPL_WGPU
|
||||
#include <webgpu/webgpu_cpp.h>
|
||||
#include <wgpu.h>
|
||||
#endif
|
||||
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
|
||||
#if MLN_WEBGPU_IMPL_WGPU
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#endif
|
||||
|
||||
@interface MLNWebGPUView : UIView
|
||||
@end
|
||||
|
||||
@implementation MLNWebGPUView
|
||||
+ (Class)layerClass {
|
||||
return [CAMetalLayer class];
|
||||
}
|
||||
@end
|
||||
|
||||
class MLNMapViewWebGPURenderableResource final : public mbgl::webgpu::RenderableResource {
|
||||
public:
|
||||
MLNMapViewWebGPURenderableResource(MLNMapViewWebGPUImpl& 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:
|
||||
MLNMapViewWebGPUImpl& backend;
|
||||
};
|
||||
|
||||
class MLNMapViewWebGPUImpl::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;
|
||||
|
||||
mbgl::Size framebufferSize;
|
||||
|
||||
MLNWebGPUView* webGPUView = nil;
|
||||
bool presentsWithTransaction = false;
|
||||
NSUInteger activationCount = 0;
|
||||
};
|
||||
|
||||
MLNMapViewWebGPUImpl::MLNMapViewWebGPUImpl(MLNMapView* nativeView_)
|
||||
: MLNMapViewImpl(nativeView_),
|
||||
mbgl::webgpu::RendererBackend(mbgl::gfx::ContextMode::Unique),
|
||||
mbgl::gfx::Renderable({0, 0}, std::make_unique<MLNMapViewWebGPURenderableResource>(*this)),
|
||||
impl(std::make_unique<Impl>()) {
|
||||
#if MLN_WEBGPU_IMPL_DAWN
|
||||
// Required when not using the monolithic Dawn library.
|
||||
dawnProcSetProcs(&dawn::native::GetProcs());
|
||||
|
||||
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);
|
||||
|
||||
wgpu::RequestAdapterOptions metalOpts = {};
|
||||
metalOpts.backendType = wgpu::BackendType::Metal;
|
||||
std::vector<dawn::native::Adapter> adapters = impl->instance->EnumerateAdapters(&metalOpts);
|
||||
if (adapters.empty()) {
|
||||
adapters = impl->instance->EnumerateAdapters();
|
||||
}
|
||||
if (adapters.empty()) {
|
||||
mbgl::Log::Error(mbgl::Event::Render, "WebGPU iOS: No adapters found");
|
||||
return;
|
||||
}
|
||||
|
||||
dawn::native::Adapter& selectedAdapter = adapters[0];
|
||||
|
||||
bool hasDepth32FloatStencil8 = false;
|
||||
WGPUSupportedFeatures features = {};
|
||||
wgpuAdapterGetFeatures(selectedAdapter.Get(), &features);
|
||||
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) {
|
||||
hasDepth32FloatStencil8 = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
wgpuSupportedFeaturesFreeMembers(features);
|
||||
}
|
||||
#elif MLN_WEBGPU_IMPL_WGPU
|
||||
wgpu::InstanceDescriptor instanceDesc = {};
|
||||
impl->instance = wgpu::createInstance(instanceDesc);
|
||||
if (!impl->instance) {
|
||||
mbgl::Log::Error(mbgl::Event::Render, "WebGPU iOS: Failed to create instance");
|
||||
return;
|
||||
}
|
||||
|
||||
wgpu::RequestAdapterOptions adapterOpts = {};
|
||||
adapterOpts.powerPreference = wgpu::PowerPreference::HighPerformance;
|
||||
adapterOpts.backendType = wgpu::BackendType::Metal;
|
||||
|
||||
impl->adapter = impl->instance.requestAdapter(adapterOpts);
|
||||
if (!impl->adapter) {
|
||||
mbgl::Log::Error(mbgl::Event::Render, "WebGPU iOS: No adapter found");
|
||||
return;
|
||||
}
|
||||
|
||||
WGPUSupportedFeatures features = {};
|
||||
bool hasDepth32FloatStencil8 = false;
|
||||
wgpuAdapterGetFeatures(static_cast<WGPUAdapter>(impl->adapter), &features);
|
||||
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) {
|
||||
hasDepth32FloatStencil8 = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
wgpuSupportedFeaturesFreeMembers(features);
|
||||
}
|
||||
#endif
|
||||
|
||||
std::vector<wgpu::FeatureName> requiredFeatures;
|
||||
wgpu::TextureFormat depthStencilFormat = wgpu::TextureFormat::Depth24PlusStencil8;
|
||||
if (hasDepth32FloatStencil8) {
|
||||
requiredFeatures.push_back(wgpu::FeatureName::Depth32FloatStencil8);
|
||||
depthStencilFormat = wgpu::TextureFormat::Depth32FloatStencil8;
|
||||
}
|
||||
|
||||
wgpu::DeviceDescriptor deviceDesc = {};
|
||||
#if MLN_WEBGPU_IMPL_DAWN
|
||||
deviceDesc.label = "MapLibre iOS WebGPU Device";
|
||||
deviceDesc.SetUncapturedErrorCallback([](const wgpu::Device&, wgpu::ErrorType type,
|
||||
wgpu::StringView message) {
|
||||
NSLog(@"[WebGPU] Device error (type=%d): %.*s", (int)type, (int)message.length, message.data);
|
||||
});
|
||||
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) {
|
||||
mbgl::Log::Error(mbgl::Event::Render, "WebGPU iOS: 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) {
|
||||
mbgl::Log::Error(mbgl::Event::Render, "WebGPU iOS: 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);
|
||||
}
|
||||
|
||||
MLNMapViewWebGPUImpl::~MLNMapViewWebGPUImpl() {
|
||||
if (!impl) return;
|
||||
|
||||
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 MLNMapViewWebGPUImpl::createView() {
|
||||
if (impl->webGPUView) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto scaleFactor = MLNEffectiveScaleFactorForView(mapView);
|
||||
|
||||
impl->webGPUView = [[MLNWebGPUView alloc] initWithFrame:mapView.bounds];
|
||||
impl->webGPUView.autoresizingMask =
|
||||
UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
||||
impl->webGPUView.contentScaleFactor = scaleFactor;
|
||||
impl->webGPUView.contentMode = UIViewContentModeCenter;
|
||||
impl->webGPUView.opaque = mapView.opaque;
|
||||
impl->webGPUView.layer.opaque = mapView.opaque;
|
||||
impl->webGPUView.userInteractionEnabled = NO;
|
||||
|
||||
CAMetalLayer* metalLayer = (CAMetalLayer*)impl->webGPUView.layer;
|
||||
metalLayer.pixelFormat = MTLPixelFormatBGRA8Unorm;
|
||||
metalLayer.framebufferOnly = NO;
|
||||
|
||||
CGSize drawableSize =
|
||||
CGSizeMake(mapView.bounds.size.width * scaleFactor, mapView.bounds.size.height * scaleFactor);
|
||||
metalLayer.drawableSize = drawableSize;
|
||||
|
||||
#if MLN_WEBGPU_IMPL_DAWN
|
||||
// Dawn needs the Metal device set on the layer before creating a surface.
|
||||
id<MTLDevice> mtlDevice = dawn::native::metal::GetMTLDevice(impl->device.Get());
|
||||
metalLayer.device = mtlDevice;
|
||||
#endif
|
||||
|
||||
if (@available(iOS 13.0, *)) {
|
||||
metalLayer.presentsWithTransaction = impl->presentsWithTransaction;
|
||||
}
|
||||
|
||||
[mapView insertSubview:impl->webGPUView atIndex:0];
|
||||
|
||||
createSurface();
|
||||
|
||||
uint32_t w = static_cast<uint32_t>(drawableSize.width);
|
||||
uint32_t h = static_cast<uint32_t>(drawableSize.height);
|
||||
if (w > 0 && h > 0) {
|
||||
impl->framebufferSize = {w, h};
|
||||
size = impl->framebufferSize;
|
||||
configureSurface(w, h);
|
||||
}
|
||||
}
|
||||
|
||||
void MLNMapViewWebGPUImpl::createSurface() {
|
||||
if (!impl->webGPUView) return;
|
||||
|
||||
CAMetalLayer* metalLayer = (CAMetalLayer*)impl->webGPUView.layer;
|
||||
|
||||
#if MLN_WEBGPU_IMPL_DAWN
|
||||
if (!impl->instance) return;
|
||||
|
||||
WGPUSurfaceSourceMetalLayer metalDesc = WGPU_SURFACE_SOURCE_METAL_LAYER_INIT;
|
||||
metalDesc.layer = (__bridge void*)metalLayer;
|
||||
|
||||
WGPUSurfaceDescriptor surfaceDesc = WGPU_SURFACE_DESCRIPTOR_INIT;
|
||||
surfaceDesc.nextInChain = &metalDesc.chain;
|
||||
|
||||
WGPUInstance rawInstance = impl->instance->Get();
|
||||
impl->surface = wgpuInstanceCreateSurface(rawInstance, &surfaceDesc);
|
||||
#elif MLN_WEBGPU_IMPL_WGPU
|
||||
if (!impl->instance) return;
|
||||
|
||||
WGPUSurfaceSourceMetalLayer metalDesc = {};
|
||||
metalDesc.chain.sType = WGPUSType_SurfaceSourceMetalLayer;
|
||||
metalDesc.chain.next = nullptr;
|
||||
metalDesc.layer = (__bridge void*)metalLayer;
|
||||
|
||||
WGPUSurfaceDescriptor surfaceDesc = {};
|
||||
surfaceDesc.nextInChain = &metalDesc.chain;
|
||||
|
||||
impl->surface =
|
||||
wgpuInstanceCreateSurface(static_cast<WGPUInstance>(impl->instance), &surfaceDesc);
|
||||
#endif
|
||||
|
||||
if (!impl->surface) {
|
||||
mbgl::Log::Error(mbgl::Event::Render, "WebGPU iOS: Failed to create surface");
|
||||
}
|
||||
}
|
||||
|
||||
void MLNMapViewWebGPUImpl::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 MLNMapViewWebGPUImpl::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) {
|
||||
mbgl::Log::Warning(mbgl::Event::Render, "WebGPU iOS: 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) {
|
||||
mbgl::Log::Warning(mbgl::Event::Render, "WebGPU iOS: Failed to create depth/stencil view");
|
||||
}
|
||||
}
|
||||
|
||||
void* MLNMapViewWebGPUImpl::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;
|
||||
|
||||
#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 MLNMapViewWebGPUImpl::markNeedsPresent() { impl->needsPresent = true; }
|
||||
|
||||
void MLNMapViewWebGPUImpl::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* MLNMapViewWebGPUImpl::getDepthStencilView() { return impl->depthStencilView; }
|
||||
|
||||
mbgl::Size MLNMapViewWebGPUImpl::getFramebufferSize() const { return impl->framebufferSize; }
|
||||
|
||||
UIView* MLNMapViewWebGPUImpl::getView() { return impl->webGPUView; }
|
||||
|
||||
void MLNMapViewWebGPUImpl::setOpaque(const bool opaque) {
|
||||
impl->webGPUView.opaque = opaque;
|
||||
impl->webGPUView.layer.opaque = opaque;
|
||||
}
|
||||
|
||||
void MLNMapViewWebGPUImpl::setPresentsWithTransaction(const bool value) {
|
||||
impl->presentsWithTransaction = value;
|
||||
|
||||
if (@available(iOS 13.0, *)) {
|
||||
if (CAMetalLayer* metalLayer = MLN_OBJC_DYNAMIC_CAST(impl->webGPUView.layer, CAMetalLayer)) {
|
||||
metalLayer.presentsWithTransaction = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MLNMapViewWebGPUImpl::display() { render(); }
|
||||
|
||||
void MLNMapViewWebGPUImpl::deleteView() {
|
||||
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);
|
||||
impl->surfaceConfigured = false;
|
||||
}
|
||||
wgpuSurfaceRelease(impl->surface);
|
||||
impl->surface = nullptr;
|
||||
}
|
||||
|
||||
[impl->webGPUView removeFromSuperview];
|
||||
impl->webGPUView = nil;
|
||||
}
|
||||
|
||||
void MLNMapViewWebGPUImpl::layoutChanged() {
|
||||
const auto scaleFactor = MLNEffectiveScaleFactorForView(mapView);
|
||||
const auto screenSize = mapView.bounds.size;
|
||||
|
||||
impl->webGPUView.contentScaleFactor = scaleFactor;
|
||||
|
||||
CGSize drawableSize = CGSizeMake(screenSize.width * scaleFactor, screenSize.height * scaleFactor);
|
||||
|
||||
CAMetalLayer* metalLayer = (CAMetalLayer*)impl->webGPUView.layer;
|
||||
metalLayer.drawableSize = drawableSize;
|
||||
|
||||
uint32_t w = static_cast<uint32_t>(drawableSize.width);
|
||||
uint32_t h = static_cast<uint32_t>(drawableSize.height);
|
||||
|
||||
impl->framebufferSize = {w, h};
|
||||
size = impl->framebufferSize;
|
||||
|
||||
if (impl->currentTextureView) {
|
||||
wgpuTextureViewRelease(impl->currentTextureView);
|
||||
impl->currentTextureView = nullptr;
|
||||
}
|
||||
if (impl->currentTexture) {
|
||||
wgpuTextureRelease(impl->currentTexture);
|
||||
impl->currentTexture = nullptr;
|
||||
}
|
||||
|
||||
if (w > 0 && h > 0) {
|
||||
configureSurface(w, h);
|
||||
}
|
||||
}
|
||||
|
||||
UIImage* MLNMapViewWebGPUImpl::snapshot() {
|
||||
if (!impl->currentTexture || !impl->device || !impl->queue || impl->framebufferSize.width == 0 ||
|
||||
impl->framebufferSize.height == 0) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
#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 = {"Snapshot Buffer", strlen("Snapshot Buffer")};
|
||||
bufferDesc.label = bufferLabel;
|
||||
#endif
|
||||
bufferDesc.size = alignedDataSize;
|
||||
bufferDesc.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_MapRead;
|
||||
|
||||
WGPUBuffer stagingBuffer = wgpuDeviceCreateBuffer(device, &bufferDesc);
|
||||
if (!stagingBuffer) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
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;
|
||||
wgpuInstanceWaitAny(instance, 1, &waitInfo, UINT64_MAX);
|
||||
}
|
||||
#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));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
UIImage* result = nil;
|
||||
|
||||
if (mapContext.status == WGPUMapAsyncStatus_Success) {
|
||||
const auto* mappedData = static_cast<const uint8_t*>(
|
||||
wgpuBufferGetConstMappedRange(stagingBuffer, 0, alignedDataSize));
|
||||
if (mappedData) {
|
||||
auto rgbaData = std::make_unique<uint8_t[]>(static_cast<size_t>(rowStride) * fbSize.height);
|
||||
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 = rgbaData.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);
|
||||
}
|
||||
}
|
||||
|
||||
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
|
||||
CGContextRef ctx =
|
||||
CGBitmapContextCreate(rgbaData.get(), fbSize.width, fbSize.height, 8, rowStride,
|
||||
colorSpace, kCGImageAlphaPremultipliedLast);
|
||||
if (ctx) {
|
||||
CGImageRef cgImage = CGBitmapContextCreateImage(ctx);
|
||||
if (cgImage) {
|
||||
const CGFloat scale = impl->webGPUView.contentScaleFactor;
|
||||
result = [UIImage imageWithCGImage:cgImage scale:scale orientation:UIImageOrientationUp];
|
||||
CGImageRelease(cgImage);
|
||||
}
|
||||
CGContextRelease(ctx);
|
||||
}
|
||||
CGColorSpaceRelease(colorSpace);
|
||||
}
|
||||
}
|
||||
|
||||
wgpuBufferUnmap(stagingBuffer);
|
||||
wgpuBufferRelease(stagingBuffer);
|
||||
|
||||
presentSurface();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
MLNBackendResource* MLNMapViewWebGPUImpl::getObject() { return [[MLNBackendResource alloc] init]; }
|
||||
|
||||
void MLNMapViewWebGPUImpl::activate() {
|
||||
if (impl->activationCount++) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void MLNMapViewWebGPUImpl::deactivate() {
|
||||
if (--impl->activationCount) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,10 @@
|
||||
#include <mbgl/map/map_options.hpp>
|
||||
#include <mbgl/map/mode.hpp>
|
||||
#include <mbgl/math/wrap.hpp>
|
||||
#if MLN_RENDER_BACKEND_METAL
|
||||
#include <mbgl/mtl/mtl_fwd.hpp>
|
||||
#include <mbgl/mtl/render_pass.hpp>
|
||||
#endif
|
||||
#include <mbgl/plugin/plugin_layer.hpp>
|
||||
#include <mbgl/plugin/plugin_layer_factory.hpp>
|
||||
#include <mbgl/plugin/plugin_layer_impl.hpp>
|
||||
@@ -7602,10 +7604,13 @@ static void *windowScreenContext = &windowScreenContext;
|
||||
|
||||
// Set the render function
|
||||
auto renderFunction = [weakPlugInLayer, weakMapView](mbgl::PaintParameters &paintParameters) {
|
||||
#if MLN_RENDER_BACKEND_METAL
|
||||
const mbgl::mtl::RenderPass &renderPass =
|
||||
static_cast<mbgl::mtl::RenderPass &>(*paintParameters.renderPass);
|
||||
id<MTLRenderCommandEncoder> encoder =
|
||||
(__bridge id<MTLRenderCommandEncoder>)renderPass.getMetalEncoder().get();
|
||||
id encoder = (__bridge id)renderPass.getMetalEncoder().get();
|
||||
#else
|
||||
id encoder = nil;
|
||||
#endif
|
||||
|
||||
MLNMapView *strongMapView = weakMapView;
|
||||
|
||||
|
||||
34
vendor/dawn.cmake
vendored
34
vendor/dawn.cmake
vendored
@@ -57,18 +57,48 @@ set(DAWN_BUILD_SAMPLES OFF CACHE BOOL "Disable Dawn samples" FORCE)
|
||||
set(DAWN_BUILD_TESTS OFF CACHE BOOL "Disable Dawn tests" FORCE)
|
||||
set(DAWN_BUILD_BENCHMARKS OFF CACHE BOOL "Disable Dawn benchmarks" FORCE)
|
||||
set(DAWN_BUILD_NODE_BINDINGS OFF CACHE BOOL "Disable Dawn Node bindings" FORCE)
|
||||
set(TINT_BUILD_CMD_TOOLS OFF CACHE BOOL "Disable Tint command line tools" FORCE)
|
||||
set(DAWN_WERROR OFF CACHE BOOL "Disable -Werror in Dawn" FORCE)
|
||||
set(DAWN_BUILD_PROTOBUF OFF CACHE BOOL "Disable protobuf for cross-compilation" FORCE)
|
||||
|
||||
if(NOT TARGET dawn::webgpu_dawn)
|
||||
# GLFW is not available on iOS (Dawn auto-detects it via UNIX AND NOT ANDROID)
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "iOS")
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/dawn.ios.cmake")
|
||||
endif()
|
||||
|
||||
# The Xcode generator does not properly handle $<TARGET_OBJECTS:...> in add_library(),
|
||||
# which breaks Dawn's bundle_libraries() macro. Disable the monolithic library for Xcode
|
||||
# and link individual Dawn component targets instead.
|
||||
if(CMAKE_GENERATOR STREQUAL "Xcode")
|
||||
set(DAWN_BUILD_MONOLITHIC_LIBRARY "OFF" CACHE STRING "" FORCE)
|
||||
endif()
|
||||
|
||||
if(NOT TARGET dawn_native)
|
||||
if(APPLE)
|
||||
enable_language(OBJC OBJCXX)
|
||||
endif()
|
||||
add_subdirectory(${DAWN_DIR} ${DAWN_BUILD_DIR})
|
||||
endif()
|
||||
|
||||
# Tint's MSL validator includes Metal headers which use GNU anonymous structs.
|
||||
# Tint compiles with -pedantic-errors, turning these into build errors.
|
||||
if(TARGET tint_lang_msl_validate)
|
||||
target_compile_options(tint_lang_msl_validate PRIVATE
|
||||
-Wno-gnu-anonymous-struct -Wno-nested-anon-types)
|
||||
endif()
|
||||
|
||||
add_library(mbgl-vendor-dawn INTERFACE)
|
||||
|
||||
target_link_libraries(mbgl-vendor-dawn INTERFACE dawn::webgpu_dawn)
|
||||
if(TARGET dawn::webgpu_dawn)
|
||||
target_link_libraries(mbgl-vendor-dawn INTERFACE dawn::webgpu_dawn)
|
||||
else()
|
||||
# Xcode path: link individual Dawn component targets.
|
||||
# Use BUILD_INTERFACE to avoid export-set requirements.
|
||||
target_link_libraries(mbgl-vendor-dawn INTERFACE
|
||||
$<BUILD_INTERFACE:dawn_native>
|
||||
$<BUILD_INTERFACE:dawn_proc>
|
||||
)
|
||||
endif()
|
||||
|
||||
target_include_directories(mbgl-vendor-dawn
|
||||
SYSTEM INTERFACE
|
||||
|
||||
2
vendor/dawn.ios.cmake
generated
vendored
Normal file
2
vendor/dawn.ios.cmake
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# GLFW is not available on iOS (Dawn auto-detects it via UNIX AND NOT ANDROID)
|
||||
set(DAWN_USE_GLFW OFF CACHE BOOL "Disable GLFW on iOS" FORCE)
|
||||
9
vendor/wgpu.android.cmake
vendored
9
vendor/wgpu.android.cmake
vendored
@@ -9,14 +9,7 @@ elseif(CMAKE_ANDROID_ARCH_ABI STREQUAL "x86")
|
||||
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)
|
||||
|
||||
69
vendor/wgpu.cmake
vendored
69
vendor/wgpu.cmake
vendored
@@ -25,10 +25,14 @@ endif()
|
||||
|
||||
# Platform-specific library names and paths
|
||||
if(APPLE)
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm64|aarch64")
|
||||
set(_wgpu_lib_arch "aarch64-apple-darwin")
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "iOS")
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/wgpu.ios.cmake")
|
||||
else()
|
||||
set(_wgpu_lib_arch "x86_64-apple-darwin")
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm64|aarch64")
|
||||
set(_wgpu_lib_arch "aarch64-apple-darwin")
|
||||
else()
|
||||
set(_wgpu_lib_arch "x86_64-apple-darwin")
|
||||
endif()
|
||||
endif()
|
||||
set(_wgpu_lib_name "libwgpu_native.a")
|
||||
elseif(ANDROID)
|
||||
@@ -53,24 +57,37 @@ endif()
|
||||
# Search platform-specific path first to prefer cross-compiled libraries
|
||||
set(_wgpu_lib_search_paths
|
||||
"${_mln_wgpu_source_dir}/target/${_wgpu_lib_arch}/release"
|
||||
)
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "iOS")
|
||||
mln_wgpu_ios_append_search_paths(_wgpu_lib_search_paths)
|
||||
endif()
|
||||
list(APPEND _wgpu_lib_search_paths
|
||||
"${_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()
|
||||
# On Android/iOS, use manual path search since find_library may not work with cross-compilation toolchains
|
||||
macro(mln_wgpu_find_library)
|
||||
if(ANDROID OR CMAKE_SYSTEM_NAME STREQUAL "iOS")
|
||||
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()
|
||||
else()
|
||||
find_library(WGPU_LIBRARY
|
||||
NAMES wgpu_native libwgpu_native
|
||||
PATHS ${_wgpu_lib_search_paths}
|
||||
NO_DEFAULT_PATH
|
||||
NO_CMAKE_FIND_ROOT_PATH
|
||||
)
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
mln_wgpu_find_library()
|
||||
|
||||
# If not found, try to build it using cargo
|
||||
if(NOT WGPU_LIBRARY)
|
||||
message(STATUS "Pre-built wgpu-native library not found, attempting to build from source...")
|
||||
|
||||
@@ -89,8 +106,22 @@ if(NOT WGPU_LIBRARY)
|
||||
# Build wgpu-native using cargo
|
||||
message(STATUS "Building wgpu-native with cargo (this may take a few minutes)...")
|
||||
|
||||
# For cross-compilation targets (iOS, Android), pass --target and platform-specific features
|
||||
set(_cargo_extra_args "")
|
||||
if(_wgpu_lib_arch AND NOT _wgpu_lib_arch MATCHES "apple-darwin$")
|
||||
list(APPEND _cargo_extra_args --target ${_wgpu_lib_arch})
|
||||
endif()
|
||||
if(_wgpu_cargo_features)
|
||||
list(APPEND _cargo_extra_args ${_wgpu_cargo_features})
|
||||
endif()
|
||||
|
||||
set(_cargo_env_prefix "")
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "iOS")
|
||||
mln_wgpu_ios_setup_cargo_env(_cargo_env_prefix)
|
||||
endif()
|
||||
|
||||
execute_process(
|
||||
COMMAND ${CARGO_EXECUTABLE} build --release
|
||||
COMMAND ${_cargo_env_prefix} ${CARGO_EXECUTABLE} build --release ${_cargo_extra_args}
|
||||
WORKING_DIRECTORY ${_mln_wgpu_source_dir}
|
||||
RESULT_VARIABLE _cargo_result
|
||||
OUTPUT_VARIABLE _cargo_output
|
||||
@@ -105,11 +136,7 @@ if(NOT WGPU_LIBRARY)
|
||||
endif()
|
||||
|
||||
# Try to find the library again
|
||||
find_library(WGPU_LIBRARY
|
||||
NAMES wgpu_native libwgpu_native
|
||||
PATHS ${_wgpu_lib_search_paths}
|
||||
NO_DEFAULT_PATH
|
||||
)
|
||||
mln_wgpu_find_library()
|
||||
|
||||
if(NOT WGPU_LIBRARY)
|
||||
message(FATAL_ERROR "Failed to locate wgpu-native library after building")
|
||||
|
||||
30
vendor/wgpu.ios.cmake
generated
vendored
Normal file
30
vendor/wgpu.ios.cmake
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
if(CMAKE_OSX_SYSROOT MATCHES "[Ss]imulator")
|
||||
set(_wgpu_lib_arch "aarch64-apple-ios-sim")
|
||||
else()
|
||||
set(_wgpu_lib_arch "aarch64-apple-ios")
|
||||
endif()
|
||||
set(_wgpu_cargo_features "--no-default-features;--features;metal,wgsl,spirv,glsl")
|
||||
|
||||
macro(mln_wgpu_ios_append_search_paths paths)
|
||||
# With the Xcode generator, CMAKE_OSX_SYSROOT is "iphoneos" at configure time even when
|
||||
# the user builds for simulator. Search both iOS architectures so the right one is found.
|
||||
if(_wgpu_lib_arch STREQUAL "aarch64-apple-ios")
|
||||
list(APPEND ${paths} "${_mln_wgpu_source_dir}/target/aarch64-apple-ios-sim/release")
|
||||
else()
|
||||
list(APPEND ${paths} "${_mln_wgpu_source_dir}/target/aarch64-apple-ios/release")
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
|
||||
|
||||
macro(mln_wgpu_ios_setup_cargo_env env_prefix_var)
|
||||
# On iOS, bindgen's clang_macro_fallback() uses a separate clang process that
|
||||
# doesn't inherit the .clang_arg() settings from build.rs. We must set
|
||||
# BINDGEN_EXTRA_CLANG_ARGS so it can find system headers (needed for UINT32_MAX etc.)
|
||||
execute_process(
|
||||
COMMAND xcrun --sdk iphoneos --show-sdk-path
|
||||
OUTPUT_VARIABLE _ios_sdk_path
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
set(${env_prefix_var} ${CMAKE_COMMAND} -E env "BINDGEN_EXTRA_CLANG_ARGS=--target=arm64-apple-ios -isysroot ${_ios_sdk_path}")
|
||||
endmacro()
|
||||
Reference in New Issue
Block a user