diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 58000156..ebd56549 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -35,7 +35,7 @@ jobs: os: ubuntu-22.04, cmakeBuildType: Release, asanEnabled: true, - cudaEnabled: false, + cudaEnabled: true, checkCodeFormat: false, }, { @@ -163,6 +163,7 @@ jobs: -DCMAKE_BUILD_TYPE=${{ matrix.config.cmakeBuildType }} \ -DCMAKE_INSTALL_PREFIX=./install \ -DCMAKE_CUDA_ARCHITECTURES=50 \ + -DCUDA_ENABLED=${{ matrix.config.cudaEnabled }} \ -DTESTS_ENABLED=ON \ -DASAN_ENABLED=${{ matrix.config.asanEnabled }} ninja -k 10000 diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 74e1fe5b..1adc3635 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -23,6 +23,13 @@ jobs: testsEnabled: true, exportPackage: false, }, + { + os: windows-2022, + cmakeBuildType: Release, + cudaEnabled: true, + testsEnabled: true, + exportPackage: true, + }, { os: windows-2022, cmakeBuildType: Release, @@ -70,6 +77,15 @@ jobs: .github/workflows/install-ccache.ps1 -Destination "${{ env.COMPILER_CACHE_DIR }}/bin" + - name: Install CUDA + uses: Jimver/cuda-toolkit@v0.2.18 + if: matrix.config.cudaEnabled + id: cuda-toolkit + with: + cuda: '12.6.2' + sub-packages: '["nvcc", "nvtx", "cudart", "curand", "curand_dev", "nvrtc_dev"]' + method: 'network' + - name: Install CMake and Ninja uses: lukka/get-cmake@latest @@ -96,10 +112,11 @@ jobs: -DCMAKE_MAKE_PROGRAM=ninja ` -DCMAKE_BUILD_TYPE=Release ` -DTESTS_ENABLED=ON ` - -DCUDA_ENABLED=OFF ` + -DCUDA_ENABLED=${{ matrix.config.cudaEnabled }} ` -DGUI_ENABLED=OFF ` -DCGAL_ENABLED=OFF ` -DCMAKE_CUDA_ARCHITECTURES=all-major ` + -DCUDAToolkit_ROOT="${{ steps.cuda-toolkit.outputs.CUDA_PATH }}" ` -DCMAKE_TOOLCHAIN_FILE="${{ github.workspace }}/vcpkg/scripts/buildsystems/vcpkg.cmake" ` -DVCPKG_TARGET_TRIPLET=x64-windows-release ` -DCMAKE_INSTALL_PREFIX=install @@ -123,15 +140,27 @@ jobs: ../vcpkg/vcpkg.exe install ` --triplet=x64-windows-release + $(if ($${{ matrix.config.cudaEnabled }}) { echo "--x-feature=cuda" }) ../vcpkg/vcpkg.exe export --raw --output-dir vcpkg_export --output glomap cp vcpkg_export/glomap/installed/x64-windows/bin/*.dll install/bin cp vcpkg_export/glomap/installed/x64-windows-release/bin/*.dll install/bin + if ($${{ matrix.config.cudaEnabled }}) { + cp "${{ steps.cuda-toolkit.outputs.CUDA_PATH }}/bin/cudart64_*.dll" install/bin + cp "${{ steps.cuda-toolkit.outputs.CUDA_PATH }}/bin/curand64_*.dll" install/bin + } + + - name: Upload package + uses: actions/upload-artifact@v4 + if: ${{ matrix.config.exportPackage && matrix.config.cudaEnabled }} + with: + name: glomap-x64-windows-cuda + path: build/install - name: Upload package uses: actions/upload-artifact@v4 - if: ${{ matrix.config.exportPackage }} + if: ${{ matrix.config.exportPackage && !matrix.config.cudaEnabled }} with: - name: glomap-x64-windows + name: glomap-x64-windows-nocuda path: build/install - name: Cleanup compiler cache diff --git a/CMakeLists.txt b/CMakeLists.txt index 1d1c2729..1c0f9ab3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,21 +1,27 @@ cmake_minimum_required(VERSION 3.28) -project(glomap VERSION 1.0.0) - -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED ON) - -set_property(GLOBAL PROPERTY GLOBAL_DEPENDS_NO_CYCLES ON) - +option(CUDA_ENABLED "Whether to enable CUDA, if available" ON) option(TESTS_ENABLED "Whether to build test binaries" OFF) option(ASAN_ENABLED "Whether to enable AddressSanitizer flags" OFF) option(CCACHE_ENABLED "Whether to enable compiler caching, if available" ON) option(FETCH_COLMAP "Whether to use COLMAP with FetchContent or with self-installed software" ON) option(FETCH_POSELIB "Whether to use PoseLib with FetchContent or with self-installed software" ON) +# Propagate options to vcpkg manifest. +if(CUDA_ENABLED) + list(APPEND VCPKG_MANIFEST_FEATURES "cuda") +endif() + +# Initialize the project. +project(glomap VERSION 1.1.0) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set_property(GLOBAL PROPERTY GLOBAL_DEPENDS_NO_CYCLES ON) + include(cmake/FindDependencies.cmake) -# Propagate options to vcpkg manifest. if (TESTS_ENABLED) enable_testing() endif() @@ -39,6 +45,7 @@ else() message(STATUS "Disabling ccache support") endif() + if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") # Some fixes for the Glog library. add_definitions("-DGLOG_USE_GLOG_EXPORT") diff --git a/cmake/FindDependencies.cmake b/cmake/FindDependencies.cmake index d7e5e586..99ecb727 100644 --- a/cmake/FindDependencies.cmake +++ b/cmake/FindDependencies.cmake @@ -28,7 +28,7 @@ FetchContent_Declare(PoseLib SYSTEM ) message(STATUS "Configuring PoseLib...") -if (FETCH_POSELIB) +if (FETCH_POSELIB) FetchContent_MakeAvailable(PoseLib) else() find_package(PoseLib REQUIRED) @@ -42,9 +42,73 @@ FetchContent_Declare(COLMAP ) message(STATUS "Configuring COLMAP...") set(UNINSTALL_ENABLED OFF CACHE INTERNAL "") -if (FETCH_COLMAP) +if (FETCH_COLMAP) FetchContent_MakeAvailable(COLMAP) else() find_package(COLMAP REQUIRED) endif() message(STATUS "Configuring COLMAP... done") + +set(CUDA_MIN_VERSION "7.0") +if(CUDA_ENABLED) + if(CMAKE_VERSION VERSION_LESS 3.17) + find_package(CUDA QUIET) + if(CUDA_FOUND) + message(STATUS "Found CUDA version ${CUDA_VERSION} installed in " + "${CUDA_TOOLKIT_ROOT_DIR} via legacy CMake (<3.17) module. " + "Using the legacy CMake module means that any installation of " + "COLMAP will require that the CUDA libraries are " + "available under LD_LIBRARY_PATH.") + message(STATUS "Found CUDA ") + message(STATUS " Includes : ${CUDA_INCLUDE_DIRS}") + message(STATUS " Libraries : ${CUDA_LIBRARIES}") + + enable_language(CUDA) + + macro(declare_imported_cuda_target module) + add_library(CUDA::${module} INTERFACE IMPORTED) + target_include_directories( + CUDA::${module} INTERFACE ${CUDA_INCLUDE_DIRS}) + target_link_libraries( + CUDA::${module} INTERFACE ${CUDA_${module}_LIBRARY} ${ARGN}) + endmacro() + + declare_imported_cuda_target(cudart ${CUDA_LIBRARIES}) + declare_imported_cuda_target(curand ${CUDA_LIBRARIES}) + + set(CUDAToolkit_VERSION "${CUDA_VERSION_STRING}") + set(CUDAToolkit_BIN_DIR "${CUDA_TOOLKIT_ROOT_DIR}/bin") + else() + message(STATUS "CUDA not found") + endif() + else() + find_package(CUDAToolkit QUIET) + if(CUDAToolkit_FOUND) + set(CUDA_FOUND ON) + enable_language(CUDA) + else() + message(STATUS "CUDA not found") + endif() + endif() +endif() + +if(CUDA_ENABLED AND CUDA_FOUND) + if(NOT DEFINED CMAKE_CUDA_ARCHITECTURES) + set(CMAKE_CUDA_ARCHITECTURES "native") + endif() + + add_definitions("-DGLOMAP_CUDA_ENABLED") + + # Do not show warnings if the architectures are deprecated. + set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -Wno-deprecated-gpu-targets") + # Explicitly set PIC flags for CUDA targets. + if(NOT IS_MSVC) + set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} --compiler-options -fPIC") + endif() + + message(STATUS "Enabling CUDA support (version: ${CUDAToolkit_VERSION}, " + "archs: ${CMAKE_CUDA_ARCHITECTURES})") +else() + set(CUDA_ENABLED OFF) + message(STATUS "Disabling CUDA support") +endif() diff --git a/glomap/controllers/option_manager.cc b/glomap/controllers/option_manager.cc index 9b8fb6a1..d8041a62 100644 --- a/glomap/controllers/option_manager.cc +++ b/glomap/controllers/option_manager.cc @@ -180,6 +180,10 @@ void OptionManager::AddGlobalPositionerOptions() { return; } added_global_positioning_options_ = true; + AddAndRegisterDefaultOption("GlobalPositioning.use_gpu", + &mapper->opt_gp.use_gpu); + AddAndRegisterDefaultOption("GlobalPositioning.gpu_index", + &mapper->opt_gp.gpu_index); AddAndRegisterDefaultOption("GlobalPositioning.optimize_positions", &mapper->opt_gp.optimize_positions); AddAndRegisterDefaultOption("GlobalPositioning.optimize_points", @@ -199,6 +203,10 @@ void OptionManager::AddBundleAdjusterOptions() { return; } added_bundle_adjustment_options_ = true; + AddAndRegisterDefaultOption("BundleAdjustment.use_gpu", + &mapper->opt_ba.use_gpu); + AddAndRegisterDefaultOption("BundleAdjustment.gpu_index", + &mapper->opt_ba.gpu_index); AddAndRegisterDefaultOption("BundleAdjustment.optimize_rotations", &mapper->opt_ba.optimize_rotations); AddAndRegisterDefaultOption("BundleAdjustment.optimize_translation", diff --git a/glomap/estimators/bundle_adjustment.cc b/glomap/estimators/bundle_adjustment.cc index 3bc83d0f..57f85b13 100644 --- a/glomap/estimators/bundle_adjustment.cc +++ b/glomap/estimators/bundle_adjustment.cc @@ -3,6 +3,8 @@ #include #include #include +#include +#include namespace glomap { @@ -36,6 +38,58 @@ bool BundleAdjuster::Solve(const ViewGraph& view_graph, // Set the solver options. ceres::Solver::Summary summary; + int num_images = images.size(); +#ifdef GLOMAP_CUDA_ENABLED + bool cuda_solver_enabled = false; + +#if (CERES_VERSION_MAJOR >= 3 || \ + (CERES_VERSION_MAJOR == 2 && CERES_VERSION_MINOR >= 2)) && \ + !defined(CERES_NO_CUDA) + if (options_.use_gpu && num_images >= options_.min_num_images_gpu_solver) { + cuda_solver_enabled = true; + options_.solver_options.dense_linear_algebra_library_type = ceres::CUDA; + } +#else + if (options_.use_gpu) { + LOG_FIRST_N(WARNING, 1) + << "Requested to use GPU for bundle adjustment, but Ceres was " + "compiled without CUDA support. Falling back to CPU-based dense " + "solvers."; + } +#endif + +#if (CERES_VERSION_MAJOR >= 3 || \ + (CERES_VERSION_MAJOR == 2 && CERES_VERSION_MINOR >= 3)) && \ + !defined(CERES_NO_CUDSS) + if (options_.use_gpu && num_images >= options_.min_num_images_gpu_solver) { + cuda_solver_enabled = true; + options_.solver_options.sparse_linear_algebra_library_type = + ceres::CUDA_SPARSE; + } +#else + if (options_.use_gpu) { + LOG_FIRST_N(WARNING, 1) + << "Requested to use GPU for bundle adjustment, but Ceres was " + "compiled without cuDSS support. Falling back to CPU-based sparse " + "solvers."; + } +#endif + + if (cuda_solver_enabled) { + const std::vector gpu_indices = + colmap::CSVToVector(options_.gpu_index); + THROW_CHECK_GT(gpu_indices.size(), 0); + colmap::SetBestCudaDevice(gpu_indices[0]); + } +#else + if (options_.use_gpu) { + LOG_FIRST_N(WARNING, 1) + << "Requested to use GPU for bundle adjustment, but COLMAP was " + "compiled without CUDA support. Falling back to CPU-based " + "solvers."; + } +#endif // GLOMAP_CUDA_ENABLED + // Do not use the iterative solver, as it does not seem to be helpful options_.solver_options.linear_solver_type = ceres::SPARSE_SCHUR; options_.solver_options.preconditioner_type = ceres::CLUSTER_TRIDIAGONAL; diff --git a/glomap/estimators/bundle_adjustment.h b/glomap/estimators/bundle_adjustment.h index 36a142f3..b78347ca 100644 --- a/glomap/estimators/bundle_adjustment.h +++ b/glomap/estimators/bundle_adjustment.h @@ -17,6 +17,10 @@ struct BundleAdjusterOptions : public OptimizationBaseOptions { bool optimize_principal_point = false; bool optimize_points = true; + bool use_gpu = true; + std::string gpu_index = "-1"; + int min_num_images_gpu_solver = 50; + // Constrain the minimum number of views per track int min_num_view_per_track = 3; diff --git a/glomap/estimators/global_positioning.cc b/glomap/estimators/global_positioning.cc index 8a9b064a..ebe1b8de 100644 --- a/glomap/estimators/global_positioning.cc +++ b/glomap/estimators/global_positioning.cc @@ -2,6 +2,9 @@ #include "glomap/estimators/cost_function.h" +#include +#include + namespace glomap { namespace { @@ -361,6 +364,58 @@ void GlobalPositioner::ParameterizeVariables( } } + int num_images = images.size(); +#ifdef GLOMAP_CUDA_ENABLED + bool cuda_solver_enabled = false; + +#if (CERES_VERSION_MAJOR >= 3 || \ + (CERES_VERSION_MAJOR == 2 && CERES_VERSION_MINOR >= 2)) && \ + !defined(CERES_NO_CUDA) + if (options_.use_gpu && num_images >= options_.min_num_images_gpu_solver) { + cuda_solver_enabled = true; + options_.solver_options.dense_linear_algebra_library_type = ceres::CUDA; + } +#else + if (options_.use_gpu) { + LOG_FIRST_N(WARNING, 1) + << "Requested to use GPU for bundle adjustment, but Ceres was " + "compiled without CUDA support. Falling back to CPU-based dense " + "solvers."; + } +#endif + +#if (CERES_VERSION_MAJOR >= 3 || \ + (CERES_VERSION_MAJOR == 2 && CERES_VERSION_MINOR >= 3)) && \ + !defined(CERES_NO_CUDSS) + if (options_.use_gpu && num_images >= options_.min_num_images_gpu_solver) { + cuda_solver_enabled = true; + options_.solver_options.sparse_linear_algebra_library_type = + ceres::CUDA_SPARSE; + } +#else + if (options_.use_gpu) { + LOG_FIRST_N(WARNING, 1) + << "Requested to use GPU for bundle adjustment, but Ceres was " + "compiled without cuDSS support. Falling back to CPU-based sparse " + "solvers."; + } +#endif + + if (cuda_solver_enabled) { + const std::vector gpu_indices = + colmap::CSVToVector(options_.gpu_index); + THROW_CHECK_GT(gpu_indices.size(), 0); + colmap::SetBestCudaDevice(gpu_indices[0]); + } +#else + if (options_.use_gpu) { + LOG_FIRST_N(WARNING, 1) + << "Requested to use GPU for bundle adjustment, but COLMAP was " + "compiled without CUDA support. Falling back to CPU-based " + "solvers."; + } +#endif // GLOMAP_CUDA_ENABLED + // Set up the options for the solver // Do not use iterative solvers, for its suboptimal performance. if (tracks.size() > 0) { diff --git a/glomap/estimators/global_positioning.h b/glomap/estimators/global_positioning.h index 3926ff7b..f318e8fa 100644 --- a/glomap/estimators/global_positioning.h +++ b/glomap/estimators/global_positioning.h @@ -29,6 +29,10 @@ struct GlobalPositionerOptions : public OptimizationBaseOptions { bool optimize_points = true; bool optimize_scales = true; + bool use_gpu = true; + std::string gpu_index = "-1"; + int min_num_images_gpu_solver = 50; + // Constrain the minimum number of views per track int min_num_view_per_track = 3; diff --git a/glomap/glomap.cc b/glomap/glomap.cc index aa300a19..19c77255 100644 --- a/glomap/glomap.cc +++ b/glomap/glomap.cc @@ -12,6 +12,12 @@ int ShowHelp( std::cout << "GLOMAP -- Global Structure-from-Motion" << std::endl << std::endl; +#ifdef GLOMAP_CUDA_ENABLED + std::cout << "This version was compiled with CUDA!" << std::endl << std::endl; +#else + std::cout << "This version was NOT compiled CUDA!" << std::endl << std::endl; +#endif + std::cout << "Usage:" << std::endl; std::cout << " glomap mapper --database_path DATABASE --output_path MODEL" << std::endl; diff --git a/vcpkg.json b/vcpkg.json index 4ec57780..a1b5010e 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -16,6 +16,7 @@ "name": "ceres", "features": [ "lapack", + "schur", "suitesparse" ] }, @@ -42,5 +43,12 @@ "suitesparse" ], "features": { + "cuda": { + "description": "Build with CUDA.", + "dependencies": [ + "glew", + "cuda" + ] + } } }