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 + + + +
+ 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)