diff --git a/Cargo.toml b/Cargo.toml
index 0bd1b69..43842e0 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -14,6 +14,7 @@ exclude = [
"wasm_examples/many_points",
"wasm_examples/lambert",
"wasm_examples/with_egui",
+ "android_examples/raster_tiles/rust",
]
[workspace.package]
diff --git a/README.md b/README.md
index 6a60e99..035f954 100644
--- a/README.md
+++ b/README.md
@@ -52,6 +52,17 @@ it can be used on any platform that `wgpu` supports:
Still, the backend is not integral part of the Galileo design, so we will probably
try other promising backends (like [vello](https://github.com/linebender/vello)).
+![Android](https://maximkaaa.github.io/galileo/android.png)
+
+## FFI
+
+At this point, you can develop an application using Galileo only in Rust. But there is
+a POC example of how we envision future development on other platforms: [wasm_examples/raster_tiles].
+When all main features of Galileo are more or less stable (or when a need arises) we
+will add FFI bindings to other languages using `wasm-bindgen` and `uniffi`. This will
+allow you to create your applications in `JS`, `Kotlin`, `Swift` or `Python` using
+common API.
+
## Features
Galileo is an active WIP, here is the list of the features that are already present:
@@ -91,10 +102,12 @@ them all done at the same time. So here's our current plan and priorities:
# Running examples
-Rust examples of using Galileo are located at [`galileo/examples`]. Refer to the [readme](galileo/examples/README.md)
+Rust examples of using Galileo are located at [`galileo/examples`](galileo/examples). Refer to the [readme](galileo/examples/README.md)
for the list, description and run instructions.
-There are also examples of running Galileo in a web-browser located at [`web_examples`] folder. These are
+## Web
+
+There are also examples of running Galileo in a web-browser located at [`wasm_examples`](wasm_examples) folder. These are
excluded from the workspace (because Cargo does not like cross-platform workspaces).
To run those you will need to [install wasm-pack](https://rustwasm.github.io/wasm-pack/installer/):
@@ -105,6 +118,10 @@ wasm-pack build wasm_examples/countries --target no-modules --release
After that open `index.html` in your browser (must be served from `localhost`, use your
favourite developer server).
+## Android
+
+Check out [this example](android_examples/raster_tiles/README.md) to run Galileo on Android.
+
## Cross-compile from Linux to Windows
Install the target:
@@ -127,14 +144,18 @@ cargo build --target x86_64-px-windows-gnu
# Sponsoring
+There is still a lot of work to be done to make Galileo feature-full, production ready and useful for many. And we
+would love to work on this full-time to bring this to you as soon as possible. So we are looking for sponsors
+to make it possible.
+
+Sponsor funds will help support maintainer's dedicated work and eventually fund freelance contributors.
+
If you think this library can be useful to you or someone you love, consider supporting its development. Sponsoring
comes with additional advantages:
-* Increase development speed. All funds will be spent on development.
+* Increase development speed.
* Make your needs our priority.
* See your logo on the project's page.
-Please, contact the maintainer of the project with any questions related to sponsoring.
-
# License
You can use this library without any worries as it is licensed under either of
diff --git a/android_examples/raster_tiles/.gitignore b/android_examples/raster_tiles/.gitignore
new file mode 100644
index 0000000..c3b0a98
--- /dev/null
+++ b/android_examples/raster_tiles/.gitignore
@@ -0,0 +1,16 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
+/app/src/main/jniLibs
\ No newline at end of file
diff --git a/android_examples/raster_tiles/README.md b/android_examples/raster_tiles/README.md
new file mode 100644
index 0000000..f1e6a3c
--- /dev/null
+++ b/android_examples/raster_tiles/README.md
@@ -0,0 +1,25 @@
+This example app demonstrates how to run Galileo map as a Rust app on Android.
+
+To run the example install Android Studio with NDK.
+
+Then to build the rust part:
+
+```shell
+# Install Cargo NDK
+cargo install cargo-ndk
+
+# Install android targets
+rustup target add \
+ aarch64-linux-android \
+ armv7-linux-androideabi \
+ x86_64-linux-android \
+ i686-linux-android
+
+# Export NDK location. The location and version number on your system may differ from the bellow
+export ANDROID_NDK_HOME=~/Android/Sdk/ndk/26.1.10909125/
+
+# Build the app
+cargo ndk -t arm64-v8a -t armeabi-v7a -t x86 -t x86_64 -o ../app/src/main/jniLibs/ build
+```
+
+After that you can run the application from the Android Studio.
\ No newline at end of file
diff --git a/android_examples/raster_tiles/app/.gitignore b/android_examples/raster_tiles/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/android_examples/raster_tiles/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/android_examples/raster_tiles/app/build.gradle.kts b/android_examples/raster_tiles/app/build.gradle.kts
new file mode 100644
index 0000000..c22f0e6
--- /dev/null
+++ b/android_examples/raster_tiles/app/build.gradle.kts
@@ -0,0 +1,70 @@
+plugins {
+ id("com.android.application")
+ id("org.jetbrains.kotlin.android")
+}
+
+android {
+ namespace = "com.example.rastertilesandroid"
+ compileSdk = 34
+ ndkVersion = "26.1.10909125"
+
+ defaultConfig {
+ applicationId = "com.example.rastertilesandroid"
+ minSdk = 24
+ targetSdk = 34
+ versionCode = 1
+ versionName = "1.0"
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ vectorDrawables {
+ useSupportLibrary = true
+ }
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = "1.8"
+ }
+ buildFeatures {
+ compose = true
+ }
+ composeOptions {
+ kotlinCompilerExtensionVersion = "1.5.1"
+ }
+ packaging {
+ resources {
+ excludes += "/META-INF/{AL2.0,LGPL2.1}"
+ }
+ }
+}
+
+dependencies {
+
+ implementation("androidx.core:core-ktx:1.12.0")
+ implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
+ implementation("androidx.activity:activity-compose:1.8.2")
+ implementation(platform("androidx.compose:compose-bom:2023.08.00"))
+ implementation("androidx.compose.ui:ui")
+ implementation("androidx.compose.ui:ui-graphics")
+ implementation("androidx.compose.ui:ui-tooling-preview")
+ implementation("androidx.compose.material3:material3")
+ testImplementation("junit:junit:4.13.2")
+ androidTestImplementation("androidx.test.ext:junit:1.1.5")
+ androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
+ androidTestImplementation(platform("androidx.compose:compose-bom:2023.08.00"))
+ androidTestImplementation("androidx.compose.ui:ui-test-junit4")
+ debugImplementation("androidx.compose.ui:ui-tooling")
+ debugImplementation("androidx.compose.ui:ui-test-manifest")
+}
\ No newline at end of file
diff --git a/android_examples/raster_tiles/app/proguard-rules.pro b/android_examples/raster_tiles/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/android_examples/raster_tiles/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/android_examples/raster_tiles/app/src/androidTest/java/com/example/rastertilesandroid/ExampleInstrumentedTest.kt b/android_examples/raster_tiles/app/src/androidTest/java/com/example/rastertilesandroid/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..5e690b7
--- /dev/null
+++ b/android_examples/raster_tiles/app/src/androidTest/java/com/example/rastertilesandroid/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.example.rastertilesandroid
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.example.rastertilesandroid", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/android_examples/raster_tiles/app/src/main/AndroidManifest.xml b/android_examples/raster_tiles/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..74efb4d
--- /dev/null
+++ b/android_examples/raster_tiles/app/src/main/AndroidManifest.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android_examples/raster_tiles/app/src/main/res/drawable/ic_launcher_background.xml b/android_examples/raster_tiles/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..07d5da9
--- /dev/null
+++ b/android_examples/raster_tiles/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android_examples/raster_tiles/app/src/main/res/drawable/ic_launcher_foreground.xml b/android_examples/raster_tiles/app/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 0000000..2b068d1
--- /dev/null
+++ b/android_examples/raster_tiles/app/src/main/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android_examples/raster_tiles/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android_examples/raster_tiles/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..6f3b755
--- /dev/null
+++ b/android_examples/raster_tiles/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android_examples/raster_tiles/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/android_examples/raster_tiles/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..6f3b755
--- /dev/null
+++ b/android_examples/raster_tiles/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android_examples/raster_tiles/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/android_examples/raster_tiles/app/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 0000000..c209e78
Binary files /dev/null and b/android_examples/raster_tiles/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ
diff --git a/android_examples/raster_tiles/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/android_examples/raster_tiles/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..b2dfe3d
Binary files /dev/null and b/android_examples/raster_tiles/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ
diff --git a/android_examples/raster_tiles/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/android_examples/raster_tiles/app/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 0000000..4f0f1d6
Binary files /dev/null and b/android_examples/raster_tiles/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ
diff --git a/android_examples/raster_tiles/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/android_examples/raster_tiles/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..62b611d
Binary files /dev/null and b/android_examples/raster_tiles/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ
diff --git a/android_examples/raster_tiles/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/android_examples/raster_tiles/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 0000000..948a307
Binary files /dev/null and b/android_examples/raster_tiles/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ
diff --git a/android_examples/raster_tiles/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/android_examples/raster_tiles/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..1b9a695
Binary files /dev/null and b/android_examples/raster_tiles/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ
diff --git a/android_examples/raster_tiles/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/android_examples/raster_tiles/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..28d4b77
Binary files /dev/null and b/android_examples/raster_tiles/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ
diff --git a/android_examples/raster_tiles/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/android_examples/raster_tiles/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9287f50
Binary files /dev/null and b/android_examples/raster_tiles/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ
diff --git a/android_examples/raster_tiles/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/android_examples/raster_tiles/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..aa7d642
Binary files /dev/null and b/android_examples/raster_tiles/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ
diff --git a/android_examples/raster_tiles/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/android_examples/raster_tiles/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9126ae3
Binary files /dev/null and b/android_examples/raster_tiles/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ
diff --git a/android_examples/raster_tiles/app/src/main/res/values/colors.xml b/android_examples/raster_tiles/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..f8c6127
--- /dev/null
+++ b/android_examples/raster_tiles/app/src/main/res/values/colors.xml
@@ -0,0 +1,10 @@
+
+
+ #FFBB86FC
+ #FF6200EE
+ #FF3700B3
+ #FF03DAC5
+ #FF018786
+ #FF000000
+ #FFFFFFFF
+
\ No newline at end of file
diff --git a/android_examples/raster_tiles/app/src/main/res/values/strings.xml b/android_examples/raster_tiles/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..9240c02
--- /dev/null
+++ b/android_examples/raster_tiles/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ RasterTilesAndroid
+
\ No newline at end of file
diff --git a/android_examples/raster_tiles/app/src/main/res/values/themes.xml b/android_examples/raster_tiles/app/src/main/res/values/themes.xml
new file mode 100644
index 0000000..f3fb376
--- /dev/null
+++ b/android_examples/raster_tiles/app/src/main/res/values/themes.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/android_examples/raster_tiles/app/src/main/res/xml/backup_rules.xml b/android_examples/raster_tiles/app/src/main/res/xml/backup_rules.xml
new file mode 100644
index 0000000..fa0f996
--- /dev/null
+++ b/android_examples/raster_tiles/app/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,13 @@
+
+
+
+
\ No newline at end of file
diff --git a/android_examples/raster_tiles/app/src/main/res/xml/data_extraction_rules.xml b/android_examples/raster_tiles/app/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 0000000..9ee9997
--- /dev/null
+++ b/android_examples/raster_tiles/app/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android_examples/raster_tiles/app/src/test/java/com/example/rastertilesandroid/ExampleUnitTest.kt b/android_examples/raster_tiles/app/src/test/java/com/example/rastertilesandroid/ExampleUnitTest.kt
new file mode 100644
index 0000000..5f8a0a9
--- /dev/null
+++ b/android_examples/raster_tiles/app/src/test/java/com/example/rastertilesandroid/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.example.rastertilesandroid
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
\ No newline at end of file
diff --git a/android_examples/raster_tiles/build.gradle.kts b/android_examples/raster_tiles/build.gradle.kts
new file mode 100644
index 0000000..4bf014c
--- /dev/null
+++ b/android_examples/raster_tiles/build.gradle.kts
@@ -0,0 +1,8 @@
+
+
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+plugins {
+ id("com.android.application") version "8.2.0" apply false
+ id("org.jetbrains.kotlin.android") version "1.9.0" apply false
+ id("org.mozilla.rust-android-gradle.rust-android") version "0.9.3" apply false
+}
\ No newline at end of file
diff --git a/android_examples/raster_tiles/gradle.properties b/android_examples/raster_tiles/gradle.properties
new file mode 100644
index 0000000..3c5031e
--- /dev/null
+++ b/android_examples/raster_tiles/gradle.properties
@@ -0,0 +1,23 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Kotlin code style for this project: "official" or "obsolete":
+kotlin.code.style=official
+# Enables namespacing of each library's R class so that its R class includes only the
+# resources declared in the library itself and none from the library's dependencies,
+# thereby reducing the size of the R class for that library
+android.nonTransitiveRClass=true
\ No newline at end of file
diff --git a/android_examples/raster_tiles/gradle/wrapper/gradle-wrapper.jar b/android_examples/raster_tiles/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..e708b1c
Binary files /dev/null and b/android_examples/raster_tiles/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/android_examples/raster_tiles/gradle/wrapper/gradle-wrapper.properties b/android_examples/raster_tiles/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..ebd3ab0
--- /dev/null
+++ b/android_examples/raster_tiles/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Tue Feb 06 14:55:12 MSK 2024
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/android_examples/raster_tiles/gradlew b/android_examples/raster_tiles/gradlew
new file mode 100755
index 0000000..4f906e0
--- /dev/null
+++ b/android_examples/raster_tiles/gradlew
@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=`expr $i + 1`
+ done
+ case $i in
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/android_examples/raster_tiles/gradlew.bat b/android_examples/raster_tiles/gradlew.bat
new file mode 100644
index 0000000..107acd3
--- /dev/null
+++ b/android_examples/raster_tiles/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/android_examples/raster_tiles/rust/Cargo.toml b/android_examples/raster_tiles/rust/Cargo.toml
new file mode 100644
index 0000000..e9a7bc7
--- /dev/null
+++ b/android_examples/raster_tiles/rust/Cargo.toml
@@ -0,0 +1,24 @@
+[package]
+name = "raster_tiles_android"
+edition = "2021"
+
+[lib]
+crate-type = ["cdylib"]
+
+[[bin]]
+name = "uniffi-bindgen"
+path = "src/uniffi-bindgen.rs"
+
+[dependencies]
+galileo = { path = "../../../galileo" }
+galileo-types = { path = "../../../galileo-types" }
+log = "0.4"
+tokio = {version = "1", features = ["rt-multi-thread"]}
+openssl-sys = "=0.9.92"
+uniffi = { version = "0.26", features = ["cli"] }
+lazy_static = "1.4"
+
+[target.'cfg(target_os = "android")'.dependencies]
+android-activity = { version = "0.5", features = ["native-activity"] }
+android_logger = "0.13"
+winit = { version = "0.29", features = ["android-native-activity"] }
diff --git a/android_examples/raster_tiles/rust/src/lib.rs b/android_examples/raster_tiles/rust/src/lib.rs
new file mode 100644
index 0000000..56cb8ee
--- /dev/null
+++ b/android_examples/raster_tiles/rust/src/lib.rs
@@ -0,0 +1,47 @@
+extern crate galileo;
+extern crate tokio;
+
+use android_activity::AndroidApp;
+use galileo::{MapBuilder, TileSchema};
+use galileo_types::latlon;
+use tokio::runtime::Runtime;
+
+#[cfg(target_os = "android")]
+#[no_mangle]
+fn android_main(app: AndroidApp) {
+ use winit::platform::android::EventLoopBuilderExtAndroid;
+
+ android_logger::init_once(
+ android_logger::Config::default().with_max_level(log::LevelFilter::Info),
+ );
+
+ log::info!("Starting up Galileo");
+
+ Runtime::new()
+ .expect("failed to create tokio runtime")
+ .block_on(async move {
+ let event_loop = winit::event_loop::EventLoopBuilder::new()
+ .with_android_app(app)
+ .build()
+ .expect("failed to create event loop");
+
+ log::info!("Building the map");
+
+ MapBuilder::new()
+ .with_event_loop(event_loop)
+ .center(latlon!(37.566, 126.9784))
+ .resolution(TileSchema::web(18).lod_resolution(8).unwrap())
+ .with_raster_tiles(
+ |index| {
+ format!(
+ "https://tile.openstreetmap.org/{}/{}/{}.png",
+ index.z, index.x, index.y
+ )
+ },
+ TileSchema::web(18),
+ )
+ .build()
+ .await
+ .run();
+ });
+}
diff --git a/android_examples/raster_tiles/rust/src/uniffi-bindgen.rs b/android_examples/raster_tiles/rust/src/uniffi-bindgen.rs
new file mode 100644
index 0000000..f6cff6c
--- /dev/null
+++ b/android_examples/raster_tiles/rust/src/uniffi-bindgen.rs
@@ -0,0 +1,3 @@
+fn main() {
+ uniffi::uniffi_bindgen_main()
+}
diff --git a/android_examples/raster_tiles/settings.gradle.kts b/android_examples/raster_tiles/settings.gradle.kts
new file mode 100644
index 0000000..b93bb84
--- /dev/null
+++ b/android_examples/raster_tiles/settings.gradle.kts
@@ -0,0 +1,18 @@
+pluginManagement {
+ repositories {
+ google()
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+rootProject.name = "RasterTilesAndroid"
+include(":app")
+
\ No newline at end of file
diff --git a/galileo-types/src/cartesian/size.rs b/galileo-types/src/cartesian/size.rs
index 7c83584..5ad9af1 100644
--- a/galileo-types/src/cartesian/size.rs
+++ b/galileo-types/src/cartesian/size.rs
@@ -1,7 +1,7 @@
use num_traits::{FromPrimitive, NumCast};
-#[derive(Debug, Clone, Copy, Default)]
-pub struct Size {
+#[derive(Debug, Clone, Copy, Default, PartialEq)]
+pub struct Size {
width: Num,
height: Num,
}
diff --git a/galileo/Cargo.toml b/galileo/Cargo.toml
index a4af854..90403f2 100644
--- a/galileo/Cargo.toml
+++ b/galileo/Cargo.toml
@@ -85,6 +85,11 @@ web-sys = { version = "0.3", features = [
"MessageEvent",
]}
+[target.'cfg(target_os = "android")'.dependencies]
+reqwest = { version = "0.11.18", features = ["native-tls-vendored"] }
+winit = {version = "0.29", features = ["android-native-activity"] }
+
+
[dev-dependencies]
tokio-test = "0.4"
env_logger = "0.10"
diff --git a/galileo/examples/render_to_file.rs b/galileo/examples/render_to_file.rs
index 9635a50..73a50fd 100644
--- a/galileo/examples/render_to_file.rs
+++ b/galileo/examples/render_to_file.rs
@@ -99,7 +99,7 @@ async fn main() -> Result<()> {
// We create a renderer without window, so it will use internal texture to render to.
// Every time the `render` method is callled, the image is updated and can be retrieved
// by the `get_image` method.
- let renderer = WgpuRenderer::create(image_size).await;
+ let renderer = WgpuRenderer::new_with_texture_rt(image_size).await;
renderer.render(&map).unwrap();
let bitmap = renderer.get_image().await.unwrap();
diff --git a/galileo/examples/with_egui/src/state/galileo_state.rs b/galileo/examples/with_egui/src/state/galileo_state.rs
index 2c6df82..6c25ed0 100644
--- a/galileo/examples/with_egui/src/state/galileo_state.rs
+++ b/galileo/examples/with_egui/src/state/galileo_state.rs
@@ -15,7 +15,7 @@ use galileo::{
use galileo_types::cartesian::impls::point::Point2d;
use galileo_types::{cartesian::size::Size, latlon};
use wgpu::{Device, Queue, Surface, SurfaceConfiguration};
-use winit::{dpi::PhysicalSize, window::Window};
+use winit::window::Window;
use super::WgpuFrame;
@@ -38,13 +38,7 @@ impl GalileoState {
) -> Self {
let messenger = galileo::winit::WinitMessenger::new(window);
- let renderer = WgpuRenderer::create_with_surface(
- device,
- surface,
- queue,
- config,
- Size::new(size.width, size.height),
- );
+ let renderer = WgpuRenderer::new_with_device_and_surface(device, surface, queue, config);
let renderer = Arc::new(RwLock::new(renderer));
let input_handler = WinitInputHandler::default();
diff --git a/galileo/src/galileo_map.rs b/galileo/src/galileo_map.rs
index 69de9f5..104ca87 100644
--- a/galileo/src/galileo_map.rs
+++ b/galileo/src/galileo_map.rs
@@ -70,7 +70,40 @@ impl GalileoMap {
target.set_control_flow(ControlFlow::Wait);
match event {
+ Event::Resumed => {
+ log::info!("Resume called");
+ let size = window.inner_size();
+ if size.width > 0 && size.height > 0 {
+ let backend = backend.clone();
+ let window = window.clone();
+ let map = map.clone();
+ crate::async_runtime::spawn(async move {
+ let renderer = WgpuRenderer::new_with_window(
+ &window,
+ Size::new(size.width, size.height),
+ )
+ .await
+ .expect("failed to init renderer");
+
+ *backend.write().expect("poisoned lock") = renderer;
+ map.write()
+ .expect("poisoned lock")
+ .set_size(Size::new(size.width as f64, size.height as f64));
+ window.request_redraw();
+ });
+ }
+ }
+ Event::Suspended => {
+ backend
+ .write()
+ .expect("poisoned lock")
+ .clear_render_target();
+ }
Event::WindowEvent { event, window_id } if window_id == window.id() => {
+ if !backend.read().expect("poisoned lock").initialized() {
+ return;
+ }
+
match event {
WindowEvent::CloseRequested => {
target.exit();
@@ -81,6 +114,7 @@ impl GalileoMap {
.write()
.unwrap()
.resize(Size::new(size.width, size.height));
+
let mut map = map.write().unwrap();
map.set_size(Size::new(size.width as f64, size.height as f64));
}
@@ -225,6 +259,9 @@ impl MapBuilder {
.event_loop
.take()
.unwrap_or_else(|| EventLoop::new().unwrap());
+
+ log::info!("Trying to get window");
+
let window = self.window.take().unwrap_or_else(|| {
WindowBuilder::new()
.with_inner_size(PhysicalSize {
@@ -239,11 +276,10 @@ impl MapBuilder {
let messenger = WinitMessenger::new(window.clone());
log::info!("Window size: {:?}", window.inner_size());
- let backend = WgpuRenderer::create_with_window(
- &window,
- Size::new(window.inner_size().width, window.inner_size().height),
- )
- .await;
+ let backend = WgpuRenderer::new()
+ .await
+ .expect("failed to create renderer");
+
let backend = Arc::new(RwLock::new(backend));
let input_handler = WinitInputHandler::default();
@@ -293,8 +329,14 @@ impl MapBuilder {
tile_source: impl UrlSource + 'static,
tile_scheme: TileSchema,
) -> RasterTileLayer> {
+ #[cfg(not(target_os = "android"))]
let cache_controller = Some(FileCacheController::new(".tile_cache"));
+ #[cfg(target_os = "android")]
+ let cache_controller = Some(FileCacheController::new(
+ "/data/data/com.example.rastertilesandroid/.tile_cache",
+ ));
+
let tile_provider = UrlImageProvider::new(tile_source, cache_controller);
RasterTileLayer::new(tile_scheme, tile_provider, None)
}
diff --git a/galileo/src/render/wgpu/mod.rs b/galileo/src/render/wgpu/mod.rs
index 2631c20..c29f8cb 100644
--- a/galileo/src/render/wgpu/mod.rs
+++ b/galileo/src/render/wgpu/mod.rs
@@ -1,3 +1,4 @@
+use cfg_if::cfg_if;
use galileo_types::cartesian::size::Size;
use lyon::tessellation::VertexBuffers;
use nalgebra::{Rotation3, Vector3};
@@ -13,6 +14,7 @@ use wgpu::{
TextureUsages, TextureView, TextureViewDescriptor,
};
+use crate::error::GalileoError;
use crate::layer::Layer;
use crate::map::Map;
use crate::render::render_bundle::tessellating::{
@@ -28,17 +30,21 @@ use super::{Canvas, PackedBundle, RenderOptions, Renderer};
mod pipelines;
+const DEFAULT_BACKGROUND: Color = Color::WHITE;
const DEPTH_FORMAT: TextureFormat = TextureFormat::Depth24PlusStencil8;
const TARGET_TEXTURE_FORMAT: TextureFormat = TextureFormat::Rgba8UnormSrgb;
pub struct WgpuRenderer {
- render_target: RenderTarget,
device: Arc,
queue: Arc,
- size: Size,
+ render_set: Option,
+ background: Color,
+}
+
+struct RenderSet {
+ render_target: RenderTarget,
pipelines: Pipelines,
multisampling_view: TextureView,
- background: Color,
stencil_view_multisample: TextureView,
stencil_view: TextureView,
}
@@ -48,7 +54,7 @@ enum RenderTarget {
config: SurfaceConfiguration,
surface: Arc,
},
- Texture(Texture),
+ Texture(Texture, Size),
}
enum RenderTargetTexture<'a> {
@@ -78,7 +84,21 @@ impl RenderTarget {
RenderTarget::Surface { surface, .. } => {
Ok(RenderTargetTexture::Surface(surface.get_current_texture()?))
}
- RenderTarget::Texture(texture) => Ok(RenderTargetTexture::Texture(texture)),
+ RenderTarget::Texture(texture, _) => Ok(RenderTargetTexture::Texture(texture)),
+ }
+ }
+
+ fn size(&self) -> Size {
+ match &self {
+ RenderTarget::Surface { config, .. } => Size::new(config.width, config.height),
+ RenderTarget::Texture(_, size) => *size,
+ }
+ }
+
+ fn format(&self) -> TextureFormat {
+ match &self {
+ RenderTarget::Surface { config, .. } => config.format,
+ RenderTarget::Texture(_, _) => TARGET_TEXTURE_FORMAT,
}
}
}
@@ -90,7 +110,11 @@ impl Renderer for WgpuRenderer {
fn pack_bundle(&self, bundle: &RenderBundle) -> Box {
match bundle {
- RenderBundle::Tessellating(inner) => Box::new(WgpuPackedBundle::new(inner, self)),
+ RenderBundle::Tessellating(inner) => Box::new(WgpuPackedBundle::new(
+ inner,
+ self,
+ self.render_set.as_ref().unwrap(),
+ )),
}
}
@@ -100,7 +124,7 @@ impl Renderer for WgpuRenderer {
}
impl WgpuRenderer {
- pub async fn create(size: Size) -> Self {
+ pub async fn new() -> Option {
let instance = Self::create_instance();
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
@@ -108,13 +132,34 @@ impl WgpuRenderer {
compatible_surface: None,
force_fallback_adapter: false,
})
- .await
- .unwrap();
+ .await?;
let (device, queue) = Self::create_device(&adapter).await;
- let target_texture = device.create_texture(&TextureDescriptor {
- label: Some("Multisampling texture"),
+ Some(Self {
+ device: Arc::new(device),
+ queue: Arc::new(queue),
+ render_set: None,
+ background: DEFAULT_BACKGROUND,
+ })
+ }
+
+ pub async fn new_with_texture_rt(size: Size) -> Option {
+ let mut renderer = Self::new().await?;
+ renderer.init_target_texture(size);
+
+ Some(renderer)
+ }
+
+ fn init_target_texture(&mut self, size: Size) {
+ let target_texture = Self::create_target_texture(&self.device, size);
+ let render_target = RenderTarget::Texture(target_texture, size);
+ self.init_render_set(render_target);
+ }
+
+ fn create_target_texture(device: &Device, size: Size) -> Texture {
+ device.create_texture(&TextureDescriptor {
+ label: Some("Render target texture"),
size: Extent3d {
width: size.width(),
height: size.height(),
@@ -126,47 +171,104 @@ impl WgpuRenderer {
format: TARGET_TEXTURE_FORMAT,
usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::COPY_SRC,
view_formats: &[],
- });
+ })
+ }
- let multisampling_view =
- Self::create_multisample_texture(&device, size, TARGET_TEXTURE_FORMAT);
- let stencil_view_multisample = Self::create_stencil_texture(&device, size, 4);
- let stencil_view = Self::create_stencil_texture(&device, size, 1);
+ fn init_render_set(&mut self, new_target: RenderTarget) {
+ let current_set = self.render_set.take();
+ match current_set {
+ Some(RenderSet {
+ render_target,
+ pipelines,
+ multisampling_view,
+ stencil_view_multisample,
+ stencil_view,
+ }) if new_target.size() == render_target.size() => {
+ let pipelines = if new_target.format() == render_target.format() {
+ pipelines
+ } else {
+ Pipelines::create(&self.device, new_target.format())
+ };
+
+ self.render_set = Some(RenderSet {
+ render_target: new_target,
+ pipelines,
+ multisampling_view,
+ stencil_view_multisample,
+ stencil_view,
+ })
+ }
+ _ => self.render_set = Some(self.create_render_set(new_target)),
+ }
+ }
- let pipelines = Pipelines::create(&device, TARGET_TEXTURE_FORMAT);
+ fn create_render_set(&self, render_target: RenderTarget) -> RenderSet {
+ let size = render_target.size();
+ let format = render_target.format();
- Self {
- render_target: RenderTarget::Texture(target_texture),
- device: Arc::new(device),
- queue: Arc::new(queue),
- size,
+ let multisampling_view = Self::create_multisample_texture(&self.device, size, format);
+ let stencil_view_multisample = Self::create_stencil_texture(&self.device, size, 4);
+ let stencil_view = Self::create_stencil_texture(&self.device, size, 1);
+
+ let pipelines = Pipelines::create(&self.device, format);
+
+ RenderSet {
+ render_target,
pipelines,
multisampling_view,
stencil_view_multisample,
stencil_view,
- background: Color::rgba(255, 255, 255, 255),
}
}
- pub async fn create_with_window(window: &W, size: Size) -> Self
+ pub async fn new_with_window(window: &W, size: Size) -> Option
+ where
+ W: raw_window_handle::HasRawWindowHandle + raw_window_handle::HasRawDisplayHandle,
+ {
+ let (surface, adapter) = Self::get_window_surface(window).await.unwrap();
+ let (device, queue) = Self::create_device(&adapter).await;
+
+ let config = Self::get_surface_configuration(&surface, &adapter, size);
+ surface.configure(&device, &config);
+
+ Some(Self::new_with_device_and_surface(
+ Arc::new(device),
+ Arc::new(surface),
+ Arc::new(queue),
+ config,
+ ))
+ }
+
+ pub async fn get_window_surface(window: &W) -> Option<(Surface, Adapter)>
where
W: raw_window_handle::HasRawWindowHandle + raw_window_handle::HasRawDisplayHandle,
{
let instance = Self::create_instance();
- let surface = unsafe { instance.create_surface(window) }.unwrap();
+ let surface = match unsafe { instance.create_surface(window) } {
+ Ok(s) => s,
+ Err(err) => {
+ log::warn!("Failed to create a surface from window: {err:?}");
+ return None;
+ }
+ };
+
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::default(),
compatible_surface: Some(&surface),
force_fallback_adapter: false,
})
- .await
- .unwrap();
-
- let (device, queue) = Self::create_device(&adapter).await;
+ .await?;
+ Some((surface, adapter))
+ }
- let surface_caps = surface.get_capabilities(&adapter);
+ fn get_surface_configuration(
+ surface: &Surface,
+ adapter: &Adapter,
+ size: Size,
+ ) -> SurfaceConfiguration {
+ let surface_caps = surface.get_capabilities(adapter);
let surface_format = surface_caps
.formats
.iter()
@@ -174,7 +276,7 @@ impl WgpuRenderer {
.find(|f| f.is_srgb())
.unwrap_or(surface_caps.formats[0]);
- let config = wgpu::SurfaceConfiguration {
+ SurfaceConfiguration {
usage: TextureUsages::RENDER_ATTACHMENT,
format: surface_format,
width: size.width(),
@@ -182,64 +284,46 @@ impl WgpuRenderer {
present_mode: surface_caps.present_modes[0],
alpha_mode: surface_caps.alpha_modes[0],
view_formats: vec![],
- };
-
- surface.configure(&device, &config);
- let multisampling_view = Self::create_multisample_texture(&device, size, config.format);
- let stencil_view_multisample = Self::create_stencil_texture(&device, size, 4);
- let stencil_view = Self::create_stencil_texture(&device, size, 1);
-
- let pipelines = Pipelines::create(&device, surface_format);
-
- Self {
- render_target: RenderTarget::Surface {
- surface: Arc::new(surface),
- config,
- },
- device: Arc::new(device),
- queue: Arc::new(queue),
- size,
- pipelines,
- multisampling_view,
- stencil_view_multisample,
- stencil_view,
- background: Color::rgba(255, 255, 255, 255),
}
}
- pub fn create_with_surface(
+ pub fn new_with_device_and_surface(
device: Arc,
surface: Arc,
queue: Arc,
config: SurfaceConfiguration,
- size: Size,
) -> Self {
- let multisampling_view = Self::create_multisample_texture(&device, size, config.format);
- let stencil_view_multisample = Self::create_stencil_texture(&device, size, 4);
- let stencil_view = Self::create_stencil_texture(&device, size, 1);
-
- let pipelines = Pipelines::create(&device, config.format);
-
- Self {
- render_target: RenderTarget::Surface { surface, config },
+ let render_target = RenderTarget::Surface { surface, config };
+ let mut renderer = Self {
device,
queue,
- size,
- pipelines,
- multisampling_view,
- stencil_view_multisample,
- stencil_view,
- background: Color::rgba(255, 255, 255, 255),
- }
+ render_set: None,
+ background: DEFAULT_BACKGROUND,
+ };
+ renderer.init_render_set(render_target);
+
+ renderer
}
pub fn set_background(&mut self, color: Color) {
self.background = color;
}
+ pub fn initialized(&self) -> bool {
+ self.render_set.is_some()
+ }
+
fn create_instance() -> wgpu::Instance {
+ cfg_if! {
+ if #[cfg(target_os = "android")] {
+ let backends = wgpu::Backends::GL;
+ } else {
+ let backends = wgpu::Backends::all();
+ }
+ };
+
wgpu::Instance::new(wgpu::InstanceDescriptor {
- backends: wgpu::Backends::all(),
+ backends,
flags: Default::default(),
dx12_shader_compiler: Default::default(),
gles_minor_version: Default::default(),
@@ -251,7 +335,7 @@ impl WgpuRenderer {
.request_device(
&wgpu::DeviceDescriptor {
features: wgpu::Features::empty(),
- limits: if cfg!(target_arch = "wasm32") {
+ limits: if cfg!(any(target_arch = "wasm32", target_os = "android")) {
wgpu::Limits {
max_texture_dimension_2d: 4096,
..wgpu::Limits::downlevel_webgl2_defaults()
@@ -309,39 +393,84 @@ impl WgpuRenderer {
texture.create_view(&TextureViewDescriptor::default())
}
- pub fn resize(&mut self, new_size: Size) {
- if new_size.width() > 0 && new_size.height() > 0 {
- self.size = new_size;
- self.resize_render_target(new_size);
-
- self.multisampling_view =
- Self::create_multisample_texture(&self.device, new_size, self.target_format());
- self.stencil_view_multisample = Self::create_stencil_texture(&self.device, new_size, 4);
- self.stencil_view = Self::create_stencil_texture(&self.device, new_size, 1);
- }
+ pub fn clear_render_target(&mut self) {
+ self.render_set = None;
+ }
+
+ pub async fn init_with_window(
+ &mut self,
+ window: &W,
+ size: Size,
+ ) -> Result<(), GalileoError>
+ where
+ W: raw_window_handle::HasRawWindowHandle + raw_window_handle::HasRawDisplayHandle,
+ {
+ let Some((surface, adapter)) = Self::get_window_surface(window).await else {
+ return Err(GalileoError::Generic("Failed to create surface".into()));
+ };
+ self.init_with_surface(surface, adapter, size);
+
+ Ok(())
+ }
+
+ pub fn init_with_surface(&mut self, surface: Surface, adapter: Adapter, size: Size) {
+ let config = Self::get_surface_configuration(&surface, &adapter, size);
+ surface.configure(&self.device, &config);
+
+ let render_target = RenderTarget::Surface {
+ surface: Arc::new(surface),
+ config,
+ };
+ self.init_render_set(render_target);
}
- fn resize_render_target(&mut self, new_size: Size) {
- match &mut self.render_target {
- RenderTarget::Surface { config, surface } => {
- config.width = new_size.width();
- config.height = new_size.height();
- surface.configure(&self.device, config);
+ pub fn resize(&mut self, new_size: Size) {
+ let format = self.target_format();
+ let Some(render_set) = &mut self.render_set else {
+ return;
+ };
+
+ if render_set.render_target.size() != new_size
+ && new_size.width() > 0
+ && new_size.height() > 0
+ {
+ match &mut render_set.render_target {
+ RenderTarget::Surface { config, surface } => {
+ config.width = new_size.width();
+ config.height = new_size.height();
+ surface.configure(&self.device, config);
+ }
+ RenderTarget::Texture(texture, size) => {
+ *texture = Self::create_target_texture(&self.device, *size);
+ *size = new_size
+ }
}
- RenderTarget::Texture(_) => {}
+
+ render_set.multisampling_view =
+ Self::create_multisample_texture(&self.device, new_size, format);
+ render_set.stencil_view_multisample =
+ Self::create_stencil_texture(&self.device, new_size, 4);
+ render_set.stencil_view = Self::create_stencil_texture(&self.device, new_size, 1);
}
}
fn target_format(&self) -> TextureFormat {
- match &self.render_target {
- RenderTarget::Surface { config, .. } => config.format,
- RenderTarget::Texture(_) => TARGET_TEXTURE_FORMAT,
+ match &self.render_set {
+ Some(RenderSet {
+ render_target: RenderTarget::Surface { config, .. },
+ ..
+ }) => config.format,
+ _ => TARGET_TEXTURE_FORMAT,
}
}
pub async fn get_image(&self) -> Result, SurfaceError> {
- let buffer_size =
- (self.size.width() * self.size.height() * size_of::() as u32) as BufferAddress;
+ let Some(render_set) = &self.render_set else {
+ return Err(SurfaceError::Lost);
+ };
+
+ let size = render_set.render_target.size();
+ let buffer_size = (size.width() * size.height() * size_of::() as u32) as BufferAddress;
let buffer_desc = BufferDescriptor {
size: buffer_size,
usage: BufferUsages::COPY_DST | BufferUsages::MAP_READ,
@@ -350,7 +479,7 @@ impl WgpuRenderer {
};
let buffer = self.device.create_buffer(&buffer_desc);
- let RenderTarget::Texture(texture) = &self.render_target else {
+ let RenderTarget::Texture(texture, _) = &render_set.render_target else {
todo!()
};
@@ -366,13 +495,13 @@ impl WgpuRenderer {
buffer: &buffer,
layout: ImageDataLayout {
offset: 0,
- bytes_per_row: Some(size_of::() as u32 * self.size.width()),
- rows_per_image: Some(self.size.height()),
+ bytes_per_row: Some(size_of::() as u32 * size.width()),
+ rows_per_image: Some(size.height()),
},
},
Extent3d {
- width: self.size.width(),
- height: self.size.height(),
+ width: size.width(),
+ height: size.height(),
depth_or_array_layers: 1,
},
);
@@ -392,42 +521,50 @@ impl WgpuRenderer {
}
pub fn render_to_texture_view(&self, map: &Map, view: &TextureView) {
- let mut encoder = self
- .device
- .create_command_encoder(&wgpu::CommandEncoderDescriptor {
- label: Some("Render Encoder"),
- });
+ if let Some(render_set) = &self.render_set {
+ let mut encoder = self
+ .device
+ .create_command_encoder(&wgpu::CommandEncoderDescriptor {
+ label: Some("Render Encoder"),
+ });
- {
- let background = self.background.to_f32_array();
- let _ = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
- label: Some("Render Pass"),
- color_attachments: &[Some(wgpu::RenderPassColorAttachment {
- view: &self.multisampling_view,
- resolve_target: Some(view),
- ops: wgpu::Operations {
- load: wgpu::LoadOp::Clear(wgpu::Color {
- r: background[0] as f64,
- g: background[1] as f64,
- b: background[2] as f64,
- a: background[3] as f64,
- }),
- store: StoreOp::Store,
- },
- })],
- depth_stencil_attachment: None,
- timestamp_writes: None,
- occlusion_query_set: None,
- });
- }
+ {
+ let background = self.background.to_f32_array();
+ let _ = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
+ label: Some("Render Pass"),
+ color_attachments: &[Some(wgpu::RenderPassColorAttachment {
+ view: &render_set.multisampling_view,
+ resolve_target: Some(view),
+ ops: wgpu::Operations {
+ load: wgpu::LoadOp::Clear(wgpu::Color {
+ r: background[0] as f64,
+ g: background[1] as f64,
+ b: background[2] as f64,
+ a: background[3] as f64,
+ }),
+ store: StoreOp::Store,
+ },
+ })],
+ depth_stencil_attachment: None,
+ timestamp_writes: None,
+ occlusion_query_set: None,
+ });
+ }
- self.queue.submit(std::iter::once(encoder.finish()));
+ self.queue.submit(std::iter::once(encoder.finish()));
+ } else {
+ return;
+ }
self.render_map(map, view);
}
pub fn render(&self, map: &Map) -> Result<(), SurfaceError> {
- let texture = self.render_target.texture()?;
+ let Some(render_set) = &self.render_set else {
+ return Ok(());
+ };
+
+ let texture = render_set.render_target.texture()?;
let view = texture.view();
self.render_to_texture_view(map, &view);
@@ -445,23 +582,37 @@ impl WgpuRenderer {
}
fn render_layer(&self, layer: &dyn Layer, view: &MapView, texture_view: &TextureView) {
- let mut canvas = WgpuCanvas::new(self, texture_view, view.clone());
+ let Some(render_set) = &self.render_set else {
+ return;
+ };
+ let mut canvas = WgpuCanvas::new(self, render_set, texture_view, view.clone());
layer.render(view, &mut canvas);
}
pub fn size(&self) -> Size {
- Size::new(self.size.width() as f64, self.size.height() as f64)
+ let size = match &self.render_set {
+ Some(set) => set.render_target.size(),
+ None => Size::default(),
+ };
+
+ Size::new(size.width() as f64, size.height() as f64)
}
}
#[allow(dead_code)]
struct WgpuCanvas<'a> {
renderer: &'a WgpuRenderer,
+ render_set: &'a RenderSet,
view: &'a TextureView,
}
impl<'a> WgpuCanvas<'a> {
- fn new(renderer: &'a WgpuRenderer, view: &'a TextureView, map_view: MapView) -> Self {
+ fn new(
+ renderer: &'a WgpuRenderer,
+ render_set: &'a RenderSet,
+ view: &'a TextureView,
+ map_view: MapView,
+ ) -> Self {
let rotation_mtx = Rotation3::new(Vector3::new(
map_view.rotation_x(),
0.0,
@@ -469,21 +620,25 @@ impl<'a> WgpuCanvas<'a> {
))
.to_homogeneous();
renderer.queue.write_buffer(
- renderer.pipelines.map_view_buffer(),
+ render_set.pipelines.map_view_buffer(),
0,
bytemuck::cast_slice(&[ViewUniform {
view_proj: map_view.map_to_scene_mtx().unwrap(),
view_rotation: rotation_mtx.cast::().data.0,
inv_screen_size: [
- 1.0 / renderer.size.width() as f32,
- 1.0 / renderer.size.height() as f32,
+ 1.0 / renderer.size().width() as f32,
+ 1.0 / renderer.size().height() as f32,
],
resolution: map_view.resolution() as f32,
_padding: [0.0; 1],
}]),
);
- Self { renderer, view }
+ Self {
+ renderer,
+ render_set,
+ view,
+ }
}
}
@@ -499,7 +654,7 @@ impl<'a> Canvas for WgpuCanvas<'a> {
fn pack_bundle(&self, bundle: &RenderBundle) -> Box {
match bundle {
RenderBundle::Tessellating(inner) => {
- Box::new(WgpuPackedBundle::new(inner, self.renderer))
+ Box::new(WgpuPackedBundle::new(inner, self.renderer, self.render_set))
}
}
}
@@ -515,12 +670,12 @@ impl<'a> Canvas for WgpuCanvas<'a> {
{
let (view, resolve_target, depth_view) = if options.antialias {
(
- &self.renderer.multisampling_view,
+ &self.render_set.multisampling_view,
Some(self.view),
- &self.renderer.stencil_view_multisample,
+ &self.render_set.stencil_view_multisample,
)
} else {
- (self.view, None, &self.renderer.stencil_view)
+ (self.view, None, &self.render_set.stencil_view)
};
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
@@ -549,8 +704,8 @@ impl<'a> Canvas for WgpuCanvas<'a> {
});
for bundle in bundles {
- if let Some(cast) = bundle.as_any().downcast_ref() {
- self.renderer
+ if let Some(cast) = bundle.as_any().downcast_ref::() {
+ self.render_set
.pipelines
.render(&mut render_pass, cast, options);
}
@@ -589,7 +744,11 @@ struct WgpuDotBuffers {
}
impl WgpuPackedBundle {
- fn new(bundle: &TessellatingRenderBundle, renderer: &WgpuRenderer) -> Self {
+ fn new(
+ bundle: &TessellatingRenderBundle,
+ renderer: &WgpuRenderer,
+ render_set: &RenderSet,
+ ) -> Self {
let TessellatingRenderBundle {
poly_tessellation,
points,
@@ -653,7 +812,7 @@ impl WgpuPackedBundle {
let textures: Vec<_> = image_store
.iter()
.map(|decoded_image| {
- renderer.pipelines.image_pipeline().create_image_texture(
+ render_set.pipelines.image_pipeline().create_image_texture(
&renderer.device,
&renderer.queue,
decoded_image,
@@ -663,7 +822,7 @@ impl WgpuPackedBundle {
let mut image_buffers = vec![];
for (image_index, vertices) in images {
- let image = renderer.pipelines.image_pipeline().create_image(
+ let image = render_set.pipelines.image_pipeline().create_image(
&renderer.device,
textures[*image_index].clone(),
vertices,