Skip to content
Merged
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
11 changes: 9 additions & 2 deletions .github/workflows/avx-clang.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Native x86-64 build + tests with Clang (the AVX2/FMA path).
# Native x86-64 build + tests with Clang (the AVX2/FMA path). The Release leg also builds bench/.
name: avx-clang

on:
Expand All @@ -7,6 +7,7 @@ on:
paths:
- 'src/**'
- 'tests/**'
- 'bench/**'
- 'cmake/**'
- '**/CMakeLists.txt'
- '.github/workflows/avx-clang.yml'
Expand All @@ -21,13 +22,19 @@ concurrency:

jobs:
build-test:
name: ${{ matrix.build_type }}
runs-on: ubuntu-latest
container:
image: ghcr.io/aotodev/sgl-ci:latest
strategy:
fail-fast: false
matrix:
build_type: [Debug, Release]
steps:
- uses: actions/checkout@v4
- name: Configure, build and test
run: |
CXX=clang++ cmake -G Ninja -B build -DCMAKE_BUILD_TYPE=Release -DSGL_TESTS=ON
CXX=clang++ cmake -G Ninja -B build -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DSGL_TESTS=ON \
-DSGL_BENCHMARKS=${{ matrix.build_type == 'Release' && 'ON' || 'OFF' }}
cmake --build build -j
ctest --test-dir build --output-on-failure
11 changes: 9 additions & 2 deletions .github/workflows/avx-gcc.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Native x86-64 build + tests with GCC (the AVX2/FMA path).
# Native x86-64 build + tests with GCC (the AVX2/FMA path). The Release leg also builds bench/.
name: avx-gcc

on:
Expand All @@ -7,6 +7,7 @@ on:
paths:
- 'src/**'
- 'tests/**'
- 'bench/**'
- 'cmake/**'
- '**/CMakeLists.txt'
- '.github/workflows/avx-gcc.yml'
Expand All @@ -21,13 +22,19 @@ concurrency:

jobs:
build-test:
name: ${{ matrix.build_type }}
runs-on: ubuntu-latest
container:
image: ghcr.io/aotodev/sgl-ci:latest
strategy:
fail-fast: false
matrix:
build_type: [Debug, Release]
steps:
- uses: actions/checkout@v4
- name: Configure, build and test
run: |
CXX=g++ cmake -G Ninja -B build -DCMAKE_BUILD_TYPE=Release -DSGL_TESTS=ON
CXX=g++ cmake -G Ninja -B build -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DSGL_TESTS=ON \
-DSGL_BENCHMARKS=${{ matrix.build_type == 'Release' && 'ON' || 'OFF' }}
cmake --build build -j
ctest --test-dir build --output-on-failure
45 changes: 45 additions & 0 deletions .github/workflows/install.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Installs sgl, then builds tests/install against it through find_package, as a downstream project would.
name: install

on:
push:
branches: [master]
paths:
- 'src/**'
- 'tests/install/**'
- 'cmake/**'
- '**/CMakeLists.txt'
- '.github/workflows/install.yml'
pull_request:

permissions:
contents: read

concurrency:
group: "${{ github.workflow }}-${{ github.ref }}"
cancel-in-progress: true

jobs:
install:
name: ${{ matrix.cxx }}
runs-on: ubuntu-latest
container:
image: ghcr.io/aotodev/sgl-ci:latest
strategy:
fail-fast: false
matrix:
cxx: [g++, clang++]
env:
CXX: ${{ matrix.cxx }}
steps:
- uses: actions/checkout@v4
- name: Build and install sgl
run: |
cmake -G Ninja -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="$RUNNER_TEMP/prefix"
cmake --build build -j
cmake --install build
- name: Build and run the consumer
run: |
cmake -G Ninja -S tests/install -B consumer -DCMAKE_PREFIX_PATH="$RUNNER_TEMP/prefix"
cmake --build consumer
ctest --test-dir consumer --output-on-failure
43 changes: 43 additions & 0 deletions .github/workflows/macos.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Apple Silicon build + tests with Homebrew LLVM Clang and its libc++. Not Apple Clang: CMake scans
# C++ modules only for LLVM Clang, GCC and MSVC.
name: macos

