Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 76 additions & 21 deletions .github/workflows/continuous.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ on:
pull_request:
branches:
- main
push:
branches:
- main

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
Expand All @@ -21,26 +24,26 @@ jobs:
Unix:
name: ${{ matrix.os }} (${{ matrix.compiler }}, ${{ matrix.config }}, ${{ matrix.sanitizer }}Sanitizer)
runs-on: ${{ matrix.os }}
env:
CCACHE_COMPRESSLEVEL: 5
strategy:
fail-fast: false
matrix:
os: [ubuntu-24.04, macos-15-intel, macos-15]
config: [RelWithDebInfo, Debug]
compiler: [gcc, apple, llvm]
sanitizer: ["Address", "Thread"] # TODO: Add Memory+Undefined Sanitizer
exclude:
- os: macos-15-intel
compiler: gcc
- os: macos-15-intel
sanitizer: Thread
- os: macos-15-intel
config: Debug
- os: macos-15
compiler: gcc
- os: ubuntu-24.04
compiler: apple
include:
# Linux release builds exercise tests disabled in Debug mode, and ASan also enables LSan.
- { os: ubuntu-24.04, compiler: gcc, config: RelWithDebInfo, sanitizer: "Address;Undefined", split_debug: ON }
- { os: ubuntu-24.04, compiler: gcc, config: RelWithDebInfo, sanitizer: Thread }
- { os: ubuntu-24.04, compiler: llvm, config: Debug, sanitizer: "Address;Undefined" }
- { os: ubuntu-24.04, compiler: llvm, config: Debug, sanitizer: Thread }

# Keep AppleClang release coverage on Intel and full sanitizer coverage on Apple Silicon.
- { os: macos-15-intel, compiler: apple, config: RelWithDebInfo, sanitizer: "Address;Undefined" }
- { os: macos-15, compiler: apple, config: RelWithDebInfo, sanitizer: "Address;Undefined" }
- { os: macos-15, compiler: apple, config: RelWithDebInfo, sanitizer: Thread }
- { os: macos-15, compiler: llvm, config: Debug, sanitizer: "Address;Undefined" }
- { os: macos-15, compiler: llvm, config: Debug, sanitizer: Thread }
steps:
- name: Free Disk Space
- name: Free Disk Space (Linux)
if: runner.os == 'Linux'
uses: jlumbroso/free-disk-space@main
with:
Expand All @@ -57,6 +60,25 @@ jobs:
docker-images: true
swap-storage: true

- name: Free Disk Space (macOS)
if: runner.os == 'macOS'
run: |
echo "Disk usage before cleanup:"
df -h
sudo rm -rf /Library/Developer/CoreSimulator
sudo rm -rf "$HOME/Library/Developer/CoreSimulator"
sudo rm -rf /System/Library/AssetsV2/com_apple_MobileAsset_iOSSimulatorRuntime
sudo rm -rf /System/Library/AssetsV2/com_apple_MobileAsset_xrOSSimulatorRuntime
sudo rm -rf /System/Library/AssetsV2/com_apple_MobileAsset_watchOSSimulatorRuntime
sudo rm -rf /System/Library/AssetsV2/com_apple_MobileAsset_appleTVOSSimulatorRuntime
sudo rm -rf "$ANDROID_HOME"
sudo rm -rf /usr/local/share/dotnet
sudo rm -rf /opt/ghc
sudo rm -rf "/usr/local/share/boost"
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
echo "Disk usage after cleanup:"
df -h

- name: Set swap space
if: runner.os == 'Linux' && matrix.compiler == 'gcc' && matrix.config == 'RelWithDebInfo'
uses: pierotofy/set-swap-space@fc79b3f67fa8a838184ce84a674ca12238d2c761
Expand Down Expand Up @@ -133,7 +155,11 @@ jobs:
- name: Ccache
uses: hendrikmuhs/ccache-action@v1.2
with:
key: ${{ matrix.os }}-${{ matrix.compiler }}-${{ matrix.sanitizer }}-${{ matrix.config }}
key: ${{ matrix.os }}-${{ matrix.compiler }}-${{ matrix.config }}-${{ matrix.sanitizer }}
# Keep more objects, and do not prune untouched entries because cancelled jobs
# would save an incomplete cache as the newest restore candidate.
max-size: 750M
verbose: 1

- name: Setup Xcode version
if: matrix.os == 'macos-15-intel'
Expand All @@ -153,11 +179,11 @@ jobs:

- name: Select embree isa (macOS x64)
if: runner.os == 'macOS' && runner.arch == 'X64'
run: echo "embree_max_isa=DEFAULT" >> $GITHUB_ENV
run: echo "embree_max_isa=SSE2" >> $GITHUB_ENV

