From 0b7ae17528369b111c5e7500bfcfbab58dc04739 Mon Sep 17 00:00:00 2001 From: adam-ce <5292991+adam-ce@users.noreply.github.com> Date: Sun, 12 Jul 2026 00:15:38 +0200 Subject: [PATCH 01/27] Update CI build and test workflow --- .github/workflows/ci.yml | 133 +++++++++++++++++++++------------- cmake/SetupCMakeProject.cmake | 25 ++++++- 2 files changed, 105 insertions(+), 53 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2e48bb99..02d8551f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,77 +2,110 @@ name: CI on: push: - branches: [ "main", "tile-downloader" ] pull_request: branches: [ "main", "tile-downloader" ] env: BUILD_TYPE: Release + CCACHE_DIR: ${{ github.workspace }}/.ccache + CCACHE_MAXSIZE: 1G jobs: - build-terrainbuilder: + build-and-test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: - submodules: true + submodules: true - - name: Install dependencies - run: sudo apt-get install libfmt-dev libglm-dev libgdal-dev catch2 libfreeimage-dev libtbb-dev - - - name: Configure CMake - run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + ccache \ + extra-cmake-modules \ + libcurl4-openssl-dev \ + libegl1-mesa-dev \ + libgl1-mesa-dev \ + libglm-dev \ + libgmp-dev \ + libjpeg-dev \ + libmpfr-dev \ + libpng-dev \ + libsqlite3-dev \ + libtbb-dev \ + libtiff-dev \ + libwayland-bin \ + libwayland-dev \ + libxkbcommon-dev \ + ninja-build \ + pkg-config \ + sqlite3 \ + wayland-protocols \ + xorg-dev \ + zlib1g-dev - - name: Build - run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --target terrainbuilder - - build-tile-downloader: - if: github.ref == 'refs/heads/tile-downloader' - runs-on: ubuntu-latest + - name: Cache installed CMake dependencies + uses: actions/cache@v4 + with: + path: | + build/alp_external/boost + build/alp_external/proj + build/alp_external/gdal + build/alp_external/cgal + build/alp_external/opencv + key: ${{ runner.os }}-alp-installs-${{ env.BUILD_TYPE }}-${{ hashFiles('CMakeLists.txt', 'src/CMakeLists.txt', 'cmake/*.cmake') }} + restore-keys: | + ${{ runner.os }}-alp-installs-${{ env.BUILD_TYPE }}- - steps: - - uses: actions/checkout@v3 + - name: Cache ccache + uses: actions/cache@v4 with: - submodules: true + path: ${{ env.CCACHE_DIR }} + key: ${{ runner.os }}-ccache-${{ github.ref_name }}-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-ccache-${{ github.ref_name }}- + ${{ runner.os }}-ccache- - - name: Install dependencies - run: sudo apt-get install libfmt-dev libglm-dev libgdal-dev catch2 libfreeimage-dev libtbb-dev libcurl4-openssl-dev - - - name: Configure CMake - run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + - name: Configure ccache + run: | + ccache --max-size="${CCACHE_MAXSIZE}" + ccache --zero-stats - - name: Build - run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --target tile-downloader - - test: - runs-on: ubuntu-latest + - name: Configure CMake + run: > + cmake + -S "${{ github.workspace }}" + -B "${{ github.workspace }}/build" + -G Ninja + -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} + -DCMAKE_C_COMPILER_LAUNCHER=ccache + -DCMAKE_CXX_COMPILER_LAUNCHER=ccache - steps: - - uses: actions/checkout@v3 - with: - submodules: true + - name: Build all targets + run: cmake --build "${{ github.workspace }}/build" --config ${{ env.BUILD_TYPE }} --parallel --target all - - name: Install dependencies - run: sudo apt-get install libfmt-dev libglm-dev libgdal-dev catch2 libfreeimage-dev libtbb-dev - - - name: Configure CMake - run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + - name: ccache stats + if: always() + run: ccache --show-stats - - name: Build - run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --target unittests_terrainlib - - - uses: actions/cache@v3 + - name: Cache unit test data id: terrain_builder_unittest_data + uses: actions/cache@v4 with: - path: ./unittests/data - key: terrain_builder_unittest_data.tar.gz - - - name: Download data - if: steps.cache.outputs.cache-hit != 'true' - run: wget -qO- https://gataki.cg.tuwien.ac.at/raw/terrain_builder_unittest_data.tar.gz | tar xvz -C ./unittests/ + path: unittests/data + key: terrain-builder-unittest-data-v1 - - name: Test - working-directory: ${{github.workspace}}/build - run: ./unittests/unittests_terrainlib + - name: Download unit test data + if: steps.terrain_builder_unittest_data.outputs.cache-hit != 'true' + run: wget -qO- https://gataki.cg.tuwien.ac.at/raw/terrain_builder_unittest_data.tar.gz | tar xz -C unittests + - name: Run unit tests + working-directory: ${{ github.workspace }}/build + run: | + ./unittests/unittests_terrainlib "~mesh::clip_on_bounds benchmark" + ./unittests/unittests_tilebuilder + ./unittests/unittests_meshbuilder + ./unittests/unittests_dagbuilder + ./unittests/unittests_sfmerger diff --git a/cmake/SetupCMakeProject.cmake b/cmake/SetupCMakeProject.cmake index ad72db11..2c2b94a0 100644 --- a/cmake/SetupCMakeProject.cmake +++ b/cmake/SetupCMakeProject.cmake @@ -64,6 +64,15 @@ function(alp_setup_cmake_project arg_NAME) set(version_var "ALP_INSTALLED_${arg_NAME}_VERSION") set(path_var "ALP_INSTALLED_${arg_NAME}_PATH") + set(build_dir "${CMAKE_BINARY_DIR}/alp_external/${arg_NAME}_build") + set(install_dir "${CMAKE_BINARY_DIR}/alp_external/${arg_NAME}") + set(stamp_file "${install_dir}/.alp_install_signature") + set(cache_signature "URL=${arg_URL} +COMMITISH=${arg_COMMITISH} +BUILD_TYPE=${CMAKE_BUILD_TYPE} +SYSTEM=${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR} +CXX=${CMAKE_CXX_COMPILER_ID}-${CMAKE_CXX_COMPILER_VERSION} +ARGS=${arg_CMAKE_ARGUMENTS}") if(DEFINED ${version_var} AND "${${version_var}}" STREQUAL "${arg_COMMITISH}${arg_CMAKE_ARGUMENTS}" AND DEFINED ${path_var} AND EXISTS "${${path_var}}") list(PREPEND CMAKE_PREFIX_PATH "${${path_var}}") @@ -71,13 +80,24 @@ function(alp_setup_cmake_project arg_NAME) return() endif() + if(EXISTS "${install_dir}" AND EXISTS "${stamp_file}") + file(READ "${stamp_file}" installed_signature) + if(installed_signature STREQUAL cache_signature) + message(STATUS "[alp] Using cached install for ${arg_NAME}: ${install_dir}") + list(PREPEND CMAKE_PREFIX_PATH "${install_dir}") + set(CMAKE_PREFIX_PATH "${CMAKE_PREFIX_PATH}" PARENT_SCOPE) + set(${path_var} "${install_dir}" CACHE PATH "Install path for ${arg_NAME}" FORCE) + set(${version_var} "${arg_COMMITISH}${arg_CMAKE_ARGUMENTS}" CACHE STRING "Installed commit/tag for $ + build flags" FORCE) + return() + endif() + endif() + alp_add_git_repository(${arg_NAME} URL ${arg_URL} COMMITISH ${arg_COMMITISH} DO_NOT_ADD_SUBPROJECT) set(src_dir "${${arg_NAME}_SOURCE_DIR}") - set(build_dir "${CMAKE_BINARY_DIR}/alp_external/${arg_NAME}_build") - set(install_dir "${CMAKE_BINARY_DIR}/alp_external/${arg_NAME}") file(REMOVE_RECURSE "${install_dir}") _alp_build_and_install(${arg_NAME} ${src_dir} ${build_dir} ${install_dir} ${arg_CMAKE_ARGUMENTS}) + file(WRITE "${stamp_file}" "${cache_signature}") list(PREPEND CMAKE_PREFIX_PATH "${install_dir}") set(CMAKE_PREFIX_PATH "${CMAKE_PREFIX_PATH}" PARENT_SCOPE) @@ -87,4 +107,3 @@ function(alp_setup_cmake_project arg_NAME) set(${version_var} "${arg_COMMITISH}${arg_CMAKE_ARGUMENTS}" CACHE STRING "Installed commit/tag for $ + build flags" FORCE) endfunction() - From 025968988252a2dabf1f77577ea1721d6a07a6b1 Mon Sep 17 00:00:00 2001 From: adam-ce <5292991+adam-ce@users.noreply.github.com> Date: Sun, 12 Jul 2026 00:38:42 +0200 Subject: [PATCH 02/27] Improve CI dependency cache iteration --- .github/workflows/ci.yml | 31 ++++++++++++++++++++++++++----- src/CMakeLists.txt | 2 +- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 02d8551f..2a823ca8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,8 +46,9 @@ jobs: xorg-dev \ zlib1g-dev - - name: Cache installed CMake dependencies - uses: actions/cache@v4 + - name: Restore installed CMake dependencies + id: cmake_dependency_cache + uses: actions/cache/restore@v4 with: path: | build/alp_external/boost @@ -59,11 +60,12 @@ jobs: restore-keys: | ${{ runner.os }}-alp-installs-${{ env.BUILD_TYPE }}- - - name: Cache ccache - uses: actions/cache@v4 + - name: Restore ccache + id: ccache_cache + uses: actions/cache/restore@v4 with: path: ${{ env.CCACHE_DIR }} - key: ${{ runner.os }}-ccache-${{ github.ref_name }}-${{ github.sha }} + key: ${{ runner.os }}-ccache-${{ github.ref_name }}-${{ github.run_id }}-${{ github.run_attempt }} restore-keys: | ${{ runner.os }}-ccache-${{ github.ref_name }}- ${{ runner.os }}-ccache- @@ -83,6 +85,18 @@ jobs: -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + - name: Save installed CMake dependencies + if: steps.cmake_dependency_cache.outputs.cache-hit != 'true' + uses: actions/cache/save@v4 + with: + path: | + build/alp_external/boost + build/alp_external/proj + build/alp_external/gdal + build/alp_external/cgal + build/alp_external/opencv + key: ${{ runner.os }}-alp-installs-${{ env.BUILD_TYPE }}-${{ hashFiles('CMakeLists.txt', 'src/CMakeLists.txt', 'cmake/*.cmake') }} + - name: Build all targets run: cmake --build "${{ github.workspace }}/build" --config ${{ env.BUILD_TYPE }} --parallel --target all @@ -90,6 +104,13 @@ jobs: if: always() run: ccache --show-stats + - name: Save ccache + if: always() + uses: actions/cache/save@v4 + with: + path: ${{ env.CCACHE_DIR }} + key: ${{ runner.os }}-ccache-${{ github.ref_name }}-${{ github.run_id }}-${{ github.run_attempt }} + - name: Cache unit test data id: terrain_builder_unittest_data uses: actions/cache@v4 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c9efb5aa..6771146b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -65,7 +65,7 @@ alp_add_git_repository(eigen URL https://gitlab.com/libeigen/eigen.git COMMITISH add_library(Eigen3 INTERFACE) target_include_directories(Eigen3 INTERFACE SYSTEM ${eigen_SOURCE_DIR}) -alp_setup_cmake_project(boost URL https://github.com/boostorg/boost.git COMMITISH "boost-1.88.0" CMAKE_ARGUMENTS -DBOOST_ENABLE_PYTHON=OFF -DBUILD_TESTING=OFF "-DBOOST_INCLUDE_LIBRARIES='graph\;multiprecision\;heap\;format'") +alp_setup_cmake_project(boost URL https://github.com/boostorg/boost.git COMMITISH "boost-1.88.0" CMAKE_ARGUMENTS -DBOOST_ENABLE_PYTHON=OFF -DBUILD_TESTING=OFF "-DBOOST_INCLUDE_LIBRARIES='graph\;multiprecision\;heap\;format\;logic'") find_package(Boost CONFIG REQUIRED) include(../cmake/SetupGDAL.cmake) From d5b955c4a471f7a5b22e6c8af5cc38d282da75b1 Mon Sep 17 00:00:00 2001 From: adam-ce <5292991+adam-ce@users.noreply.github.com> Date: Sun, 12 Jul 2026 01:08:02 +0200 Subject: [PATCH 03/27] Set CI test runtime library path --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2a823ca8..9036d421 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -125,6 +125,7 @@ jobs: - name: Run unit tests working-directory: ${{ github.workspace }}/build run: | + export LD_LIBRARY_PATH="${{ github.workspace }}/build/alp_external/proj/lib:${{ github.workspace }}/build/alp_external/proj/lib64:${{ github.workspace }}/build/alp_external/gdal/lib:${{ github.workspace }}/build/alp_external/gdal/lib64:${{ github.workspace }}/build/alp_external/opencv/lib:${{ github.workspace }}/build/alp_external/opencv/lib64:${{ github.workspace }}/build/alp_external/boost/lib:${{ github.workspace }}/build/alp_external/boost/lib64:${LD_LIBRARY_PATH:-}" ./unittests/unittests_terrainlib "~mesh::clip_on_bounds benchmark" ./unittests/unittests_tilebuilder ./unittests/unittests_meshbuilder From 9950ae482bcdfff690f345558b7c6ea63165404e Mon Sep 17 00:00:00 2001 From: adam-ce <5292991+adam-ce@users.noreply.github.com> Date: Sun, 12 Jul 2026 01:23:42 +0200 Subject: [PATCH 04/27] Set CI geodata runtime paths --- .github/workflows/ci.yml | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9036d421..8be3ffda 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -126,8 +126,13 @@ jobs: working-directory: ${{ github.workspace }}/build run: | export LD_LIBRARY_PATH="${{ github.workspace }}/build/alp_external/proj/lib:${{ github.workspace }}/build/alp_external/proj/lib64:${{ github.workspace }}/build/alp_external/gdal/lib:${{ github.workspace }}/build/alp_external/gdal/lib64:${{ github.workspace }}/build/alp_external/opencv/lib:${{ github.workspace }}/build/alp_external/opencv/lib64:${{ github.workspace }}/build/alp_external/boost/lib:${{ github.workspace }}/build/alp_external/boost/lib64:${LD_LIBRARY_PATH:-}" - ./unittests/unittests_terrainlib "~mesh::clip_on_bounds benchmark" - ./unittests/unittests_tilebuilder - ./unittests/unittests_meshbuilder - ./unittests/unittests_dagbuilder - ./unittests/unittests_sfmerger + export PROJ_DATA="${{ github.workspace }}/build/alp_external/proj/share/proj" + export PROJ_LIB="${PROJ_DATA}" + export GDAL_DATA="${{ github.workspace }}/build/alp_external/gdal/share/gdal" + status=0 + ./unittests/unittests_terrainlib "~mesh::clip_on_bounds benchmark" || status=$? + ./unittests/unittests_tilebuilder || status=$? + ./unittests/unittests_meshbuilder || status=$? + ./unittests/unittests_dagbuilder || status=$? + ./unittests/unittests_sfmerger || status=$? + exit $status From d9b4a28c7f82f3aa6124c13bf28642d5d3422657 Mon Sep 17 00:00:00 2001 From: adam-ce <5292991+adam-ce@users.noreply.github.com> Date: Sun, 12 Jul 2026 02:08:52 +0200 Subject: [PATCH 05/27] Avoid CI library path setup --- .github/workflows/ci.yml | 1 - cmake/SetupGDAL.cmake | 2 +- src/terrainlib/Dataset.cpp | 6 ++++++ src/terrainlib/Dataset.h | 6 +++++- unittests/tilebuilder/dataset_reading.cpp | 20 ++++++++++++++++++++ 5 files changed, 32 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8be3ffda..d7d4e0b8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -125,7 +125,6 @@ jobs: - name: Run unit tests working-directory: ${{ github.workspace }}/build run: | - export LD_LIBRARY_PATH="${{ github.workspace }}/build/alp_external/proj/lib:${{ github.workspace }}/build/alp_external/proj/lib64:${{ github.workspace }}/build/alp_external/gdal/lib:${{ github.workspace }}/build/alp_external/gdal/lib64:${{ github.workspace }}/build/alp_external/opencv/lib:${{ github.workspace }}/build/alp_external/opencv/lib64:${{ github.workspace }}/build/alp_external/boost/lib:${{ github.workspace }}/build/alp_external/boost/lib64:${LD_LIBRARY_PATH:-}" export PROJ_DATA="${{ github.workspace }}/build/alp_external/proj/share/proj" export PROJ_LIB="${PROJ_DATA}" export GDAL_DATA="${{ github.workspace }}/build/alp_external/gdal/share/gdal" diff --git a/cmake/SetupGDAL.cmake b/cmake/SetupGDAL.cmake index b16081ba..c1e2185c 100644 --- a/cmake/SetupGDAL.cmake +++ b/cmake/SetupGDAL.cmake @@ -46,9 +46,9 @@ function(alp_setup_gdal) -DBUILD_CSHARP_BINDINGS=OFF -DGDAL_USE_ICONV=OFF -DGDAL_USE_EXTERNAL_LIBS=OFF + "-DCMAKE_INSTALL_RPATH=\$ORIGIN/../../proj/lib" ) find_package(GDAL CONFIG REQUIRED) set(CMAKE_PREFIX_PATH "${CMAKE_PREFIX_PATH}" PARENT_SCOPE) endfunction() - diff --git a/src/terrainlib/Dataset.cpp b/src/terrainlib/Dataset.cpp index fce50853..fbd77975 100644 --- a/src/terrainlib/Dataset.cpp +++ b/src/terrainlib/Dataset.cpp @@ -38,6 +38,12 @@ static GDALDataset *open_gdal_dataset(const std::filesystem::path &path, unsigne return static_cast(GDALOpenEx(path_str.c_str(), flags, nullptr, nullptr, nullptr)); } +void GdalDatasetDeleter::operator()(GDALDataset *dataset) const { + if (dataset) { + GDALClose(dataset); + } +} + std::optional Dataset::open_raster(std::filesystem::path path) { if (GDALDataset *dataset = open_gdal_dataset(path, GDAL_OF_RASTER)) { return std::optional(std::move(Dataset(path, dataset))); diff --git a/src/terrainlib/Dataset.h b/src/terrainlib/Dataset.h index 0e9c96e7..5f31df4d 100644 --- a/src/terrainlib/Dataset.h +++ b/src/terrainlib/Dataset.h @@ -30,6 +30,10 @@ class GDALDataset; class OGRSpatialReference; +struct GdalDatasetDeleter { + void operator()(GDALDataset *dataset) const; +}; + class Dataset { public: Dataset(std::filesystem::path path); @@ -66,6 +70,6 @@ class Dataset { private: Dataset(const std::filesystem::path path, GDALDataset *dataset); - std::unique_ptr m_gdal_dataset; + std::unique_ptr m_gdal_dataset; std::optional m_path; }; diff --git a/unittests/tilebuilder/dataset_reading.cpp b/unittests/tilebuilder/dataset_reading.cpp index fabbf5e2..e4c0c37c 100644 --- a/unittests/tilebuilder/dataset_reading.cpp +++ b/unittests/tilebuilder/dataset_reading.cpp @@ -18,6 +18,7 @@ *****************************************************************************/ #include +#include #include #include #include @@ -30,6 +31,22 @@ using namespace radix; +namespace { +void require_projection_available(const Dataset& dataset, const OGRSpatialReference& target_srs) +{ + const auto dataset_srs = dataset.srs(); + const auto source_bounds = dataset.bounds(); + + std::shared_ptr transform; + REQUIRE_NOTHROW(transform = srs::transformation(dataset_srs, target_srs)); + REQUIRE(transform != nullptr); + + std::array xs = { (source_bounds.min.x + source_bounds.max.x) / 2.0 }; + std::array ys = { (source_bounds.min.y + source_bounds.max.y) / 2.0 }; + REQUIRE(transform->Transform(static_cast(xs.size()), xs.data(), ys.data())); +} +} + TEST_CASE("reading") { const std::vector at100m = { @@ -97,6 +114,7 @@ TEST_CASE("reading") const auto srs_bounds = srs::nonExactBoundsTransform(geodetic_bounds, geodetic_srs, srs); + require_projection_available(*dataset, srs); const DatasetReader reader(dataset, srs, 1); if (ATB_UNITTESTS_DEBUG_IMAGES) { const auto heights = reader.read(srs_bounds, 1000, 1000); @@ -148,6 +166,7 @@ TEST_CASE("reading") auto [test_name, datasets, ref_bounds, render_width, render_height, max_abs_diff, max_mse] = test; const auto ref_dataset = Dataset::open_shared_raster(ATB_TEST_DATA_DIR + std::string(datasets.front())).value(); + require_projection_available(*ref_dataset, geodetic_srs); const auto ref_reader = DatasetReader(ref_dataset, geodetic_srs, 1); const auto ref_heights = ref_reader.read(ref_bounds, render_width, render_height); if (ATB_UNITTESTS_DEBUG_IMAGES) @@ -155,6 +174,7 @@ TEST_CASE("reading") for (std::string dataset_name : datasets) { const auto dataset = Dataset::open_shared_raster(ATB_TEST_DATA_DIR + std::string(dataset_name)).value(); + require_projection_available(*dataset, geodetic_srs); const auto reader = DatasetReader(dataset, geodetic_srs, 1); const auto heights = reader.read(ref_bounds, render_width, render_height); From dbeb24eddb1bb5fb572d03cfab83ce57aec3d4d4 Mon Sep 17 00:00:00 2001 From: adam-ce <5292991+adam-ce@users.noreply.github.com> Date: Sun, 12 Jul 2026 11:47:04 +0200 Subject: [PATCH 06/27] Test CI with newer compiler matrix --- .github/workflows/ci.yml | 55 ++++++++++++++++++++++++++++++++-------- 1 file changed, 44 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d7d4e0b8..720c1f09 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,13 +6,35 @@ on: branches: [ "main", "tile-downloader" ] env: - BUILD_TYPE: Release CCACHE_DIR: ${{ github.workspace }}/.ccache CCACHE_MAXSIZE: 1G jobs: build-and-test: - runs-on: ubuntu-latest + name: ${{ matrix.name }} + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + include: + - name: gcc-14-normal + cc: gcc-14 + cxx: g++-14 + build_type: Release + sanitizer: normal + cmake_flags: "" + - name: clang-18-asan + cc: clang-18 + cxx: clang++-18 + build_type: RelWithDebInfo + sanitizer: asan + cmake_flags: -DALP_ENABLE_ADDRESS_SANITIZER=ON + - name: clang-23-tsan + cc: clang-23 + cxx: clang++-23 + build_type: RelWithDebInfo + sanitizer: tsan + cmake_flags: -DALP_ENABLE_THREAD_SANITIZER=ON steps: - uses: actions/checkout@v4 @@ -46,6 +68,14 @@ jobs: xorg-dev \ zlib1g-dev + - name: Install LLVM 23 + if: matrix.sanitizer == 'tsan' + run: | + wget -qO /tmp/llvm.sh https://apt.llvm.org/llvm.sh + chmod +x /tmp/llvm.sh + sudo /tmp/llvm.sh 23 + sudo apt-get install -y --no-install-recommends libclang-rt-23-dev + - name: Restore installed CMake dependencies id: cmake_dependency_cache uses: actions/cache/restore@v4 @@ -56,19 +86,19 @@ jobs: build/alp_external/gdal build/alp_external/cgal build/alp_external/opencv - key: ${{ runner.os }}-alp-installs-${{ env.BUILD_TYPE }}-${{ hashFiles('CMakeLists.txt', 'src/CMakeLists.txt', 'cmake/*.cmake') }} + key: ${{ runner.os }}-alp-installs-${{ matrix.name }}-${{ matrix.build_type }}-${{ matrix.sanitizer }}-${{ hashFiles('CMakeLists.txt', 'src/CMakeLists.txt', 'cmake/*.cmake') }} restore-keys: | - ${{ runner.os }}-alp-installs-${{ env.BUILD_TYPE }}- + ${{ runner.os }}-alp-installs-${{ matrix.name }}-${{ matrix.build_type }}-${{ matrix.sanitizer }}- - name: Restore ccache id: ccache_cache uses: actions/cache/restore@v4 with: path: ${{ env.CCACHE_DIR }} - key: ${{ runner.os }}-ccache-${{ github.ref_name }}-${{ github.run_id }}-${{ github.run_attempt }} + key: ${{ runner.os }}-ccache-${{ matrix.name }}-${{ matrix.sanitizer }}-${{ github.ref_name }}-${{ github.run_id }}-${{ github.run_attempt }} restore-keys: | - ${{ runner.os }}-ccache-${{ github.ref_name }}- - ${{ runner.os }}-ccache- + ${{ runner.os }}-ccache-${{ matrix.name }}-${{ matrix.sanitizer }}-${{ github.ref_name }}- + ${{ runner.os }}-ccache-${{ matrix.name }}-${{ matrix.sanitizer }}- - name: Configure ccache run: | @@ -81,9 +111,12 @@ jobs: -S "${{ github.workspace }}" -B "${{ github.workspace }}/build" -G Ninja - -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} + -DCMAKE_C_COMPILER=${{ matrix.cc }} + -DCMAKE_CXX_COMPILER=${{ matrix.cxx }} -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + ${{ matrix.cmake_flags }} - name: Save installed CMake dependencies if: steps.cmake_dependency_cache.outputs.cache-hit != 'true' @@ -95,10 +128,10 @@ jobs: build/alp_external/gdal build/alp_external/cgal build/alp_external/opencv - key: ${{ runner.os }}-alp-installs-${{ env.BUILD_TYPE }}-${{ hashFiles('CMakeLists.txt', 'src/CMakeLists.txt', 'cmake/*.cmake') }} + key: ${{ runner.os }}-alp-installs-${{ matrix.name }}-${{ matrix.build_type }}-${{ matrix.sanitizer }}-${{ hashFiles('CMakeLists.txt', 'src/CMakeLists.txt', 'cmake/*.cmake') }} - name: Build all targets - run: cmake --build "${{ github.workspace }}/build" --config ${{ env.BUILD_TYPE }} --parallel --target all + run: cmake --build "${{ github.workspace }}/build" --config ${{ matrix.build_type }} --parallel --target all - name: ccache stats if: always() @@ -109,7 +142,7 @@ jobs: uses: actions/cache/save@v4 with: path: ${{ env.CCACHE_DIR }} - key: ${{ runner.os }}-ccache-${{ github.ref_name }}-${{ github.run_id }}-${{ github.run_attempt }} + key: ${{ runner.os }}-ccache-${{ matrix.name }}-${{ matrix.sanitizer }}-${{ github.ref_name }}-${{ github.run_id }}-${{ github.run_attempt }} - name: Cache unit test data id: terrain_builder_unittest_data From d30653aeec17f527f54be1ce842945f4b7855b00 Mon Sep 17 00:00:00 2001 From: adam-ce <5292991+adam-ce@users.noreply.github.com> Date: Sun, 12 Jul 2026 14:06:16 +0200 Subject: [PATCH 07/27] Update fmt to 12.2.0 --- src/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6771146b..ea6628e6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -58,7 +58,7 @@ find_package(TBB REQUIRED) find_package(ZLIB REQUIRED) find_package(CURL REQUIRED) -alp_add_git_repository(fmt URL https://github.com/fmtlib/fmt COMMITISH 10.2.1) +alp_add_git_repository(fmt URL https://github.com/fmtlib/fmt COMMITISH 12.2.0) alp_add_git_repository(cli11 URL https://github.com/CLIUtils/CLI11.git COMMITISH v2.6.1) alp_add_git_repository(eigen URL https://gitlab.com/libeigen/eigen.git COMMITISH 3.4.0 DO_NOT_ADD_SUBPROJECT) From 05ba0a8e7d5a6ad12eaf3c8eab05e3c9b851266a Mon Sep 17 00:00:00 2001 From: adam-ce <5292991+adam-ce@users.noreply.github.com> Date: Sun, 12 Jul 2026 14:16:48 +0200 Subject: [PATCH 08/27] Update spdlog to v1.17.0 --- src/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ea6628e6..ba22589f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -95,7 +95,7 @@ add_library(tl_expected INTERFACE) target_include_directories(tl_expected INTERFACE SYSTEM ${tl_expected_SOURCE_DIR}/include) set(SPDLOG_FMT_EXTERNAL ON CACHE BOOL "SPDLOG_FMT_EXTERNAL" FORCE) -alp_add_git_repository(spdlog URL https://github.com/gabime/spdlog.git COMMITISH v1.13.0) +alp_add_git_repository(spdlog URL https://github.com/gabime/spdlog.git COMMITISH v1.17.0) add_compile_definitions(SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_TRACE) alp_add_git_repository(zpp_bits URL https://github.com/eyalz800/zpp_bits.git COMMITISH v4.5 DO_NOT_ADD_SUBPROJECT) From 8eeb7c1ebd3fd9348a38175b5af352502d926720 Mon Sep 17 00:00:00 2001 From: adam-ce <5292991+adam-ce@users.noreply.github.com> Date: Sun, 12 Jul 2026 18:07:41 +0200 Subject: [PATCH 09/27] Fix fmt 12 formatter compatibility --- src/terrainlib/io/Error.h | 2 +- src/terrainlib/mesh/io/error.h | 4 ++-- src/terrainlib/octree/Id.h | 2 +- src/terrainlib/octree/NodeStatus.h | 2 +- src/terrainlib/octree/NodeStatusOrMissing.h | 4 ++-- unittests/terrainlib/mesh_holes.cpp | 1 + 6 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/terrainlib/io/Error.h b/src/terrainlib/io/Error.h index be85f515..831c9842 100644 --- a/src/terrainlib/io/Error.h +++ b/src/terrainlib/io/Error.h @@ -54,7 +54,7 @@ struct fmt::formatter { } template - auto format(const io::Error &error, FormatContext &ctx) { + auto format(const io::Error &error, FormatContext &ctx) const { const char *name = "Unknown"; switch (error) { diff --git a/src/terrainlib/mesh/io/error.h b/src/terrainlib/mesh/io/error.h index 4dfc9cd0..40df1267 100644 --- a/src/terrainlib/mesh/io/error.h +++ b/src/terrainlib/mesh/io/error.h @@ -111,7 +111,7 @@ struct fmt::formatter { } template - auto format(const mesh::io::SaveMeshError &error, FormatContext &ctx) { + auto format(const mesh::io::SaveMeshError &error, FormatContext &ctx) const { return fmt::format_to(ctx.out(), "{}", error.description()); } }; @@ -124,7 +124,7 @@ struct fmt::formatter { } template - auto format(const mesh::io::LoadMeshError &error, FormatContext &ctx) { + auto format(const mesh::io::LoadMeshError &error, FormatContext &ctx) const { return fmt::format_to(ctx.out(), "{}", error.description()); } }; diff --git a/src/terrainlib/octree/Id.h b/src/terrainlib/octree/Id.h index b73b0d71..1ae28794 100644 --- a/src/terrainlib/octree/Id.h +++ b/src/terrainlib/octree/Id.h @@ -283,7 +283,7 @@ struct fmt::formatter { // Format the Id object. template - auto format(const octree::Id &id, FormatContext &ctx) { + auto format(const octree::Id &id, FormatContext &ctx) const { return fmt::format_to( ctx.out(), "Id(level={}, coords=({}, {}, {}), index={})", diff --git a/src/terrainlib/octree/NodeStatus.h b/src/terrainlib/octree/NodeStatus.h index 9187bc97..9e00d97c 100644 --- a/src/terrainlib/octree/NodeStatus.h +++ b/src/terrainlib/octree/NodeStatus.h @@ -56,7 +56,7 @@ struct fmt::formatter { } template - auto format(const octree::NodeStatus &status, FormatContext &ctx) { + auto format(const octree::NodeStatus &status, FormatContext &ctx) const { switch (status) { case octree::NodeStatus::Leaf: return fmt::format_to(ctx.out(), "Leaf"); diff --git a/src/terrainlib/octree/NodeStatusOrMissing.h b/src/terrainlib/octree/NodeStatusOrMissing.h index 803ebc0c..227fc121 100644 --- a/src/terrainlib/octree/NodeStatusOrMissing.h +++ b/src/terrainlib/octree/NodeStatusOrMissing.h @@ -84,7 +84,7 @@ struct fmt::formatter { } template - auto format(const octree::NodeStatusOrMissing &status, FormatContext &ctx) { + auto format(const octree::NodeStatusOrMissing &status, FormatContext &ctx) const { switch (status) { case octree::NodeStatusOrMissing::Leaf: return fmt::format_to(ctx.out(), "Leaf"); @@ -140,4 +140,4 @@ consteval bool _verify_enum() { } static_assert(_verify_enum()); } -} \ No newline at end of file +} diff --git a/unittests/terrainlib/mesh_holes.cpp b/unittests/terrainlib/mesh_holes.cpp index e9d158d0..76805864 100644 --- a/unittests/terrainlib/mesh_holes.cpp +++ b/unittests/terrainlib/mesh_holes.cpp @@ -1,6 +1,7 @@ #include #include +#include #include #include "../catch2_helpers.h" From 2d918023e341cdd0156a7943f2fcea5124e5006c Mon Sep 17 00:00:00 2001 From: adam-ce <5292991+adam-ce@users.noreply.github.com> Date: Sun, 12 Jul 2026 23:48:09 +0200 Subject: [PATCH 10/27] Use Clang 23 for ASAN CI --- .github/workflows/ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 720c1f09..6f14a5aa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,9 +23,9 @@ jobs: build_type: Release sanitizer: normal cmake_flags: "" - - name: clang-18-asan - cc: clang-18 - cxx: clang++-18 + - name: clang-23-asan + cc: clang-23 + cxx: clang++-23 build_type: RelWithDebInfo sanitizer: asan cmake_flags: -DALP_ENABLE_ADDRESS_SANITIZER=ON @@ -69,7 +69,7 @@ jobs: zlib1g-dev - name: Install LLVM 23 - if: matrix.sanitizer == 'tsan' + if: matrix.cxx == 'clang++-23' run: | wget -qO /tmp/llvm.sh https://apt.llvm.org/llvm.sh chmod +x /tmp/llvm.sh From da1ac463cf1b9893171f773746c095512f8503c3 Mon Sep 17 00:00:00 2001 From: adam-ce <5292991+adam-ce@users.noreply.github.com> Date: Mon, 13 Jul 2026 13:36:54 +0200 Subject: [PATCH 11/27] compilation fix for zpp_bits: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit zpp_bits probes custom serializers with an internal zpp::bits::traits::visitor. That visitor’s archive(...) returns a size_t member count, not zpp::bits::errc. The custom deserializers assume archive(...) returns an error result: auto result = archive(...); if (failure(result)) { ... } So during zpp’s probe, failure(result) becomes failure(size_t), which is invalid. --- src/dag_builder/encoded.h | 80 ++++++++++++++++++++------------ src/terrainlib/mesh/io/texture.h | 14 ++++-- src/terrainlib/octree/Id.h | 22 +++++---- 3 files changed, 74 insertions(+), 42 deletions(-) diff --git a/src/dag_builder/encoded.h b/src/dag_builder/encoded.h index c1ff780c..8e10878a 100644 --- a/src/dag_builder/encoded.h +++ b/src/dag_builder/encoded.h @@ -24,42 +24,52 @@ template auto serialize(Archive &archive, const TextureSet &textures) { size_t size = textures.size(); auto result = archive(size); - if (failure(result)) { + using Result = std::remove_cvref_t; + if constexpr (!std::is_same_v && !std::is_same_v) { return result; - } - - for (const auto &texture : textures) { - const mesh::io::ImageAndExt item{texture, ".jpeg"}; - result = archive(item); + } else { if (failure(result)) { return result; } - } - return result; + for (const auto &texture : textures) { + const mesh::io::ImageAndExt item{texture, ".jpeg"}; + result = archive(item); + if (failure(result)) { + return result; + } + } + + return result; + } } template auto serialize(Archive &archive, TextureSet &textures) { size_t size; auto result = archive(size); - if (failure(result)) { + using Result = std::remove_cvref_t; + if constexpr (!std::is_same_v && !std::is_same_v) { return result; - } - - std::vector images; - images.reserve(size); - for (size_t i = 0; i < size; i++) { - mesh::io::ImageAndExt item; - result = archive(item); + } else { if (failure(result)) { return result; } - images.push_back(item.image); - } - textures = TextureSet(images); + std::vector images; + images.reserve(size); + for (size_t i = 0; i < size; i++) { + mesh::io::ImageAndExt item; + result = archive(item); + if (failure(result)) { + return result; + } - return result; + images.push_back(item.image); + } + textures = TextureSet(images); + + return result; + } } template @@ -104,15 +114,20 @@ auto serialize(Archive &archive, Cluster &cluster) { cluster.texture_id, cluster.absolute_error); - if (failure(result)) { + using Result = std::remove_cvref_t; + if constexpr (!std::is_same_v && !std::is_same_v) { return result; - } + } else { + if (failure(result)) { + return result; + } - meshopt::decode_index_buffer(cluster.local_triangles, triangle_count, encoded_triangles); - meshopt::decode_vertex_buffer(cluster.vertex_indices, vertex_count, encoded_vertex_indices); - meshopt::decode_vertex_buffer(cluster.uvs, uv_count, encoded_uvs); + meshopt::decode_index_buffer(cluster.local_triangles, triangle_count, encoded_triangles); + meshopt::decode_vertex_buffer(cluster.vertex_indices, vertex_count, encoded_vertex_indices); + meshopt::decode_vertex_buffer(cluster.uvs, uv_count, encoded_uvs); - return result; + return result; + } } template @@ -138,13 +153,18 @@ auto serialize(Archive &archive, Clustering &clustering) { clustering.clusters, clustering.textures); - if (failure(result)) { + using Result = std::remove_cvref_t; + if constexpr (!std::is_same_v && !std::is_same_v) { return result; - } + } else { + if (failure(result)) { + return result; + } - meshopt::decode_vertex_buffer(clustering.positions, vertex_count, encoded_positions); + meshopt::decode_vertex_buffer(clustering.positions, vertex_count, encoded_positions); - return result; + return result; + } } } // namespace zpp::bits diff --git a/src/terrainlib/mesh/io/texture.h b/src/terrainlib/mesh/io/texture.h index 7edb5582..0d1d8113 100644 --- a/src/terrainlib/mesh/io/texture.h +++ b/src/terrainlib/mesh/io/texture.h @@ -3,6 +3,7 @@ #include #include #include +#include #include @@ -36,12 +37,17 @@ auto serialize(Archive &archive, mesh::io::ImageAndExt &item) { std::vector encoded; auto result = archive(item.ext, encoded); - if (failure(result)) { + using Result = std::remove_cvref_t; + if constexpr (!std::is_same_v && !std::is_same_v) { return result; - } + } else { + if (failure(result)) { + return result; + } - item.image = mesh::io::read_texture_from_encoded_bytes(encoded); - return result; + item.image = mesh::io::read_texture_from_encoded_bytes(encoded); + return result; + } } } // namespace zpp::bits diff --git a/src/terrainlib/octree/Id.h b/src/terrainlib/octree/Id.h index 1ae28794..a593be3b 100644 --- a/src/terrainlib/octree/Id.h +++ b/src/terrainlib/octree/Id.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -323,17 +324,22 @@ constexpr auto serialize(auto &archive, octree::Id &id) { octree::Id::Level level; octree::Id::Index index; auto result = archive(level, index); - if (failure(result)) { + using Result = std::remove_cvref_t; + if constexpr (!std::is_same_v && !std::is_same_v) { return result; - } + } else { + if (failure(result)) { + return result; + } - auto maybe_id = octree::Id::try_make(level, index); - if (!maybe_id) { - return zpp::bits::errc(std::errc::bad_message); - } + auto maybe_id = octree::Id::try_make(level, index); + if (!maybe_id) { + return zpp::bits::errc(std::errc::bad_message); + } - id = *maybe_id; - return success(); + id = *maybe_id; + return success(); + } } constexpr auto serialize(auto &archive, const octree::Id &id) { return archive(id.level(), id.index_on_level()); From fa760ee36776db3a05b228ce4ab8b17341c67c38 Mon Sep 17 00:00:00 2001 From: adam-ce <5292991+adam-ce@users.noreply.github.com> Date: Mon, 13 Jul 2026 23:26:14 +0200 Subject: [PATCH 12/27] Pin zpp_bits before structured binding pack support --- src/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ba22589f..bcc8a64b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -98,7 +98,7 @@ set(SPDLOG_FMT_EXTERNAL ON CACHE BOOL "SPDLOG_FMT_EXTERNAL" FORCE) alp_add_git_repository(spdlog URL https://github.com/gabime/spdlog.git COMMITISH v1.17.0) add_compile_definitions(SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_TRACE) -alp_add_git_repository(zpp_bits URL https://github.com/eyalz800/zpp_bits.git COMMITISH v4.5 DO_NOT_ADD_SUBPROJECT) +alp_add_git_repository(zpp_bits URL https://github.com/eyalz800/zpp_bits.git COMMITISH v4.4.25 DO_NOT_ADD_SUBPROJECT) add_library(zpp_bits INTERFACE) target_include_directories(zpp_bits INTERFACE SYSTEM ${zpp_bits_SOURCE_DIR}) From 1f327a543f214476281188238cac07318cfa92b0 Mon Sep 17 00:00:00 2001 From: Adam <5292991+adam-ce@users.noreply.github.com> Date: Tue, 14 Jul 2026 01:26:10 +0200 Subject: [PATCH 13/27] Update CI workflow to trigger only on 'main' branch Removed 'tile-downloader' branch from pull request triggers. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6f14a5aa..5e6c2f86 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,7 +3,7 @@ name: CI on: push: pull_request: - branches: [ "main", "tile-downloader" ] + branches: [ "main" ] env: CCACHE_DIR: ${{ github.workspace }}/.ccache From 04df7e0068cec873c280b222db8ecc7e3375a575 Mon Sep 17 00:00:00 2001 From: adam-ce <5292991+adam-ce@users.noreply.github.com> Date: Tue, 14 Jul 2026 01:52:05 +0200 Subject: [PATCH 14/27] Update zpp_bits to v4.7.1 --- src/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index bcc8a64b..81ad1d09 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -98,7 +98,7 @@ set(SPDLOG_FMT_EXTERNAL ON CACHE BOOL "SPDLOG_FMT_EXTERNAL" FORCE) alp_add_git_repository(spdlog URL https://github.com/gabime/spdlog.git COMMITISH v1.17.0) add_compile_definitions(SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_TRACE) -alp_add_git_repository(zpp_bits URL https://github.com/eyalz800/zpp_bits.git COMMITISH v4.4.25 DO_NOT_ADD_SUBPROJECT) +alp_add_git_repository(zpp_bits URL https://github.com/eyalz800/zpp_bits.git COMMITISH v4.7.1 DO_NOT_ADD_SUBPROJECT) add_library(zpp_bits INTERFACE) target_include_directories(zpp_bits INTERFACE SYSTEM ${zpp_bits_SOURCE_DIR}) From 365e48b74fa7bf877ac0832624f67b2b4f9074ee Mon Sep 17 00:00:00 2001 From: adam-ce <5292991+adam-ce@users.noreply.github.com> Date: Tue, 14 Jul 2026 02:19:28 +0200 Subject: [PATCH 15/27] Allow zpp_bits access to octree Id --- src/terrainlib/octree/Id.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/terrainlib/octree/Id.h b/src/terrainlib/octree/Id.h index a593be3b..0594f500 100644 --- a/src/terrainlib/octree/Id.h +++ b/src/terrainlib/octree/Id.h @@ -14,6 +14,10 @@ #include "hash_utils.h" #include "int_math.h" +namespace zpp::bits { +struct access; +} + namespace octree { class Id { @@ -237,6 +241,8 @@ class Id { Level _level; Index _index; + friend zpp::bits::access; + [[nodiscard]] constexpr Id _child(const uint32_t child_index) const { DEBUG_ASSERT(child_index <= 7); return Id(this->level() + 1, (this->index_on_level() << 3) | child_index); From 16ca87830e49b1dc8800a2c471b7d05af3c28adc Mon Sep 17 00:00:00 2001 From: adam-ce <5292991+adam-ce@users.noreply.github.com> Date: Tue, 14 Jul 2026 07:31:29 +0200 Subject: [PATCH 16/27] Fix mesh simplify CLI parse overloads --- src/mesh_simplify/cli.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mesh_simplify/cli.h b/src/mesh_simplify/cli.h index 147290a1..ea102601 100644 --- a/src/mesh_simplify/cli.h +++ b/src/mesh_simplify/cli.h @@ -42,11 +42,11 @@ namespace cli { } template - Args cli::parse(const std::span args) { + Args parse(const std::span args) { return _parse(args); } template - Args cli::parse(const std::span args) { + Args parse(const std::span args) { return _parse(args); } } From d24fd54f6c223ec51a2077ebf00e9828f8fa04b3 Mon Sep 17 00:00:00 2001 From: adam-ce <5292991+adam-ce@users.noreply.github.com> Date: Tue, 14 Jul 2026 13:41:54 +0200 Subject: [PATCH 17/27] Fix sf merger visitor template dispatch --- src/sf_merger/merge.h | 2 +- src/sf_merger/merge/visitor/Masked.h | 22 +++++++++++----------- src/sf_merger/merge/visitor/Simple.h | 8 ++++---- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/sf_merger/merge.h b/src/sf_merger/merge.h index 26a674be..ea3a1b18 100644 --- a/src/sf_merger/merge.h +++ b/src/sf_merger/merge.h @@ -124,7 +124,7 @@ class Merger { case get_index(Status::left_status, Status::right_status): { \ merge::NodeData left_node(id, this->_left); \ merge::NodeData right_node(id, this->_right); \ - return this->_visitor.template visit(id, left_node, right_node, ctx); \ + return this->_visitor.template visit(id, left_node, right_node, ctx); \ } ALP_GENERATE_CASE(Missing, Missing); diff --git a/src/sf_merger/merge/visitor/Masked.h b/src/sf_merger/merge/visitor/Masked.h index a577fde0..04580b03 100644 --- a/src/sf_merger/merge/visitor/Masked.h +++ b/src/sf_merger/merge/visitor/Masked.h @@ -50,11 +50,11 @@ class Masked { const NodeData &left, const NodeData &right, const Context &ctx) { - if constexpr (left.status() == Status::Inner || right.status() == Status::Inner) { + if constexpr (LeftStatus == Status::Inner || RightStatus == Status::Inner) { UNREACHABLE(); } - if constexpr (left.status() == Status::Missing && right.status() == Status::Missing) { + if constexpr (LeftStatus == Status::Missing && RightStatus == Status::Missing) { DEBUG_ASSERT(!(ctx.has_left_parent && ctx.has_right_parent)); if (!ctx.has_left_parent && !ctx.has_right_parent) { return Ignore{}; @@ -62,7 +62,7 @@ class Masked { } // If the parent mask is empty, we can just return whatevers on the left - if constexpr (left.status() != Status::Missing) { + if constexpr (LeftStatus != Status::Missing) { if (ctx.mask.mesh.is_empty()) { return Unchanged{Source::Left}; } @@ -73,20 +73,20 @@ class Masked { MeshMask mask(mesh::clip_on_bounds_and_cap(ctx.mask.mesh, bounds)); // Same when the current mask is empty - if constexpr (left.status() != Status::Missing) { + if constexpr (LeftStatus != Status::Missing) { if (mask.mesh.is_empty()) { return Unchanged{Source::Left}; } } // If we have two leaf nodes we can directly merge them. - if constexpr (left.status() == Status::Leaf && right.status() == Status::Leaf) { + if constexpr (LeftStatus == Status::Leaf && RightStatus == Status::Leaf) { return this->merge_meshes(left.mesh(), right.mesh(), true, true, mask); } // If we have a left leaf we either directly return it (after clipping) // or merge if right has a non-missing parent - if constexpr (left.status() == Status::Leaf && right.status() == Status::Missing) { + if constexpr (LeftStatus == Status::Leaf && RightStatus == Status::Missing) { if (ctx.has_right_parent) { return this->merge_meshes(left.mesh(), right.mesh().value(), true, false, mask); } @@ -104,7 +104,7 @@ class Masked { // If we have a right leaf we either directly return it (after clipping) // or merge if left has a non-missing parent - if constexpr (left.status() == Status::Missing && right.status() == Status::Leaf) { + if constexpr (LeftStatus == Status::Missing && RightStatus == Status::Leaf) { if (ctx.has_left_parent) { return this->merge_meshes(left.mesh().value(), right.mesh(), false, true, mask); } @@ -120,7 +120,7 @@ class Masked { } } - if constexpr (left.status() == Status::Missing && right.status() == Status::Missing) { + if constexpr (LeftStatus == Status::Missing && RightStatus == Status::Missing) { DEBUG_ASSERT(ctx.has_left_parent || ctx.has_right_parent); if (ctx.has_left_parent) { auto result = clip_on_mask(left.mesh().value(), mask, false); @@ -140,12 +140,12 @@ class Masked { } } - if constexpr (left.status() == Status::Virtual || right.status() == Status::Virtual) { + if constexpr (LeftStatus == Status::Virtual || RightStatus == Status::Virtual) { return Recurse{ Context{ .mask = mask, - .has_left_parent = ctx.has_left_parent || left.status() == Status::Leaf, - .has_right_parent = ctx.has_right_parent || right.status() == Status::Leaf}}; + .has_left_parent = ctx.has_left_parent || LeftStatus == Status::Leaf, + .has_right_parent = ctx.has_right_parent || RightStatus == Status::Leaf}}; } UNREACHABLE(); diff --git a/src/sf_merger/merge/visitor/Simple.h b/src/sf_merger/merge/visitor/Simple.h index 9dcec6a1..531cd686 100644 --- a/src/sf_merger/merge/visitor/Simple.h +++ b/src/sf_merger/merge/visitor/Simple.h @@ -26,15 +26,15 @@ class Simple { const NodeData &right, const Context& ctx) { - if constexpr (right.status() == Status::Missing) { + if constexpr (RightStatus == Status::Missing) { return Unchanged{Source::Left}; } - if constexpr (left.status() == Status::Missing) { + if constexpr (LeftStatus == Status::Missing) { return Unchanged{Source::Right}; } - if constexpr (left.status() == Status::Leaf && right.status() == Status::Leaf) { + if constexpr (LeftStatus == Status::Leaf && RightStatus == Status::Leaf) { return merge_meshes(right.mesh(), left.mesh()); } @@ -62,4 +62,4 @@ class Simple { static_assert(Visitor); -} \ No newline at end of file +} From d01ac4224938090ee938d96d7a2b8a6469b7e2a0 Mon Sep 17 00:00:00 2001 From: adam-ce <5292991+adam-ce@users.noreply.github.com> Date: Tue, 14 Jul 2026 13:55:09 +0200 Subject: [PATCH 18/27] Fix Buffer constructor declaration --- src/browser/core/Buffer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/core/Buffer.cpp b/src/browser/core/Buffer.cpp index 84c82d4c..1de7cffc 100644 --- a/src/browser/core/Buffer.cpp +++ b/src/browser/core/Buffer.cpp @@ -1,7 +1,7 @@ #include "Buffer.h" #include -Buffer::Buffer(GLenum target = GL_ARRAY_BUFFER, GLenum usage = GL_STATIC_DRAW) : m_target(target), m_usage(usage) { +Buffer::Buffer(GLenum target, GLenum usage) : m_target(target), m_usage(usage) { glCreateBuffers(1, &m_handle); } From 6a96b29b67f41f90753675b4e8a15b672669a613 Mon Sep 17 00:00:00 2001 From: adam-ce <5292991+adam-ce@users.noreply.github.com> Date: Wed, 15 Jul 2026 01:20:20 +0200 Subject: [PATCH 19/27] Move sanitizer options to top-level CMake --- CMakeLists.txt | 36 ++++++++++++++++++++++++++++++++++++ src/CMakeLists.txt | 36 ------------------------------------ 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 24882125..dcf3b2b4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,6 +3,42 @@ cmake_minimum_required(VERSION 3.21) project(alpine-terrain-builder) option(ALP_UNITTESTS "include unit test targets in the buildsystem" ON) +# ASAN & TSAN +option(ALP_ENABLE_THREAD_SANITIZER "compiles with thread sanitizer enabled" OFF) +option(ALP_ENABLE_ADDRESS_SANITIZER "compiles with address sanitizer enabled" OFF) + +if(ALP_ENABLE_ADDRESS_SANITIZER) + if(ALP_ENABLE_THREAD_SANITIZER) + message(FATAL_ERROR "Cannot enable both AddressSanitizer and ThreadSanitizer") + endif() +endif() + +if(ALP_ENABLE_ADDRESS_SANITIZER) + message(STATUS "AddressSanitizer enabled") + if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + set(ASAN_FLAGS -fsanitize=address -fsanitize=leak -fsanitize=undefined -fno-omit-frame-pointer -g) + add_compile_options(${ASAN_FLAGS}) + add_link_options(${ASAN_FLAGS}) + else() + message(WARNING "ALP_ENABLE_ADDRESS_SANITIZER set but compiler is not GCC/Clang; ignoring") + endif() +endif() + +if(ALP_ENABLE_THREAD_SANITIZER) + if(CMAKE_BUILD_TYPE STREQUAL "Release") + message(WARNING "ThreadSanitizer is not recommended with Release builds. " + "Use Debug or RelWithDebInfo for accurate race detection.") + endif() + message(STATUS "ThreadSanitizer enabled") + if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + set(TSAN_FLAGS -fsanitize=thread -fno-omit-frame-pointer -fno-optimize-sibling-calls -g) + add_compile_options(${TSAN_FLAGS}) + add_link_options(${TSAN_FLAGS}) + else() + message(WARNING "ALP_ENABLE_THREAD_SANITIZER set but compiler is not GCC/Clang; ignoring") + endif() +endif() + include(cmake/AddRepo.cmake) include(cmake/SetupCMakeProject.cmake) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 81ad1d09..f95557d6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -11,42 +11,6 @@ endif() option(ALP_ENABLE_OVERVIEW_READING "enable GDAL overview reading (broken with newer GDAL versions)" OFF) -# ASAN & TSAN -option(ALP_ENABLE_THREAD_SANITIZER "compiles with thread sanitizer enabled" OFF) -option(ALP_ENABLE_ADDRESS_SANITIZER "compiles with address sanitizer enabled" OFF) - -if(ALP_ENABLE_ADDRESS_SANITIZER) - if(ALP_ENABLE_THREAD_SANITIZER) - message(FATAL_ERROR "Cannot enable both AddressSanitizer and ThreadSanitizer") - endif() -endif() - -if(ALP_ENABLE_ADDRESS_SANITIZER) - message(STATUS "AddressSanitizer enabled") - if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") - set(ASAN_FLAGS -fsanitize=address -fsanitize=leak -fsanitize=undefined -fno-omit-frame-pointer -g) - add_compile_options(${ASAN_FLAGS}) - add_link_options(${ASAN_FLAGS}) - else() - message(WARNING "ALP_ENABLE_ADDRESS_SANITIZER set but compiler is not GCC/Clang; ignoring") - endif() -endif() - -if(ALP_ENABLE_THREAD_SANITIZER) - if(CMAKE_BUILD_TYPE STREQUAL "Release") - message(WARNING "ThreadSanitizer is not recommended with Release builds. " - "Use Debug or RelWithDebInfo for accurate race detection.") - endif() - message(STATUS "ThreadSanitizer enabled") - if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") - set(TSAN_FLAGS -fsanitize=thread -fno-omit-frame-pointer -fno-optimize-sibling-calls -g) - add_compile_options(${TSAN_FLAGS}) - add_link_options(${TSAN_FLAGS}) - else() - message(WARNING "ALP_ENABLE_THREAD_SANITIZER set but compiler is not GCC/Clang; ignoring") - endif() -endif() - set(ALP_RELEASE_OPT_LEVEL "3" CACHE STRING "Optimization level (0=O0,1=O1,2=O2,3=O3)") if (CMAKE_BUILD_TYPE STREQUAL "Release") From 7455f47bee69472c95df3f2173d66e1f812c1831 Mon Sep 17 00:00:00 2001 From: adam-ce <5292991+adam-ce@users.noreply.github.com> Date: Wed, 15 Jul 2026 13:22:37 +0200 Subject: [PATCH 20/27] Build dependencies with ThreadSanitizer --- CMakeLists.txt | 12 ++++++------ cmake/SetupCMakeProject.cmake | 18 ++++++++++++++---- src/CMakeLists.txt | 8 +++++++- src/terrainlib/Dataset.cpp | 5 ++++- .../tilebuilder/parallel_tile_generator.cpp | 16 +++++++++------- 5 files changed, 40 insertions(+), 19 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index dcf3b2b4..f8cef9fe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,9 +16,9 @@ endif() if(ALP_ENABLE_ADDRESS_SANITIZER) message(STATUS "AddressSanitizer enabled") if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") - set(ASAN_FLAGS -fsanitize=address -fsanitize=leak -fsanitize=undefined -fno-omit-frame-pointer -g) - add_compile_options(${ASAN_FLAGS}) - add_link_options(${ASAN_FLAGS}) + set(ALP_SANITIZER_FLAGS -fsanitize=address -fsanitize=leak -fsanitize=undefined -fno-omit-frame-pointer -g) + add_compile_options(${ALP_SANITIZER_FLAGS}) + add_link_options(${ALP_SANITIZER_FLAGS}) else() message(WARNING "ALP_ENABLE_ADDRESS_SANITIZER set but compiler is not GCC/Clang; ignoring") endif() @@ -31,9 +31,9 @@ if(ALP_ENABLE_THREAD_SANITIZER) endif() message(STATUS "ThreadSanitizer enabled") if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") - set(TSAN_FLAGS -fsanitize=thread -fno-omit-frame-pointer -fno-optimize-sibling-calls -g) - add_compile_options(${TSAN_FLAGS}) - add_link_options(${TSAN_FLAGS}) + set(ALP_SANITIZER_FLAGS -fsanitize=thread -fno-omit-frame-pointer -fno-optimize-sibling-calls -g) + add_compile_options(${ALP_SANITIZER_FLAGS}) + add_link_options(${ALP_SANITIZER_FLAGS}) else() message(WARNING "ALP_ENABLE_THREAD_SANITIZER set but compiler is not GCC/Clang; ignoring") endif() diff --git a/cmake/SetupCMakeProject.cmake b/cmake/SetupCMakeProject.cmake index 2c2b94a0..09245677 100644 --- a/cmake/SetupCMakeProject.cmake +++ b/cmake/SetupCMakeProject.cmake @@ -23,11 +23,20 @@ endif() function(_alp_build_and_install NAME SRC_DIR BUILD_DIR INSTALL_DIR) message(STATUS "[alp] Configuring ${NAME}") + string(JOIN " " _alp_sanitizer_flags ${ALP_SANITIZER_FLAGS}) + execute_process( COMMAND ${CMAKE_COMMAND} -G ${CMAKE_GENERATOR} -S ${SRC_DIR} -B ${BUILD_DIR} + -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} + -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} + "-DCMAKE_C_FLAGS=${CMAKE_C_FLAGS} ${_alp_sanitizer_flags}" + "-DCMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS} ${_alp_sanitizer_flags}" + "-DCMAKE_EXE_LINKER_FLAGS=${CMAKE_EXE_LINKER_FLAGS} ${_alp_sanitizer_flags}" + "-DCMAKE_MODULE_LINKER_FLAGS=${CMAKE_MODULE_LINKER_FLAGS} ${_alp_sanitizer_flags}" + "-DCMAKE_SHARED_LINKER_FLAGS=${CMAKE_SHARED_LINKER_FLAGS} ${_alp_sanitizer_flags}" -DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH} -DCMAKE_INSTALL_PREFIX=${INSTALL_DIR} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} @@ -67,14 +76,16 @@ function(alp_setup_cmake_project arg_NAME) set(build_dir "${CMAKE_BINARY_DIR}/alp_external/${arg_NAME}_build") set(install_dir "${CMAKE_BINARY_DIR}/alp_external/${arg_NAME}") set(stamp_file "${install_dir}/.alp_install_signature") + set(version_signature "${arg_COMMITISH}${arg_CMAKE_ARGUMENTS}${ALP_SANITIZER_FLAGS}") set(cache_signature "URL=${arg_URL} COMMITISH=${arg_COMMITISH} BUILD_TYPE=${CMAKE_BUILD_TYPE} SYSTEM=${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR} CXX=${CMAKE_CXX_COMPILER_ID}-${CMAKE_CXX_COMPILER_VERSION} +SANITIZER_FLAGS=${ALP_SANITIZER_FLAGS} ARGS=${arg_CMAKE_ARGUMENTS}") - if(DEFINED ${version_var} AND "${${version_var}}" STREQUAL "${arg_COMMITISH}${arg_CMAKE_ARGUMENTS}" AND DEFINED ${path_var} AND EXISTS "${${path_var}}") + if(DEFINED ${version_var} AND "${${version_var}}" STREQUAL "${version_signature}" AND DEFINED ${path_var} AND EXISTS "${${path_var}}") list(PREPEND CMAKE_PREFIX_PATH "${${path_var}}") set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} PARENT_SCOPE) return() @@ -87,7 +98,7 @@ ARGS=${arg_CMAKE_ARGUMENTS}") list(PREPEND CMAKE_PREFIX_PATH "${install_dir}") set(CMAKE_PREFIX_PATH "${CMAKE_PREFIX_PATH}" PARENT_SCOPE) set(${path_var} "${install_dir}" CACHE PATH "Install path for ${arg_NAME}" FORCE) - set(${version_var} "${arg_COMMITISH}${arg_CMAKE_ARGUMENTS}" CACHE STRING "Installed commit/tag for $ + build flags" FORCE) + set(${version_var} "${version_signature}" CACHE STRING "Installed commit/tag for $ + build flags" FORCE) return() endif() endif() @@ -104,6 +115,5 @@ ARGS=${arg_CMAKE_ARGUMENTS}") set(${path_var} "${install_dir}" CACHE PATH "Install path for ${arg_NAME}" FORCE) - set(${version_var} "${arg_COMMITISH}${arg_CMAKE_ARGUMENTS}" CACHE STRING "Installed commit/tag for $ + build flags" FORCE) + set(${version_var} "${version_signature}" CACHE STRING "Installed commit/tag for $ + build flags" FORCE) endfunction() - diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f95557d6..e6e3b0dc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -18,7 +18,6 @@ if (CMAKE_BUILD_TYPE STREQUAL "Release") set(CMAKE_CXX_FLAGS_RELEASE "-O${ALP_RELEASE_OPT_LEVEL}") endif() -find_package(TBB REQUIRED) find_package(ZLIB REQUIRED) find_package(CURL REQUIRED) @@ -32,6 +31,13 @@ target_include_directories(Eigen3 INTERFACE SYSTEM ${eigen_SOURCE_DIR}) alp_setup_cmake_project(boost URL https://github.com/boostorg/boost.git COMMITISH "boost-1.88.0" CMAKE_ARGUMENTS -DBOOST_ENABLE_PYTHON=OFF -DBUILD_TESTING=OFF "-DBOOST_INCLUDE_LIBRARIES='graph\;multiprecision\;heap\;format\;logic'") find_package(Boost CONFIG REQUIRED) +set(_alp_tbb_cmake_arguments -DTBB_TEST=OFF) +if(ALP_ENABLE_THREAD_SANITIZER) + list(APPEND _alp_tbb_cmake_arguments -DTBB_SANITIZE=thread) +endif() +alp_setup_cmake_project(tbb URL https://github.com/uxlfoundation/oneTBB.git COMMITISH v2023.1.0 CMAKE_ARGUMENTS ${_alp_tbb_cmake_arguments}) +find_package(TBB REQUIRED) + include(../cmake/SetupGDAL.cmake) alp_setup_gdal(GDAL_VERSION v3.10.3 PROJ_VERSION 9.6.0) diff --git a/src/terrainlib/Dataset.cpp b/src/terrainlib/Dataset.cpp index fbd77975..6542e687 100644 --- a/src/terrainlib/Dataset.cpp +++ b/src/terrainlib/Dataset.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -35,6 +36,8 @@ static GDALDataset *open_gdal_dataset(const std::filesystem::path &path, unsigned int flags) { initialize_gdal_once(); const std::string path_str = path.string(); + static std::mutex gdal_open_mutex; + const std::lock_guard lock(gdal_open_mutex); return static_cast(GDALOpenEx(path_str.c_str(), flags, nullptr, nullptr, nullptr)); } @@ -59,7 +62,7 @@ std::optional Dataset::open_vector(std::filesystem::path path) { return std::nullopt; } std::optional> Dataset::open_shared_raster(std::filesystem::path path) { - if (GDALDataset *dataset = open_gdal_dataset(path, GDAL_OF_RASTER | GDAL_OF_SHARED | GDAL_OF_THREAD_SAFE)) { + if (GDALDataset *dataset = open_gdal_dataset(path, GDAL_OF_RASTER)) { return std::make_shared(std::move(Dataset(path, dataset))); } LOG_ERROR("Couldn't open shared raster dataset {}.\n", path); diff --git a/unittests/tilebuilder/parallel_tile_generator.cpp b/unittests/tilebuilder/parallel_tile_generator.cpp index ade4164e..350c046c 100644 --- a/unittests/tilebuilder/parallel_tile_generator.cpp +++ b/unittests/tilebuilder/parallel_tile_generator.cpp @@ -29,24 +29,24 @@ using namespace radix; TEST_CASE("parallel tile generator") { std::atomic tile_counter = 0; + std::atomic validation_error_counter = 0; std::filesystem::remove_all("./unittest_tiles/"); class MockTileWriter : public ParallelTileWriterInterface { std::atomic* m_tile_counter = nullptr; + std::atomic* m_validation_error_counter = nullptr; public: - MockTileWriter(std::atomic* tile_counter) + MockTileWriter(std::atomic* tile_counter, std::atomic* validation_error_counter) : ParallelTileWriterInterface(radix::tile::Border::No, "empty") , m_tile_counter(tile_counter) + , m_validation_error_counter(validation_error_counter) { } void write(const std::string& file_path, const radix::tile::Descriptor& tile, const HeightData& heights) const override { - CHECK(!file_path.empty()); - CHECK(tile.gridSize == 256); - CHECK(heights.width() == 256); - CHECK(heights.height() == 256); - REQUIRE(m_tile_counter != nullptr); + if (file_path.empty() || tile.gridSize != 256 || heights.width() != 256 || heights.height() != 256) + (*m_validation_error_counter)++; (*m_tile_counter)++; std::ofstream ofs(file_path); @@ -55,11 +55,12 @@ TEST_CASE("parallel tile generator") }; std::filesystem::path base_path = "./unittest_tiles/"; - auto generator = ParallelTileGenerator::make(ATB_TEST_DATA_DIR "/austria/at_mgi.tif", ctb::Grid::Srs::SphericalMercator, radix::tile::Scheme::Tms, std::make_unique(&tile_counter), base_path); + auto generator = ParallelTileGenerator::make(ATB_TEST_DATA_DIR "/austria/at_mgi.tif", ctb::Grid::Srs::SphericalMercator, radix::tile::Scheme::Tms, std::make_unique(&tile_counter, &validation_error_counter), base_path); generator.setWarnOnMissingOverviews(false); SECTION("dataset tiles only") { generator.process({ 0, 7 }); + CHECK(validation_error_counter == 0); CHECK(tile_counter == 27); CHECK(std::filesystem::exists(base_path / "0" / "0" / "0.empty")); CHECK(std::filesystem::exists(base_path / "1" / "1" / "1.empty")); @@ -72,6 +73,7 @@ TEST_CASE("parallel tile generator") SECTION("world wide tiles") { generator.process({ 0, 2 }, false, true); + CHECK(validation_error_counter == 0); CHECK(tile_counter == 21); CHECK(std::filesystem::exists(base_path / "0" / "0" / "0.empty")); From 1b275a99ad6c60684171a3319a28fd4a64799e9b Mon Sep 17 00:00:00 2001 From: adam-ce <5292991+adam-ce@users.noreply.github.com> Date: Wed, 15 Jul 2026 13:24:31 +0200 Subject: [PATCH 21/27] Ignore local build directories --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 3382e319..be34b3ce 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,5 @@ build-release/ .vs /extern/* src/scratch +/.qtcreator/ +/build*/ From a1f767db8bcf94f4798283c718508f048a5e3e94 Mon Sep 17 00:00:00 2001 From: adam-ce <5292991+adam-ce@users.noreply.github.com> Date: Wed, 15 Jul 2026 17:18:53 +0200 Subject: [PATCH 22/27] Use terrainlib SRS transformations in tilebuilder tests --- src/tile_builder/srs.h | 136 ---------------------- unittests/tilebuilder/dataset_reading.cpp | 8 +- 2 files changed, 4 insertions(+), 140 deletions(-) delete mode 100644 src/tile_builder/srs.h diff --git a/src/tile_builder/srs.h b/src/tile_builder/srs.h deleted file mode 100644 index 5032536b..00000000 --- a/src/tile_builder/srs.h +++ /dev/null @@ -1,136 +0,0 @@ -/***************************************************************************** - * Alpine Terrain Builder - * Copyright (C) 2022 alpinemaps.org - * Copyright (C) 2022 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 - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * 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 - * 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 . - *****************************************************************************/ - -#ifndef SRS_H -#define SRS_H - -#include -#include -#include - -#include -#include -#include - -#include "Exception.h" -#include - -namespace srs { - -inline std::unique_ptr transformation(const OGRSpatialReference& source, const OGRSpatialReference& targetSrs) -{ - const auto data_srs = source; - auto transformer = std::unique_ptr(OGRCreateCoordinateTransformation(&data_srs, &targetSrs)); - if (!transformer) - throw Exception("Couldn't create SRS transformation"); - return transformer; -} - -// this transform is non exact, because we are only transforming the corner vertices. however, due to projection warping, a rectangle can become an trapezoid with curved edges. -inline radix::tile::SrsBounds nonExactBoundsTransform(const radix::tile::SrsBounds& bounds, const OGRSpatialReference& sourceSrs, const OGRSpatialReference& targetSrs) -{ - const auto transform = transformation(sourceSrs, targetSrs); - std::array xes = { bounds.min.x, bounds.max.x }; - std::array yes = { bounds.min.y, bounds.max.y }; - if (!transform->Transform(2, xes.data(), yes.data())) - throw Exception("nonExactBoundsTransform failed"); - return { {xes[0], yes[0]}, {xes[1], yes[1]} }; -} - -template -inline glm::tvec3 to(const OGRSpatialReference& source_srs, const OGRSpatialReference& target_srs, glm::tvec3 p) -{ - const auto transform = transformation(source_srs, target_srs); - if (!transform->Transform(1, &p.x, &p.y, &p.z)) - throw Exception("srs::to(glm::tvec3) failed"); - return p; -} - -template -inline glm::tvec3 toECEF(const OGRSpatialReference& source_srs, const glm::tvec3& p) -{ - OGRSpatialReference ecef_srs; - ecef_srs.importFromEPSG(4978); - ecef_srs.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); - return to(source_srs, ecef_srs, p); -} - -template -inline std::vector> toECEF(const OGRSpatialReference& source_srs, std::vector> points) -{ - std::vector xes; - std::vector ys; - std::vector zs; - xes.reserve(points.size()); - ys.reserve(points.size()); - zs.reserve(points.size()); - - for (const auto& p : points) { - xes.push_back(p.x); - ys.push_back(p.y); - zs.push_back(p.z); - } - - OGRSpatialReference ecef_srs; - ecef_srs.importFromEPSG(4978); - ecef_srs.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); - const auto transform = transformation(source_srs, ecef_srs); - if (!transform->Transform(int(points.size()), xes.data(), ys.data(), zs.data())) - throw Exception("toECEF(glm::tvec3) failed"); - - for (size_t i = 0; i < points.size(); ++i) { - points[i] = { xes[i], ys[i], zs[i] }; - } - return points; -} - -template -inline std::array, n> toECEF(const OGRSpatialReference& source_srs, std::array, n> points) -{ - std::array xes; - std::array ys; - std::array zs; - - for (size_t i = 0; i < points.size(); ++i) { - xes[i] = points[i].x; - ys[i] = points[i].y; - zs[i] = points[i].z; - } - - OGRSpatialReference ecef_srs; - ecef_srs.importFromEPSG(4978); - ecef_srs.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); - const auto transform = transformation(source_srs, ecef_srs); - if (!transform->Transform(points.size(), xes.data(), ys.data(), zs.data())) - throw Exception("toECEF(glm::tvec3) failed"); - - for (size_t i = 0; i < points.size(); ++i) { - points[i] = { xes[i], ys[i], zs[i] }; - } - return points; -} - -template -inline std::array, 2> toECEF(const OGRSpatialReference& source_srs, const glm::tvec3& p1, const glm::tvec3& p2) -{ - return toECEF(source_srs, { p1, p2 }); -} -} - -#endif // SRS_H diff --git a/unittests/tilebuilder/dataset_reading.cpp b/unittests/tilebuilder/dataset_reading.cpp index e4c0c37c..b14f0e5e 100644 --- a/unittests/tilebuilder/dataset_reading.cpp +++ b/unittests/tilebuilder/dataset_reading.cpp @@ -112,7 +112,7 @@ TEST_CASE("reading") srs.importFromEPSG(test_srs); srs.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); - const auto srs_bounds = srs::nonExactBoundsTransform(geodetic_bounds, geodetic_srs, srs); + const auto srs_bounds = srs::non_exact_bounds_transform(geodetic_bounds, geodetic_srs, srs); require_projection_available(*dataset, srs); const DatasetReader reader(dataset, srs, 1); @@ -225,7 +225,7 @@ TEST_CASE("reading") const auto pixel_width = low_res_ds->pixelWidthIn(srs); const auto pixel_height = low_res_ds->pixelHeightIn(srs); - auto srs_bounds = srs::nonExactBoundsTransform(low_res_ds->bounds(), low_res_ds->srs(), srs); + auto srs_bounds = srs::non_exact_bounds_transform(low_res_ds->bounds(), low_res_ds->srs(), srs); srs_bounds.min = { srs_bounds.min.x + border * pixel_width, srs_bounds.min.y + border * pixel_height }; srs_bounds.max = { srs_bounds.max.x - border * pixel_width, srs_bounds.max.y - border * pixel_height }; @@ -271,7 +271,7 @@ TEST_CASE("reading") const auto srs = low_res_ds->srs(); const auto low_res_reader = DatasetReader(low_res_ds, srs, 1); const auto high_res_reader = DatasetReader(high_res_ds, srs, 1); - const auto srs_bounds = srs::nonExactBoundsTransform(radix::tile::SrsBounds{{9.5, 46.4}, {17.1, 49.0}}, geodetic_srs, srs); + const auto srs_bounds = srs::non_exact_bounds_transform(radix::tile::SrsBounds{{9.5, 46.4}, {17.1, 49.0}}, geodetic_srs, srs); const auto render_width = unsigned(low_res_ds->widthInPixels(srs_bounds, srs)); const auto render_height = unsigned(low_res_ds->heightInPixels(srs_bounds, srs)); @@ -313,7 +313,7 @@ TEST_CASE("reading") const auto srs = low_res_ds->srs(); const auto low_res_reader = DatasetReader(low_res_ds, srs, 1); const auto high_res_reader = DatasetReader(high_res_ds, srs, 1); - const auto srs_bounds = srs::nonExactBoundsTransform(radix::tile::SrsBounds{{9.5, 46.4}, {17.1, 49.0}}, geodetic_srs, srs); + const auto srs_bounds = srs::non_exact_bounds_transform(radix::tile::SrsBounds{{9.5, 46.4}, {17.1, 49.0}}, geodetic_srs, srs); const auto render_width = unsigned(low_res_ds->widthInPixels(srs_bounds, srs)) / 10; const auto render_height = unsigned(low_res_ds->heightInPixels(srs_bounds, srs)) / 10; From 8c059e004307e1a2898348d0c5dddd4097b8eec7 Mon Sep 17 00:00:00 2001 From: adam-ce <5292991+adam-ce@users.noreply.github.com> Date: Wed, 15 Jul 2026 22:16:44 +0200 Subject: [PATCH 23/27] Fix sanitizer CI builds --- .github/workflows/ci.yml | 3 +++ cmake/SetupOpenCV.cmake | 7 ++++++- src/CMakeLists.txt | 6 +----- src/terrainlib/mesh/manifold.h | 2 +- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5e6c2f86..ad78afe7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -161,6 +161,9 @@ jobs: export PROJ_DATA="${{ github.workspace }}/build/alp_external/proj/share/proj" export PROJ_LIB="${PROJ_DATA}" export GDAL_DATA="${{ github.workspace }}/build/alp_external/gdal/share/gdal" + if [[ "${{ matrix.sanitizer }}" == "tsan" ]]; then + export TSAN_OPTIONS="suppressions=${{ github.workspace }}/extern/tbb/cmake/suppressions/tsan.suppressions" + fi status=0 ./unittests/unittests_terrainlib "~mesh::clip_on_bounds benchmark" || status=$? ./unittests/unittests_tilebuilder || status=$? diff --git a/cmake/SetupOpenCV.cmake b/cmake/SetupOpenCV.cmake index 50afb1fa..e73cbbe9 100644 --- a/cmake/SetupOpenCV.cmake +++ b/cmake/SetupOpenCV.cmake @@ -21,9 +21,15 @@ if(NOT COMMAND alp_setup_cmake_project) endif() function(alp_setup_opencv version) + set(_alp_opencv_sanitizer_arguments) + if(ALP_ENABLE_ADDRESS_SANITIZER OR ALP_ENABLE_THREAD_SANITIZER) + list(APPEND _alp_opencv_sanitizer_arguments -DOPENCV_SKIP_LINK_NO_UNDEFINED=ON) + endif() + alp_setup_cmake_project(opencv URL https://github.com/opencv/opencv.git COMMITISH ${version} CMAKE_ARGUMENTS + ${_alp_opencv_sanitizer_arguments} -DBUILD_TESTS=OFF -DBUILD_PERF_TESTS=OFF -DBUILD_EXAMPLES=OFF @@ -41,4 +47,3 @@ function(alp_setup_opencv version) ) find_package(OpenCV REQUIRED) endfunction() - diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e6e3b0dc..d9e75595 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -31,11 +31,7 @@ target_include_directories(Eigen3 INTERFACE SYSTEM ${eigen_SOURCE_DIR}) alp_setup_cmake_project(boost URL https://github.com/boostorg/boost.git COMMITISH "boost-1.88.0" CMAKE_ARGUMENTS -DBOOST_ENABLE_PYTHON=OFF -DBUILD_TESTING=OFF "-DBOOST_INCLUDE_LIBRARIES='graph\;multiprecision\;heap\;format\;logic'") find_package(Boost CONFIG REQUIRED) -set(_alp_tbb_cmake_arguments -DTBB_TEST=OFF) -if(ALP_ENABLE_THREAD_SANITIZER) - list(APPEND _alp_tbb_cmake_arguments -DTBB_SANITIZE=thread) -endif() -alp_setup_cmake_project(tbb URL https://github.com/uxlfoundation/oneTBB.git COMMITISH v2023.1.0 CMAKE_ARGUMENTS ${_alp_tbb_cmake_arguments}) +alp_setup_cmake_project(tbb URL https://github.com/uxlfoundation/oneTBB.git COMMITISH v2023.1.0 CMAKE_ARGUMENTS -DTBB_TEST=OFF) find_package(TBB REQUIRED) include(../cmake/SetupGDAL.cmake) diff --git a/src/terrainlib/mesh/manifold.h b/src/terrainlib/mesh/manifold.h index be3c380b..bf524625 100644 --- a/src/terrainlib/mesh/manifold.h +++ b/src/terrainlib/mesh/manifold.h @@ -47,7 +47,7 @@ void duplicate_non_manifold_vertices( template void duplicate_non_manifold_vertices( std::span triangles, - const uint32_t vertex_count, + uint32_t vertex_count, Duplicate &&duplicate_vertex); template From 3e1ddb10e26dfe6a5ea397aa7261801f3d06f5a4 Mon Sep 17 00:00:00 2001 From: adam-ce <5292991+adam-ce@users.noreply.github.com> Date: Wed, 15 Jul 2026 23:57:24 +0200 Subject: [PATCH 24/27] Update zpp_bits and remove obsolete access workarounds --- src/CMakeLists.txt | 2 +- src/terrainlib/octree/Id.h | 6 ------ src/terrainlib/octree/NodeStatusOrMissing.h | 5 ----- 3 files changed, 1 insertion(+), 12 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d9e75595..05452981 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -64,7 +64,7 @@ set(SPDLOG_FMT_EXTERNAL ON CACHE BOOL "SPDLOG_FMT_EXTERNAL" FORCE) alp_add_git_repository(spdlog URL https://github.com/gabime/spdlog.git COMMITISH v1.17.0) add_compile_definitions(SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_TRACE) -alp_add_git_repository(zpp_bits URL https://github.com/eyalz800/zpp_bits.git COMMITISH v4.7.1 DO_NOT_ADD_SUBPROJECT) +alp_add_git_repository(zpp_bits URL https://github.com/eyalz800/zpp_bits.git COMMITISH v4.7.2 DO_NOT_ADD_SUBPROJECT) add_library(zpp_bits INTERFACE) target_include_directories(zpp_bits INTERFACE SYSTEM ${zpp_bits_SOURCE_DIR}) diff --git a/src/terrainlib/octree/Id.h b/src/terrainlib/octree/Id.h index 0594f500..a593be3b 100644 --- a/src/terrainlib/octree/Id.h +++ b/src/terrainlib/octree/Id.h @@ -14,10 +14,6 @@ #include "hash_utils.h" #include "int_math.h" -namespace zpp::bits { -struct access; -} - namespace octree { class Id { @@ -241,8 +237,6 @@ class Id { Level _level; Index _index; - friend zpp::bits::access; - [[nodiscard]] constexpr Id _child(const uint32_t child_index) const { DEBUG_ASSERT(child_index <= 7); return Id(this->level() + 1, (this->index_on_level() << 3) | child_index); diff --git a/src/terrainlib/octree/NodeStatusOrMissing.h b/src/terrainlib/octree/NodeStatusOrMissing.h index 227fc121..0868cb93 100644 --- a/src/terrainlib/octree/NodeStatusOrMissing.h +++ b/src/terrainlib/octree/NodeStatusOrMissing.h @@ -2,7 +2,6 @@ #include -#include #include #include "octree/NodeStatus.h" @@ -67,10 +66,6 @@ class NodeStatusOrMissing { public: Value _value; - -public: - using serialize = zpp::bits::members<1>; - friend zpp::bits::access; }; } From 0af3a608c915ee798ab79063c3d607f6f3d789e4 Mon Sep 17 00:00:00 2001 From: adam-ce <5292991+adam-ce@users.noreply.github.com> Date: Thu, 16 Jul 2026 00:55:03 +0200 Subject: [PATCH 25/27] Move zpp_bits serializers to associated namespaces --- src/dag_builder/dag_node.h | 4 ++-- src/dag_builder/encoded.h | 16 ++++++---------- src/terrainlib/mesh/io/texture.h | 6 +++--- src/terrainlib/octree/Id.h | 6 +++--- 4 files changed, 14 insertions(+), 18 deletions(-) diff --git a/src/dag_builder/dag_node.h b/src/dag_builder/dag_node.h index 5bb44598..13970d72 100644 --- a/src/dag_builder/dag_node.h +++ b/src/dag_builder/dag_node.h @@ -37,7 +37,7 @@ std::vector> build_parent_map(const std::vector auto serialize(Archive &archive, const dag::Id &id) { @@ -58,4 +58,4 @@ template auto serialize(Archive &archive, dag::ClusterBatch &node) { return archive(node.clustering, node.child_map); } -} +} // namespace dag diff --git a/src/dag_builder/encoded.h b/src/dag_builder/encoded.h index 8e10878a..769e567f 100644 --- a/src/dag_builder/encoded.h +++ b/src/dag_builder/encoded.h @@ -18,8 +18,6 @@ #include "mesh/io/texture.h" #include "meshopt.h" -namespace zpp::bits { - template auto serialize(Archive &archive, const TextureSet &textures) { size_t size = textures.size(); @@ -28,14 +26,14 @@ auto serialize(Archive &archive, const TextureSet &textures) { if constexpr (!std::is_same_v && !std::is_same_v) { return result; } else { - if (failure(result)) { + if (zpp::bits::failure(result)) { return result; } for (const auto &texture : textures) { const mesh::io::ImageAndExt item{texture, ".jpeg"}; result = archive(item); - if (failure(result)) { + if (zpp::bits::failure(result)) { return result; } } @@ -51,7 +49,7 @@ auto serialize(Archive &archive, TextureSet &textures) { if constexpr (!std::is_same_v && !std::is_same_v) { return result; } else { - if (failure(result)) { + if (zpp::bits::failure(result)) { return result; } @@ -60,7 +58,7 @@ auto serialize(Archive &archive, TextureSet &textures) { for (size_t i = 0; i < size; i++) { mesh::io::ImageAndExt item; result = archive(item); - if (failure(result)) { + if (zpp::bits::failure(result)) { return result; } @@ -118,7 +116,7 @@ auto serialize(Archive &archive, Cluster &cluster) { if constexpr (!std::is_same_v && !std::is_same_v) { return result; } else { - if (failure(result)) { + if (zpp::bits::failure(result)) { return result; } @@ -157,7 +155,7 @@ auto serialize(Archive &archive, Clustering &clustering) { if constexpr (!std::is_same_v && !std::is_same_v) { return result; } else { - if (failure(result)) { + if (zpp::bits::failure(result)) { return result; } @@ -167,8 +165,6 @@ auto serialize(Archive &archive, Clustering &clustering) { } } -} // namespace zpp::bits - inline tl::expected save_clustering(const Clustering &clustering, const std::filesystem::path &path, diff --git a/src/terrainlib/mesh/io/texture.h b/src/terrainlib/mesh/io/texture.h index 0d1d8113..c6849014 100644 --- a/src/terrainlib/mesh/io/texture.h +++ b/src/terrainlib/mesh/io/texture.h @@ -24,7 +24,7 @@ std::vector write_texture_to_encoded_buffer(const ImageAndExt &item); #include -namespace zpp::bits { +namespace mesh::io { template auto serialize(Archive &archive, const mesh::io::ImageAndExt &item) { @@ -41,7 +41,7 @@ auto serialize(Archive &archive, mesh::io::ImageAndExt &item) { if constexpr (!std::is_same_v && !std::is_same_v) { return result; } else { - if (failure(result)) { + if (zpp::bits::failure(result)) { return result; } @@ -50,4 +50,4 @@ auto serialize(Archive &archive, mesh::io::ImageAndExt &item) { } } -} // namespace zpp::bits +} // namespace mesh::io diff --git a/src/terrainlib/octree/Id.h b/src/terrainlib/octree/Id.h index a593be3b..a8468aa3 100644 --- a/src/terrainlib/octree/Id.h +++ b/src/terrainlib/octree/Id.h @@ -314,7 +314,7 @@ struct hash { } // namespace std #include -namespace zpp::bits { +namespace octree { namespace { constexpr zpp::bits::errc success() { return zpp::bits::errc(std::errc()); @@ -328,7 +328,7 @@ constexpr auto serialize(auto &archive, octree::Id &id) { if constexpr (!std::is_same_v && !std::is_same_v) { return result; } else { - if (failure(result)) { + if (zpp::bits::failure(result)) { return result; } @@ -344,4 +344,4 @@ constexpr auto serialize(auto &archive, octree::Id &id) { constexpr auto serialize(auto &archive, const octree::Id &id) { return archive(id.level(), id.index_on_level()); } -} +} // namespace octree From ece600929cec23785a34be4fd34c75b429ed1348 Mon Sep 17 00:00:00 2001 From: adam-ce <5292991+adam-ce@users.noreply.github.com> Date: Thu, 16 Jul 2026 10:02:38 +0200 Subject: [PATCH 26/27] Add Clang unity build to CI --- .github/workflows/ci.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ad78afe7..9d4f94da 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,6 +35,12 @@ jobs: build_type: RelWithDebInfo sanitizer: tsan cmake_flags: -DALP_ENABLE_THREAD_SANITIZER=ON + - name: clang-23-unity + cc: clang-23 + cxx: clang++-23 + build_type: Release + sanitizer: normal + cmake_flags: -DALP_ENABLE_UNITY_BUILD=ON steps: - uses: actions/checkout@v4 From bec661464f353bbc5b46258c04e13c542c1d7e53 Mon Sep 17 00:00:00 2001 From: adam-ce <5292991+adam-ce@users.noreply.github.com> Date: Thu, 16 Jul 2026 14:21:38 +0200 Subject: [PATCH 27/27] Fix texture assembler unity build --- src/mesh_builder/texture_assembler.h | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/mesh_builder/texture_assembler.h b/src/mesh_builder/texture_assembler.h index 3e795d4c..90ee1ac2 100644 --- a/src/mesh_builder/texture_assembler.h +++ b/src/mesh_builder/texture_assembler.h @@ -24,7 +24,7 @@ namespace terrainbuilder { /// Estimates the zoom level of the target bounds in relation to some reference tile given by its zoom level and bounds. -[[nodiscard]] unsigned int estimate_zoom_level( +[[nodiscard]] inline unsigned int estimate_zoom_level( const unsigned int reference_zoom_level, const radix::tile::SrsBounds reference_tile_bounds, const radix::tile::SrsBounds target_bounds) { @@ -38,7 +38,7 @@ namespace terrainbuilder { /// Find all the tiles needed to construct the texture for the given target bounds under the root tile. /// The result tiles are ordered in such a way that they can be sequentially written to a texture. /// Larger tiles are only included if they are not covered by smaller tiles. -[[nodiscard]] std::vector find_relevant_tiles_to_splatter_in_bounds( +[[nodiscard]] inline std::vector find_relevant_tiles_to_splatter_in_bounds( /// The root tile specifying the tiles to consider. const radix::tile::Id root_tile, /// Specifes the grid used to organize the image tiles. @@ -119,7 +119,7 @@ namespace terrainbuilder { } /// Calculate the offset and size of the target bounds inside the root tile. -[[nodiscard]] radix::geometry::Aabb2ui calculate_target_image_region( +[[nodiscard]] inline radix::geometry::Aabb2ui calculate_target_image_region( /// The bounds for which texture data should be created. const radix::tile::SrsBounds target_bounds, /// The bounds of the root tile. @@ -143,7 +143,7 @@ namespace terrainbuilder { return target_image_region; } -[[nodiscard]] radix::geometry::Aabb2ui calculate_pixel_tile_bounds( +[[nodiscard]] inline radix::geometry::Aabb2ui calculate_pixel_tile_bounds( radix::tile::Id tile, const radix::tile::Id root_tile, const glm::uvec2 tile_image_pixel_size, @@ -157,7 +157,7 @@ namespace terrainbuilder { return radix::geometry::Aabb2ui(tile_position, tile_position + tile_size); } -[[nodiscard]] cv::Rect to_cv_rect(radix::geometry::Aabb2ui aabb) { +[[nodiscard]] inline cv::Rect to_cv_rect(radix::geometry::Aabb2ui aabb) { const glm::uvec2 size = aabb.size(); return cv::Rect{ static_cast(aabb.min.x), @@ -166,14 +166,14 @@ namespace terrainbuilder { static_cast(size.y) }; } -[[nodiscard]] cv::Size to_cv_size(glm::uvec2 vec) { +[[nodiscard]] inline cv::Size to_cv_size(glm::uvec2 vec) { return cv::Size{ static_cast(vec.x), static_cast(vec.y) }; } -void copy_paste_image( +inline void copy_paste_image( cv::Mat &target, const cv::Mat &source, const radix::geometry::Aabb2i target_bounds, @@ -254,7 +254,7 @@ void copy_paste_image( final_source.copyTo(target(target_rect)); } -void copy_paste_image( +inline void copy_paste_image( cv::Mat &target, const cv::Mat &source, const glm::ivec2 target_position, @@ -264,7 +264,7 @@ void copy_paste_image( } namespace { -std::optional try_get_tile_path(const radix::tile::Id tile, const TileProvider &tile_provider) { +inline std::optional try_get_tile_path(const radix::tile::Id tile, const TileProvider &tile_provider) { const TilePathProvider *tile_path_provider = dynamic_cast(&tile_provider); if (tile_path_provider != nullptr) { return tile_path_provider->get_tile_path(tile).value(); @@ -273,7 +273,7 @@ std::optional try_get_tile_path(const radix::tile::Id til } } -[[nodiscard]] cv::Mat splatter_tiles_to_texture( +[[nodiscard]] inline cv::Mat splatter_tiles_to_texture( const radix::tile::Id root_tile, /// Specifes the grid used to organize the image tiles. const ctb::Grid &grid, @@ -358,7 +358,7 @@ std::optional try_get_tile_path(const radix::tile::Id til } /// Creates a texture for the given region. -[[nodiscard]] std::optional assemble_texture_from_tiles( +[[nodiscard]] inline std::optional assemble_texture_from_tiles( /// Specifes the grid used to organize the image tiles. const ctb::Grid &grid, /// Specifies the srs the target bounds are in.