on:
push:
branches: [master]
paths:
- 'src/**'
- 'tests/**'
- 'cmake/**'
- '**/CMakeLists.txt'
- '.github/workflows/macos.yml'
pull_request:

permissions:
contents: read

concurrency:
group: "${{ github.workflow }}-${{ github.ref }}"
cancel-in-progress: true

jobs:
build-test:
name: ${{ matrix.build_type }}
runs-on: macos-latest
strategy:
fail-fast: false
matrix:
build_type: [Debug, Release]
steps:
- uses: actions/checkout@v4
- name: Install LLVM and Ninja
run: brew install llvm ninja
- name: Configure, build and test
# Links Homebrew's libc++: its headers are newer than the system libc++.
run: |
llvm="$(brew --prefix llvm)"
cmake -G Ninja -B build -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DSGL_TESTS=ON \
-DCMAKE_C_COMPILER="$llvm/bin/clang" -DCMAKE_CXX_COMPILER="$llvm/bin/clang++" \
-DCMAKE_EXE_LINKER_FLAGS="-L$llvm/lib/c++ -L$llvm/lib/unwind -lunwind -Wl,-rpath,$llvm/lib/c++"
cmake --build build -j
ctest --test-dir build --output-on-failure
37 changes: 37 additions & 0 deletions .github/workflows/msvc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Windows x64 build + tests with MSVC, on the hosted runner: the CI image cannot run MSVC.
name: msvc

on:
push:
branches: [master]
paths:
- 'src/**'
- 'tests/**'
- 'cmake/**'
- '**/CMakeLists.txt'
- '.github/workflows/msvc.yml'
pull_request:

permissions:
contents: read

concurrency:
group: "${{ github.workflow }}-${{ github.ref }}"
cancel-in-progress: true

jobs:
build-test:
name: ${{ matrix.build_type }}
runs-on: windows-latest
strategy:
fail-fast: false
matrix:
build_type: [Debug, Release]
steps:
- uses: actions/checkout@v4
- uses: ilammy/msvc-dev-cmd@v1
- name: Configure, build and test
run: |
cmake -G Ninja -B build -DCMAKE_CXX_COMPILER=cl -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DSGL_TESTS=ON
cmake --build build -j
ctest --test-dir build --output-on-failure
7 changes: 6 additions & 1 deletion .github/workflows/neon.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,20 @@ concurrency:

jobs:
build-test:
name: ${{ matrix.build_type }}
runs-on: ubuntu-latest
container:
image: ghcr.io/aotodev/sgl-ci:latest
strategy:
fail-fast: false
matrix:
build_type: [Debug, Release]
steps:
- uses: actions/checkout@v4
- name: Configure, build and test
run: |
cmake -G Ninja -B build \
-DCMAKE_TOOLCHAIN_FILE=cmake/toolchain-aarch64.cmake \
-DCMAKE_BUILD_TYPE=Release -DSGL_TESTS=ON
-DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DSGL_TESTS=ON
cmake --build build -j
ctest --test-dir build --output-on-failure
42 changes: 42 additions & 0 deletions .github/workflows/sanitize.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# ASan + UBSan over the test suite. Both compilers: only Clang's UBSan checks pointer-offset overflow.
name: sanitize

on:
push:
branches: [master]
paths:
- 'src/**'
- 'tests/**'
- 'cmake/**'
- '**/CMakeLists.txt'
- '.github/workflows/sanitize.yml'
pull_request:

permissions:
contents: read

concurrency:
group: "${{ github.workflow }}-${{ github.ref }}"
cancel-in-progress: true