- name: Select embree isa (macOS arm64)
if: runner.os == 'macOS' && runner.arch == 'ARM64'
run: echo "embree_max_isa=NONE" >> $GITHUB_ENV
run: echo "embree_max_isa=NEON" >> $GITHUB_ENV

- name: Get number of CPU cores
uses: SimenB/github-actions-cpu-cores@v1
Expand All @@ -171,13 +197,35 @@ jobs:
-DLAGRANGE_JENKINS=ON \
-DLAGRANGE_ALL=ON \
-DLAGRANGE_LIMIT_PARALLELISM=ON \
-DLAGRANGE_LIMIT_GITHUB_ACTIONS_PARALLELISM=ON \
-DOPENVDB_CORE_SHARED=ON \
-DOPENVDB_CORE_STATIC=OFF \
-DUSE_EXPLICIT_INSTANTIATION=OFF \
-DUSE_EXPLICIT_INSTANTIATION=ON \
-DLAGRANGE_POLYSCOPE_MOCK_BACKEND=ON \
-DLAGRANGE_USE_SPLIT_DEBUG_INFO=${{ matrix.split_debug }} \
-DEMBREE_MAX_ISA=${{ env.embree_max_isa }} \
-DUSE_SANITIZER="${{ matrix.sanitizer }}"

- name: Build Embree
if: runner.os == 'Linux' || matrix.compiler == 'llvm'
run: cmake --build ${{ env.build_dir }} --target embree -j 2

- name: Show disk space
if: always()
run: |
echo "disk usage:"
df -h

- name: Build OpenVDB
if: runner.os == 'Linux' || matrix.compiler == 'llvm'
run: cmake --build ${{ env.build_dir }} --target openvdb_shared -j 2

- name: Show disk space
if: always()
run: |
echo "disk usage:"
df -h

- name: Build
run: cmake --build ${{ env.build_dir }} -j ${{ steps.cpu-cores.outputs.count }}

Expand All @@ -190,6 +238,12 @@ jobs:
- name: Tests
run: cd ${{ env.build_dir }}; ctest --verbose -j ${{ steps.cpu-cores.outputs.count }}

- name: Clean and report ccache
if: always()
run: |
ccache --cleanup
ccache --show-stats --verbose

####################
# Windows
####################
Expand All @@ -199,6 +253,7 @@ jobs:
runs-on: ${{ matrix.os }}
env:
SCCACHE_GHA_ENABLED: "true"
SCCACHE_CACHE_ZSTD_LEVEL: "5"
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -250,7 +305,7 @@ jobs:
id: cpu-cores

- name: Sccache
uses: mozilla-actions/sccache-action@v0.0.10
uses: mozilla-actions/sccache-action@v0.0.11

