Introduction
maplibre-rs is a portable and performant vector maps renderer.
Name
The name of the library is "maplibre-rs". The camel-case version of it is "MapLibreRs".
Supported Platforms
For development the following platforms are recommended:
- Linux X11/Wayland
- MacOS
- Latest Firefox Nightly/Chrome Canary with WebGPU (Because WebGPU is a living spec, sometimes a bleeding-edge browser release is required)
Short-term Obstacles
Platform | Obstacles |
---|---|
Linux X11 | |
Linux Wayland | |
Windows | |
MacOS | |
Android | * Unable to get window size before resume |
iOS | * Touches are crashing the app on real devices * Instanced indices drawing is not supported * Drawing zero-length indices is prohibited |
Firefox | * Shared Memory is currently not working because it a parallel web worker corrupts memory |
Chrome | |
Safari | |
Mobile Firefox | |
Mobile Chrome | |
Mobile Safari |
Long-term Goals
WebGPU is not enabled by default for all platforms.
WebGPU Status:
Platform | Linux & Android | Graphics API | Note |
---|---|---|---|
Linux X11 | ✅ | Vulkan | |
Linux Wayland | ✅ | Vulkan | |
Windows | ❓ | ❓ | |
MacOS | ✅ | ❓ | |
Android | ✅ | Vulkan/OpenGL ES/Angle | Not tested, but should work on all devices if Angle is used. Vulkan is not yet supported widely. |
iOS | ✅ | Metal | Not tested. |
Firefox | ✅ | WebGL/WebGPU | |
Chrome | ✅ | WebGL/WebGPU | WebGPU is significantly faster because WASM output is smaller. |
Safari | 🛠️ | WebGL/WebGPU | Safari does not yet support Shared Array Buffer |
Mobile Firefox | 🆗 | WebGL/WebGPU | |
Mobile Chrome | 🆗 | WebGL | WebGPU is not implemented. |
Mobile Safari | 🛠️ | WebGL | WebGPU is not implemented. Safari does not yet support Shared Array Buffer |
✅ = First Class Support — 🆗= Best Effort Support — 🛠️ = Unsupported, but support in progress
Developer Log
I'm regularly releasing blog posts on my blog.
User Guide
Development Guide
Running maplibre-rs demos on various platforms
During development, you will want to run the maplibre demos on your local machine to test out your changes. There are multiple demos of maplibre-rs for different targets. Some targets have prerequisites depending on your operating system.
- maplibre-demo - targets Windows, macOS and Linux, it is built directly with cargo.
- apple - targets iOS and macOS and relies on the xcode IDE.
- android - targets Android devices and builds in Android Studio.
- web - targets the web using a WASM binary.
- maplibre-headless - TBD
All the targets below require you to install rustup to manage your Rust toolchain.
Note: Make sure you have selected the right toolchain target within rustup. You can use
rustup show
to see your active toolchain. If you want to change the target of the build manually, use the cargo--target
parameter.
Maplibre-demo
Linux/macOS
The build for desktop is very simple, you just have to run the following command from the root of the maplibre-rs project:
cargo run -p maplibre-demo
Windows
Windows has two additional prerequisites to be able to run. You will need CMake, Visual Studio C++ build tools and the sqlite3 library.
Install CMake and add it to your path environment variables.
For the C++ build tools, download the Visual Studio 2022 Build tools from the Microsoft website. After the download, while installing the Build tools, make sure that you select the C++ build tools.
To install sqlite3 you need to build the sqlite3.lib manually with the following steps. This will generate a .lib file that you will have to add to the SQLITE3_LIB_DIR environment variable.
Restart your shell to make sure you are using up-to-date environment variables.
Finally, the command below should execute successfully:
cargo run -p maplibre-demo
Android
Start by installing the Android Studio IDE.
Make sure the NDK is installed. The Native Development Kit (NDK) is a set of tools that allows
you to use C and C++ code with Android. You have to install manually the version that is used in
./android/gradle/lib/build.gradle
.
ANDROID STUDIO -> tools -> SDK manager -> SDK tools -> tick show package details -> ndk (side by side)
Open the project within ./android/gradle
and create a new virtual device with the device manager. Minimum SDK version
should be 21. This was tested on an x86_64 emulator. Finally, run the demo configuration. It should open your virtual device and
run the maplibre-rs Android demo on it. Alternatively you can also run it on your own Android device.
Note: If you are building for an x86 Android device, you probably need to install the following target using
rustup with the following commandrustup target add i686-linux-android
.
Note: Android is configured to support OpenGL ES 3.1 (This API specification is supported by Android 5.0 (API level 21) and higher). Your Android device is required to support OpenGL ES 3.1 at least. There are some issues here and here that discuss configuration of Android Studio for OpenGL ES 3.1 support in emulators.
Apple
Apple builds rely on the XCode IDE.
Start by installing XCode and open the project within ./apple/xcode
.
Cargo is used in to build the maplibre library in the build phases of the XCode project configuration.
iOS
You can use XCode to run on a iOS Simulator or a real device. Install a simulator in XCode. Version 9 is the minimum version supported theoretically.
Select the scheme called example (iOS) and click on run. This will start the iOS application.
macOS
As you might have seen in the maplibre-demo section, you can build Unix executables directly with Cargo.
In order to build a proper macOS application (in OSX terminology) you have to use the ./apple/xcode
project.
Open the project from the folder ./apple/xcode
with XCode. Select the scheme called example (macOS) and
click on run. This will start the macOS application.
The minimum target OSX version for the macOS build is defined inside Build settings -> Deployment -> macOS deployment target. If you are using a lower version of OSX, you will not be able to run the application on your computer.
Web (WebGL, WebGPU)
You need to first build the library for WebGL or WebGPU. Optionally, you can also enabled multi-threading support, which requires that the library is used in a secure environment: isSecureContext and crossOriginIsolated. The demo runs this such an environment.
If you have a browser which already supports a recent version of the WebGPU specification then you can build the library with WebGPU:
just web-lib build # WebGPU
If not, then you must enable WebGL support:
just web-lib build --webgl # WebGL
just web-lib build --webgl --multithreaded # WebGL + multithreaded
Instead of building it is also possible to watch for changes. The same flags as with web-lib build
are supported:
just web-lib watch --webgl
After building the library you can run the demo server:
just web-demo start
Building Libraries
TODO
Debugging
- log crate
GPU Debugging
- For WebGL there is SpectorJS is enabled by default right now. For debugging on a desktop environment you can use RenderDoc.
Frame Profiling
maplibre-rs is set up to use the Tracy profiler (https://github.com/wolfpld/tracy). It's mainly designed for C++ but has some Rust support.
The connection to Rust uses a project that connects to the tracing crate (https://github.com/nagisa/rust_tracy_client). This uses a set of three crates (tracing-tracy, tracy-client, tracy-client-sys).
Unfortunately, the Tracy project does not use semantic versioning, whereas tracing-tracy, tracy-client, tracy-client sys do.
The current version of the Rust client is at v0.8.1 of Tracy. See the correlating versions in the table below (original at https://github.com/nagisa/rust_tracy_client#version-support-table):
Tracy | tracy-client-sys | tracy-client | tracing-tracy |
---|---|---|---|
0.7.1 | 0.9.0 | 0.8.0 | 0.2.0 |
0.7.3 | 0.10.0 | 0.9.0 | 0.3.0 |
0.7.4 | 0.11.0 | 0.10.0 | 0.4.0 |
0.7.5 | 0.12.0 | 0.11.0 | 0.5.0 |
0.7.6 | 0.13.0, 0.14.0 | 0.12.* | 0.6.* |
v0.7.7 | 0.15.0 | 0.12.* | 0.6.* |
v0.7.8 | 0.16.0 | 0.12.* | 0.6.* |
v0.7.8 | 0.16.0 | 0.12.* | 0.7.* |
v0.7.8 | 0.16.0 | 0.12.* | 0.8.* |
v0.8.1 | 0.17.* | 0.13.* | 0.9.* |
v0.8.1 | 0.17.* | 0.14.* | 0.10.* |
Development Documents
Architecture
Rendering Architecture
The big picture of wgpu is as follows:
A simplified version is shown below:
A further simplified version:
Notes:
- wgpu is able to create an interface through which we can reach any device with a GPU.
Notes:
- The ability to use shared memory or the atomic instruction set of WASM comes by enabling compilation features.
threads
support here does not introduce threads like we know them from Linux. It introduces- support for atomics like specified in a working draft to WebAssembly. Threads are simulated using WebWorkers by the browser.
Design
Domain Model
Data Model
Crate and Project Structure
Caching
The caching for maplibre-rs is handled on the networking layer. This means that data which is fetched over slow IO is cached in the format of the network requests. The maplibre-rs library is not introducing a separate serialization format for caching.
Instead, caching functionality of HTTP client libraries of the web platform are used. This has the advantage that we can honor HTTP headers which configure caching. This is very important for fetched tiles, as they can have an expiry date.
- On the web the browser is automatically caching raw tiles.
- On Linux, macOS, iOS and Android we are utilizing reqwest-middleware-cache, which writes raw network requests to disk.
Stencil Masking
The following diagram shows a method which has been used in the beginning of maplibre-rs. It is not used currently.
Font Rendering
There exists no universally perfect solution to font rendering. Depending on the runtime environment a method needs to be chosen. This StackOverflow post outlines some state-of-the-art methods. Some more approaches are described here.
From my perspective the following approaches could work potentially:
- Tessellate Fonts
- SDF Font Rendering
- GPU Text Rendering directly from Bezier Curves
- Draw text using a Web Canvas and load them to GPU
There is a thesis which summarizes some methods here. A link collection about font related projects can be viewed here.
Approaches
Tessellate Fonts
There is ttf2mesh which generates meshes. I was already able to generate about 1k glyphs with ~40FPS.
SDF Font Rendering
There is a blogpost by Mapbox here. Some more implementation documents are available here. A good foundation for SDF fonts was created by Chlumsky with msdfgen.
GPU Text Rendering from Bezier Curves
The solutions exist:
- By Will Dobbie with an implementation here
- Slug Library which is patented and probably therefore not usable
Here is the whitepaper of the Slug library. There is also a poster about it. There also exists an open implementation.
Draw text using a Web Canvas
This approach has the downside that we can not dynamically scale rendered fonts according to the current zoom level.
Other Approaches
Library Packaging
Apple
On Apple maplibre-rs is packaged as:
- Multiple .xcarchive packages which include a framework. Each for a different architecture and platform.
- A single .xcframework package which contains multiple frameworks of different architectures and platforms.
- A swift package which just references the .xcframework package and makes distributing easier.
The following diffs are extracted from this diff. They should serve as documentation for the XCode project. This is required because XCode is a mess.
XCode Project description
Library Entry
@@ -0,0 +1,5 @@
|
|
1
|
+
public class MapLibre {
|
2
|
+
public static func start() {
|
3
|
+
maplibre_apple_main();
|
4
|
+
}
|
5
|
+
}
|
The swift code above is the main entry for the Swift API. From this entry file we can expose more API of maplibre-rs. Any C functions which are referenced in the XCode framework's header are available automatically in Swift.
Framework
29
33
|
|
30
34
|
/* Begin PBXFrameworksBuildPhase section */
|
31
|
-
|
35
|
+
0B85D5682812903700906D21 /* Frameworks */ = {
|
32
36
|
isa = PBXFrameworksBuildPhase;
|
33
37
|
buildActionMask = 2147483647;
|
34
38
|
files = (
|
39
|
+
0B85D5A42812991100906D21 /* libmaplibre_apple.a in Frameworks */,
|
35
40
|
);
|
36
41
|
runOnlyForDeploymentPostprocessing = 0;
|
37
42
|
};
|
38
|
-
|
43
|
+
0B85D57B281290F800906D21 /* Frameworks */ = {
|
39
44
|
isa = PBXFrameworksBuildPhase;
|
40
45
|
buildActionMask = 2147483647;
|
41
46
|
files = (
|
47
|
+
0B85D596281291A400906D21 /* maplibre_rs.framework in Frameworks */,
|
42
48
|
);
|
43
49
|
runOnlyForDeploymentPostprocessing = 0;
|
44
50
|
};
|
45
|
-
|
51
|
+
0B85D580281290F800906D21 /* Frameworks */ = {
|
46
52
|
isa = PBXFrameworksBuildPhase;
|
47
53
|
buildActionMask = 2147483647;
|
48
54
|
files = (
|
55
|
+
0B85D599281291A700906D21 /* maplibre_rs.framework in Frameworks */,
|
49
56
|
);
|
50
57
|
runOnlyForDeploymentPostprocessing = 0;
|
51
58
|
};
|
59
|
/* End PBXFrameworksBuildPhase section */
|
The framework needs to link against the static library libmaplibre_apple.a
, which has been generated by Cargo.
In order to allow XCode to dynamically select the library based on the Library Search Path
(Build Settings) one needs
to add a relative file to XCode. The entry in the project.pbxproj
should look like that:
B085D5A32812987B00906D21 /* libmaplibre_apple.a */ = {
isa = PBXFileReference;
lastKnownFileType = archive.ar;
path = libmaplibre_apple.a;
sourceTree = SOURCE_ROOT;
};
Note the path = libmaplibre_apple.a
. This path does not link to a concrete file, but to a file which can be found
during building.
A file can be added to the frameworks and library link phase in XCode.
Cargo Build Phase
114
131
|
/* Begin PBXNativeTarget section */
|
115
|
-
|
132
|
+
0B85D56A2812903700906D21 /* maplibre-rs */ = {
|
116
133
|
isa = PBXNativeTarget;
|
117
|
-
buildConfigurationList =
|
134
|
+
buildConfigurationList = 0B85D5702812903700906D21 /* Build configuration list for PBXNativeTarget "maplibre-rs" */;
|
118
135
|
buildPhases = (
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
136
|
+
0B85D5662812903700906D21 /* Headers */,
|
137
|
+
0B85D5672812903700906D21 /* Sources */,
|
138
|
+
0B85D5682812903700906D21 /* Frameworks */,
|
139
|
+
0B85D5692812903700906D21 /* Resources */,
|
140
|
+
0BE452D72812EFC1003BD2A5 /* Cargo Build */,
|
229
248
|
);
|
230
249
|
runOnlyForDeploymentPostprocessing = 0;
|
231
250
|
};
|
232
251
|
/* End PBXResourcesBuildPhase section */
|
233
252
|
|
253
|
+
/* Begin PBXShellScriptBuildPhase section */
|
254
|
+
0BE452D72812EFC1003BD2A5 /* Cargo Build */ = {
|
255
|
+
isa = PBXShellScriptBuildPhase;
|
256
|
+
buildActionMask = 2147483647;
|
257
|
+
files = (
|
258
|
+
);
|
259
|
+
inputFileListPaths = (
|
260
|
+
);
|
261
|
+
inputPaths = (
|
262
|
+
);
|
263
|
+
name = "Cargo Build";
|
264
|
+
outputFileListPaths = (
|
265
|
+
);
|
266
|
+
outputPaths = (
|
267
|
+
);
|
268
|
+
runOnlyForDeploymentPostprocessing = 0;
|
269
|
+
shellPath = /bin/bash;
|
270
|
+
shellScript = ". \"$HOME/.cargo/env\"\n\narch=\"unknown\"\nvendor=\"apple\"\nos_type=\"unknown\"\nenvironment_type=\"\"\n\nmode=\"\"\n\necho \"ARCH: $ARCHS\"\n\nif [[ $CONFIGURATION == \"Release\" ]]\nthen\n mode=\"--release\"\nfi\n\nif [[ $ARCHS == \"x86_64\" ]]\nthen\n arch=\"x86_64\"\nelif [[ $ARCHS == \"arm64\" ]]\nthen\n arch=\"aarch64\"\nfi\n\nif [[ $SDK_NAME == *\"iphoneos\"* ]]\nthen\n os_type=\"ios\"\nelif [[ $SDK_NAME == *\"macos\"* ]]\nthen\n os_type=\"darwin\"\nelif [[ $SDK_NAME == *\"iphonesimulator\"* ]]\nthen\n os_type=\"ios\"\n environment_type=\"sim\"\nfi\n\n\ntriplet=\"$arch-$vendor-$os_type\"\n\nif [ -n \"$environment_type\" ]\nthen\n triplet=\"$triplet-$environment_type\"\nfi\n\necho \"$mode\"\necho \"$triplet\"\n\nenv -i zsh -c \"cargo build -p maplibre-apple $mode --target $triplet --lib\"\n\n";
|
271
|
+
};
|
272
|
+
/* End PBXShellScriptBuildPhase section */
|
In order to trigger Cargo builds when starting a XCode build we include a Cargo Build
script. This build script needs
to run before the linking phase (drag and drop it to the top).
The following build script builds based on XCode environment variables the correct static library. We depend on
the $ARCHS
environment variable, as the others seem unreliable. Note that this can include multiple architectures, unless the build
setting ONLY_ACTIVE_ARCH
is set to YES
.
arch="unknown"
vendor="apple"
os_type="unknown"
environment_type=""
mode=""
echo "ARCH: $ARCHS"
if [[ $CONFIGURATION == "Release" ]]
then
mode="--release"
fi
if [[ $ARCHS == "x86_64" ]]
then
arch="x86_64"
elif [[ $ARCHS == "arm64" ]]
then
arch="aarch64"
fi
if [[ $SDK_NAME == *"iphoneos"* ]]
then
os_type="ios"
elif [[ $SDK_NAME == *"macos"* ]]
then
os_type="darwin"
elif [[ $SDK_NAME == *"iphonesimulator"* ]]
then
os_type="ios"
if [[ $ARCHS == "arm64" ]]
then
environment_type="sim"
fi
fi
triplet="$arch-$vendor-$os_type"
if [ -n "$environment_type" ]
then
triplet="$triplet-$environment_type"
fi
echo "Mode: $mode"
echo "Triplet: $triplet"
echo "Shell: $SHELL"
cmd="export HOME=$HOME && . $HOME/.cargo/env && cargo build -p apple $mode --target $triplet --lib"
echo "Command: $cmd"
env -i /bin/bash -c "$cmd"
Build Settings
260
299
|
/* End PBXSourcesBuildPhase section */
|
275
|
-
|
314
|
+
0B85D5712812903700906D21 /* Debug */ = {
|
276
315
|
isa = XCBuildConfiguration;
|
277
316
|
buildSettings = {
|
317
|
+
ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES;
|
278
318
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
319
|
+
BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
|
279
320
|
CLANG_ANALYZER_NONNULL = YES;
|
280
321
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
281
322
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
@@ -304,11 +345,13 @@
|
|
304
345
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
305
346
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
306
347
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
307
|
-
|
348
|
+
CODE_SIGN_IDENTITY = "Apple Development";
|
349
|
+
CODE_SIGN_STYLE = Manual;
|
308
350
|
COPY_PHASE_STRIP = NO;
|
309
351
|
CURRENT_PROJECT_VERSION = 1;
|
310
352
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
311
353
|
DEFINES_MODULE = YES;
|
354
|
+
DEVELOPMENT_TEAM = "";
|
312
355
|
DYLIB_COMPATIBILITY_VERSION = 1;
|
313
356
|
DYLIB_CURRENT_VERSION = 1;
|
314
357
|
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
@@ -337,14 +380,28 @@
|
|
337
380
|
"@executable_path/Frameworks",
|
338
381
|
"@loader_path/Frameworks",
|
339
382
|
);
|
383
|
+
LIBRARY_SEARCH_PATHS = (
|
384
|
+
"$(inherited)",
|
385
|
+
"$(PROJECT_DIR)",
|
386
|
+
);
|
387
|
+
"LIBRARY_SEARCH_PATHS[sdk=iphoneos*][arch=arm64]" = "$(PROJECT_DIR)/libs/aarch64-apple-ios/debug";
|
388
|
+
"LIBRARY_SEARCH_PATHS[sdk=iphoneos*][arch=x86_64]" = "$(PROJECT_DIR)/libs/x86_64-apple-ios/debug";
|
389
|
+
"LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=arm64]" = "$(PROJECT_DIR)/libs/aarch64-apple-ios-sim/debug";
|
390
|
+
"LIBRARY_SEARCH_PATHS[sdk=macosx*][arch=arm64]" = "$(PROJECT_DIR)/libs/aarch64-apple-darwin/debug";
|
391
|
+
"LIBRARY_SEARCH_PATHS[sdk=macosx*][arch=x86_64]" = "$(PROJECT_DIR)/libs/x86_64-apple-darwin/debug";
|
392
|
+
MACH_O_TYPE = staticlib;
|
340
393
|
MARKETING_VERSION = 1.0;
|
341
394
|
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
342
395
|
MTL_FAST_MATH = YES;
|
343
396
|
ONLY_ACTIVE_ARCH = YES;
|
344
397
|
PRODUCT_BUNDLE_IDENTIFIER = "org.maplibre.maplibre-rs";
|
345
398
|
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
399
|
+
PROVISIONING_PROFILE_SPECIFIER = "";
|
400
|
+
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "";
|
346
401
|
SDKROOT = iphoneos;
|
347
|
-
SKIP_INSTALL =
|
402
|
+
SKIP_INSTALL = NO;
|
403
|
+
SUPPORTED_PLATFORMS = "watchsimulator watchos macosx iphonesimulator iphoneos driverkit appletvsimulator appletvos";
|
404
|
+
SUPPORTS_MACCATALYST = YES;
|
348
405
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
349
406
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
350
407
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
@@ -355,10 +412,12 @@
|
|
355
412
|
};
|
356
413
|
name = Debug;
|
357
414
|
};
|
358
|
-
|
415
|
+
0B85D5722812903700906D21 /* Release */ = {
|
359
416
|
isa = XCBuildConfiguration;
|
360
417
|
buildSettings = {
|
418
|
+
ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES;
|
361
419
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
420
|
+
BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
|
362
421
|
CLANG_ANALYZER_NONNULL = YES;
|
363
422
|
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
364
423
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
@@ -387,11 +446,13 @@
|
|
387
446
|
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
388
447
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
389
448
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
390
|
-
|
449
|
+
CODE_SIGN_IDENTITY = "Apple Development";
|
450
|
+
CODE_SIGN_STYLE = Manual;
|
391
451
|
COPY_PHASE_STRIP = NO;
|
392
452
|
CURRENT_PROJECT_VERSION = 1;
|
393
453
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
394
454
|
DEFINES_MODULE = YES;
|
455
|
+
DEVELOPMENT_TEAM = "";
|
395
456
|
DYLIB_COMPATIBILITY_VERSION = 1;
|
396
457
|
DYLIB_CURRENT_VERSION = 1;
|
397
458
|
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
@@ -414,13 +475,28 @@
|
|
414
475
|
"@executable_path/Frameworks",
|
415
476
|
"@loader_path/Frameworks",
|
416
477
|
);
|
478
|
+
LIBRARY_SEARCH_PATHS = (
|
479
|
+
"$(inherited)",
|
480
|
+
"$(PROJECT_DIR)",
|
481
|
+
);
|
482
|
+
"LIBRARY_SEARCH_PATHS[sdk=iphoneos*][arch=arm64]" = "$(PROJECT_DIR)/libs/aarch64-apple-ios/release";
|
483
|
+
"LIBRARY_SEARCH_PATHS[sdk=iphoneos*][arch=x86_64]" = "$(PROJECT_DIR)/libs/x86_64-apple-ios/release";
|
484
|
+
"LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=arm64]" = "$(PROJECT_DIR)/libs/aarch64-apple-ios-sim/release";
|
485
|
+
"LIBRARY_SEARCH_PATHS[sdk=macosx*][arch=arm64]" = "$(PROJECT_DIR)/libs/aarch64-apple-darwin/release";
|
486
|
+
"LIBRARY_SEARCH_PATHS[sdk=macosx*][arch=x86_64]" = "$(PROJECT_DIR)/libs/x86_64-apple-darwin/release";
|
487
|
+
MACH_O_TYPE = staticlib;
|
417
488
|
MARKETING_VERSION = 1.0;
|
418
489
|
MTL_ENABLE_DEBUG_INFO = NO;
|
419
490
|
MTL_FAST_MATH = YES;
|
491
|
+
ONLY_ACTIVE_ARCH = YES;
|
420
492
|
PRODUCT_BUNDLE_IDENTIFIER = "org.maplibre.maplibre-rs";
|
421
493
|
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
494
|
+
PROVISIONING_PROFILE_SPECIFIER = "";
|
495
|
+
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "";
|
422
496
|
SDKROOT = iphoneos;
|
423
|
-
SKIP_INSTALL =
|
497
|
+
SKIP_INSTALL = NO;
|
498
|
+
SUPPORTED_PLATFORMS = "watchsimulator watchos macosx iphonesimulator iphoneos driverkit appletvsimulator appletvos";
|
499
|
+
SUPPORTS_MACCATALYST = YES;
|
424
500
|
SWIFT_COMPILATION_MODE = wholemodule;
|
425
501
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
426
502
|
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
@@ -432,7 +508,7 @@
|
|
432
508
|
};
|
433
509
|
name = Release;
|
510
|
};
|
Explanations for the settings:
BUILD_LIBRARY_FOR_DISTRIBUTION
: Define that this is a library (effect unknown to me)CODE_SIGN_STYLE
: The framework is not signedDEVELOPMENT_TEAM
: No development team is setLIBRARY_SEARCH_PATHS[sdk=x][arch=y]
: We set the path for thelibmaplibre_apple.a
liesMACH_O_TYPE
/SKIP_INSTALL
: If this is not set tostaticlib
andNO
, then thelibmaplibre_apple.a
binary is not included in the final framework xcarchive.SUPPORTED_PLATFORMS
: Explicitly says that this library works on any platform.SUPPORTS_MACCATALYST
: Explicitly says that this library works on Mac Catalyst.
The same settings are done for Release and Debug.
xcframework packaging
Creating a xcframework is usually quite straight forward. Just execute the following:
xargs xcodebuild -create-xcframework -framework ./a -framework ./b -output out.xcframework
Unfortunately, it is not possible to bundle some frameworks together like:
- macOS-arm64 and macOS-x86_64
In order to package these architectures and platforms together a fat binary needs to be created using the lipo
tool.
This means from two frameworks we create a unified framework with a fat binary.
There are two important steps:
- Create a fat binary using
lipo -create binA binB -output binfat
- Copy for example the arm64 framework and add the
.swiftmodule
definitions from the x86_64 framework
Single UIApplication
Right now winit
only allows the usage of a UIApplication
. This means the application needs to run in fullscreen.
Tracking Issue
Example App
The following settings are important for the example application within the XCode project.
Info Plist for Applications
435
|
-
|
511
|
+
0B85D58D281290F800906D21 /* Debug */ = {
|
436
512
|
isa = XCBuildConfiguration;
|
437
513
|
buildSettings = {
|
438
514
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
@@ -489,7 +565,7 @@
|
|
489
565
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
490
566
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
491
567
|
GENERATE_INFOPLIST_FILE = YES;
|
492
|
-
|
568
|
+
INFOPLIST_FILE = "example--iOS--Info.plist";
|
493
569
|
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
494
570
|
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
495
571
|
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
@@ -514,7 +590,7 @@
|
|
514
590
|
};
|
515
591
|
name = Debug;
|
516
592
|
};
|
517
|
-
|
593
|
+
0B85D58E281290F800906D21 /* Release */ = {
|
518
594
|
isa = XCBuildConfiguration;
|
519
595
|
buildSettings = {
|
520
596
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
@@ -565,7 +641,7 @@
|
|
565
641
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
566
642
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
567
643
|
GENERATE_INFOPLIST_FILE = YES;
|
568
|
-
|
644
|
+
INFOPLIST_FILE = "example--iOS--Info.plist";
|
569
645
|
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
570
646
|
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
571
647
|
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
@@ -590,7 +666,7 @@
|
- The
INFOPLIST_KEY_UIApplicationSceneManifest_Generation
needs to be unset. Else the application screen is just black.
Files & Assets
@@ -7,93 +7,110 @@
|
|
7
7
|
objects = {
|
8
8
|
|
9
9
|
/* Begin PBXBuildFile section */
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
10
|
+
0B85D56F2812903700906D21 /* maplibre_rs.h in Headers */ = {isa = PBXBuildFile; fileRef = 0B85D56E2812903700906D21 /* maplibre_rs.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
11
|
+
0B85D586281290F800906D21 /* exampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B85D577281290F800906D21 /* exampleApp.swift */; };
|
12
|
+
0B85D587281290F800906D21 /* exampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B85D577281290F800906D21 /* exampleApp.swift */; };
|
13
|
+
0B85D58A281290F800906D21 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0B85D579281290F800906D21 /* Assets.xcassets */; };
|
14
|
+
0B85D58B281290F800906D21 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0B85D579281290F800906D21 /* Assets.xcassets */; };
|
15
|
+
0B85D5952812913700906D21 /* maplibre.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B85D5942812913700906D21 /* maplibre.swift */; };
|
16
|
+
0B85D596281291A400906D21 /* maplibre_rs.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0B85D56B2812903700906D21 /* maplibre_rs.framework */; };
|
17
|
+
0B85D599281291A700906D21 /* maplibre_rs.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0B85D56B2812903700906D21 /* maplibre_rs.framework */; };
|
18
|
+
0B85D5A42812991100906D21 /* libmaplibre_apple.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0B85D5A32812987B00906D21 /* libmaplibre_apple.a */; };
|
17
19
|
/* End PBXBuildFile section */
|
18
20
|
|
19
21
|
/* Begin PBXFileReference section */
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
22
|
+
0B85D56B2812903700906D21 /* maplibre_rs.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = maplibre_rs.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
23
|
+
0B85D56E2812903700906D21 /* maplibre_rs.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = maplibre_rs.h; sourceTree = "<group>"; };
|
24
|
+
0B85D577281290F800906D21 /* exampleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = exampleApp.swift; sourceTree = "<group>"; };
|
25
|
+
0B85D579281290F800906D21 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
26
|
+
0B85D57E281290F800906D21 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = example.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
27
|
+
0B85D583281290F800906D21 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = example.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
28
|
+
0B85D585281290F800906D21 /* macOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = macOS.entitlements; sourceTree = "<group>"; };
|
29
|
+
0B85D5942812913700906D21 /* maplibre.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = maplibre.swift; sourceTree = "<group>"; };
|
30
|
+
0B85D5A32812987B00906D21 /* libmaplibre_apple.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libmaplibre_apple.a; sourceTree = SOURCE_ROOT; };
|
31
|
+
0BE452D62812EEA8003BD2A5 /* example--iOS--Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "example--iOS--Info.plist"; sourceTree = "<group>"; };
|
32
|
/* End PBXFileReference section */
|
- The example/demo application within the XCode project references the
maplibre_rs.framework
. Some default files have been removed.
MacOS Entitlements
@@ -0,0 +1,12 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
3
|
+
<plist version="1.0">
|
4
|
+
<dict>
|
5
|
+
<key>com.apple.security.app-sandbox</key>
|
6
|
+
<true/>
|
7
|
+
<key>com.apple.security.files.user-selected.read-only</key>
|
8
|
+
<true/>
|
9
|
+
<key>com.apple.security.network.client</key>
|
10
|
+
<true/>
|
11
|
+
</dict>
|
12
|
+
</plist>
|
- On macOS one needs to allow network access via
com.apple.security.network.client
Android
Gradle Project Setup
In order to package an Android .aar
archive we use
the rust-android-gradle.
Except some customisations for the latest NDK toolchain release everything worked flawlessly.
JNI
There is no way right now to automatically generate JNI stubs for Rust. A manual example is available in the android crate of maplibre-rs.
Single NativeActivity
Right now winit
only allows the usage of a NativeActivity
. This means the application needs to run in fullscreen.
This native activity is referenced in the ´AndroidManifest.xml` by defining the name of the shared library.
Tracking Issue
Web
This document describes issues and challenges when packaging maplibre-rs as a npm package.
Required Formats
ESM
The ESM module format is the standard nowadays which should be followed. If a JS bundler encounters an ESM module it can resolve WebAssembly files or WebWorkers dynamically. The following syntax is used to resolve referenced WebWorkers:
new Worker(new URL("./multithreaded-pool.worker.ts", import.meta.url), {
type: 'module'
});
Similarly, the following works:
new URL('index_bg.wasm', import.meta.url);
IIFE (immediately-invoked function expression)
This format is used when including maplibre-rs in a
<script>
tag. The library is "written" onto the window/global object. This allows quick prototyping/playgrounds/experiments using maplibre-rs.
In order to support this we need to create a bundle which works on any modern browser. Additionally, a WASM file and WebWorker needs to be deployed at a predictable path, because there is no bundler active which manages assets. Users of these libraries have to specify where WASM or non-inlined WebWorkers are located.
Both assets could be inlined theoretically. This is common for WebWorkers, but not for WASM files.
UMD
UMD modules are needed when creating a library which should run in Node as well as browsers. This is not a use case for maplibre-rs. If we support node, then we probably would ship a separate package called "maplibre-rs-node" which bundles to CJS directly.
CJS/CommonJS
Not needed for the browser build of maplibre-rs, possibly needed when supporting Node
With a CommonJS module its is not possible for bundlers to dynamically resolve WebWorkers or WASM files.
The import.meta.url
token can not exist in a CommonJS module. Therefore, bundlers which encounter a CommonJS module
have to use a different mechanism of resolving files.
Generally, we do not need to support CommonJS, because we are not targeting Node with maplibre-rs. It's properly good to support it as a fallback though, for bundlers which can not deal with ESM modules yet. This is for example true for test runners like Jest which require that dependencies are available as CJS module.
wasm-pack output
wasm-pack can output multiple formats. The web
and bundler
outputs offer the most modular modules.
Unfortunately, the
function wasm_bindgen::module()
is only supported in web
and no-modules
. We currently are using this in order to send loaded instances
of WebAssembly.Module
to WebWorkers. nodejs
should not be used because MapLibre does not target Node.
Therefore, we should stick to the web
output format.
Required Features
- WASM Bundling: Make the WASM binary available to users of the maplibre-rs library
- WebWorker Bundling: Make the WebWorker available to users of the maplibre-rs library. This could also be achived by inlining.
- WebWorker Inlining: Inline the WebWorker bundle in the library bundle as a string.
- Predictable Paths: Without predictable paths, it's difficult for users to reference the wasm file directly from the
node_modules
directory if requried.
Bundler Feature Comparison
Bundler | ESM | IIFE | CJS | UMD | WebWorker Inlining | Web Worker Bundling | WASM Bundling | Predictable Paths | Inlining Environment Variables |
---|---|---|---|---|---|---|---|---|---|
Babel 1) | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ |
TypeScript 1) | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ |
Webpack | ❌ 4) | ❓ | ❌ | ❓ | ❌ 2) | ✅ | ✅ | ❓ | ✅ |
Parcel | ✅ | ❌ | ✅ | ❌ | 🛠️ 3) | ✅ | ✅ | ❌ 5) | ✅ |
ESBuild | ✅ | ✅ | ✅ | ❌ | ✅ 6) | ❓ | ✅ 6) | ✅ | ✅ |
Rollup | ❓ | ❓ | ❓ | ❓ | ❓ | ❓ | ❓ | ❓ | ✅ |
Features in italics are required for maplibre-rs.
- Technically not a bundler but can be used to emit ES modules
- Was Supported in Webpack 4, but currently is not supported
- https://github.com/parcel-bundler/parcel/issues/8004
- As of the time of writing Webpack can not output ESM libraries
- Plugins exist, but they don't work reliably
- Plugins exist, and work reliably
ESBuild
ESBuild supports CJS, ESM and IIFI modules equally well. Plugins exist for WebWorker inlining and resolving assets
through import.meta.url
. The plugin quality seems to be superior compared to Parcel. It is also very fast compared to
all other bundlers.
- IIFI: The esbuild bundler translates to
new URL('index_bg.wasm', import.meta.url);
tovar __currentScriptUrl__ = document.currentScript && document.currentScript.src || document.baseURI; new URL("./assets/index_bg.wasm?emit=file", __currentScriptUrl__);
See config in web/lib/build.mjs
for an example usage.
Babel & TypeScript
Babel and TypeScript both can produce ESM modules, but they fail with transforming references within the source code
like new URL("./multithreaded-pool.worker.ts", import.meta.url)
. There exist some Babel plugins, but none of them is stable.
Therefore, we actually need a proper bundler which supports outputting ESM modules.
The only stable solution to this is Parcel. Parcel also has good documentation around the bundling of WebWorkers.
WebPack
WebPack supports older module formats like CommonJS or UMD very well. It falls short when bundling the format ESM format which is not yet stable. It also does not support inlining WebWorkers in version 5. The wasm-pack plugin for WebPack makes including Cargo projects easy.
- CJS: Webpack translates
new URL('index_bg.wasm', import.meta.url);
to something that is equivalent to'./index_bg.wasm'
. It just expects that assets are resolvable from the current file.
Example scripts for package.json
:
{
"scripts": {
"webpack": "webpack --mode=development",
"webpack-webgl": "npm run build -- --env webgl",
"webpack-production": "webpack --mode=production",
"webpack-webgl-production": "npm run production-build -- --env webgl"
}
}
Example config:
const path = require("path");
const webpack = require("webpack");
const WasmPackPlugin = require("@wasm-tool/wasm-pack-plugin");
let dist = path.join(__dirname, 'dist/maplibre-rs');
module.exports = (env) => ({
mode: "development",
entry: "./src/index.ts",
experiments: {
syncWebAssembly: true,
},
performance: {
maxEntrypointSize: 400000,
maxAssetSize: 400000000,
},
output: {
path: dist,
filename: "maplibre-rs.js",
library: {
name: 'maplibre_rs',
type: 'umd',
},
},
module: {
rules: [
{
test: /\.ts$/,
exclude: /node_modules/,
use: [
{
loader: 'ts-loader',
options: {}
}
]
},
],
},
resolve: {
extensions: ['.ts', '.js'],
},
plugins: [
new webpack.DefinePlugin({
'process.env.WEBGL': !!env.webgl
}),
new WasmPackPlugin({
crateDirectory: path.resolve(__dirname, '../'),
// Check https://rustwasm.github.io/wasm-pack/book/commands/build.html for
// the available set of arguments.
//
// Optional space delimited arguments to appear before the wasm-pack
// command. Default arguments are `--verbose`.
//args: '--log-level warn',
// Default arguments are `--typescript --target browser --mode normal`.
extraArgs: ` --target web -- . -Z build-std=std,panic_abort ${env.webgl ? '--features web-webgl' : ''} ${env.tracing ? '--features trace' : ''}`,
// Optional array of absolute paths to directories, changes to which
// will trigger the build.
// watchDirectories: [
// path.resolve(__dirname, "another-crate/src")
// ],
// The same as the `--out-dir` option for `wasm-pack`
outDir: path.resolve(__dirname, 'src/wasm-pack'),
// The same as the `--out-name` option for `wasm-pack`
// outName: "index",
// If defined, `forceWatch` will force activate/deactivate watch mode for
// `.rs` files.
//
// The default (not set) aligns watch mode for `.rs` files to Webpack's
// watch mode.
// forceWatch: true,
// If defined, `forceMode` will force the compilation mode for `wasm-pack`
//
// Possible values are `development` and `production`.
//
// the mode `development` makes `wasm-pack` build in `debug` mode.
// the mode `production` makes `wasm-pack` build in `release` mode.
// forceMode: "production",
// Controls plugin output verbosity, either 'info' or 'error'.
// Defaults to 'info'.
// pluginLogLevel: 'info'
}),
]
});
Parcel
Parcel supports CommonJS and ESM modules equally good. The documentation about import.meta.url
is very good. In other
bundlers documentations around this feature is missing. In the latest Parcel version inlining WebWorkers is not working.
- CJS: The Parcel bundler translates to
new URL('index_bg.wasm', import.meta.url);
tonew URL("index_bg.wasm", "file:" + __filename);
While depending onfile:
andfilename
works for NodeJS, it is unsupported in the browser.
Example scripts for package.json
:
{
"scripts": {
"parcel": "npm run clean && npm run wasm-pack && WEBGL=false parcel build --no-cache src/index.ts",
"parcel-webgl": "npm run clean && FEATURES=web-webgl npm run wasm-pack && WEBGL=true parcel build --no-cache src/index.ts"
}
}
Example config in `package.json:
{
"module": "dist/parcel-esm/module.js",
"main": "dist/parcel-cjs/main.js",
"types": "dist/parcel/types.d.ts",
"targets": {
"main": {
"distDir": "./dist/parcel-cjs",
"context": "browser",
"outputFormat": "commonjs"
},
"module": {
"distDir": "./dist/parcel-esm",
"context": "browser",
"outputFormat": "esmodule"
}
},
"@parcel/transformer-js": {
"inlineFS": false,
"inlineEnvironment": [
"WEBGL"
]
}
}
Rollup
Not yet evaluated
Appendix
Goals
Next Major Goals
Improve buffer_pool eviction rulesUse MPI: https://doc.rust-lang.org/book/ch16-02-message-passing.html- Input-handling via events and functional pipelines
Show old tiles until new tile is ready / Show mixed tiles, based on availability- Use a simple style definition
- type: background/fill/line
- minzoom/maxzoom
- source
- source-layer
- paint (fill-color)
- Map feeling:
- Wrap world around in x direction
- Limit panning in y direction
- Nicer default map style
Intermediate Goals
- Support multiple projections? PoC such that we are sure the renderer is acceptable
Future Goals
- Very simple text rendering
- Cache tessellation results
- We have three "caches": downloaded tiles, tessellated tiles, gpu tiles
- Handle missing tiles
- Support different tile raster addressing
Future Ideas
- Use rust-gpu as shading language
- Focus on accessibility of maps: https://www.w3.org/WAI/RD/wiki/Accessible_Maps
- Display in AR: https://developer.apple.com/documentation/arkit/displaying_an_ar_experience_with_metal
- Use tracing framework: tracing
Challenges:
- Accuracy of floating point numbers is very bad for big world view coordinates (Plot)
- Perils of World Space
Create paths for tessellating streets
Streets can have unusual shaped like shown here in Munich. OSM does not offer such data and therefore just renders an ordinary street contour like shown here. Because the data is probably not available this is a very hard challenge.
Related Resources
Talks
Related Projects
GIS
- Google Maps Projection
- Grid Calculation Examples
- Slippy map tilenames (also known as XYZ)
- TMS
- Mapbox Adaptive Projections
- Bing Map Tile System
- Bing: Understanding Scale and Resolution
WebAssembly and WebWorkers
Specs:
Projects:
- Experiment with shared memory and the idea behind it
- Shared channel
- Bridge for async executors
- Rayon for WebAssembly
- wasm-mt: postMessage message passing
Articles:
- WebAssembly Threads (official)
- Multithreading Rust and Wasm 2018
- postMessage Performance
- A practical guide to WebAssembly memory
Examples:
Rendering
Specs:
Articles:
Tutorials:
Examples:
Maths
Articles:
Examples:
Font Rendering
Specs:
Articles:
Projects:
- Mapbox fontnik
- TinySDK (JS)
- RustType
- SDF Render
- pbf_font_tools
- Multi-channel signed distance field generator
Tessellation
Projects:
Specifications
Render Graphs
- https://github.com/metal-by-example/simple-instancing/blob/master/MetalSimpleInstancing/Renderer.swift
- https://github.com/troughton/Substrate
- https://de.slideshare.net/DICEStudio/framegraph-extensible-rendering-architecture-in-frostbite
- http://themaister.net/blog/2017/08/15/render-graphs-and-vulkan-a-deep-dive/
- http://themaister.net/blog/2017/08/15/render-graphs-and-vulkan-a-deep-dive/
Animation
- https://crates.io/crates/pareen
- https://crates.io/crates/keyframe
- Start Date: 2022-10-29
- RFC PR: maplibre/maplibre-rs#??
- maplibre-rs Issue: N/A
Summary
The "RFC" (request for comments) process is intended to provide a consistent and controlled path for new features to enter the map renderer and standard libraries, so that all stakeholders can be confident about the direction the map renderer is evolving in.
This RFC is copied from rust-lang/rfcs. Thanks to everyone who contributed to the Rust RFC process RFC! Thanks for the great inspiration!
Motivation
The freewheeling way that we add new features to maplibre-rs has been good for early development, but for maplibre-rs to become a mature platform we need to develop some more self-discipline when it comes to changing the system. This is a proposal for a more principled RFC process to make it a more integral part of the overall development process, and one that is followed consistently to introduce features to maplibre-rs.
Detailed design
Many changes, including bug fixes and documentation improvements can be implemented and reviewed via the normal GitHub pull request workflow.
Some changes though are "substantial", and we ask that these be put through a bit of a design process and produce a consensus among the maplibre-rs community.
When you need to follow this process
You need to follow this process if you intend to make "substantial" changes to the maplibre-rs distribution. What constitutes a "substantial" change is evolving based on community norms, but may include the following.
- Any semantic or syntactic change to the map renderer that is not a bugfix.
- Removing map renderer features, including those that are feature-gated.
Some changes do not require an RFC:
- Rephrasing, reorganizing, refactoring, or otherwise "changing shape does not change meaning".
- Additions that strictly improve objective, numerical quality criteria (warning removal, speedup, better platform coverage, more parallelism, trap more errors, etc.)
- Additions only likely to be noticed by other developers-of-maplibre-rs, invisible to users-of-maplibre-rs.
If you submit a pull request to implement a new feature without going through the RFC process, it may be closed with a polite request to submit an RFC first.
What the process is
In short, to get a major feature added to maplibre-rs, one must first get the RFC merged into the RFC repo as a markdown file. At that point the RFC is 'active' and may be implemented with the goal of eventual inclusion into maplibre-rs.
- Fork the repo https://github.com/maplibre/maplibre-rs
- Copy
rfc/0000-template.md
torfc/0000-my-feature.md
(where 'my-feature' is descriptive. don't assign an RFC number yet). - Fill in the RFC
- Submit a pull request. The pull request is the time to get review of the design from the larger community.
- Build consensus and integrate feedback. RFCs that have broad support are much more likely to make progress than those that don't receive any comments.
Eventually, somebody on the maplibre-rs team will either accept the RFC by merging the pull request, at which point the RFC is 'active', or reject it by closing the pull request.
Who ever merges the RFC should do the following:
- Assign an id, using the PR number of the RFC pull request. (If the RFC has multiple pull requests associated with it, choose one PR number, preferably the minimal one.)
- Add the file in the
docs/src/rfc/
directory. - Create a corresponding issue on maplibre-rs repo
- Fill in the remaining metadata in the RFC header, including links for the original pull request(s) and the newly created maplibre-rs issue.
- Communicate that given RFC is now considered 'active' on the chat platform of maplibre-rs.
- Commit everything.
Once an RFC becomes active then authors may implement it and submit the feature as a pull request to the maplibre-rs repo. An 'active' is not a rubber stamp, and in particular still does not mean the feature will ultimately be merged; it does mean that in principle all the major stakeholders have agreed to the feature and are amenable to merging it.
Modifications to active RFC's can be done in followup PR's. An RFC that makes it through the entire process to implementation is considered 'complete'; an RFC that fails after becoming active is 'inactive'.
Alternatives
Retain the current informal RFC process. The newly proposed RFC process is designed to improve over the informal process in the following ways:
- Discourage non-actionable or vague RFCs
- Ensure that all serious RFCs are considered equally
- Give confidence to those with a stake in maplibre-rs development that they understand why new features are being merged
As an alternative, we could adopt an even stricter RFC process than the one proposed here. If desired, we should likely look to Python's PEP process for inspiration.
Unresolved questions
- Does this RFC strike a favorable balance between formality and agility?
- Does this RFC successfully address the aforementioned issues with the current informal RFC process?
- Should we retain rejected RFCs in the archive?
- Feature Name: (fill me in with a unique ident,
my_awesome_feature
) - Start Date: (fill me in with today's date, YYYY-MM-DD)
- RFC PR: maplibre/maplibre-rs#0000
- maplibre-rs Issue: maplibre/maplibre-rs#0000
Summary
One paragraph explanation of the feature.
Motivation
Why are we doing this? What use cases does it support? What is the expected outcome?
Guide-level explanation
Explain the proposal as if it was already included in the project, and you were teaching it to another maplibre-rs user. That generally means:
- Introducing new named concepts.
- Explaining the feature largely in terms of examples.
- Explaining how maplibre-rs users should think about the feature, and how it should impact the way they use maplibre-rs. It should explain the impact as concretely as possible.
- If applicable, provide sample error messages, deprecation warnings, or migration guidance.
- If applicable, describe the differences between teaching this to the existing maplibre-rs users and new maplibre-rs users.
For implementation-oriented RFCs (e.g. for internals), this section should focus on how maplibre-rs contributors should think about the change, and give examples of its concrete impact. For policy RFCs, this section should provide an example-driven introduction to the policy, and explain its impact in concrete terms.
Reference-level explanation
This is the technical portion of the RFC. Explain the design in sufficient detail that:
- Its interaction with other features is clear.
- It is reasonably clear how the feature would be implemented.
- Corner cases are dissected by example.
The section should return to the examples given in the previous section, and explain more fully how the detailed proposal makes those examples work.
Drawbacks
Why should we not do this?
Rationale and alternatives
- Why is this design the best in the space of possible designs?
- What other designs have been considered and what is the rationale for not choosing them?
- What is the impact of not doing this?
Prior art
Discuss prior art, both the good and the bad, in relation to this proposal. A few examples of what this can include are:
- Does this feature exist in other map renderers and what experience have their community had?
- For community proposals: Is this done by some other community and what were their experiences with it?
- For other teams: What lessons can we learn from what other communities have done here?
- Papers: Are there any published papers or great posts that discuss this? If you have some relevant papers to refer to, this can serve as a more detailed theoretical background.
This section is intended to encourage you as an author to think about the lessons from other renderers, provide readers of your RFC with a fuller picture. If there is no prior art, that is fine - your ideas are interesting to us whether they are brand new or if it is an adaptation from other map renderers.
Note that while precedent set by other map renderers is some motivation, it does not on its own motivate an RFC.
Unresolved questions
- What parts of the design do you expect to resolve through the RFC process before this gets merged?
- What parts of the design do you expect to resolve through the implementation of this feature before stabilization?
- What related issues do you consider out of scope for this RFC that could be addressed in the future independently of the solution that comes out of this RFC?
Future possibilities
Think about what the natural extension and evolution of your proposal would be and how it would affect the renderer and project as a whole in a holistic way. Try to use this section as a tool to more fully consider all possible interactions with the project and renderer in your proposal. Also consider how this all fits into the roadmap for the project and of the relevant sub-team.
This is also a good place to "dump ideas", if they are out of scope for the RFC you are writing but otherwise related.
If you have tried and cannot think of any future possibilities, you may simply state that you cannot think of anything.
Note that having something written down in the future-possibilities section is not a reason to accept the current or a future RFC; such notes should be in the section on motivation or rationale in this or subsequent RFCs. The section merely provides additional information.
- Start Date: 2022-10-29
- RFC PR: maplibre/maplibre-rs#??
- maplibre-rs Issue: N/A
Summary
The "RFC" (request for comments) process is intended to provide a consistent and controlled path for new features to enter the map renderer and standard libraries, so that all stakeholders can be confident about the direction the map renderer is evolving in.
This RFC is copied from rust-lang/rfcs. Thanks to everyone who contributed to the Rust RFC process RFC! Thanks for the great inspiration!
Motivation
The freewheeling way that we add new features to maplibre-rs has been good for early development, but for maplibre-rs to become a mature platform we need to develop some more self-discipline when it comes to changing the system. This is a proposal for a more principled RFC process to make it a more integral part of the overall development process, and one that is followed consistently to introduce features to maplibre-rs.
Detailed design
Many changes, including bug fixes and documentation improvements can be implemented and reviewed via the normal GitHub pull request workflow.
Some changes though are "substantial", and we ask that these be put through a bit of a design process and produce a consensus among the maplibre-rs community.
When you need to follow this process
You need to follow this process if you intend to make "substantial" changes to the maplibre-rs distribution. What constitutes a "substantial" change is evolving based on community norms, but may include the following.
- Any semantic or syntactic change to the map renderer that is not a bugfix.
- Removing map renderer features, including those that are feature-gated.
Some changes do not require an RFC:
- Rephrasing, reorganizing, refactoring, or otherwise "changing shape does not change meaning".
- Additions that strictly improve objective, numerical quality criteria (warning removal, speedup, better platform coverage, more parallelism, trap more errors, etc.)
- Additions only likely to be noticed by other developers-of-maplibre-rs, invisible to users-of-maplibre-rs.
If you submit a pull request to implement a new feature without going through the RFC process, it may be closed with a polite request to submit an RFC first.
What the process is
In short, to get a major feature added to maplibre-rs, one must first get the RFC merged into the RFC repo as a markdown file. At that point the RFC is 'active' and may be implemented with the goal of eventual inclusion into maplibre-rs.
- Fork the repo https://github.com/maplibre/maplibre-rs
- Copy
rfc/0000-template.md
torfc/0000-my-feature.md
(where 'my-feature' is descriptive. don't assign an RFC number yet). - Fill in the RFC
- Submit a pull request. The pull request is the time to get review of the design from the larger community.
- Build consensus and integrate feedback. RFCs that have broad support are much more likely to make progress than those that don't receive any comments.
Eventually, somebody on the maplibre-rs team will either accept the RFC by merging the pull request, at which point the RFC is 'active', or reject it by closing the pull request.
Who ever merges the RFC should do the following:
- Assign an id, using the PR number of the RFC pull request. (If the RFC has multiple pull requests associated with it, choose one PR number, preferably the minimal one.)
- Add the file in the
docs/src/rfc/
directory. - Create a corresponding issue on maplibre-rs repo
- Fill in the remaining metadata in the RFC header, including links for the original pull request(s) and the newly created maplibre-rs issue.
- Communicate that given RFC is now considered 'active' on the chat platform of maplibre-rs.
- Commit everything.
Once an RFC becomes active then authors may implement it and submit the feature as a pull request to the maplibre-rs repo. An 'active' is not a rubber stamp, and in particular still does not mean the feature will ultimately be merged; it does mean that in principle all the major stakeholders have agreed to the feature and are amenable to merging it.
Modifications to active RFC's can be done in followup PR's. An RFC that makes it through the entire process to implementation is considered 'complete'; an RFC that fails after becoming active is 'inactive'.
Alternatives
Retain the current informal RFC process. The newly proposed RFC process is designed to improve over the informal process in the following ways:
- Discourage non-actionable or vague RFCs
- Ensure that all serious RFCs are considered equally
- Give confidence to those with a stake in maplibre-rs development that they understand why new features are being merged
As an alternative, we could adopt an even stricter RFC process than the one proposed here. If desired, we should likely look to Python's PEP process for inspiration.
Unresolved questions
- Does this RFC strike a favorable balance between formality and agility?
- Does this RFC successfully address the aforementioned issues with the current informal RFC process?
- Should we retain rejected RFCs in the archive?