jobs:
sanitize:
name: ${{ matrix.cxx }}
runs-on: ubuntu-latest
container:
image: ghcr.io/aotodev/sgl-ci:latest
# LeakSanitizer suspends threads through ptrace.
options: --cap-add=SYS_PTRACE
strategy:
fail-fast: false
matrix:
cxx: [clang++, g++]
env:
UBSAN_OPTIONS: print_stacktrace=1
steps:
- uses: actions/checkout@v4
- name: Configure, build and test
run: |
CXX=${{ matrix.cxx }} cmake -G Ninja -B build -DCMAKE_BUILD_TYPE=Debug -DSGL_TESTS=ON -DSGL_SANITIZERS=ON
cmake --build build -j
ctest --test-dir build --output-on-failure
16 changes: 15 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# Compiler support check: each minimum is the higher of the C++20 named-module floor
# and the CWG 2518 floor.
# ---------------------------------------------------------------------------------------
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
if(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
message(FATAL_ERROR "sgl: Apple Clang is not supported: CMake scans C++20 modules only for LLVM "
"Clang, GCC and MSVC. On macOS use LLVM Clang, e.g. Homebrew's llvm.")
elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "17")
message(FATAL_ERROR "sgl: Clang >= 17 required (C++20 modules + CWG 2518) "
"(found ${CMAKE_CXX_COMPILER_VERSION}).")
Expand Down Expand Up @@ -73,6 +76,13 @@ option(SGL_PIC "Build with position-independent code" OFF)
option(SGL_TESTS "Build sgl tests" OFF)
option(SGL_BENCHMARKS "Build sgl benchmarks" OFF)
option(SGL_INSTALL "Generate install + find_package() rules" "${PROJECT_IS_TOP_LEVEL}")
option(SGL_SANITIZERS "Build everything with ASan + UBSan (GCC/Clang, top level only)" OFF)

# Directory-wide so the module units, the tests and the fetched GoogleTest all agree.
if(SGL_SANITIZERS AND PROJECT_IS_TOP_LEVEL AND NOT MSVC)
add_compile_options(-fsanitize=address,undefined -fno-sanitize-recover=all -fno-omit-frame-pointer)
add_link_options(-fsanitize=address,undefined)
endif()

# ---------------------------------------------------------------------------------------
# Library (always STATIC: the module interface is the real deliverable)
Expand All @@ -90,6 +100,10 @@ target_compile_options(${PROJECT_NAME} PUBLIC
"$<$<AND:$<BOOL:${IS_X86_64}>,$<CXX_COMPILER_FRONTEND_VARIANT:MSVC>>:/arch:AVX2>"
)

# C4324: vec3 and the boxes are padded to a register width on purpose. PUBLIC: MSVC repeats it in
# every TU that uses the types, so a consumer on /W4 /WX needs it too.
target_compile_options(${PROJECT_NAME} PUBLIC "$<$<CXX_COMPILER_ID:MSVC>:/wd4324>")