- name: Set x64 vars
if: matrix.os == 'windows-2025'
Expand Down
5 changes: 5 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -258,11 +258,13 @@ option(LAGRANGE_EXAMPLES "Build all examples"
option(LAGRANGE_FMT_EIGEN_FIX "Avoid MSVC C1001 error releated to fmt and eigen" ${LAGRANGE_FMT_EIGEN_FIX_DEFAULT})
option(LAGRANGE_INSTALL "Enable installation" ${LAGRANGE_TOPLEVEL_PROJECT})
option(LAGRANGE_LIMIT_PARALLELISM "Limit parallelism according to available cpu/memory" OFF)
option(LAGRANGE_LIMIT_GITHUB_ACTIONS_PARALLELISM "Limit heavy compile jobs on GitHub Actions" OFF)
option(LAGRANGE_MORE_WARNINGS "Increase the level of warnings when compiling" OFF)
option(LAGRANGE_PROPAGATE_WARNINGS "Propagate warning flags to dependent targets" ON)
option(LAGRANGE_PERFORMANCE_TESTS "Build all performance tests" OFF)
option(LAGRANGE_UNIT_TESTS "Build all unit tests" ${LAGRANGE_TOPLEVEL_PROJECT})
option(LAGRANGE_USE_PCH "Enable precompiled headers" OFF)
option(LAGRANGE_USE_SPLIT_DEBUG_INFO "Store debug information in separate compressed files" OFF)
option(LAGRANGE_USE_WASM_EXCEPTIONS "Use -fwasm-exception flag with Emscripten" ON)
option(LAGRANGE_USE_WASM_THREADS "Enable threads (-pthread) with Emscripten" ON)
option(LAGRANGE_WITH_TRACY "Build tracy client with Lagrange" OFF)
Expand Down Expand Up @@ -441,6 +443,9 @@ set_property(GLOBAL PROPERTY USE_FOLDERS ON)
if(LAGRANGE_LIMIT_PARALLELISM)
lagrange_limit_parallelism()
endif()
if(LAGRANGE_LIMIT_GITHUB_ACTIONS_PARALLELISM)
lagrange_limit_github_actions_parallelism()
endif()

set(TBB_ENABLE_WASM_THREADS ${LAGRANGE_USE_WASM_THREADS})

Expand Down
5 changes: 5 additions & 0 deletions cmake/lagrange/lagrange_global_flags.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ else()
add_compile_options(${LAGRANGE_GLOBAL_FLAGS})
endif()

if(LAGRANGE_USE_SPLIT_DEBUG_INFO)
add_compile_options(-gsplit-dwarf -gz=zstd)
add_link_options(-Wl,--compress-debug-sections=zstd)
endif()

if(LAGRANGE_WITH_TRACY)
include(lagrange_filter_flags)
set(LAGRANGE_GLOBAL_FLAGS
Expand Down
13 changes: 13 additions & 0 deletions cmake/lagrange/lagrange_limit_parallelism.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,19 @@ function(lagrange_limit_parallelism)
endif()
endfunction()

function(lagrange_limit_github_actions_parallelism)
set(num_heavy_compile_jobs 2)
if(CMAKE_SYSTEM_NAME STREQUAL "Linux"
AND CMAKE_CXX_COMPILER_ID STREQUAL "Clang"
AND USE_SANITIZER MATCHES "([Aa]ddress)"
AND USE_SANITIZER MATCHES "([Uu]ndefined)")
set(num_heavy_compile_jobs 1)
endif()

message(STATUS "Parallelism: Limiting heavy compile pool to ${num_heavy_compile_jobs}")
set_property(GLOBAL APPEND PROPERTY JOB_POOLS pool-heavy-compile=${num_heavy_compile_jobs})
endfunction()

# If this file is run in script mode, it echoes the number of physical cores for use as
# the -j flag for cmake --build and ctest. Link parallelism is not relevant here — it is
# enforced by Ninja job pools set during the configure step.
Expand Down
12 changes: 12 additions & 0 deletions cmake/lagrange/lagrange_set_sanitizers.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,18 @@

include(sanitizers)

# Some bundled dependencies intentionally use operations rejected by UBSan (for example,
# function-pointer casts in Embree). Keep the sanitizer enabled for Lagrange while allowing
# concrete third-party targets to opt out without propagating the flag to their consumers.
function(lagrange_disable_ubsan_for_external target)
if(TARGET "${target}" AND USE_SANITIZER MATCHES "([Uu]ndefined)")
target_compile_options("${target}" PRIVATE
$<$<COMPILE_LANG_AND_ID:C,GNU,Clang,AppleClang>:-fno-sanitize=undefined>
$<$<COMPILE_LANG_AND_ID:CXX,GNU,Clang,AppleClang>:-fno-sanitize=undefined>
)
endif()
endfunction()

if(MSVC AND USE_SANITIZER)
# To use sanitizers with MSVC you need to disable incompatible options. See details here:
# https://learn.microsoft.com/en-us/cpp/sanitizers/asan?view=msvc-170#ide-msbuild
Expand Down
5 changes: 5 additions & 0 deletions cmake/recipes/external/OpenVDB.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,11 @@ function(openvdb_import_target)
# Inject real Boost dependencies instead of dummy Boost:headers one
foreach(name IN ITEMS openvdb_static openvdb_shared)
if(TARGET ${name})
if(LAGRANGE_LIMIT_GITHUB_ACTIONS_PARALLELISM
AND CMAKE_SYSTEM_NAME STREQUAL "Linux"
AND CMAKE_GENERATOR MATCHES "^Ninja")
set_property(TARGET ${name} PROPERTY JOB_POOL_COMPILE pool-heavy-compile)
endif()
target_link_libraries(${name}
PUBLIC
Boost::algorithm
Expand Down
27 changes: 27 additions & 0 deletions cmake/recipes/external/embree.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,14 @@ function(embree_import_target)

unignore_package(TBB)

# Embree has several memory-intensive translation units. Limit their concurrency on
# Linux when the project's memory-aware parallelism policy is enabled.
if(LAGRANGE_LIMIT_GITHUB_ACTIONS_PARALLELISM
AND CMAKE_SYSTEM_NAME STREQUAL "Linux"
AND CMAKE_GENERATOR MATCHES "^Ninja")
set_property(TARGET embree PROPERTY JOB_POOL_COMPILE pool-heavy-compile)
endif()

# Disable warnings
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
# Embree's subgrid.h is known for causing array subscript out of bound
Expand All @@ -146,6 +154,25 @@ function(embree_import_target)
target_compile_options(embree PRIVATE "-Wno-unused-but-set-variable")
endif()

# Embree intentionally dispatches through ABI-compatible function pointers and uses
# type-punning downcasts internally. Exclude only its implementation from UBSan; consumers
# and Lagrange's raycasting code remain instrumented.
foreach(target IN ITEMS
embree
embree_sse42
embree_avx
embree_avx2
embree_avx512
algorithms
lexers
math
simd
sys
tasking
)
lagrange_disable_ubsan_for_external(${target})
endforeach()

# Now we need to do some juggling to propagate the include directory properties
# along with the `embree` target
add_library(embree::embree INTERFACE IMPORTED GLOBAL)
Expand Down
4 changes: 4 additions & 0 deletions cmake/recipes/external/ufbx.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ target_include_directories(ufbx PUBLIC
set_target_properties(ufbx PROPERTIES FOLDER third_party)
set_target_properties(ufbx PROPERTIES POSITION_INDEPENDENT_CODE ON)

# ufbx's bit reader intentionally shifts by the encoded bit count before validating it.
# UBSan diagnoses this in valid FBX inputs, so exclude only the bundled implementation.
lagrange_disable_ubsan_for_external(ufbx)

# Install rules
set(CMAKE_INSTALL_DEFAULT_COMPONENT_NAME ufbx)
install(DIRECTORY ${ufbx_SOURCE_DIR} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
Expand Down
63 changes: 35 additions & 28 deletions modules/core/src/SurfaceMesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
// clang-format on

#include <array>
#include <cstring>
#include <map>
#include <string>

Expand Down Expand Up @@ -3697,40 +3698,46 @@ SurfaceMesh<Scalar, Index> to_surface_mesh(const SurfaceMeshInfo& info)

if (ai->is_indexed) {
switch (value_type) {
#define LA_X_restore_indexed(_, ValueType) \
case make_attribute_value_type<ValueType>(): { \
auto values = span<const ValueType>( \
reinterpret_cast<const ValueType*>(ai->values_bytes.data()), \
ai->values_bytes.size() / sizeof(ValueType)); \
auto indices = span<const Index>( \
reinterpret_cast<const Index*>(ai->indices_bytes.data()), \
ai->indices_bytes.size() / sizeof(Index)); \
id = mesh.template create_attribute_internal<ValueType>( \
ai->name, \
element, \
usage, \
ai->values_num_channels, \
values, \
indices); \
break; \
#define LA_X_restore_indexed(_, ValueType) \
case make_attribute_value_type<ValueType>(): { \
la_runtime_assert(ai->values_bytes.size() % sizeof(ValueType) == 0); \
la_runtime_assert(ai->indices_bytes.size() % sizeof(Index) == 0); \
std::vector<ValueType> values(ai->values_bytes.size() / sizeof(ValueType)); \
std::vector<Index> indices(ai->indices_bytes.size() / sizeof(Index)); \
if (!values.empty()) { \
std::memcpy(values.data(), ai->values_bytes.data(), ai->values_bytes.size()); \
} \
if (!indices.empty()) { \
std::memcpy(indices.data(), ai->indices_bytes.data(), ai->indices_bytes.size()); \
} \
id = mesh.template create_attribute_internal<ValueType>( \
ai->name, \
element, \
usage, \
ai->values_num_channels, \
span<const ValueType>(values.data(), values.size()), \
span<const Index>(indices.data(), indices.size())); \
break; \
}
LA_ATTRIBUTE_X(restore_indexed, 0)
#undef LA_X_restore_indexed
}
} else {
switch (value_type) {
#define LA_X_restore_attr(_, ValueType) \
case make_attribute_value_type<ValueType>(): { \
auto data = span<const ValueType>( \
reinterpret_cast<const ValueType*>(ai->data_bytes.data()), \
ai->data_bytes.size() / sizeof(ValueType)); \
id = mesh.template create_attribute_internal<ValueType>( \
ai->name, \
element, \
usage, \
ai->num_channels, \
data); \
break; \
#define LA_X_restore_attr(_, ValueType) \
case make_attribute_value_type<ValueType>(): { \
la_runtime_assert(ai->data_bytes.size() % sizeof(ValueType) == 0); \
std::vector<ValueType> data(ai->data_bytes.size() / sizeof(ValueType)); \
if (!data.empty()) { \
std::memcpy(data.data(), ai->data_bytes.data(), ai->data_bytes.size()); \
} \
id = mesh.template create_attribute_internal<ValueType>( \
ai->name, \
element, \
usage, \
ai->num_channels, \
span<const ValueType>(data.data(), data.size())); \
break; \
}
LA_ATTRIBUTE_X(restore_attr, 0)
#undef LA_X_restore_attr
Expand Down
Loading