diff --git a/.github/workflows/cmake-presets.yml b/.github/workflows/cmake-presets.yml
index c0bbee54a..0261834df 100644
--- a/.github/workflows/cmake-presets.yml
+++ b/.github/workflows/cmake-presets.yml
@@ -15,7 +15,7 @@ permissions:
jobs:
msvc-debug:
- runs-on: windows-latest
+ runs-on: windows-2022
defaults:
run:
shell: powershell
@@ -53,7 +53,10 @@ jobs:
sccache-${{ runner.os }}-${{ env.ALP_CACHE_CONFIG }}-${{ env.ALP_CACHE_TOOLCHAIN }}-v1-
- name: Make sure MSVC is found when Ninja generator is in use
- uses: ilammy/msvc-dev-cmd@v1
+ uses: TheMrMilchmann/setup-msvc-dev@v4
+ with:
+ arch: x64
+ sdk: 10.0.26100.0
- name: Install tools
run: choco install 7zip.install ninja
@@ -85,6 +88,7 @@ jobs:
- name: Configure
env:
CMAKE_PREFIX_PATH: ${{ env.QT_ROOT_DIR }}/lib/cmake
+ GITHUB_TOKEN: ${{ github.token }}
run: >
cmake --preset ${{ matrix.preset }}
-DCMAKE_POLICY_DEFAULT_CMP0141=NEW
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index e62da659a..4ea7d5e6e 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -193,7 +193,7 @@ jobs:
find "$BUILD_DIR" -type f -iname "*.apk" ! -name "android-build-*.apk" -exec cp {} "$INSTALL_DIR" \;
- name: Create artifact
- uses: actions/upload-artifact@v5
+ uses: actions/upload-artifact@v7
with:
name: files_${{ matrix.qtarch }}
path: ${{ github.workspace }}/install/
@@ -383,14 +383,18 @@ jobs:
if: always()
run: ccache --show-stats --verbose
- - name: Verify WebGPU app artifacts
+ - name: Verify app artifacts
run: |
- test -f "$INSTALL_DIR/webgpu_app.html"
- test -f "$INSTALL_DIR/webgpu_app.js"
- test -f "$INSTALL_DIR/webgpu_app.wasm"
+ test -f "$INSTALL_DIR/alpineapp/alpineapp.html"
+ test -f "$INSTALL_DIR/alpineapp/alpineapp.js"
+ test -f "$INSTALL_DIR/alpineapp/alpineapp.wasm"
+ test -f "$INSTALL_DIR/alpineapp/logo_vertical_light_bg.png"
+ test -f "$INSTALL_DIR/webgpu_app/webgpu_app.html"
+ test -f "$INSTALL_DIR/webgpu_app/webgpu_app.js"
+ test -f "$INSTALL_DIR/webgpu_app/webgpu_app.wasm"
- name: Create artifact
- uses: actions/upload-artifact@v5
+ uses: actions/upload-artifact@v7
with:
name: files_${{ matrix.config}}
path: ${{ github.workspace }}/install/
@@ -451,9 +455,10 @@ jobs:
run: |
for config in wasm_mt wasm_mt_debug wasm_mt_lto; do
cd "$GITHUB_WORKSPACE/github_page/$config"
- wget -q https://raw.githubusercontent.com/gzuidhof/coi-serviceworker/master/coi-serviceworker.min.js
- for html in alpineapp.html webgpu_app.html; do
+ for target in alpineapp webgpu_app; do
+ html="$target/$target.html"
test -f "$html"
+ wget -q -O "$target/coi-serviceworker.min.js" https://raw.githubusercontent.com/gzuidhof/coi-serviceworker/master/coi-serviceworker.min.js
sed -i -e 's#
##g' "$html"
done
done
@@ -464,13 +469,13 @@ jobs:
target: github_page
- name: Create Pages artifact
- uses: actions/upload-pages-artifact@v4
+ uses: actions/upload-pages-artifact@v5
with:
path: ${{github.workspace}}/github_page
- name: Setup Pages
- uses: actions/configure-pages@v5
+ uses: actions/configure-pages@v6
- name: Deploy to GitHub Pages
id: deployment
- uses: actions/deploy-pages@v4
+ uses: actions/deploy-pages@v5
diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml
index ef198d386..3f46d1e47 100644
--- a/.github/workflows/linux.yml
+++ b/.github/workflows/linux.yml
@@ -110,6 +110,7 @@ jobs:
CC: ${{ matrix.CC }}
CXX: ${{ matrix.CXX }}
CMAKE_PREFIX_PATH: ${{env.QT_ROOT_DIR}}/lib/cmake
+ GITHUB_TOKEN: ${{ github.token }}
run: >
cmake -G Ninja
-DCMAKE_BUILD_TYPE=${{matrix.BUILD_TYPE}}
diff --git a/.github/workflows/play-store.yml b/.github/workflows/play-store.yml
index 91149b4a9..d7ccb70f7 100644
--- a/.github/workflows/play-store.yml
+++ b/.github/workflows/play-store.yml
@@ -201,7 +201,7 @@ jobs:
find "$PLAY_STORE_DIR" -type f -iname "*.aab"
- name: Create Play Store artifact
- uses: actions/upload-artifact@v5
+ uses: actions/upload-artifact@v7
with:
name: play_store_aab
path: ${{ github.workspace }}/play-store/
diff --git a/.github/workflows/release-web.yml b/.github/workflows/release-web.yml
new file mode 100644
index 000000000..9bd8a4410
--- /dev/null
+++ b/.github/workflows/release-web.yml
@@ -0,0 +1,303 @@
+name: "Release Web"
+
+on:
+ push:
+ tags: [ "*" ]
+
+concurrency:
+ group: release-web-${{ github.ref }}
+ cancel-in-progress: true
+
+permissions:
+ contents: read
+
+jobs:
+ build-wasm:
+ if: github.repository == 'AlpineMapsOrg/renderer'
+ runs-on: ubuntu-24.04
+
+ steps:
+ - name: Install dependencies
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y build-essential ccache ninja-build
+
+ - uses: actions/checkout@v7
+ with:
+ fetch-depth: 0
+ fetch-tags: true
+
+ - name: Load CI versions
+ run: cat .github/ci-versions.env >> "$GITHUB_ENV"
+
+ - uses: mymindstorm/setup-emsdk@v15
+ with:
+ version: ${{ env.WEBASSEMBLY_VERSION }}
+ actions-cache-folder: emsdk-cache
+ cache-key: emsdk-${{ runner.os }}-${{ env.WEBASSEMBLY_VERSION }}
+
+ - name: Use stable Emscripten path
+ shell: bash
+ run: |
+ STABLE_EMSDK_ROOT="${{ github.workspace }}/emsdk-cache/emsdk-main"
+ if [ ! -x "$STABLE_EMSDK_ROOT/emsdk" ]; then
+ echo "Expected emsdk at $STABLE_EMSDK_ROOT/emsdk"
+ exit 1
+ fi
+
+ "$STABLE_EMSDK_ROOT/emsdk" activate "$WEBASSEMBLY_VERSION"
+ "$STABLE_EMSDK_ROOT/emsdk" construct_env | while IFS= read -r line; do
+ case "$line" in
+ "PATH += "*)
+ echo "${line#PATH += }" >> "$GITHUB_PATH"
+ ;;
+ *" = "*)
+ name="${line%% = *}"
+ value="${line#* = }"
+ echo "$name=$value" >> "$GITHUB_ENV"
+ ;;
+ esac
+ done
+
+ - name: Install Qt native version
+ uses: jurplel/install-qt-action@v4
+ with:
+ version: ${{ env.QT_VERSION }}
+ host: linux
+ target: desktop
+ arch: linux_gcc_64
+ dir: '${{ github.workspace }}/qt_host'
+ install-deps: 'true'
+ modules: qtshadertools
+ cache: true
+ tools: tools_cmake
+
+ - name: Set reusable strings
+ shell: bash
+ run: |
+ echo "BUILD_DIR=${{ github.workspace }}/build" >> "$GITHUB_ENV"
+ echo "INSTALL_DIR=${{ github.workspace }}/install/wasm_mt_lto" >> "$GITHUB_ENV"
+ echo "QT_WASM_ROOT_DIR=${{ github.workspace }}/qt_wasm_lite" >> "$GITHUB_ENV"
+ echo "QT_SRC_CONFIGURE=${{ github.workspace }}/qt_src/configure" >> "$GITHUB_ENV"
+ echo "QT_LITE_CONFIG=${{ github.workspace }}/misc/qt_lite.txt" >> "$GITHUB_ENV"
+
+ - name: Cache Qt WebAssembly build
+ id: qt-cache
+ uses: actions/cache@v6
+ with:
+ path: qt_wasm_lite
+ key: wasm_mt_lto_qt-${{ env.QT_VERSION }}_emcc-${{ env.WEBASSEMBLY_VERSION }}_qtpositioning_${{ hashFiles('misc/qt_lite.txt', 'misc/qt_68_qtbase_remove_dejavu_fonts.patch') }}
+
+ - name: Download and patch Qt
+ if: steps.qt-cache.outputs.cache-hit != 'true'
+ shell: bash
+ run: |
+ git clone --branch "$QT_VERSION" git://code.qt.io/qt/qt5.git qt_src
+ mkdir qt_wasm_build
+ cd qt_wasm_build
+ "$QT_SRC_CONFIGURE" -qt-host-path "$QT_ROOT_DIR" -platform wasm-emscripten -init-submodules -submodules qtdeclarative,qtbase,qtpositioning -skip qtlanguageserver,qtquicktimeline,qtimageformats
+ cd "${{ github.workspace }}/qt_src/qtbase"
+ git apply "${{ github.workspace }}/misc/qt_68_qtbase_remove_dejavu_fonts.patch"
+ cd "${{ github.workspace }}"
+ rm -rf qt_wasm_build
+ mkdir qt_wasm_build
+
+ - name: Build Qt for WebAssembly
+ if: steps.qt-cache.outputs.cache-hit != 'true'
+ shell: bash
+ run: |
+ cd qt_wasm_build
+ "$QT_SRC_CONFIGURE" \
+ -qt-host-path "$QT_ROOT_DIR" \
+ -platform wasm-emscripten \
+ -release \
+ -optimize-size \
+ -submodules qtdeclarative,qtbase,qtpositioning \
+ -skip qtlanguageserver,qtquicktimeline,qtimageformats \
+ -gui \
+ -ltcg \
+ $(cat "$QT_LITE_CONFIG") \
+ -feature-thread \
+ -prefix "$QT_WASM_ROOT_DIR/"
+ # Work around https://bugreports.qt.io/browse/QTBUG-129522.
+ sed -i 's/-flto=thin/-flto/g' ./build.ninja
+ cmake --build . --parallel
+ cmake --install .
+ test -d "$QT_WASM_ROOT_DIR"
+ test -n "$(ls -A "$QT_WASM_ROOT_DIR")"
+
+ - name: Remove StatsWindow
+ run: rm ./app/StatsWindow.qml
+
+ - name: Set ccache config
+ shell: bash
+ run: |
+ echo "CCACHE_DIR=${{ github.workspace }}/.ccache" >> "$GITHUB_ENV"
+ echo "CCACHE_BASEDIR=${{ github.workspace }}" >> "$GITHUB_ENV"
+ echo "CCACHE_COMPRESS=true" >> "$GITHUB_ENV"
+ echo "CCACHE_COMPILERCHECK=content" >> "$GITHUB_ENV"
+ echo "CCACHE_MAXSIZE=500M" >> "$GITHUB_ENV"
+
+ - name: Cache ccache
+ uses: actions/cache@v6
+ with:
+ path: ${{ github.workspace }}/.ccache
+ key: ccache-${{ runner.os }}-wasm_mt_lto-emscripten-${{ env.WEBASSEMBLY_VERSION }}-qt-${{ env.QT_VERSION }}-v3-${{ github.run_id }}-${{ github.run_attempt }}
+ restore-keys: |
+ ccache-${{ runner.os }}-wasm_mt_lto-emscripten-${{ env.WEBASSEMBLY_VERSION }}-qt-${{ env.QT_VERSION }}-v3-
+
+ - name: Configure AlpineApp
+ run: |
+ "$QT_WASM_ROOT_DIR/bin/qt-cmake" \
+ -G Ninja \
+ -B "$BUILD_DIR" \
+ -DCMAKE_BUILD_TYPE=MinSizeRel \
+ -DALP_WWW_INSTALL_DIR="$INSTALL_DIR" \
+ -DALP_ENABLE_THREADING=ON \
+ -DALP_ENABLE_ASSERTS=OFF \
+ -DALP_ENABLE_LTO=ON \
+ -DALP_BUILD_UNITTESTS=OFF \
+ -DALP_BUILD_PLAIN_RENDERER=OFF \
+ -DALP_BUILD_ALPINEAPP=ON \
+ -DALP_BUILD_WEBGPU_BASE=OFF \
+ -DALP_BUILD_WEBGPU_ENGINE=OFF \
+ -DALP_BUILD_WEBGPU_COMPUTE=OFF \
+ -DALP_BUILD_WEBGPU_APP=OFF \
+ -DFMT_INSTALL=OFF \
+ -DCMAKE_C_COMPILER_LAUNCHER=ccache \
+ -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
+ -S "${{ github.workspace }}"
+
+ - name: Reset ccache stats
+ run: ccache --zero-stats
+
+ - name: Build AlpineApp
+ run: cmake --build "$BUILD_DIR" --target install
+
+ - name: Show ccache stats
+ if: always()
+ run: ccache --show-stats --verbose
+
+ - name: Stage release
+ shell: bash
+ run: |
+ APP_DIR="$INSTALL_DIR/alpineapp"
+ test -f "$APP_DIR/alpineapp.html"
+ test -f "$APP_DIR/alpineapp.js"
+ test -f "$APP_DIR/alpineapp.wasm"
+ test -f "$APP_DIR/alpineapp.worker.js"
+ test -f "$APP_DIR/qtloader.js"
+ test -f "$APP_DIR/logo_vertical_light_bg.png"
+
+ mkdir "${{ github.workspace }}/release"
+ cp -a "$APP_DIR/." "${{ github.workspace }}/release/"
+ mv "${{ github.workspace }}/release/alpineapp.html" "${{ github.workspace }}/release/index.html"
+
+ - name: Create release artifact
+ uses: actions/upload-artifact@v7
+ with:
+ name: release_web
+ path: ${{ github.workspace }}/release/
+ if-no-files-found: error
+
+ deploy:
+ if: github.repository == 'AlpineMapsOrg/renderer'
+ needs: build-wasm
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Install lftp
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y lftp
+
+ - name: Download release artifact
+ uses: actions/download-artifact@v8
+ with:
+ name: release_web
+ path: ${{ github.workspace }}/release
+
+ - name: Deploy to alpinemaps.org
+ shell: bash
+ env:
+ FTP_RELEASE_HOST: ${{ secrets.FTP_RELEASE_HOST }}
+ FTP_RELEASE_USER: ${{ secrets.FTP_RELEASE_USER }}
+ FTP_RELEASE_PASS: ${{ secrets.FTP_RELEASE_PASS }}
+ run: |
+ set -euo pipefail
+
+ for name in FTP_RELEASE_HOST FTP_RELEASE_USER FTP_RELEASE_PASS; do
+ if [ -z "${!name}" ]; then
+ echo "$name is required for the release deployment."
+ exit 1
+ fi
+ done
+
+ RELEASE_DIR="${{ github.workspace }}/release"
+ test -f "$RELEASE_DIR/index.html"
+ test ! -e "$RELEASE_DIR/alpineapp.html"
+ test ! -e "$RELEASE_DIR/coi-serviceworker.min.js"
+
+ lftp < "${{ runner.temp }}/release-root-entries" < "$ROTATE_SCRIPT"
+
+ {
+ echo "set cmd:fail-exit true"
+ echo "set ftp:ssl-force true"
+ echo "set ftp:ssl-protect-data true"
+ echo "set ssl:verify-certificate true"
+ echo "open -u \"$FTP_RELEASE_USER\",\"$FTP_RELEASE_PASS\" \"$FTP_RELEASE_HOST\""
+ cat "$ROTATE_SCRIPT"
+ } | lftp
diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml
index 7a8bb0334..a53ba35ac 100644
--- a/.github/workflows/windows.yml
+++ b/.github/workflows/windows.yml
@@ -15,7 +15,7 @@ permissions:
jobs:
build:
- runs-on: windows-latest
+ runs-on: windows-2022
defaults:
run:
shell: 'powershell'
@@ -49,7 +49,10 @@ jobs:
# showed no useful speedup from restoring/saving the cache.
- name: Make sure MSVC is found when Ninja generator is in use
- uses: ilammy/msvc-dev-cmd@v1
+ uses: TheMrMilchmann/setup-msvc-dev@v4
+ with:
+ arch: x64
+ sdk: 10.0.26100.0
- name: Install tools
run: choco install 7zip.install ninja
@@ -89,6 +92,7 @@ jobs:
- name: Configure
env:
CMAKE_PREFIX_PATH: ${{env.QT_ROOT_DIR}}/lib/cmake
+ GITHUB_TOKEN: ${{ github.token }}
run: >
cmake -G Ninja
-DCMAKE_BUILD_TYPE=Debug
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ca11dbf61..3594dccfb 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -141,6 +141,7 @@ if (ALP_BUILD_ALPINEAPP)
endif()
if (ALP_BUILD_WEBGPU_BASE OR ALP_BUILD_WEBGPU_ENGINE OR ALP_BUILD_WEBGPU_COMPUTE OR ALP_BUILD_WEBGPU_APP)
+ include(${CMAKE_SOURCE_DIR}/cmake/SetupWebGPUPlatform.cmake)
add_subdirectory(webgpu/base)
endif()
if (ALP_BUILD_WEBGPU_ENGINE OR ALP_BUILD_WEBGPU_APP)
diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt
index 1b6501e97..af6c0df2a 100644
--- a/app/CMakeLists.txt
+++ b/app/CMakeLists.txt
@@ -199,18 +199,17 @@ if (ANDROID)
endif()
if (EMSCRIPTEN)
- target_link_options(alpineapp PUBLIC -sASYNCIFY)
-
install(
FILES
"$/alpineapp.js"
"$/alpineapp.wasm"
- "$/alpineapp.html"
"$/qtloader.js"
+ "${CMAKE_CURRENT_SOURCE_DIR}/shell/alpineapp.html"
+ "${CMAKE_CURRENT_SOURCE_DIR}/icons/logo_vertical_light_bg.png"
DESTINATION
- ${ALP_WWW_INSTALL_DIR})
+ "${ALP_WWW_INSTALL_DIR}/alpineapp")
install(FILES "$/alpineapp.worker.js"
- DESTINATION ${ALP_WWW_INSTALL_DIR}
+ DESTINATION "${ALP_WWW_INSTALL_DIR}/alpineapp"
OPTIONAL
)
else()
diff --git a/app/FloatingActionButtonGroup.qml b/app/FloatingActionButtonGroup.qml
index f01bc04be..55f49cb4c 100644
--- a/app/FloatingActionButtonGroup.qml
+++ b/app/FloatingActionButtonGroup.qml
@@ -47,11 +47,15 @@ ColumnLayout {
FloatingActionButton {
image: _r + "icons/material/add.png"
size: parent.width
- onClicked: {
- _track_model.upload_track()
- let pos = _track_model.lat_long(_track_model.n_tracks() - 1);
- if (pos.x !== 0 && pos.y !== 0)
- map.set_position(pos.x, pos.y)
+ onClicked: _track_model.upload_track()
+ }
+
+ Connections {
+ target: _track_model
+
+ function onTrack_added(position) {
+ if (position.x !== 0 && position.y !== 0)
+ map.set_position(position.x, position.y)
}
}
@@ -457,4 +461,3 @@ ColumnLayout {
-
diff --git a/app/ModelBinding.cpp b/app/ModelBinding.cpp
index 6a7de1d08..ddd136a1c 100644
--- a/app/ModelBinding.cpp
+++ b/app/ModelBinding.cpp
@@ -95,19 +95,6 @@ template std::function std::function write_fun_for_vec(const QString& member)
-{
- if (member == "x")
- return write_fun_for_vec_x();
- if (member == "y")
- return write_fun_for_vec_y();
- if (member == "z")
- return write_fun_for_vec_z();
- if (member == "w")
- return write_fun_for_vec_w();
- return {};
-}
-
std::function write_fun_for(const QMetaType& t, const QString& member)
{
if (t.id() == QMetaType::Type::QVector2D && member == "x")
diff --git a/app/TerrainRendererItem.cpp b/app/TerrainRendererItem.cpp
index da2f4d525..e323dbc2e 100644
--- a/app/TerrainRendererItem.cpp
+++ b/app/TerrainRendererItem.cpp
@@ -61,9 +61,6 @@ template
struct overloaded : Ts... {
using Ts::operator()...;
};
-// explicit deduction guide (not needed as of C++20)
-template
-overloaded(Ts...) -> overloaded;
}
TerrainRendererItem::TerrainRendererItem(QQuickItem* parent)
diff --git a/app/TrackModel.cpp b/app/TrackModel.cpp
index 333ae2cb4..7e80ec99d 100644
--- a/app/TrackModel.cpp
+++ b/app/TrackModel.cpp
@@ -59,64 +59,70 @@ QPointF TrackModel::lat_long(unsigned int index)
#ifdef __EMSCRIPTEN__
// clang-format off
-EM_ASYNC_JS(void, alpine_app_open_file_picker_and_mount, (), {
- const file_reader = new FileReader();
- const fileReadPromise = new Promise((resolve, reject) => {
- file_reader.onload = (event) => {
+EM_JS(void, alpine_app_open_file_picker_and_mount, (TrackModel* track_model), {
+ const file_selector = document.createElement('input');
+ file_selector.type = 'file';
+ file_selector.accept = '.gpx';
+ file_selector.addEventListener('change', event => {
+ const file = event.target.files[0];
+ if (!file)
+ return;
+
+ const file_reader = new FileReader();
+ file_reader.addEventListener('load', load_event => {
try {
- const uint8Arr = new Uint8Array(event.target.result);
- const stream = FS.open("/tmp/track_upload/" + file_reader.filename.replace(/[^a-zA-Z0-9_\\-()]/g, '_'), "w+");
- FS.write(stream, uint8Arr, 0, uint8Arr.length, 0);
+ const data = new Uint8Array(load_event.target.result);
+ const filename = file.name.replace(/[^a-zA-Z0-9_\\-()]/g, '_');
+ const stream = FS.open('/tmp/track_upload/' + filename, 'w+');
+ FS.write(stream, data, 0, data.length, 0);
FS.close(stream);
- resolve();
+ _alpine_app_track_file_ready(track_model);
} catch (error) {
- reject(error);
+ console.error('Failed to load GPX file', error);
}
- };
- });
- globalThis["open_file"] = function(e)
- {
- file_reader.filename = e.target.files[0].name;
- file_reader.mime_type = e.target.files[0].type;
- file_reader.readAsArrayBuffer(e.target.files[0]);
- };
-
- var file_selector = document.createElement('input');
- file_selector.setAttribute('type', 'file');
- file_selector.setAttribute('onchange', 'globalThis["open_file"](event)');
- file_selector.setAttribute('accept', '.gpx');
+ }, { once: true });
+ file_reader.addEventListener('error', () => {
+ console.error('Failed to read GPX file', file_reader.error);
+ }, { once: true });
+ file_reader.readAsArrayBuffer(file);
+ }, { once: true });
file_selector.click();
- await fileReadPromise;
});
// clang-format on
+
+extern "C" EMSCRIPTEN_KEEPALIVE void alpine_app_track_file_ready(TrackModel* track_model)
+{
+ if (track_model)
+ track_model->finish_wasm_upload();
+}
#endif
-void TrackModel::upload_track()
+void TrackModel::add_track(const QString& file_name, const QByteArray& file_content)
{
- auto fileContentReady = [this](const QString& /*fileName*/, const QByteArray& fileContent) {
- (void)fileContent;
- QXmlStreamReader xmlReader(fileContent);
-
- std::unique_ptr gpx = nucleus::track::parse(xmlReader);
- if (gpx != nullptr) {
- m_data.push_back(*gpx);
- emit tracks_changed(m_data);
- } else {
- qDebug("Could not parse GPX file!");
- }
- };
+ Q_UNUSED(file_name);
+ QXmlStreamReader xml_reader(file_content);
+
+ std::unique_ptr gpx = nucleus::track::parse(xml_reader);
+ if (gpx != nullptr) {
+ m_data.push_back(*gpx);
+ emit tracks_changed(m_data);
+ emit track_added(lat_long(m_data.size() - 1));
+ } else {
+ qDebug("Could not parse GPX file!");
+ }
+}
#ifdef __EMSCRIPTEN__
+void TrackModel::finish_wasm_upload()
+{
QDir dir("/tmp/track_upload/");
- dir.mkpath("/tmp/track_upload/");
- alpine_app_open_file_picker_and_mount();
- QStringList fileList = dir.entryList(QDir::Files);
+ const QStringList file_list = dir.entryList(QDir::Files);
- if (fileList.isEmpty()) {
+ if (file_list.isEmpty()) {
qDebug() << "No files in /tmp/track_upload/";
return;
}
- QString file_name = fileList.first();
+ const QString file_name = file_list.first();
QFile file(dir.absoluteFilePath(file_name));
if (!file.open(QIODevice::ReadOnly)) {
@@ -124,18 +130,26 @@ void TrackModel::upload_track()
return;
}
- QByteArray file_data = file.readAll();
+ const QByteArray file_data = file.readAll();
file.close();
if (!QFile::remove(dir.absoluteFilePath(file_name))) {
qDebug() << "Failed to delete the file:" << file_name;
}
- fileContentReady(file_name, file_data);
+ add_track(file_name, file_data);
+}
+#endif
+
+void TrackModel::upload_track()
+{
+#ifdef __EMSCRIPTEN__
+ QDir().mkpath("/tmp/track_upload/");
+ alpine_app_open_file_picker_and_mount(this);
#else
const auto path = QFileDialog::getOpenFileName(nullptr, tr("Open GPX track"), "", "GPX (*.gpx *.xml)");
auto file = QFile(path);
if (file.open(QFile::ReadOnly))
- fileContentReady(file.fileName(), file.readAll());
+ add_track(file.fileName(), file.readAll());
else
qDebug() << "TrackModel::upload_track: failed to read file!" << path;
#endif
diff --git a/app/TrackModel.h b/app/TrackModel.h
index 3e1f5871e..ad989cf5f 100644
--- a/app/TrackModel.h
+++ b/app/TrackModel.h
@@ -21,6 +21,11 @@
#include "nucleus/track/Manager.h"
#include
+#ifdef __EMSCRIPTEN__
+class TrackModel;
+extern "C" void alpine_app_track_file_ready(TrackModel* track_model);
+#endif
+
class TrackModel : public QObject {
Q_OBJECT
public:
@@ -40,12 +45,19 @@ public slots:
signals:
void tracks_changed(const QVector& tracks);
+ void track_added(const QPointF& position);
void display_width_changed(float display_width);
void shading_style_changed(unsigned int shading_style);
private:
+#ifdef __EMSCRIPTEN__
+ friend void alpine_app_track_file_ready(TrackModel* track_model);
+ void finish_wasm_upload();
+#endif
+ void add_track(const QString& file_name, const QByteArray& file_content);
+
QVector m_data;
float m_display_width = 7.f;
diff --git a/app/main.cpp b/app/main.cpp
index fda92b831..46858055b 100644
--- a/app/main.cpp
+++ b/app/main.cpp
@@ -123,7 +123,9 @@ int main(int argc, char **argv)
QSurfaceFormat fmt;
fmt.setDepthBufferSize(24);
+#ifdef ALP_ENABLE_DEV_TOOLS
fmt.setOption(QSurfaceFormat::DebugContext);
+#endif
if (QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGL) {
qDebug("Requesting 3.3 core context");
diff --git a/app/shell/alpineapp.html b/app/shell/alpineapp.html
new file mode 100644
index 000000000..69cea2751
--- /dev/null
+++ b/app/shell/alpineapp.html
@@ -0,0 +1,88 @@
+
+
+
+
+
+ Alpine Maps
+
+
+
+
+

+
+
+
+
+
+
+
+
+
+
diff --git a/apps/webgpu_app/CMakeLists.txt b/apps/webgpu_app/CMakeLists.txt
index acd62046a..961f5e780 100644
--- a/apps/webgpu_app/CMakeLists.txt
+++ b/apps/webgpu_app/CMakeLists.txt
@@ -93,6 +93,10 @@ set_target_properties(webgpu_app PROPERTIES
target_link_libraries(webgpu_app PUBLIC webgpu webgpu_engine Qt::Core Qt::Network)
target_include_directories(webgpu_app PRIVATE .)
+if (NOT EMSCRIPTEN)
+ target_link_libraries(webgpu_app PRIVATE SDL2::SDL2main)
+endif()
+
if (ALP_WEBGPU_APP_ENABLE_COMPUTE)
target_link_libraries(webgpu_app PUBLIC webgpu_compute)
target_compile_definitions(webgpu_app PRIVATE ALP_WEBGPU_APP_ENABLE_COMPUTE)
@@ -102,7 +106,8 @@ endif()
alp_add_git_repository(iconfontcppheaders URL https://github.com/juliettef/IconFontCppHeaders.git COMMITISH f30b1e73b2d71eb331d77619c3f1de34199afc38 DO_NOT_ADD_SUBPROJECT)
target_include_directories(webgpu_app PRIVATE ${CMAKE_SOURCE_DIR}/extern/iconfontcppheaders)
- alp_add_git_repository(imgui URL https://github.com/AlpineMapsOrgDependencies/imgui_slim.git COMMITISH 4d8ecbf58ebf32e757ff56a60f1fb0446033edb8)
+ alp_add_git_repository(imgui URL https://github.com/AlpineMapsOrgDependencies/imgui_slim.git COMMITISH 522f9c88b486a472baeb356398899ae798f06b9b DO_NOT_ADD_SUBPROJECT)
+ include(${CMAKE_SOURCE_DIR}/cmake/SetupImGui.cmake)
set(IMNODES_IMGUI_TARGET_NAME imgui)
set(BUILD_SHARED_LIBS_SAVED ${BUILD_SHARED_LIBS})
@@ -110,7 +115,7 @@ endif()
alp_add_git_repository(imnodes URL https://github.com/AlpineMapsOrgDependencies/imnodes_slim.git COMMITISH 324355e64ed8b5bea02fe1439cdcb5c7773b4436)
set(BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS_SAVED})
- target_link_libraries(webgpu_app PRIVATE imgui imnodes)
+ target_link_libraries(webgpu_app PRIVATE imgui_sdl2_wgpu imnodes)
if (NOT EMSCRIPTEN)
alp_add_git_repository(ImGuiFileDialog URL https://github.com/AlpineMapsOrgDependencies/ImGuiFileDialog_slim.git COMMITISH 39f98eb131ee00d476f1002336fc7e9e1795ccc5)
@@ -231,6 +236,6 @@ if (EMSCRIPTEN)
get_filename_component(SHELL_FILE_NAME ${SHELL_FILE} NAME)
list(APPEND WASM_OUTPUT_FILES ${CMAKE_CURRENT_BINARY_DIR}/${SHELL_FILE_NAME})
endforeach()
- install(FILES ${WASM_OUTPUT_FILES} DESTINATION ${ALP_WWW_INSTALL_DIR})
+ install(FILES ${WASM_OUTPUT_FILES} DESTINATION "${ALP_WWW_INSTALL_DIR}/webgpu_app")
# ================================================
endif()
diff --git a/apps/webgpu_app/ImGuiManager.cpp b/apps/webgpu_app/ImGuiManager.cpp
index d4fd67ad0..2c275510e 100644
--- a/apps/webgpu_app/ImGuiManager.cpp
+++ b/apps/webgpu_app/ImGuiManager.cpp
@@ -231,12 +231,12 @@ void ImGuiManager::on_file_uploaded(const std::string& filename, const std::stri
#endif
bool ImGuiManager::FilePicker(const char* dialog_id,
- const char* title,
+ [[maybe_unused]] const char* title,
const char* filters,
bool wants_open,
std::vector& out_paths,
bool allow_multiple,
- const char* initial_path,
+ [[maybe_unused]] const char* initial_path,
FilePickerMode mode,
const char* default_filename)
{
diff --git a/apps/webgpu_app/main.cpp b/apps/webgpu_app/main.cpp
index b1d053668..ba94cb3c3 100644
--- a/apps/webgpu_app/main.cpp
+++ b/apps/webgpu_app/main.cpp
@@ -29,8 +29,7 @@ void print_logo()
%4 * / \_ * / \_ %1 . ___ %2___%3 ___ . %4 /\'__
%4 / \ / \, %1__ __ _____| _ )%2_ _%3/ __|___ ___ %4 . _/ / \ *'.
%4 . /\/\ /\/ :' __ \_ %1\ V V / -_) _ \%2| |%3 (_ / -_) _ \%4 _^/ ^/ `--.
-%4 / \/ \ _/ \-'\%1 \_/\_/\___|___/%2___%3\___\___\___/%4 /.' ^_ \_ .'\
-==============================================================================
+%4 / \/ \ _/ \-'\%1 \_/\_/\___|___/%2___%3\___\___\___/%4 /.' ^_ \_ .')" "\\ \n" R"(==============================================================================
)";
QString formatted_logo = QString(logo).remove(0, 1);
formatted_logo = formatted_logo.arg("\033[36m").arg("\033[38;5;245m").arg("\033[0m").arg("\033[38;5;245m");
diff --git a/cmake/AddRepo.cmake b/cmake/AddRepo.cmake
index b3e669e79..db810e7cb 100644
--- a/cmake/AddRepo.cmake
+++ b/cmake/AddRepo.cmake
@@ -1,6 +1,6 @@
#############################################################################
# AlpineMaps.org
-# Copyright (C) 2023 Adam Celarek
+# Copyright (C) 2023-2026 Adam Celarek
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -9,234 +9,498 @@
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
+# along with this program. If not, see .
#############################################################################
-find_package(Git 2.22 REQUIRED)
+include_guard(GLOBAL)
-# CMake's FetchContent caches information about the downloads / clones in the build dir.
-# Therefore it walks over the clones every time we switch the build type (release, debug, webassembly, android etc),
-# which takes forever. Moreover, it messes up changes to subprojects. This function, on the other hand, checks whether
-# we are on a branch and in that case only issues a warning. Use origin/main or similar, if you want to stay up-to-date
-# with upstream.
-
-if(NOT DEFINED _alp_add_repo_check_flag)
- set_property(GLOBAL PROPERTY _alp_add_repo_check_flag FALSE)
-endif()
+function(_alp_add_repo_fail name repo_dir revision operation diagnostic)
+ string(STRIP "${diagnostic}" _diagnostic)
+ if(_diagnostic STREQUAL "")
+ set(_diagnostic "no diagnostic was produced")
+ endif()
+ message(FATAL_ERROR
+ "[alp/git] ${name}: ${operation} failed in '${repo_dir}' for "
+ "revision '${revision}': ${_diagnostic}")
+endfunction()
-function(_alp_git_checkout repo repo_dir commitish)
- message(STATUS "[alp/git] ${repo}: Checking out ${commitish}.")
+function(_alp_add_repo_resolve_commit repo_dir revision output_var)
execute_process(
- COMMAND ${GIT_EXECUTABLE} checkout --quiet ${commitish}
- WORKING_DIRECTORY ${repo_dir}
- RESULT_VARIABLE GIT_CHECKOUT_RESULT
- ERROR_VARIABLE checkout_output
- OUTPUT_VARIABLE checkout_output
- )
- if (NOT GIT_CHECKOUT_RESULT)
- # message(STATUS "[alp/git] In ${repo}, checking out ${commitish} succeeded.")
+ COMMAND "${GIT_EXECUTABLE}" rev-parse --verify "${revision}^{commit}"
+ WORKING_DIRECTORY "${repo_dir}"
+ RESULT_VARIABLE _result
+ OUTPUT_VARIABLE _output
+ ERROR_QUIET
+ OUTPUT_STRIP_TRAILING_WHITESPACE)
+ if(_result EQUAL 0)
+ set("${output_var}" "${_output}" PARENT_SCOPE)
else()
- message(WARNING "[alp/git] ${repo}: Checking out ${commitish} was NOT successful: ${checkout_output}")
+ set("${output_var}" "" PARENT_SCOPE)
+ endif()
+endfunction()
+
+function(_alp_add_repo_assert_postconditions name repo_dir revision expected_commit)
+ execute_process(
+ COMMAND "${GIT_EXECUTABLE}" rev-parse --verify HEAD
+ WORKING_DIRECTORY "${repo_dir}"
+ RESULT_VARIABLE _head_result
+ OUTPUT_VARIABLE _head
+ ERROR_VARIABLE _error
+ OUTPUT_STRIP_TRAILING_WHITESPACE)
+ if(NOT _head_result EQUAL 0 OR NOT _head STREQUAL expected_commit)
+ _alp_add_repo_fail("${name}" "${repo_dir}" "${revision}"
+ "verifying HEAD" "expected '${expected_commit}', got '${_head}'; ${_error}")
+ endif()
+
+ execute_process(
+ COMMAND "${GIT_EXECUTABLE}" symbolic-ref -q HEAD
+ WORKING_DIRECTORY "${repo_dir}"
+ RESULT_VARIABLE _attached_result
+ OUTPUT_VARIABLE _attached_ref
+ ERROR_VARIABLE _error
+ OUTPUT_STRIP_TRAILING_WHITESPACE)
+ if(_attached_result EQUAL 0)
+ _alp_add_repo_fail("${name}" "${repo_dir}" "${revision}"
+ "verifying detached HEAD" "HEAD is attached to '${_attached_ref}'")
+ elseif(NOT _attached_result EQUAL 1)
+ _alp_add_repo_fail("${name}" "${repo_dir}" "${revision}"
+ "verifying detached HEAD" "${_error}")
+ endif()
+
+ execute_process(
+ COMMAND "${GIT_EXECUTABLE}" status --porcelain --untracked-files=all --ignored=no
+ WORKING_DIRECTORY "${repo_dir}"
+ RESULT_VARIABLE _status_result
+ OUTPUT_VARIABLE _status
+ ERROR_VARIABLE _error
+ OUTPUT_STRIP_TRAILING_WHITESPACE)
+ if(NOT _status_result EQUAL 0 OR NOT _status STREQUAL "")
+ _alp_add_repo_fail("${name}" "${repo_dir}" "${revision}"
+ "verifying the prepared working tree" "${_error}${_status}")
+ endif()
+
+ execute_process(
+ COMMAND "${GIT_EXECUTABLE}" submodule status --recursive
+ WORKING_DIRECTORY "${repo_dir}"
+ RESULT_VARIABLE _submodule_result
+ OUTPUT_VARIABLE _submodule_status
+ ERROR_VARIABLE _error
+ OUTPUT_STRIP_TRAILING_WHITESPACE)
+ if(NOT _submodule_result EQUAL 0)
+ _alp_add_repo_fail("${name}" "${repo_dir}" "${revision}"
+ "verifying recursive submodules" "${_error}")
+ endif()
+ if(_submodule_status MATCHES "(^|\n)[-+U]")
+ _alp_add_repo_fail("${name}" "${repo_dir}" "${revision}"
+ "verifying recursive submodules" "${_submodule_status}")
+ endif()
+endfunction()
+
+function(_alp_add_repo_prepare_submodules name repo_dir revision checkout_performed)
+ execute_process(
+ COMMAND "${GIT_EXECUTABLE}" submodule status --recursive
+ WORKING_DIRECTORY "${repo_dir}"
+ RESULT_VARIABLE _status_result
+ OUTPUT_VARIABLE _status
+ ERROR_VARIABLE _error
+ OUTPUT_STRIP_TRAILING_WHITESPACE)
+ if(NOT _status_result EQUAL 0)
+ _alp_add_repo_fail("${name}" "${repo_dir}" "${revision}"
+ "inspecting recursive submodules" "${_error}")
endif()
- if (EXISTS "${repo_dir}/.gitmodules")
- # init/update submodules; for shallow clones this will still be shallow because the super‑project is.
+ if(checkout_performed AND EXISTS "${repo_dir}/.gitmodules")
+ message(STATUS "[alp/git] ${name}: synchronizing recursive submodule URLs in '${repo_dir}'.")
execute_process(
- COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive ${_ALP_SUBMODULE_UPDATE_ARGS}
- WORKING_DIRECTORY ${repo_dir}
- RESULT_VARIABLE GIT_SUBMODULE_RESULT
- ERROR_VARIABLE checkout_output
- OUTPUT_VARIABLE checkout_output
- )
- if(GIT_SUBMODULE_RESULT EQUAL 0)
- # message(STATUS "[alp/git] ${repo}: Submodules updated to match ${commitish}.")
- else()
- message(WARNING "[alp/git] ${repo}: Submodule update failed after checking out ${commitish}: ${checkout_output}")
+ COMMAND "${GIT_EXECUTABLE}" submodule sync --recursive
+ WORKING_DIRECTORY "${repo_dir}"
+ RESULT_VARIABLE _sync_result
+ OUTPUT_VARIABLE _output
+ ERROR_VARIABLE _error)
+ if(NOT _sync_result EQUAL 0)
+ _alp_add_repo_fail("${name}" "${repo_dir}" "${revision}"
+ "synchronizing recursive submodule URLs" "${_error}${_output}")
+ endif()
+ endif()
+
+ if(_status STREQUAL "" OR NOT _status MATCHES "(^|\n)[-+U]")
+ message(STATUS "[alp/git] ${name}: recursive submodules are already correct in '${repo_dir}'.")
+ return()
+ endif()
+
+ if(NOT checkout_performed)
+ message(STATUS "[alp/git] ${name}: synchronizing recursive submodule URLs in '${repo_dir}'.")
+ execute_process(
+ COMMAND "${GIT_EXECUTABLE}" submodule sync --recursive
+ WORKING_DIRECTORY "${repo_dir}"
+ RESULT_VARIABLE _sync_result
+ OUTPUT_VARIABLE _output
+ ERROR_VARIABLE _error)
+ if(NOT _sync_result EQUAL 0)
+ _alp_add_repo_fail("${name}" "${repo_dir}" "${revision}"
+ "synchronizing recursive submodule URLs" "${_error}${_output}")
+ endif()
+ endif()
+
+ message(STATUS "[alp/git] ${name}: updating recursive submodules from local objects in '${repo_dir}'.")
+ execute_process(
+ COMMAND "${GIT_EXECUTABLE}" -c protocol.file.allow=always submodule update
+ --init --recursive --checkout --depth 1 --no-fetch
+ WORKING_DIRECTORY "${repo_dir}"
+ RESULT_VARIABLE _local_result
+ OUTPUT_VARIABLE _output
+ ERROR_VARIABLE _error)
+
+ if(NOT _local_result EQUAL 0)
+ message(STATUS "[alp/git] ${name}: fetching missing recursive submodule revisions in '${repo_dir}'.")
+ execute_process(
+ COMMAND "${GIT_EXECUTABLE}" -c protocol.file.allow=always submodule update
+ --init --recursive --checkout --depth 1
+ WORKING_DIRECTORY "${repo_dir}"
+ RESULT_VARIABLE _update_result
+ OUTPUT_VARIABLE _output
+ ERROR_VARIABLE _error)
+ if(NOT _update_result EQUAL 0)
+ _alp_add_repo_fail("${name}" "${repo_dir}" "${revision}"
+ "updating recursive submodules" "${_error}${_output}")
endif()
endif()
endfunction()
function(alp_add_git_repository name)
- set(options DO_NOT_ADD_SUBPROJECT NOT_SYSTEM DEEP_CLONE PRIVATE_DO_NOT_CHECK_FOR_SCRIPT_UPDATES)
- set(oneValueArgs URL COMMITISH DESTINATION_PATH)
- set(multiValueArgs )
- cmake_parse_arguments(PARSE_ARGV 1 PARAM "${options}" "${oneValueArgs}" "${multiValueArgs}")
-
- # Determine whether we should do a shallow or deep clone/fetch.
- if(PARAM_DEEP_CLONE)
- # message(STATUS "[alp/git] Cloning ${PARAM_URL} DEEPLY to ${repo_dir}.")
- set(_ALP_GIT_CLONE_ARGS)
- set(_ALP_SUBMODULE_UPDATE_ARGS)
+ if(CMAKE_VERSION VERSION_LESS 3.25)
+ message(FATAL_ERROR
+ "[alp/git] ${name}: AddRepo.cmake requires CMake 3.25 or newer; "
+ "found ${CMAKE_VERSION}.")
+ endif()
+
+ set(options
+ DO_NOT_ADD_SUBPROJECT
+ NOT_SYSTEM
+ PRIVATE_DO_NOT_CHECK_FOR_SCRIPT_UPDATES)
+ set(one_value_args URL COMMITISH DESTINATION_PATH)
+ cmake_parse_arguments(PARSE_ARGV 1 PARAM "${options}" "${one_value_args}" "")
+
+ if("${name}" STREQUAL "")
+ message(FATAL_ERROR "[alp/git] : dependency name must be non-empty.")
+ endif()
+ if(PARAM_UNPARSED_ARGUMENTS)
+ message(FATAL_ERROR
+ "[alp/git] ${name}: unknown arguments: ${PARAM_UNPARSED_ARGUMENTS}")
+ endif()
+ if(PARAM_KEYWORDS_MISSING_VALUES)
+ message(FATAL_ERROR
+ "[alp/git] ${name}: arguments require non-empty values: "
+ "${PARAM_KEYWORDS_MISSING_VALUES}")
+ endif()
+ if(NOT DEFINED PARAM_URL OR PARAM_URL STREQUAL "")
+ message(FATAL_ERROR "[alp/git] ${name}: URL must be non-empty.")
+ endif()
+ if(NOT DEFINED PARAM_COMMITISH OR PARAM_COMMITISH STREQUAL "")
+ message(FATAL_ERROR "[alp/git] ${name}: COMMITISH must be non-empty.")
+ endif()
+
+ find_package(Git 2.22 QUIET)
+ if(NOT Git_FOUND)
+ message(FATAL_ERROR
+ "[alp/git] ${name}: Git 2.22 or newer is required but was not found.")
+ endif()
+
+ if(DEFINED PARAM_DESTINATION_PATH)
+ set(_relative_destination "${PARAM_DESTINATION_PATH}")
else()
- # message(STATUS "[alp/git] Cloning ${PARAM_URL} SHALLOWY to ${repo_dir}.")
- set(_ALP_GIT_CLONE_ARGS --no-checkout --depth 1 --shallow-submodules)
- set(_ALP_SUBMODULE_UPDATE_ARGS --depth 1 --recommend-shallow)
+ if(NOT DEFINED ALP_EXTERN_DIR OR ALP_EXTERN_DIR STREQUAL "")
+ set(_alp_extern_dir "extern")
+ else()
+ set(_alp_extern_dir "${ALP_EXTERN_DIR}")
+ endif()
+ set(_relative_destination "${_alp_extern_dir}/${name}")
endif()
+ cmake_path(IS_ABSOLUTE _relative_destination _destination_is_absolute)
+ if(_destination_is_absolute)
+ message(FATAL_ERROR
+ "[alp/git] ${name}: destination '${_relative_destination}' must be relative to "
+ "CMAKE_SOURCE_DIR ('${CMAKE_SOURCE_DIR}').")
+ endif()
+ cmake_path(NORMAL_PATH _relative_destination OUTPUT_VARIABLE _normalized_destination)
+ if(_normalized_destination STREQUAL ".." OR _normalized_destination MATCHES "^\.\./")
+ message(FATAL_ERROR
+ "[alp/git] ${name}: destination '${_relative_destination}' escapes "
+ "CMAKE_SOURCE_DIR ('${CMAKE_SOURCE_DIR}').")
+ endif()
+ cmake_path(ABSOLUTE_PATH _normalized_destination
+ BASE_DIRECTORY "${CMAKE_SOURCE_DIR}" NORMALIZE OUTPUT_VARIABLE repo_dir)
+
get_property(_check_ran GLOBAL PROPERTY _alp_add_repo_check_flag)
if(NOT PARAM_PRIVATE_DO_NOT_CHECK_FOR_SCRIPT_UPDATES AND NOT _check_ran)
+ # Set the guard before entering the checker. The private option remains the
+ # explicit recursion escape hatch used by CheckForScriptUpdates.cmake.
+ set_property(GLOBAL PROPERTY _alp_add_repo_check_flag TRUE)
if(NOT COMMAND alp_check_for_script_updates)
- include(${CMAKE_CURRENT_FUNCTION_LIST_DIR}/CheckForScriptUpdates.cmake)
+ include("${CMAKE_CURRENT_FUNCTION_LIST_DIR}/CheckForScriptUpdates.cmake")
endif()
- alp_check_for_script_updates(${CMAKE_CURRENT_FUNCTION_LIST_FILE})
- set_property(GLOBAL PROPERTY _alp_add_repo_check_flag TRUE)
- endif()
-
- if (NOT DEFINED ALP_EXTERN_DIR OR ALP_EXTERN_DIR STREQUAL "")
- set(ALP_EXTERN_DIR extern)
+ alp_check_for_script_updates("${CMAKE_CURRENT_FUNCTION_LIST_FILE}")
endif()
- set(repo_dir ${CMAKE_SOURCE_DIR}/${ALP_EXTERN_DIR}/${name})
- set(short_repo_dir ${ALP_EXTERN_DIR}/${name})
- if (DEFINED PARAM_DESTINATION_PATH AND NOT PARAM_DESTINATION_PATH STREQUAL "")
- set(repo_dir ${CMAKE_SOURCE_DIR}/${PARAM_DESTINATION_PATH})
- set(short_repo_dir ${PARAM_DESTINATION_PATH})
+ set(_new_repository FALSE)
+ if(EXISTS "${repo_dir}")
+ if(NOT IS_DIRECTORY "${repo_dir}")
+ message(FATAL_ERROR
+ "[alp/git] ${name}: destination '${repo_dir}' exists but is not a directory.")
+ endif()
+ if(NOT EXISTS "${repo_dir}/.git")
+ file(GLOB _destination_entries LIST_DIRECTORIES TRUE
+ "${repo_dir}/*" "${repo_dir}/.[!.]*" "${repo_dir}/..?*")
+ if(_destination_entries)
+ message(FATAL_ERROR
+ "[alp/git] ${name}: destination '${repo_dir}' is non-empty and is not "
+ "a Git working tree; refusing to overwrite it.")
+ endif()
+ set(_new_repository TRUE)
+ endif()
+ else()
+ get_filename_component(_repo_parent "${repo_dir}" DIRECTORY)
+ file(MAKE_DIRECTORY "${_repo_parent}")
+ set(_new_repository TRUE)
endif()
- file(MAKE_DIRECTORY ${repo_dir})
- set(${name}_SOURCE_DIR "${repo_dir}" PARENT_SCOPE)
+ if(_new_repository)
+ message(STATUS
+ "[alp/git] ${name}: shallow-cloning '${PARAM_URL}' into '${repo_dir}'.")
+ execute_process(
+ COMMAND "${GIT_EXECUTABLE}" clone --no-checkout --depth 1
+ "${PARAM_URL}" "${repo_dir}"
+ RESULT_VARIABLE _clone_result
+ OUTPUT_VARIABLE _output
+ ERROR_VARIABLE _error)
+ if(NOT _clone_result EQUAL 0)
+ _alp_add_repo_fail("${name}" "${repo_dir}" "${PARAM_COMMITISH}"
+ "shallow clone" "${_error}${_output}")
+ endif()
+ else()
+ # This must be the first Git command for an existing working tree. An
+ # attached HEAD makes it developer managed and suppresses all other Git work.
+ execute_process(
+ COMMAND "${GIT_EXECUTABLE}" symbolic-ref -q HEAD
+ WORKING_DIRECTORY "${repo_dir}"
+ RESULT_VARIABLE _attached_result
+ OUTPUT_VARIABLE _attached_ref
+ ERROR_VARIABLE _error
+ OUTPUT_STRIP_TRAILING_WHITESPACE)
+ if(_attached_result EQUAL 0)
+ message(WARNING
+ "[alp/git] ${name}: protected developer-managed repository at "
+ "'${_normalized_destination}' is attached to '${_attached_ref}'. "
+ "Requested revision '${PARAM_COMMITISH}' was NOT checked out; no other "
+ "Git operations were performed.")
+ set("${name}_SOURCE_DIR" "${repo_dir}" PARENT_SCOPE)
+ if(NOT PARAM_DO_NOT_ADD_SUBPROJECT)
+ if(PARAM_NOT_SYSTEM)
+ add_subdirectory("${repo_dir}" "${CMAKE_BINARY_DIR}/alp_external/${name}")
+ else()
+ add_subdirectory("${repo_dir}" "${CMAKE_BINARY_DIR}/alp_external/${name}" SYSTEM)
+ endif()
+ endif()
+ return()
+ elseif(NOT _attached_result EQUAL 1)
+ _alp_add_repo_fail("${name}" "${repo_dir}" "${PARAM_COMMITISH}"
+ "classifying repository ownership" "${_error}")
+ endif()
- # Detect if the requested ref looks like a remote branch (e.g. origin/main)
- string(REGEX MATCH "^[^/]+/.+" force_fetch "${PARAM_COMMITISH}")
+ execute_process(
+ COMMAND "${GIT_EXECUTABLE}" remote get-url origin
+ WORKING_DIRECTORY "${repo_dir}"
+ RESULT_VARIABLE _origin_result
+ OUTPUT_VARIABLE _origin_url
+ ERROR_VARIABLE _error
+ OUTPUT_STRIP_TRAILING_WHITESPACE)
+ if(NOT _origin_result EQUAL 0)
+ _alp_add_repo_fail("${name}" "${repo_dir}" "${PARAM_COMMITISH}"
+ "reading origin URL" "${_error}")
+ endif()
+ if(NOT _origin_url STREQUAL PARAM_URL)
+ _alp_add_repo_fail("${name}" "${repo_dir}" "${PARAM_COMMITISH}"
+ "validating origin URL"
+ "origin is '${_origin_url}', but URL is '${PARAM_URL}'. Attach HEAD to make this a developer-managed repository")
+ endif()
- set(force_checkout FALSE)
- if(NOT EXISTS "${repo_dir}/.git")
- # Do a fresh clone
- message(STATUS "[alp/git] ${short_repo_dir}: Cloning ${PARAM_URL} to ${repo_dir}.")
execute_process(
- COMMAND ${GIT_EXECUTABLE} clone ${_ALP_GIT_CLONE_ARGS} --recurse-submodules ${PARAM_URL} ${repo_dir}
- RESULT_VARIABLE GIT_CLONE_RESULT
- ERROR_VARIABLE clone_output
- OUTPUT_VARIABLE clone_output
- )
- if (NOT GIT_CLONE_RESULT EQUAL 0)
- message(SEND_ERROR "[alp/git] ${short_repo_dir}: Cloning was NOT successful: ${clone_output}")
+ COMMAND "${GIT_EXECUTABLE}" status --porcelain --untracked-files=all --ignored=no
+ WORKING_DIRECTORY "${repo_dir}"
+ RESULT_VARIABLE _status_result
+ OUTPUT_VARIABLE _status
+ ERROR_VARIABLE _error
+ OUTPUT_STRIP_TRAILING_WHITESPACE)
+ if(NOT _status_result EQUAL 0)
+ _alp_add_repo_fail("${name}" "${repo_dir}" "${PARAM_COMMITISH}"
+ "checking repository cleanliness" "${_error}")
endif()
- if (NOT PARAM_DEEP_CLONE)
- # shallow clone, fetch ref and checkout
- set(force_fetch TRUE)
+ if(NOT _status STREQUAL "")
+ _alp_add_repo_fail("${name}" "${repo_dir}" "${PARAM_COMMITISH}"
+ "checking repository cleanliness"
+ "the CMake-managed repository is dirty:\n${_status}\nAttach HEAD to make this a developer-managed repository")
endif()
- set(force_checkout TRUE)
endif()
- # Check out the correct branch:
- # First, see if PARAM_COMMITISH is a valid local ref:
- execute_process(
- COMMAND ${GIT_EXECUTABLE} rev-parse --verify ${PARAM_COMMITISH}
- WORKING_DIRECTORY ${repo_dir}
- OUTPUT_VARIABLE GIT_COMMIT_OUTPUT
- OUTPUT_STRIP_TRAILING_WHITESPACE
- RESULT_VARIABLE commit_present_result
- ERROR_QUIET
- )
- if (commit_present_result EQUAL 0 AND NOT force_fetch)
- # PARAM_COMMITISH is recognized by Git => no need to fetch
- # (could be a tag (lightweight or annotated) or a direct commit SHA).
- # if it's an *annotated* tag, rev-parse gives us the tag object's hash, not the commit hash.
- # => Force resolve the actual commit object with ^{commit}:
+ string(REGEX MATCH "^origin/(.+)$" _remote_match "${PARAM_COMMITISH}")
+ if(_remote_match)
+ set(_branch "${CMAKE_MATCH_1}")
execute_process(
- COMMAND ${GIT_EXECUTABLE} rev-parse --verify ${PARAM_COMMITISH}^{commit}
- WORKING_DIRECTORY ${repo_dir}
- OUTPUT_VARIABLE GIT_COMMIT_OBJECT
- OUTPUT_STRIP_TRAILING_WHITESPACE
- RESULT_VARIABLE commit_object_result
- )
-
- if (commit_object_result EQUAL 0)
- # Successfully resolved a commit object
- set(commitish_hash "${GIT_COMMIT_OBJECT}")
- else()
- # Fallback if that fails (should rarely happen if it's a proper commit/tag)
- set(commitish_hash "${GIT_COMMIT_OUTPUT}")
+ COMMAND "${GIT_EXECUTABLE}" check-ref-format "refs/heads/${_branch}"
+ WORKING_DIRECTORY "${repo_dir}"
+ RESULT_VARIABLE _ref_result
+ ERROR_VARIABLE _error)
+ if(NOT _ref_result EQUAL 0)
+ _alp_add_repo_fail("${name}" "${repo_dir}" "${PARAM_COMMITISH}"
+ "validating remote-tracking revision" "${_error}")
endif()
- # Grab HEAD commit
+ message(STATUS
+ "[alp/git] ${name}: fetching moving revision '${PARAM_COMMITISH}' in '${repo_dir}'.")
execute_process(
- COMMAND ${GIT_EXECUTABLE} rev-parse --verify HEAD
- WORKING_DIRECTORY ${repo_dir}
- OUTPUT_VARIABLE git_head_hash
- OUTPUT_STRIP_TRAILING_WHITESPACE
- )
-
- if (git_head_hash STREQUAL commitish_hash AND NOT force_checkout)
- message(STATUS "[alp/git] ${short_repo_dir}: Already at ${PARAM_COMMITISH}. Skipping checkout.")
+ COMMAND "${GIT_EXECUTABLE}" fetch --depth 1 origin
+ "+refs/heads/${_branch}:refs/remotes/origin/${_branch}"
+ WORKING_DIRECTORY "${repo_dir}"
+ RESULT_VARIABLE _fetch_result
+ OUTPUT_VARIABLE _output
+ ERROR_VARIABLE _error)
+ if(NOT _fetch_result EQUAL 0)
+ _alp_add_repo_resolve_commit("${repo_dir}"
+ "refs/remotes/origin/${_branch}" _target_commit)
+ if(_target_commit STREQUAL "")
+ _alp_add_repo_fail("${name}" "${repo_dir}" "${PARAM_COMMITISH}"
+ "refreshing remote-tracking revision" "${_error}${_output}")
+ endif()
+ string(STRIP "${_error}${_output}" _fetch_diagnostic)
+ message(WARNING
+ "[alp/git] ${name}: refresh of '${PARAM_COMMITISH}' failed in "
+ "'${repo_dir}'; reusing cached commit '${_target_commit}'. "
+ "Git diagnostic: ${_fetch_diagnostic}")
else()
- _alp_git_checkout(${short_repo_dir} ${repo_dir} ${PARAM_COMMITISH})
+ _alp_add_repo_resolve_commit("${repo_dir}"
+ "refs/remotes/origin/${_branch}" _target_commit)
+ if(_target_commit STREQUAL "")
+ _alp_add_repo_fail("${name}" "${repo_dir}" "${PARAM_COMMITISH}"
+ "resolving fetched remote-tracking revision" "the ref does not resolve to a commit")
+ endif()
endif()
else()
- # either remote branch or commitish not recognised
- message(STATUS "[alp/git] ${short_repo_dir}: Fetching updates.")
+ execute_process(
+ COMMAND "${GIT_EXECUTABLE}" rev-parse --verify --quiet
+ "refs/heads/${PARAM_COMMITISH}"
+ WORKING_DIRECTORY "${repo_dir}"
+ RESULT_VARIABLE _local_branch_result
+ OUTPUT_QUIET ERROR_QUIET)
+ if(_local_branch_result EQUAL 0)
+ _alp_add_repo_fail("${name}" "${repo_dir}" "${PARAM_COMMITISH}"
+ "classifying fixed revision"
+ "local branch names are not accepted; use a tag, commit ID, or origin/")
+ endif()
- if(PARAM_DEEP_CLONE)
- # Original deep‑clone logic: fetch everything (incl. all tags) deeply.
- # message(STATUS "[alp/git] Deep clone, fetching all.")
- execute_process(
- COMMAND ${GIT_EXECUTABLE} fetch origin --tags
- WORKING_DIRECTORY ${repo_dir}
- RESULT_VARIABLE fetch_result
- OUTPUT_QUIET
- ERROR_QUIET
- )
- else()
- # ── Shallow path ─────────────────────────
- # 1. Try to fetch COMMITISH as a tag
- # message(STATUS "[alp/git] Shallow clone, trying to fetch tag ${PARAM_COMMITISH}.")
- execute_process(
- COMMAND ${GIT_EXECUTABLE} fetch origin tag ${PARAM_COMMITISH} --depth 1
- WORKING_DIRECTORY ${repo_dir}
- RESULT_VARIABLE fetch_result
- OUTPUT_QUIET
- ERROR_QUIET
- )
-
- # 2. If that failed, try it as branch‑name or raw hash
- if(NOT fetch_result EQUAL 0)
- # message(STATUS "[alp/git] Shallow clone, failed to fetch tag ${PARAM_COMMITISH}. Probably it is a branch or hash. Trying again..")
- set(_FETCH_REF "${PARAM_COMMITISH}")
- string(REGEX REPLACE "^origin/(.+)" "\\1" _FETCH_REF "${_FETCH_REF}")
+ _alp_add_repo_resolve_commit("${repo_dir}" "${PARAM_COMMITISH}" _target_commit)
+ if(_target_commit STREQUAL "")
+ message(STATUS
+ "[alp/git] ${name}: fixed revision '${PARAM_COMMITISH}' is not cached; "
+ "fetching it into '${repo_dir}'.")
+ string(LENGTH "${PARAM_COMMITISH}" _revision_length)
+ if(PARAM_COMMITISH MATCHES "^[0-9a-fA-F]+$"
+ AND _revision_length GREATER_EQUAL 4
+ AND _revision_length LESS_EQUAL 40)
execute_process(
- COMMAND ${GIT_EXECUTABLE} fetch origin ${_FETCH_REF} --depth 1
- WORKING_DIRECTORY ${repo_dir}
- RESULT_VARIABLE fetch_result
- OUTPUT_QUIET
- ERROR_QUIET
- )
+ COMMAND "${GIT_EXECUTABLE}" fetch --depth 1 origin "${PARAM_COMMITISH}"
+ WORKING_DIRECTORY "${repo_dir}"
+ RESULT_VARIABLE _fetch_result
+ OUTPUT_VARIABLE _output
+ ERROR_VARIABLE _error)
+ else()
+ execute_process(
+ COMMAND "${GIT_EXECUTABLE}" fetch --depth 1 origin
+ "refs/tags/${PARAM_COMMITISH}:refs/tags/${PARAM_COMMITISH}"
+ WORKING_DIRECTORY "${repo_dir}"
+ RESULT_VARIABLE _fetch_result
+ OUTPUT_VARIABLE _output
+ ERROR_VARIABLE _error)
+ endif()
+ if(NOT _fetch_result EQUAL 0)
+ _alp_add_repo_fail("${name}" "${repo_dir}" "${PARAM_COMMITISH}"
+ "fetching fixed revision" "${_error}${_output}")
endif()
+ _alp_add_repo_resolve_commit("${repo_dir}" "${PARAM_COMMITISH}" _target_commit)
+ if(_target_commit STREQUAL "")
+ # A raw commit fetch is normally available only through FETCH_HEAD.
+ _alp_add_repo_resolve_commit("${repo_dir}" FETCH_HEAD _target_commit)
+ endif()
+ if(_target_commit STREQUAL "")
+ _alp_add_repo_fail("${name}" "${repo_dir}" "${PARAM_COMMITISH}"
+ "resolving fetched fixed revision" "the fetched object does not resolve to a commit")
+ endif()
+ else()
+ message(STATUS
+ "[alp/git] ${name}: fixed revision '${PARAM_COMMITISH}' is available locally in '${repo_dir}'.")
endif()
+ endif()
- if (fetch_result EQUAL 0)
- # message(STATUS "[alp/git] Fetch successful for ${short_repo_dir}.")
+ set(_checkout_performed FALSE)
+ execute_process(
+ COMMAND "${GIT_EXECUTABLE}" rev-parse --verify HEAD
+ WORKING_DIRECTORY "${repo_dir}"
+ RESULT_VARIABLE _head_result
+ OUTPUT_VARIABLE _head_commit
+ ERROR_QUIET
+ OUTPUT_STRIP_TRAILING_WHITESPACE)
+ if(_new_repository OR NOT _head_result EQUAL 0 OR NOT _head_commit STREQUAL _target_commit)
+ if(_head_result EQUAL 0 AND EXISTS "${repo_dir}/.gitmodules")
+ # Deinitializing before switching prevents removed submodule worktrees
+ # from becoming untracked files after the checkout.
execute_process(
- COMMAND ${GIT_EXECUTABLE} branch --show-current
- WORKING_DIRECTORY ${repo_dir}
- OUTPUT_STRIP_TRAILING_WHITESPACE
- OUTPUT_VARIABLE current_branch
- RESULT_VARIABLE branch_result
- )
- if (NOT branch_result EQUAL 0)
- message(FATAL_ERROR "[alp/git] ${short_repo_dir}: git branch --show-current failed")
+ COMMAND "${GIT_EXECUTABLE}" submodule deinit --force --all
+ WORKING_DIRECTORY "${repo_dir}"
+ RESULT_VARIABLE _deinit_result
+ OUTPUT_VARIABLE _output
+ ERROR_VARIABLE _error)
+ if(NOT _deinit_result EQUAL 0)
+ _alp_add_repo_fail("${name}" "${repo_dir}" "${PARAM_COMMITISH}"
+ "deinitializing old submodules before checkout" "${_error}${_output}")
endif()
+ endif()
- if (current_branch STREQUAL "" OR force_checkout)
- # not on a branch. that's what we usually have (detached head state)
- _alp_git_checkout(${short_repo_dir} ${repo_dir} ${PARAM_COMMITISH})
- else()
- message(WARNING "[alp/git] ${short_repo_dir}: On branch ${current_branch}, leaving it there. NOT checking out ${PARAM_COMMITISH}! Use origin/main or similar if you want to stay up‑to‑date with upstream.")
- endif()
- else()
- message(WARNING "[alp/git] ${short_repo_dir}: Unable to fetch updates for; ${PARAM_COMMITISH} not found locally or is a remote branch.")
+ message(STATUS
+ "[alp/git] ${name}: checking out '${PARAM_COMMITISH}' at '${_target_commit}' in '${repo_dir}'.")
+ execute_process(
+ COMMAND "${GIT_EXECUTABLE}" checkout --quiet --detach "${_target_commit}"
+ WORKING_DIRECTORY "${repo_dir}"
+ RESULT_VARIABLE _checkout_result
+ OUTPUT_VARIABLE _output
+ ERROR_VARIABLE _error)
+ if(NOT _checkout_result EQUAL 0)
+ _alp_add_repo_fail("${name}" "${repo_dir}" "${PARAM_COMMITISH}"
+ "checking out requested revision" "${_error}${_output}")
endif()
+ set(_checkout_performed TRUE)
+ else()
+ message(STATUS
+ "[alp/git] ${name}: HEAD is already at '${PARAM_COMMITISH}' in '${repo_dir}'; skipping checkout.")
endif()
- if (NOT PARAM_DO_NOT_ADD_SUBPROJECT)
- if (NOT PARAM_NOT_SYSTEM)
- add_subdirectory(${repo_dir} ${CMAKE_BINARY_DIR}/alp_external/${name} SYSTEM)
+ _alp_add_repo_prepare_submodules(
+ "${name}" "${repo_dir}" "${PARAM_COMMITISH}" "${_checkout_performed}")
+ _alp_add_repo_assert_postconditions(
+ "${name}" "${repo_dir}" "${PARAM_COMMITISH}" "${_target_commit}")
+
+ set("${name}_SOURCE_DIR" "${repo_dir}" PARENT_SCOPE)
+
+ if(NOT PARAM_DO_NOT_ADD_SUBPROJECT)
+ message(STATUS
+ "[alp/git] ${name}: adding '${repo_dir}' as a CMake subproject.")
+ if(PARAM_NOT_SYSTEM)
+ add_subdirectory("${repo_dir}" "${CMAKE_BINARY_DIR}/alp_external/${name}")
else()
- add_subdirectory(${repo_dir} ${CMAKE_BINARY_DIR}/alp_external/${name})
+ add_subdirectory("${repo_dir}" "${CMAKE_BINARY_DIR}/alp_external/${name}" SYSTEM)
endif()
endif()
endfunction()
diff --git a/cmake/SetupImGui.cmake b/cmake/SetupImGui.cmake
new file mode 100644
index 000000000..2eb04eee8
--- /dev/null
+++ b/cmake/SetupImGui.cmake
@@ -0,0 +1,42 @@
+#############################################################################
+# weBIGeo
+# Copyright (C) 2024 Gerald Kimmersdorfer
+# Copyright (C) 2025 Patrick Komon
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#############################################################################
+
+include_guard(GLOBAL)
+
+add_library(imgui STATIC
+ ${imgui_SOURCE_DIR}/misc/cpp/imgui_stdlib.h
+ ${imgui_SOURCE_DIR}/misc/cpp/imgui_stdlib.cpp
+ ${imgui_SOURCE_DIR}/imconfig.h
+ ${imgui_SOURCE_DIR}/imgui.h
+ ${imgui_SOURCE_DIR}/imgui.cpp
+ ${imgui_SOURCE_DIR}/imgui_draw.cpp
+ ${imgui_SOURCE_DIR}/imgui_internal.h
+ ${imgui_SOURCE_DIR}/imgui_tables.cpp
+ ${imgui_SOURCE_DIR}/imgui_widgets.cpp
+ ${imgui_SOURCE_DIR}/imstb_rectpack.h
+ ${imgui_SOURCE_DIR}/imstb_textedit.h
+ ${imgui_SOURCE_DIR}/imstb_truetype.h
+)
+target_include_directories(imgui SYSTEM PUBLIC ${imgui_SOURCE_DIR})
+
+add_library(imgui_sdl2_wgpu STATIC
+ ${imgui_SOURCE_DIR}/backends/imgui_impl_sdl2.h
+ ${imgui_SOURCE_DIR}/backends/imgui_impl_sdl2.cpp
+ ${imgui_SOURCE_DIR}/backends/imgui_impl_wgpu.h
+ ${imgui_SOURCE_DIR}/backends/imgui_impl_wgpu.cpp
+)
+target_link_libraries(imgui_sdl2_wgpu PUBLIC
+ imgui
+ alp_webgpu_api
+ alp_sdl2_platform
+)
+
+target_compile_definitions(imgui_sdl2_wgpu PRIVATE IMGUI_IMPL_WEBGPU_BACKEND_DAWN)
diff --git a/cmake/SetupWebGPUPlatform.cmake b/cmake/SetupWebGPUPlatform.cmake
new file mode 100644
index 000000000..877a2fb6e
--- /dev/null
+++ b/cmake/SetupWebGPUPlatform.cmake
@@ -0,0 +1,207 @@
+#############################################################################
+# weBIGeo
+# Copyright (C) 2024 Gerald Kimmersdorfer
+# Copyright (C) 2025 Patrick Komon
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#############################################################################
+
+include_guard(GLOBAL)
+
+set(ALP_DAWN_VERSION "20260420.230544")
+set(ALP_DXC_URL "https://github.com/microsoft/DirectXShaderCompiler/releases/download/v1.9.2602/dxc_2026_02_20.zip"
+ CACHE INTERNAL "DXC release archive whose dxcompiler.dll/dxil.dll match ALP_DAWN_VERSION")
+set(ALP_DAWN_DIR "${CMAKE_SOURCE_DIR}/${ALP_EXTERN_DIR}/dawn")
+set(ALP_SDL_INSTALL_DIR "${CMAKE_SOURCE_DIR}/${ALP_EXTERN_DIR}/sdl")
+set(ALP_DAWN_PORT_DIR "${CMAKE_SOURCE_DIR}/${ALP_EXTERN_DIR}/emdawnwebgpu_pkg/emdawnwebgpu.port.py")
+
+add_library(alp_webgpu_api INTERFACE)
+add_library(alp_sdl2_platform INTERFACE)
+
+find_package(Python3 COMPONENTS Interpreter REQUIRED)
+
+function(alp_find_local_dawn_package out_var)
+ set(ALP_DAWN_INSTALL_DIR "${ALP_DAWN_DIR}/install/${ALP_DAWN_CONFIG}")
+ set(ALP_DAWN_VERSION_FILE "${ALP_DAWN_INSTALL_DIR}/.alp_dawn_version")
+ if (NOT EXISTS "${ALP_DAWN_VERSION_FILE}")
+ set(${out_var} "" PARENT_SCOPE)
+ return()
+ endif()
+
+ file(READ "${ALP_DAWN_VERSION_FILE}" ALP_DAWN_INSTALLED_VERSION)
+ string(STRIP "${ALP_DAWN_INSTALLED_VERSION}" ALP_DAWN_INSTALLED_VERSION)
+ if (NOT ALP_DAWN_INSTALLED_VERSION STREQUAL ALP_DAWN_VERSION)
+ set(${out_var} "" PARENT_SCOPE)
+ return()
+ endif()
+
+ foreach(ALP_DAWN_LIB_DIR lib lib64)
+ set(ALP_DAWN_PACKAGE_CANDIDATE "${ALP_DAWN_INSTALL_DIR}/${ALP_DAWN_LIB_DIR}/cmake/Dawn")
+ if (EXISTS "${ALP_DAWN_PACKAGE_CANDIDATE}/DawnConfig.cmake")
+ set(${out_var} "${ALP_DAWN_PACKAGE_CANDIDATE}" PARENT_SCOPE)
+ return()
+ endif()
+ endforeach()
+ set(${out_var} "" PARENT_SCOPE)
+endfunction()
+
+function(alp_mark_local_dawn_package)
+ foreach(ALP_DAWN_MARK_CONFIG Debug Release)
+ set(ALP_DAWN_MARK_DIR "${ALP_DAWN_DIR}/install/${ALP_DAWN_MARK_CONFIG}")
+ if (EXISTS "${ALP_DAWN_MARK_DIR}")
+ file(WRITE "${ALP_DAWN_MARK_DIR}/.alp_dawn_version" "${ALP_DAWN_VERSION}\n")
+ endif()
+ endforeach()
+endfunction()
+
+if (EMSCRIPTEN)
+ if (NOT EXISTS "${ALP_DAWN_PORT_DIR}")
+ message(STATUS "Dawn port not found, fetching...")
+ execute_process(
+ COMMAND ${Python3_EXECUTABLE}
+ "${CMAKE_SOURCE_DIR}/misc/scripts/fetch_dawn_port.py"
+ --dawn-version "${ALP_DAWN_VERSION}"
+ --extern-dir "${ALP_EXTERN_DIR}"
+ WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
+ RESULT_VARIABLE FETCH_RESULT
+ )
+ if (NOT FETCH_RESULT EQUAL 0)
+ message(FATAL_ERROR "Failed to fetch Dawn Emscripten port.")
+ endif()
+ endif()
+
+ target_compile_options(alp_webgpu_api INTERFACE
+ "--use-port=${ALP_DAWN_PORT_DIR}"
+ )
+ target_link_options(alp_webgpu_api INTERFACE
+ "-sASYNCIFY=1"
+ "--use-port=${ALP_DAWN_PORT_DIR}"
+ )
+ target_compile_options(alp_sdl2_platform INTERFACE
+ "--use-port=sdl2"
+ )
+ target_link_options(alp_sdl2_platform INTERFACE
+ "--use-port=sdl2"
+ )
+else()
+ if (CMAKE_BUILD_TYPE STREQUAL "Debug")
+ set(ALP_DAWN_CONFIG "Debug")
+ else()
+ set(ALP_DAWN_CONFIG "Release")
+ endif()
+
+ if (WIN32)
+ set(ALP_DAWN_NATIVE_PLATFORM "windows-latest")
+ elseif(APPLE)
+ set(ALP_DAWN_NATIVE_PLATFORM "macos-latest")
+ else()
+ set(ALP_DAWN_NATIVE_PLATFORM "ubuntu-latest")
+ endif()
+
+ alp_find_local_dawn_package(ALP_DAWN_CMAKE_PACKAGE)
+ if (NOT ALP_DAWN_CMAKE_PACKAGE)
+ message(STATUS "Dawn installation missing - fetching native Dawn package...")
+ execute_process(
+ COMMAND ${Python3_EXECUTABLE} "${CMAKE_SOURCE_DIR}/misc/scripts/fetch_dawn_native.py"
+ --dawn-version "${ALP_DAWN_VERSION}"
+ --extern-dir "${ALP_EXTERN_DIR}"
+ --build-type "${ALP_DAWN_CONFIG}"
+ --platform "${ALP_DAWN_NATIVE_PLATFORM}"
+ WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
+ RESULT_VARIABLE DAWN_FETCH_RESULT
+ )
+
+ if (NOT DAWN_FETCH_RESULT EQUAL 0)
+ message(STATUS "Fetching native Dawn package failed - running install_dawn.py...")
+ if (NOT DEFINED ALP_CMAKE_EXECUTABLE)
+ find_program(ALP_CMAKE_EXECUTABLE cmake)
+ if (NOT ALP_CMAKE_EXECUTABLE)
+ message(FATAL_ERROR "cmake not found in PATH")
+ endif()
+ endif()
+ if (NOT DEFINED ALP_NINJA_EXECUTABLE)
+ find_program(ALP_NINJA_EXECUTABLE ninja)
+ if (NOT ALP_NINJA_EXECUTABLE)
+ message(FATAL_ERROR "ninja not found in PATH")
+ endif()
+ endif()
+
+ execute_process(
+ COMMAND ${Python3_EXECUTABLE} "${CMAKE_SOURCE_DIR}/misc/scripts/install_dawn.py"
+ --dawn-version ${ALP_DAWN_VERSION}
+ --extern-dir "${ALP_EXTERN_DIR}"
+ --cmake-path "${ALP_CMAKE_EXECUTABLE}"
+ --ninja-path "${ALP_NINJA_EXECUTABLE}"
+ WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
+ RESULT_VARIABLE DAWN_RESULT
+ )
+ if (NOT DAWN_RESULT EQUAL 0)
+ message(FATAL_ERROR "install_dawn.py failed.")
+ endif()
+ endif()
+
+ alp_mark_local_dawn_package()
+ alp_find_local_dawn_package(ALP_DAWN_CMAKE_PACKAGE)
+ endif()
+
+ if (NOT ALP_DAWN_CMAKE_PACKAGE)
+ message(FATAL_ERROR "Dawn ${ALP_DAWN_VERSION} was not found in ${ALP_DAWN_DIR}/install/${ALP_DAWN_CONFIG}.")
+ endif()
+
+ set(Dawn_DIR "${ALP_DAWN_CMAKE_PACKAGE}")
+ find_package(Dawn REQUIRED CONFIG PATHS "${ALP_DAWN_CMAKE_PACKAGE}" NO_DEFAULT_PATH)
+ target_link_libraries(alp_webgpu_api INTERFACE dawn::webgpu_dawn)
+
+ if (NOT EXISTS "${ALP_SDL_INSTALL_DIR}")
+ message(STATUS "SDL not found - running install_sdl.py...")
+ if (NOT DEFINED ALP_CMAKE_EXECUTABLE)
+ find_program(ALP_CMAKE_EXECUTABLE cmake)
+ if (NOT ALP_CMAKE_EXECUTABLE)
+ message(FATAL_ERROR "cmake not found")
+ endif()
+ endif()
+ if (NOT DEFINED ALP_NINJA_EXECUTABLE)
+ find_program(ALP_NINJA_EXECUTABLE ninja)
+ if (NOT ALP_NINJA_EXECUTABLE)
+ message(FATAL_ERROR "ninja not found")
+ endif()
+ endif()
+ if (NOT DEFINED ALP_GIT_EXECUTABLE)
+ find_program(ALP_GIT_EXECUTABLE git)
+ if (NOT ALP_GIT_EXECUTABLE)
+ message(FATAL_ERROR "git not found")
+ endif()
+ endif()
+
+ execute_process(
+ COMMAND ${Python3_EXECUTABLE} "${CMAKE_SOURCE_DIR}/misc/scripts/install_sdl.py"
+ --install-prefix "${ALP_SDL_INSTALL_DIR}"
+ --cmake-path "${ALP_CMAKE_EXECUTABLE}"
+ --ninja-path "${ALP_NINJA_EXECUTABLE}"
+ --git-path "${ALP_GIT_EXECUTABLE}"
+ WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
+ RESULT_VARIABLE SDL_RESULT
+ )
+ if (NOT SDL_RESULT EQUAL 0)
+ message(FATAL_ERROR "install_sdl.py failed.")
+ endif()
+ endif()
+
+ list(APPEND CMAKE_PREFIX_PATH "${ALP_SDL_INSTALL_DIR}")
+ find_package(SDL2 REQUIRED)
+ target_link_libraries(alp_sdl2_platform INTERFACE SDL2::SDL2)
+
+ file(GLOB_RECURSE TINT_LIB_PATH "${ALP_DAWN_DIR}/out/Debug/src/tint/tint_lang_hlsl_writer.lib")
+ if (TINT_LIB_PATH)
+ message(STATUS "Found tint_lang_hlsl_writer.lib, Dawn seems to be compiled with DX-Backends. Will link against dxguid.lib.")
+ find_library(DXGUID_LIB dxguid.lib)
+ if (DXGUID_LIB)
+ target_link_libraries(alp_webgpu_api INTERFACE ${DXGUID_LIB})
+ else()
+ message(FATAL_ERROR "dxguid.lib not found.")
+ endif()
+ endif()
+endif()
diff --git a/cmake/alp_add_unittest.cmake b/cmake/alp_add_unittest.cmake
index 557f20965..ddfa51674 100644
--- a/cmake/alp_add_unittest.cmake
+++ b/cmake/alp_add_unittest.cmake
@@ -44,10 +44,10 @@ function(alp_add_unittest name)
"$/${name}.html"
"$/qtloader.js"
DESTINATION
- ${ALP_WWW_INSTALL_DIR})
+ "${ALP_WWW_INSTALL_DIR}/${name}")
install(
FILES "$/${name}.worker.js"
- DESTINATION ${ALP_WWW_INSTALL_DIR}
+ DESTINATION "${ALP_WWW_INSTALL_DIR}/${name}"
OPTIONAL
)
elseif(ANDROID)
diff --git a/gl_engine/Texture.cpp b/gl_engine/Texture.cpp
index c343af264..7d613d23e 100644
--- a/gl_engine/Texture.cpp
+++ b/gl_engine/Texture.cpp
@@ -23,8 +23,7 @@
#include
#include
#ifdef __EMSCRIPTEN__
-#include
-#include
+#include
#endif
#ifdef ANDROID
#include
@@ -275,20 +274,19 @@ GLenum gl_engine::Texture::compressed_texture_format()
// DXT1, also called s3tc, old desktop compression
// ETC1, old mobile compression
#if defined(__EMSCRIPTEN__)
- // clang-format off
- static int gl_texture_format = EM_ASM_INT({
- var canvas = document.createElement('canvas');
- var gl = canvas.getContext("webgl2");
- const ext = gl.getExtension("WEBGL_compressed_texture_etc");
- if (ext === null)
- return 0;
- return ext.COMPRESSED_SRGB8_ETC2;
- });
- // qDebug() << "gl_engine::Texture::compressed_texture_format: gl_texture_format from js: " << gl_texture_format;
- // clang-format on
- if (gl_texture_format == 0) {
- gl_texture_format = GL_COMPRESSED_SRGB_S3TC_DXT1_EXT; // not on mobile
- }
+ static const GLenum gl_texture_format = []() {
+ const auto context = emscripten_webgl_get_current_context();
+ if (!context)
+ qFatal("No current WebGL context while detecting texture compression");
+ if (emscripten_webgl_enable_extension(context, "WEBGL_compressed_texture_etc"))
+ return GLenum(GL_COMPRESSED_SRGB8_ETC2);
+
+ const bool s3tc = emscripten_webgl_enable_extension(context, "WEBGL_compressed_texture_s3tc");
+ const bool s3tc_srgb = emscripten_webgl_enable_extension(context, "WEBGL_compressed_texture_s3tc_srgb");
+ if (!s3tc || !s3tc_srgb)
+ qFatal("Neither ETC nor sRGB S3TC texture compression is supported");
+ return GLenum(GL_COMPRESSED_SRGB_S3TC_DXT1_EXT);
+ }();
return gl_texture_format;
#elif defined(__ANDROID__)
return GL_COMPRESSED_SRGB8_ETC2;
@@ -300,21 +298,20 @@ GLenum gl_engine::Texture::compressed_texture_format()
nucleus::utils::ColourTexture::Format gl_engine::Texture::compression_algorithm()
{
#if defined(__EMSCRIPTEN__)
- // clang-format off
- static const int gl_texture_format = EM_ASM_INT({
- var canvas = document.createElement('canvas');
- var gl = canvas.getContext("webgl2");
- const ext = gl.getExtension("WEBGL_compressed_texture_etc");
- if (ext === null)
- return 0;
- return ext.COMPRESSED_RGB8_ETC2;
- });
- // clang-format on
- // qDebug() << "gl_engine::Texture::compression_algorithm: gl_texture_format from js: " << gl_texture_format;
- if (gl_texture_format == 0) {
+ static const auto compression_algorithm = []() {
+ const auto context = emscripten_webgl_get_current_context();
+ if (!context)
+ qFatal("No current WebGL context while detecting texture compression");
+ if (emscripten_webgl_enable_extension(context, "WEBGL_compressed_texture_etc"))
+ return nucleus::utils::ColourTexture::Format::ETC1;
+
+ const bool s3tc = emscripten_webgl_enable_extension(context, "WEBGL_compressed_texture_s3tc");
+ const bool s3tc_srgb = emscripten_webgl_enable_extension(context, "WEBGL_compressed_texture_s3tc_srgb");
+ if (!s3tc || !s3tc_srgb)
+ qFatal("Neither ETC nor sRGB S3TC texture compression is supported");
return nucleus::utils::ColourTexture::Format::DXT1;
- }
- return nucleus::utils::ColourTexture::Format::ETC1;
+ }();
+ return compression_algorithm;
#elif defined(__ANDROID__)
return nucleus::utils::ColourTexture::Format::ETC1;
#else
@@ -325,19 +322,12 @@ nucleus::utils::ColourTexture::Format gl_engine::Texture::compression_algorithm(
GLenum gl_engine::Texture::max_anisotropy_param()
{
#if defined(__EMSCRIPTEN__)
- // clang-format off
- static const int param = EM_ASM_INT({
- var canvas = document.createElement('canvas');
- var gl = canvas.getContext("webgl2");
- const ext =
- gl.getExtension("EXT_texture_filter_anisotropic") ||
- gl.getExtension("MOZ_EXT_texture_filter_anisotropic") ||
- gl.getExtension("WEBKIT_EXT_texture_filter_anisotropic");
- if (ext)
- return ext.TEXTURE_MAX_ANISOTROPY_EXT;
- return 0;
- });
- // clang-format on
+ static const GLenum param = []() {
+ const auto context = emscripten_webgl_get_current_context();
+ if (!context)
+ qFatal("No current WebGL context while detecting anisotropic filtering");
+ return emscripten_webgl_enable_extension(context, "EXT_texture_filter_anisotropic") ? GLenum(GL_TEXTURE_MAX_ANISOTROPY_EXT) : GLenum(0);
+ }();
return param;
#elif defined(__ANDROID__)
return GL_TEXTURE_MAX_ANISOTROPY_EXT;
@@ -349,19 +339,17 @@ GLenum gl_engine::Texture::max_anisotropy_param()
float gl_engine::Texture::max_anisotropy()
{
#if defined(__EMSCRIPTEN__)
- // clang-format off
- static const float max_anisotropy = std::min(32.f, float(EM_ASM_DOUBLE({
- var canvas = document.createElement('canvas');
- var gl = canvas.getContext("webgl2");
- const ext =
- gl.getExtension("EXT_texture_filter_anisotropic") ||
- gl.getExtension("MOZ_EXT_texture_filter_anisotropic") ||
- gl.getExtension("WEBKIT_EXT_texture_filter_anisotropic");
- if (ext)
- return gl.getParameter(ext.MAX_TEXTURE_MAX_ANISOTROPY_EXT);
- return 0;
- })));
- // clang-format on
+ static const float max_anisotropy = []() {
+ const auto context = emscripten_webgl_get_current_context();
+ if (!context)
+ qFatal("No current WebGL context while detecting anisotropic filtering");
+ if (!emscripten_webgl_enable_extension(context, "EXT_texture_filter_anisotropic"))
+ return 0.f;
+
+ GLfloat value = 0.f;
+ QOpenGLContext::currentContext()->extraFunctions()->glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &value);
+ return std::min(32.f, value);
+ }();
return max_anisotropy;
#elif defined(__ANDROID__)
static const float max_anisotropy = []() {
diff --git a/site/mascot.png b/misc/mascot.png
similarity index 100%
rename from site/mascot.png
rename to misc/mascot.png
diff --git a/misc/scripts/fetch_dawn_native.py b/misc/scripts/fetch_dawn_native.py
index b19859c38..0e1f5c00e 100644
--- a/misc/scripts/fetch_dawn_native.py
+++ b/misc/scripts/fetch_dawn_native.py
@@ -1,6 +1,7 @@
#!/usr/bin/env python3
import argparse
+import hashlib
import json
import os
import shutil
@@ -12,6 +13,34 @@
from setup_utils import log, fail, download
+def verify_digest(path, digest):
+ if not digest:
+ fail("Dawn release asset does not provide a digest")
+
+ try:
+ algorithm, expected = digest.split(":", 1)
+ except ValueError:
+ fail(f"Invalid Dawn release asset digest: {digest}")
+
+ if algorithm.lower() != "sha256" or len(expected) != 64:
+ fail(f"Unsupported Dawn release asset digest: {digest}")
+
+ try:
+ int(expected, 16)
+ except ValueError:
+ fail(f"Invalid Dawn release asset digest: {digest}")
+
+ hasher = hashlib.sha256()
+ with open(path, "rb") as archive:
+ for chunk in iter(lambda: archive.read(1024 * 1024), b""):
+ hasher.update(chunk)
+
+ if hasher.hexdigest() != expected.lower():
+ fail(f"Dawn release asset digest mismatch for {os.path.basename(path)}")
+
+ log(f"Verified SHA-256 digest for {os.path.basename(path)}")
+
+
def safe_extract(tar_path, extract_to):
extract_root = os.path.abspath(extract_to)
try:
@@ -27,7 +56,11 @@ def safe_extract(tar_path, extract_to):
def release_assets(version):
url = f"https://api.github.com/repos/google/dawn/releases/tags/v{version}"
- request = urllib.request.Request(url, headers={"User-Agent": "webigeo-build"})
+ headers = {"User-Agent": "webigeo-build"}
+ github_token = os.environ.get("GITHUB_TOKEN")
+ if github_token:
+ headers["Authorization"] = f"Bearer {github_token}"
+ request = urllib.request.Request(url, headers=headers)
try:
with urllib.request.urlopen(request, timeout=30) as response:
return json.load(response).get("assets", [])
@@ -66,11 +99,13 @@ def main():
fail(f"Multiple Dawn release assets match *{asset_suffix} for v{args.dawn_version}")
asset = matches[0]
+ asset_digest = asset.get("digest")
tmp_dir = tempfile.mkdtemp(prefix="fetchdawn_native_")
archive_path = os.path.join(tmp_dir, asset["name"])
try:
download(asset["browser_download_url"], archive_path)
+ verify_digest(archive_path, asset_digest)
safe_extract(archive_path, tmp_dir)
package_root = find_package_root(tmp_dir)
diff --git a/nucleus/CMakeLists.txt b/nucleus/CMakeLists.txt
index 0ea915fab..77581fff1 100644
--- a/nucleus/CMakeLists.txt
+++ b/nucleus/CMakeLists.txt
@@ -22,7 +22,7 @@
project(alpine-renderer-nucleus LANGUAGES C CXX)
alp_add_git_repository(stb_slim URL https://github.com/AlpineMapsOrgDependencies/stb_slim.git COMMITISH 547fade2a12793e1bea4733d59646b4f436e25a4)
-alp_add_git_repository(radix URL https://github.com/AlpineMapsOrg/radix.git COMMITISH 9720bce6399f4581e35edc88f87651f020348b34 NOT_SYSTEM)
+alp_add_git_repository(radix URL https://github.com/AlpineMapsOrg/radix.git COMMITISH 9d3a4b7a38d1339da015e958c9825eaf6afcb6ae NOT_SYSTEM)
alp_add_git_repository(zppbits URL https://github.com/eyalz800/zpp_bits.git COMMITISH v4.4.25 DO_NOT_ADD_SUBPROJECT)
if(ALP_ENABLE_LABELS)
alp_add_git_repository(vector_tiles URL https://github.com/AlpineMapsOrg/vector-tile.git COMMITISH faba88257716c4bc01ebd44d8b8b98f711ecb78c)
@@ -205,9 +205,5 @@ if (MSVC)
# /WX fails with an unreachable code warning/error in zpp_bits.h. the system property doesn't seem to work (even though it appears in the build log as
# "-external:ID:\a\renderer\renderer\extern\zppbits -external:W0")
else()
- # imgui does not compile with -Werror. so we have to make it private for now.
- # better solutions welcome
- target_compile_options(nucleus PRIVATE -Wall -Wextra -pedantic -Werror)
- # PUBLIC so every target linking nucleus inherits the suppression
- target_compile_options(nucleus PUBLIC -Wno-missing-field-initializers)
+ target_compile_options(nucleus PUBLIC -Wall -Wextra -pedantic -Werror -Wno-missing-field-initializers)
endif()
diff --git a/nucleus/tile/Scheduler.cpp b/nucleus/tile/Scheduler.cpp
index 64c493eff..4c7c39b00 100644
--- a/nucleus/tile/Scheduler.cpp
+++ b/nucleus/tile/Scheduler.cpp
@@ -256,13 +256,13 @@ void Scheduler::clear_full_cache()
set_ram_quad_limit(old_ram_quad_limit);
}
-std::expected Scheduler::read_disk_cache()
+bool Scheduler::read_disk_cache()
{
if (m_name == "unnamed" || m_name.isEmpty()) {
const auto error = QString("Not reading tiles as the scheduler is not named, and this would cause name conflicts in the file system."
"Name your scheduler, e.g., by using the scheduler director.");
qDebug() << error;
- return std::unexpected(error);
+ return false;
}
const auto r = m_ram_cache.read_from_disk(disk_cache_path());
if (r.has_value()) {
@@ -275,8 +275,9 @@ std::expected Scheduler::read_disk_cache()
.arg(QString::fromStdString(disk_cache_path().string()))
.arg(r.error());
std::filesystem::remove_all(disk_cache_path());
+ return false;
}
- return r;
+ return true;
}
std::vector Scheduler::quads_for_current_camera_position() const
diff --git a/nucleus/tile/Scheduler.h b/nucleus/tile/Scheduler.h
index 5c85ae2e3..f2435af79 100644
--- a/nucleus/tile/Scheduler.h
+++ b/nucleus/tile/Scheduler.h
@@ -82,7 +82,7 @@ class Scheduler : public QObject {
[[nodiscard]] unsigned int persist_timeout() const;
void set_persist_timeout(unsigned int new_persist_timeout);
- std::expected read_disk_cache();
+ bool read_disk_cache();
void set_retirement_age_for_tile_cache(unsigned int new_retirement_age_for_tile_cache);
diff --git a/plain_renderer/CMakeLists.txt b/plain_renderer/CMakeLists.txt
index 8ef70f152..6cd2c1f90 100644
--- a/plain_renderer/CMakeLists.txt
+++ b/plain_renderer/CMakeLists.txt
@@ -33,7 +33,6 @@ alp_configure_target(plain_renderer)
if (EMSCRIPTEN)
message(NOTICE "ALP_WWW_INSTALL_DIR = ${ALP_WWW_INSTALL_DIR}")
- configure_file(../site/mascot.png mascot.png COPYONLY)
set(ALP_INSTALL_FILES
)
install(
@@ -42,13 +41,12 @@ if (EMSCRIPTEN)
"$/plain_renderer.wasm"
"$/plain_renderer.html"
"$/qtloader.js"
- "${CMAKE_SOURCE_DIR}/site/mascot.png"
DESTINATION
- ${ALP_WWW_INSTALL_DIR}
+ "${ALP_WWW_INSTALL_DIR}/plain_renderer"
)
install(
FILES "$/plain_renderer.worker.js"
- DESTINATION ${ALP_WWW_INSTALL_DIR}
+ DESTINATION "${ALP_WWW_INSTALL_DIR}/plain_renderer"
OPTIONAL
)
diff --git a/unittests/gl_engine/texture.cpp b/unittests/gl_engine/texture.cpp
index 6f1c531e9..89f14b6ee 100644
--- a/unittests/gl_engine/texture.cpp
+++ b/unittests/gl_engine/texture.cpp
@@ -76,11 +76,11 @@ QString texel_component_float(const glm::vec& texel, int i)
return "0.0";
};
-template
-QString texel_component_float(const Type& texel, int)
-{
- return QString("float(%1)").arg(texel);
-};
+// template
+// QString texel_component_float(const Type& texel, int)
+// {
+// return QString("float(%1)").arg(texel);
+// };
template >
void test_unsigned_texture_with(const TexelType& texel_value, gl_engine::Texture::Format format)
diff --git a/webgpu/base/CMakeLists.txt b/webgpu/base/CMakeLists.txt
index ac966abd2..d55fe94dd 100644
--- a/webgpu/base/CMakeLists.txt
+++ b/webgpu/base/CMakeLists.txt
@@ -19,18 +19,7 @@
project(alpine-renderer-webgpu LANGUAGES C CXX)
-# NOTE: The idea behind this target is to accumulate all the webgpu related things that are platform
-# dependent such that we only have the webgpu cmake target for the other projects to link against.
-
-set(ALP_DAWN_VERSION "20260420.230544") # Version of Dawn to download (see https://github.com/google/dawn/releases)
-set(ALP_DXC_URL "https://github.com/microsoft/DirectXShaderCompiler/releases/download/v1.9.2602/dxc_2026_02_20.zip"
- CACHE INTERNAL "DXC release archive whose dxcompiler.dll/dxil.dll match ALP_DAWN_VERSION")
-set(ALP_DAWN_DIR "${CMAKE_SOURCE_DIR}/${ALP_EXTERN_DIR}/dawn")
-set(ALP_SDL_INSTALL_DIR "${CMAKE_SOURCE_DIR}/${ALP_EXTERN_DIR}/sdl")
-set(ALP_DAWN_PORT_DIR "${CMAKE_SOURCE_DIR}/${ALP_EXTERN_DIR}/emdawnwebgpu_pkg/emdawnwebgpu.port.py")
-
set(SOURCES
-
raii/base_types.h
raii/Texture.h raii/Texture.cpp
raii/TextureWithSampler.h raii/TextureWithSampler.cpp
@@ -61,217 +50,11 @@ set(SOURCES
RenderResourceRegistry.h RenderResourceRegistry.cpp
gpu_utils.h gpu_utils.cpp
wgpu_string.h
- webgpu_interface.hpp webgpu_interface.cpp)
+ webgpu_interface.hpp webgpu_interface.cpp
+)
add_library(webgpu STATIC ${SOURCES})
-find_package(Python3 COMPONENTS Interpreter REQUIRED)
-
-function(alp_find_local_dawn_package out_var)
- set(ALP_DAWN_INSTALL_DIR "${ALP_DAWN_DIR}/install/${ALP_DAWN_CONFIG}")
- set(ALP_DAWN_VERSION_FILE "${ALP_DAWN_INSTALL_DIR}/.alp_dawn_version")
- if (NOT EXISTS "${ALP_DAWN_VERSION_FILE}")
- set(${out_var} "" PARENT_SCOPE)
- return()
- endif()
-
- file(READ "${ALP_DAWN_VERSION_FILE}" ALP_DAWN_INSTALLED_VERSION)
- string(STRIP "${ALP_DAWN_INSTALLED_VERSION}" ALP_DAWN_INSTALLED_VERSION)
- if (NOT ALP_DAWN_INSTALLED_VERSION STREQUAL ALP_DAWN_VERSION)
- set(${out_var} "" PARENT_SCOPE)
- return()
- endif()
-
- foreach(ALP_DAWN_LIB_DIR lib lib64)
- set(ALP_DAWN_PACKAGE_CANDIDATE "${ALP_DAWN_INSTALL_DIR}/${ALP_DAWN_LIB_DIR}/cmake/Dawn")
- if (EXISTS "${ALP_DAWN_PACKAGE_CANDIDATE}/DawnConfig.cmake")
- set(${out_var} "${ALP_DAWN_PACKAGE_CANDIDATE}" PARENT_SCOPE)
- return()
- endif()
- endforeach()
- set(${out_var} "" PARENT_SCOPE)
-endfunction()
-
-function(alp_mark_local_dawn_package)
- foreach(ALP_DAWN_MARK_CONFIG Debug Release)
- set(ALP_DAWN_MARK_DIR "${ALP_DAWN_DIR}/install/${ALP_DAWN_MARK_CONFIG}")
- if (EXISTS "${ALP_DAWN_MARK_DIR}")
- file(WRITE "${ALP_DAWN_MARK_DIR}/.alp_dawn_version" "${ALP_DAWN_VERSION}\n")
- endif()
- endforeach()
-endfunction()
-
-if (EMSCRIPTEN)
-
- # Ensure Dawn port exists, otherwise fetch it
- if (NOT EXISTS "${ALP_DAWN_PORT_DIR}")
- message(STATUS "Dawn port not found, fetching...")
-
- execute_process(
- COMMAND ${Python3_EXECUTABLE}
- "${CMAKE_SOURCE_DIR}/misc/scripts/fetch_dawn_port.py"
- --dawn-version "${ALP_DAWN_VERSION}"
- --extern-dir "${ALP_EXTERN_DIR}"
- WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
- RESULT_VARIABLE FETCH_RESULT
- )
-
- if (NOT FETCH_RESULT EQUAL 0)
- message(FATAL_ERROR "Failed to fetch Dawn Emscripten port.")
- endif()
- endif()
-
-
-
- target_compile_options(webgpu PUBLIC
- "--use-port=sdl2"
- "--use-port=${ALP_DAWN_PORT_DIR}"
- )
-
-
- target_link_options(webgpu PUBLIC
- "-sASYNCIFY=1"
- "--use-port=sdl2"
- "--use-port=${ALP_DAWN_PORT_DIR}"
- )
-
-else()
-
- if (CMAKE_BUILD_TYPE STREQUAL "Debug")
- set(ALP_DAWN_CONFIG "Debug")
- else()
- set(ALP_DAWN_CONFIG "Release")
- endif()
-
- if (WIN32)
- set(ALP_DAWN_NATIVE_PLATFORM "windows-latest")
- elseif(APPLE)
- set(ALP_DAWN_NATIVE_PLATFORM "macos-latest")
- else()
- set(ALP_DAWN_NATIVE_PLATFORM "ubuntu-latest")
- endif()
-
- alp_find_local_dawn_package(ALP_DAWN_CMAKE_PACKAGE)
-
- if (NOT ALP_DAWN_CMAKE_PACKAGE)
- message(STATUS "Dawn installation missing - fetching native Dawn package...")
- execute_process(
- COMMAND ${Python3_EXECUTABLE} "${CMAKE_SOURCE_DIR}/misc/scripts/fetch_dawn_native.py"
- --dawn-version "${ALP_DAWN_VERSION}"
- --extern-dir "${ALP_EXTERN_DIR}"
- --build-type "${ALP_DAWN_CONFIG}"
- --platform "${ALP_DAWN_NATIVE_PLATFORM}"
- WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
- RESULT_VARIABLE DAWN_FETCH_RESULT
- )
-
- if (NOT DAWN_FETCH_RESULT EQUAL 0)
- message(STATUS "Fetching native Dawn package failed - running install_dawn.py...")
-
- if (NOT DEFINED ALP_CMAKE_EXECUTABLE)
- find_program(ALP_CMAKE_EXECUTABLE cmake)
- if (NOT ALP_CMAKE_EXECUTABLE)
- message(FATAL_ERROR "cmake not found in PATH")
- endif()
- endif()
-
- if (NOT DEFINED ALP_NINJA_EXECUTABLE)
- find_program(ALP_NINJA_EXECUTABLE ninja)
- if (NOT ALP_NINJA_EXECUTABLE)
- message(FATAL_ERROR "ninja not found in PATH")
- endif()
- endif()
-
- execute_process(
- COMMAND ${Python3_EXECUTABLE} "${CMAKE_SOURCE_DIR}/misc/scripts/install_dawn.py"
- --dawn-version ${ALP_DAWN_VERSION}
- --extern-dir "${ALP_EXTERN_DIR}"
- --cmake-path "${ALP_CMAKE_EXECUTABLE}"
- --ninja-path "${ALP_NINJA_EXECUTABLE}"
- WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
- RESULT_VARIABLE DAWN_RESULT
- )
-
- if (NOT DAWN_RESULT EQUAL 0)
- message(FATAL_ERROR "install_dawn.py failed.")
- endif()
- endif()
-
- alp_mark_local_dawn_package()
- alp_find_local_dawn_package(ALP_DAWN_CMAKE_PACKAGE)
- endif()
-
- if (NOT ALP_DAWN_CMAKE_PACKAGE)
- message(FATAL_ERROR "Dawn ${ALP_DAWN_VERSION} was not found in ${ALP_DAWN_DIR}/install/${ALP_DAWN_CONFIG}.")
- endif()
-
- set(Dawn_DIR "${ALP_DAWN_CMAKE_PACKAGE}")
- find_package(Dawn REQUIRED CONFIG PATHS "${ALP_DAWN_CMAKE_PACKAGE}" NO_DEFAULT_PATH)
- target_link_libraries(webgpu PUBLIC dawn::webgpu_dawn)
-
- # Ensure SDL is installed
- if (NOT EXISTS "${ALP_SDL_INSTALL_DIR}")
- message(STATUS "SDL not found - running install_sdl.py...")
-
- if (NOT DEFINED ALP_CMAKE_EXECUTABLE)
- find_program(ALP_CMAKE_EXECUTABLE cmake)
- if (NOT ALP_CMAKE_EXECUTABLE)
- message(FATAL_ERROR "cmake not found")
- endif()
- endif()
-
- if (NOT DEFINED ALP_NINJA_EXECUTABLE)
- find_program(ALP_NINJA_EXECUTABLE ninja)
- if (NOT ALP_NINJA_EXECUTABLE)
- message(FATAL_ERROR "ninja not found")
- endif()
- endif()
-
- if (NOT DEFINED ALP_GIT_EXECUTABLE)
- find_program(ALP_GIT_EXECUTABLE git)
- if (NOT ALP_GIT_EXECUTABLE)
- message(FATAL_ERROR "git not found")
- endif()
- endif()
-
- execute_process(
- COMMAND ${Python3_EXECUTABLE} "${CMAKE_SOURCE_DIR}/misc/scripts/install_sdl.py"
- --install-prefix "${ALP_SDL_INSTALL_DIR}"
- --cmake-path "${ALP_CMAKE_EXECUTABLE}"
- --ninja-path "${ALP_NINJA_EXECUTABLE}"
- --git-path "${ALP_GIT_EXECUTABLE}"
- WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
- RESULT_VARIABLE SDL_RESULT
- )
- if (NOT SDL_RESULT EQUAL 0)
- message(FATAL_ERROR "install_sdl.py failed.")
- endif()
- endif()
-
- # find and add SDL2 (you may install it to the default install folder or ALP_SDL_INSTALL_DIR)
- # Add ALP_SDL_INSTALL_DIR to CMake's search path for find_package
- list(APPEND CMAKE_PREFIX_PATH "${ALP_SDL_INSTALL_DIR}")
- find_package(SDL2 REQUIRED)
- target_link_libraries(webgpu PUBLIC SDL2::SDL2 SDL2::SDL2main)
-
- # TODO verify whether this is still needed and adapt accordingly
- # If tint_lang_hlsl_writer.lib within ${ALP_DAWN_DIR}/build/debug/src exists DAWN was compiled with DX-Backends.
- file(GLOB_RECURSE TINT_LIB_PATH "${ALP_DAWN_DIR}/out/Debug/src/tint/tint_lang_hlsl_writer.lib")
- if (TINT_LIB_PATH)
- # NOTE: With Direct3D Backends we additionally need to link against dx.
- message(STATUS "Found tint_lang_hlsl_writer.lib, DAWN seems to be compiled with DX-Backends. Will link against dxguid.lib.")
- find_library(DXGUID_LIB dxguid.lib)
- if (DXGUID_LIB)
- target_link_libraries(webgpu PUBLIC ${DXGUID_LIB})
- else()
- message(FATAL_ERROR "dxguid.lib not found.")
- endif()
- endif()
-endif()
-
-# set public Dawn backend flags
-target_compile_definitions(webgpu PUBLIC IMGUI_IMPL_WEBGPU_BACKEND_DAWN WEBGPU_BACKEND_DAWN)
-
# --- WGSL shaders owned by the webgpu target: shared util/* helpers + the mipmap shader ---
if (ALP_ENABLE_WGSL_MINIFICATION)
set(WEBGPU_SHADER_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/shaders")
@@ -318,7 +101,15 @@ qt_add_resources(webgpu "webgpu_shaders"
"${WEBGPU_SHADER_BASE_DIR}/noise.wgsl"
)
-target_compile_definitions(webgpu PUBLIC ALP_SHADER_DIR_WEBGPU="${CMAKE_CURRENT_SOURCE_DIR}/shaders/")
-
-target_link_libraries(webgpu PUBLIC Qt::Core nucleus)
+target_compile_definitions(webgpu
+ PUBLIC ALP_SHADER_DIR_WEBGPU="${CMAKE_CURRENT_SOURCE_DIR}/shaders/"
+ PRIVATE WEBGPU_BACKEND_DAWN
+)
+
+target_link_libraries(webgpu PUBLIC
+ Qt::Core
+ nucleus
+ alp_webgpu_api
+ alp_sdl2_platform
+)
target_include_directories(webgpu PRIVATE .)
diff --git a/webgpu/base/webgpu_interface.cpp b/webgpu/base/webgpu_interface.cpp
index 4388c4c97..7a228d5ae 100644
--- a/webgpu/base/webgpu_interface.cpp
+++ b/webgpu/base/webgpu_interface.cpp
@@ -256,7 +256,7 @@ void waitForFlag(const WGPUDevice& device, bool* flag, int sleepInterval, int ti
}
}
-void checkForTimingSupport(const WGPUAdapter& adapter, const WGPUDevice& device)
+void checkForTimingSupport([[maybe_unused]] const WGPUAdapter& adapter, [[maybe_unused]] const WGPUDevice& device)
{
// Check wether timing is supported
#ifdef __EMSCRIPTEN__
diff --git a/webgpu/compute/nodes/ComputeReleasePointsNode.cpp b/webgpu/compute/nodes/ComputeReleasePointsNode.cpp
index 9a1bd4471..c34720374 100644
--- a/webgpu/compute/nodes/ComputeReleasePointsNode.cpp
+++ b/webgpu/compute/nodes/ComputeReleasePointsNode.cpp
@@ -140,7 +140,7 @@ void ComputeReleasePointsNode::run_impl()
.userdata2 = nullptr,
};
- WGPUFuture future = wgpuQueueOnSubmittedWorkDone(m_ctx->queue(), callback_info);
+ wgpuQueueOnSubmittedWorkDone(m_ctx->queue(), callback_info);
}
std::unique_ptr ComputeReleasePointsNode::create_release_points_texture(
diff --git a/webgpu/compute/nodes/Node.h b/webgpu/compute/nodes/Node.h
index d34fb4799..c2108bf0c 100644
--- a/webgpu/compute/nodes/Node.h
+++ b/webgpu/compute/nodes/Node.h
@@ -174,8 +174,8 @@ class Node : public QObject {
virtual std::string get_type_name() const = 0;
- virtual void serialize_settings(QJsonObject& out) const { }
- virtual void deserialize_settings(const QJsonObject& in) { }
+ virtual void serialize_settings(QJsonObject& /* out */) const { }
+ virtual void deserialize_settings(const QJsonObject& /* in */) { }
[[nodiscard]] bool has_input_socket(const std::string& name) const;
[[nodiscard]] InputSocket& input_socket(const std::string& name);
diff --git a/webgpu/engine/cloud/CloudRenderer.cpp b/webgpu/engine/cloud/CloudRenderer.cpp
index e46787f12..ff9ba38bd 100644
--- a/webgpu/engine/cloud/CloudRenderer.cpp
+++ b/webgpu/engine/cloud/CloudRenderer.cpp
@@ -494,10 +494,10 @@ void CloudRenderer::update_gpu_tiles_cloud(const std::vector&
Q_ASSERT(atlas_y < ATLAS_SCALE_XY);
Q_ASSERT(atlas_z < ATLAS_SCALE_Z);
// Note: z is "up" in texture space
- for (int i = 0; i < tile.texture->size(); ++i) {
+ for (std::size_t i = 0; i < tile.texture->size(); ++i) {
const auto& level = tile.texture->at(i);
glm::uvec3 atlas_offset = { atlas_x * level.width(), atlas_y * level.height(), atlas_z * level.depth() };
- m_cloud_atlas_texture->write(m_ctx->queue(), level, atlas_offset, i);
+ m_cloud_atlas_texture->write(m_ctx->queue(), level, atlas_offset, uint32_t(i));
}
// convert to coords at max zoom level
diff --git a/webgpu/engine/overlay/TextureOverlay.cpp b/webgpu/engine/overlay/TextureOverlay.cpp
index 15b40936e..c49a740c9 100644
--- a/webgpu/engine/overlay/TextureOverlay.cpp
+++ b/webgpu/engine/overlay/TextureOverlay.cpp
@@ -138,7 +138,7 @@ void TextureOverlay::init(Context& context)
create_texture(context.webgpu_ctx(), 1, 1);
}
-void TextureOverlay::ready(webgpu::Context& ctx) { m_is_ready = true; }
+void TextureOverlay::ready(webgpu::Context&) { m_is_ready = true; }
void TextureOverlay::create_texture(webgpu::Context& ctx, uint32_t width, uint32_t height)
{
diff --git a/webgpu/engine/tile_mesh/TileMeshRenderer.cpp b/webgpu/engine/tile_mesh/TileMeshRenderer.cpp
index 9fd869936..f01d4796f 100644
--- a/webgpu/engine/tile_mesh/TileMeshRenderer.cpp
+++ b/webgpu/engine/tile_mesh/TileMeshRenderer.cpp
@@ -31,14 +31,6 @@
using webgpu_engine::TileMeshRenderer;
-namespace {
-template
-int bufferLengthInBytes(const std::vector& vec)
-{
- return int(vec.size() * sizeof(T));
-}
-} // namespace
-
namespace webgpu_engine {
TileMeshRenderer::TileMeshRenderer(uint32_t height_resolution, uint32_t ortho_resolution)