if(SGL_PIC)
set_target_properties(${PROJECT_NAME} PROPERTIES POSITION_INDEPENDENT_CODE ON)
endif()
Expand Down
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
[![avx-gcc](https://github.com/aotodev/sgl/actions/workflows/avx-gcc.yml/badge.svg?branch=master)](https://github.com/aotodev/sgl/actions/workflows/avx-gcc.yml)
[![avx-clang](https://github.com/aotodev/sgl/actions/workflows/avx-clang.yml/badge.svg?branch=master)](https://github.com/aotodev/sgl/actions/workflows/avx-clang.yml)
[![neon](https://github.com/aotodev/sgl/actions/workflows/neon.yml/badge.svg?branch=master)](https://github.com/aotodev/sgl/actions/workflows/neon.yml)
[![msvc](https://github.com/aotodev/sgl/actions/workflows/msvc.yml/badge.svg?branch=master)](https://github.com/aotodev/sgl/actions/workflows/msvc.yml)
[![macos](https://github.com/aotodev/sgl/actions/workflows/macos.yml/badge.svg?branch=master)](https://github.com/aotodev/sgl/actions/workflows/macos.yml)
[![sanitize](https://github.com/aotodev/sgl/actions/workflows/sanitize.yml/badge.svg?branch=master)](https://github.com/aotodev/sgl/actions/workflows/sanitize.yml)
[![install](https://github.com/aotodev/sgl/actions/workflows/install.yml/badge.svg?branch=master)](https://github.com/aotodev/sgl/actions/workflows/install.yml)

A C++20 module-based, SIMD-accelerated computational geometry library.

Expand Down Expand Up @@ -116,6 +120,8 @@ compiles unchanged against the other.
floor for C++20 named modules plus CWG 2518 (`static_assert(false)` in
uninstantiated template branches)
- A C++20 standard library and toolchain with named-module support
- On macOS, LLVM Clang (e.g. Homebrew's `llvm`): Apple Clang is not supported, because
CMake does not scan C++20 modules for it

## Building

Expand All @@ -142,6 +148,11 @@ CXX=clang++ cmake -B build -G Ninja -DCMAKE_CXX_FLAGS="-stdlib=libc++"

# MSVC (x64 Native Tools prompt, or a cross wrapper), Ninja generator
cmake -B build -G Ninja

# macOS: Homebrew LLVM Clang, linking its own libc++ rather than the older system one
llvm="$(brew --prefix llvm)"
CXX="$llvm/bin/clang++" cmake -B build -G Ninja \
-DCMAKE_EXE_LINKER_FLAGS="-L$llvm/lib/c++ -L$llvm/lib/unwind -lunwind -Wl,-rpath,$llvm/lib/c++"
```

Then `cmake --build build` as usual.
Expand Down Expand Up @@ -190,6 +201,7 @@ module interface with the matching instruction set.
| `SGL_BENCHMARKS` | `OFF` | Build the Google Benchmark suite |
| `SGL_PIC` | `OFF` | Build with position-independent code |
| `SGL_INSTALL` | top-level only | Generate install + `find_package()` rules |
| `SGL_SANITIZERS` | `OFF` | Build with ASan + UBSan (GCC/Clang) |

### Running the tests

Expand Down
27 changes: 17 additions & 10 deletions src/sgl/soa.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,11 @@ template <> struct vocab_point<ivec3> {
template <class V>
concept vocab_point_type = requires { vocab_point<std::remove_cvref_t<V>>::dimension; };

template <class V> constexpr std::size_t dimension_of = vocab_point<V>::dimension;
template <class V> constexpr std::size_t dimension_of = 0;
template <> constexpr std::size_t dimension_of<vec2> = 2;
template <> constexpr std::size_t dimension_of<vec3> = 3;
template <> constexpr std::size_t dimension_of<ivec2> = 2;
template <> constexpr std::size_t dimension_of<ivec3> = 3;
template <> constexpr std::size_t dimension_of<box2d> = 2;
template <> constexpr std::size_t dimension_of<box3d> = 3;

Expand Down Expand Up @@ -711,7 +715,8 @@ ray_hit<T, D> hit_by(
const std::array<T, D>& origin, const std::array<T, D>& direction, const T t_min = 0, const T t_max = std::numeric_limits<T>::infinity()) noexcept {
std::array<T, D> inv{};
for (std::size_t a{}; a < D; ++a) {
inv[a] = T{1} / direction[a];
/* No division by zero: keeps the FP divide-by-zero flag clear, and MSVC flags a constant one (C4723) in the caller. */
inv[a] = direction[a] == T{0} ? std::copysign(std::numeric_limits<T>::infinity(), direction[a]) : T{1} / direction[a];
}
return {origin, inv, t_min, t_max};
}
Expand Down Expand Up @@ -922,14 +927,16 @@ template <point_range R> auto bounds(const R& range) noexcept {
auto hi1{hi0};

std::size_t i{};
for (; std::floating_point<T> && i + 2 * lanes <= n; i += 2 * lanes) {
for (std::size_t a{}; a < D; ++a) {
const auto x0{wide::load(view.axis[a] + i)};
const auto x1{wide::load(view.axis[a] + i + lanes)};
lo0[a] = min(x0, lo0[a]);
hi0[a] = max(x0, hi0[a]);
lo1[a] = min(x1, lo1[a]);
hi1[a] = max(x1, hi1[a]);
if constexpr (std::floating_point<T>) {
for (; i + 2 * lanes <= n; i += 2 * lanes) {
for (std::size_t a{}; a < D; ++a) {
const auto x0{wide::load(view.axis[a] + i)};
const auto x1{wide::load(view.axis[a] + i + lanes)};
lo0[a] = min(x0, lo0[a]);
hi0[a] = max(x0, hi0[a]);
lo1[a] = min(x1, lo1[a]);
hi1[a] = max(x1, hi1[a]);
}
}
}
for (; i + lanes <= n; i += lanes) {
Expand Down
Loading
Loading