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
1 change: 1 addition & 0 deletions .github/workflows/root-ci-config/buildconfig/global.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ arrow=OFF
asan=OFF
asimage=ON
asserts=OFF
builtin_bvh=ON
builtin_cdt=ON
builtin_cfitsio=OFF
builtin_civetweb=OFF
Expand Down
21 changes: 21 additions & 0 deletions builtins/bvh/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
cmake_minimum_required(VERSION 3.21)
project(bvh VERSION 2.0)

option(BVH_BUILD_C_API "Builds the C API library wrapper" OFF)
option(BVH_C_API_STATIC_LINK_STDLIB "Link the C API library statically against the standard C++ library (only supported by clang/gcc)" OFF)
option(BVH_C_API_UNSAFE_CASTS "Enable unsafe casts in C API" OFF)

set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)

add_subdirectory(src/bvh/v2)

if (PROJECT_IS_TOP_LEVEL)
include(CTest)
if (BUILD_TESTING)
add_subdirectory(test)
endif()

include(cmake/Install.cmake)
endif()
83 changes: 83 additions & 0 deletions builtins/bvh/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# BVH Construction and Traversal Library

![Build Status](https://github.com/madmann91/bvh/workflows/build-and-test/badge.svg)

> Note: This is the 2nd version of this library. Check the `v1` branch for the older, first version
> of this library.

This library is a small, standalone library for BVH construction and traversal. It is licensed
under the MIT license.

![Example rendering generated by a path tracer using this library](render.jpg)
(Scene by Blend Swap user MaTTeSr, available [here](https://www.blendswap.com/blend/18762),
distributed under CC-BY 3.0)

## Performance

Here is a comparison of this library with other alternatives
([Embree](https://github.com/embree/embree),
[Fast-BVH](https://github.com/brandonpelfrey/Fast-BVH), and
[nanort](https://github.com/lighttransport/nanort)):

![Performance comparison with Embree, nanort, and Fast-BVH](chart.png)

## Features

Here is a list of features supported by this library (changes from `v1` are indicated with [NEW]):

- [NEW] C++20 interface using `std::span` instead of raw pointers,
- Low-level API with direct access to various builders,
- [NEW] High-level `DefaultBuilder` API which selects the best builder depending on the desired
BVH quality level.
- High-quality, single-threaded sweeping SAH builder,
- Fast, medium-quality, single-threaded binned SAH builder inspired by
"On Fast Construction of SAH-based Bounding Volume Hierarchies", by I. Wald,
- Fast, high-quality, multithreaded mini-tree BVH builder inspired by
"Rapid Bounding Volume Hierarchy Generation using Mini Trees", by P. Ganestam et al.,
- Reinsertion optimizer based on "Parallel Reinsertion for Bounding Volume Hierarchy
Optimization", by D. Meister and J. Bittner,
- Fast and robust traversal algorithm using "Robust BVH Ray Traversal", by T. Ize.
- Fast ray-triangle intersection algorithm based on
"Fast, Minimum Storage Ray/Triangle Intersection", by T. Möller and B. Trumbore,
- [NEW] Surface area traversal order heuristic for shadow rays based on
"SATO: Surface Area Traversal Order for Shadow Ray Tracing", by J. Nah and D. Manocha,
- Fast ray-sphere intersection routine,
- [NEW] Serialization/deserialization interface,
- [NEW] Variable amount of dimensions (e.g. 2D, 3D, 4D BVHs are supported) and different scalar types
(e.g. `float` or `double`),
- [NEW] Only depends on the standard library (parallelization uses a custom thread pool based on
`std::thread`),
- [NEW] C API for the high-level parts of the library is available.

## Building

This library is header-only, and can be added as a CMake subproject by cloning or adding as this
repository as submodule, for instance in `<your-project>/contrib/bvh`, and then adding this to
`<your-project>/CMakeLists.txt`:

add_subdirectory(contrib/bvh)
target_link_library(my_project PUBLIC bvh)

If you want to build the examples, use:

mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=<Debug|Release> -DENABLE_TESTING=ON
cmake --build .

## C API

The library can be used via a small set of high-level C bindings. These bindings are not enabled by
default, but can be built by configuring CMake with `-DBVH_BUILD_C_API=ON`. Additionally, if the
intent is to use the library in a pure C environment which does not have the C++ standard library as
a dependency, it might be a good idea to statically the C++ standard library. That can be done by
adding the flag `-DBVH_STATIC_LINK_STDLIB_C_API=ON` to the CMake command line.

## Usage

The library contains several examples that are kept up-to-date with the API:

- A [basic example](test/simple_example.cpp) that traces one ray on a scene made of a couple of triangles,
- A [benchmarking utility](test/benchmark.cpp) that showcases what the library can do.
- A [serialization test](test/serialize.cpp) that shows how to save and load a BVH from a file.
- A [C API example](test/c_api_example.c) that shows how to use the C bindings to this library.
44 changes: 44 additions & 0 deletions builtins/bvh/cmake/Install.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
include(GNUInstallDirs)
set(bvh_targets bvh)
if (BVH_BUILD_C_API)
list(APPEND bvh_targets bvh_c)
endif()

install(
DIRECTORY ${PROJECT_SOURCE_DIR}/src/bvh
DESTINATION include
FILES_MATCHING PATTERN "*.h"
PATTERN "c_api" EXCLUDE)
install(
FILES ${PROJECT_SOURCE_DIR}/src/bvh/v2/c_api/bvh.h
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/bvh/v2/c_api/)
install(
TARGETS ${bvh_targets}
EXPORT bvh_exports
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
install(
EXPORT bvh_exports
FILE bvh-targets.cmake
NAMESPACE bvh::v2::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/bvh/v2/)

include(CMakePackageConfigHelpers)
set(CMAKE_INSTALL_CMAKEDIR ${CMAKE_INSTALL_LIBDIR}/cmake/bvh/v2/)

configure_package_config_file(
"${PROJECT_SOURCE_DIR}/cmake/bvh-config.cmake.in"
"${CMAKE_CURRENT_BINARY_DIR}/bvh-config.cmake"
INSTALL_DESTINATION ${CMAKE_INSTALL_CMAKEDIR})

write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/bvh-config-version.cmake"
COMPATIBILITY AnyNewerVersion)

install(
FILES
"${CMAKE_CURRENT_BINARY_DIR}/bvh-config.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/bvh-config-version.cmake"
DESTINATION ${CMAKE_INSTALL_CMAKEDIR})
5 changes: 5 additions & 0 deletions builtins/bvh/cmake/bvh-config.cmake.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@PACKAGE_INIT@

include("${CMAKE_CURRENT_LIST_DIR}/bvh-targets.cmake")

check_required_components(bvh_c)
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ target_include_directories(bvh INTERFACE

set_target_properties(bvh PROPERTIES CXX_STANDARD 20)

install(
DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/
DESTINATION include/bvh/v2
FILES_MATCHING PATTERN "*.h"
PATTERN "c_api" EXCLUDE)
if (CMAKE_CXX_STANDARD LESS 20)
# backup implementation of span
target_include_directories(bvh INTERFACE ${CMAKE_SOURCE_DIR}/core/foundation/inc) # for ROOT/span.hxx
endif()

add_library(bvh::bvh ALIAS bvh)

if (BVH_BUILD_C_API)
add_subdirectory(c_api)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ Minor changes have been subsequently been applied to achieve compilation with C+
- old-style struct construction for objects of type "Reinsertion"
- use of std::inner_product instead of std::transform_reduce (gcc 8.5 had problems)

This is needed since ROOT should compile with C++17.
This is needed since ROOT should compile with C++17. TODO: remove these patches
when minimum required is C++20 and move builtin source to LCG. Or move to LCG link
and use PATCH_COMMAND in ExternalProject_Add to apply these C++17 fallback patches.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class BinnedSahBuilder : public TopDownSahBuilder<Node> {
public:
using typename TopDownSahBuilder<Node>::Config;

BVH_ALWAYS_INLINE static Bvh<Node> build(
[[nodiscard]] BVH_ALWAYS_INLINE static Bvh<Node> build(
std::span<const BBox> bboxes,
std::span<const Vec> centers,
const Config& config = {})
Expand Down
47 changes: 27 additions & 20 deletions builtins/bvh/v2/bvh.h → builtins/bvh/src/bvh/v2/bvh.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,17 @@

Bvh& operator = (Bvh&&) = default;

//bool operator == (const Bvh& other) const = default;
//bool operator != (const Bvh& other) const = default;
#if defined(__cplusplus) && __cplusplus >= 202002L
bool operator == (const Bvh& other) const = default;
bool operator != (const Bvh& other) const = default;
#else
bool operator == (const Bvh& other) const {
return other.nodes == nodes && other.prim_ids == prim_ids;
}
bool operator != (const Bvh& other) const {
return other.nodes != nodes || other.prim_ids != prim_ids;
}
#endif

/// Returns whether the node located at the given index is the left child of its parent.
static BVH_ALWAYS_INLINE bool is_left_sibling(size_t node_id) { return node_id % 2 == 1; }
Expand All @@ -60,11 +63,11 @@
BVH_ALWAYS_INLINE const Node& get_root() const { return nodes[0]; }

/// Extracts the BVH rooted at the given node index.
inline Bvh extract_bvh(size_t root_id) const;
[[nodiscard]] inline Bvh extract_bvh(size_t root_id) const;

/// Traverses the BVH from the given index in `start` using the provided stack. Every leaf
/// encountered on the way is processed using the given `LeafFn` function, and every pair of
/// nodes is processed with the function in `HitFn`, which returns a triplet of booleans
/// nodes is processed with the function in `InnerFn`, which returns a triplet of booleans
/// indicating whether the first child should be processed, whether the second child should be
/// processed, and whether to traverse the second child first instead of the other way around.
template <bool IsAnyHit, typename Stack, typename LeafFn, typename InnerFn>
Expand All @@ -87,8 +90,11 @@
template <typename LeafFn = IgnoreArgs>
inline void refit(LeafFn&& = {});

template <typename IndexType = typename Index::Type>
inline void serialize(OutputStream&) const;
static inline Bvh deserialize(InputStream&);

template <typename IndexType = typename Index::Type>
[[nodiscard]] static inline Bvh deserialize(InputStream&);
};

template <typename Node>
Expand Down Expand Up @@ -146,12 +152,10 @@
stack.push(far_index);
}
top = near_index;
} else if (hit_right) {
} else if (hit_right)
top = right.index;
}
else [[unlikely]] {
else [[unlikely]]
goto restart;
}
}

[[maybe_unused]] auto was_hit = leaf_fn(top.first_id(), top.first_id() + top.prim_count());
Expand All @@ -165,18 +169,19 @@
template <bool IsAnyHit, bool IsRobust, typename Stack, typename LeafFn, typename InnerFn>
void Bvh<Node>::intersect(const Ray& ray, Index start, Stack& stack, LeafFn&& leaf_fn, InnerFn&& inner_fn) const {
auto inv_dir = ray.template get_inv_dir<!IsRobust>();
auto inv_dir_pad_or_inv_org = IsRobust ? ray.pad_inv_dir(inv_dir) : -inv_dir * ray.org;
auto inv_org = -inv_dir * ray.org;

Check warning on line 172 in builtins/bvh/src/bvh/v2/bvh.h

View workflow job for this annotation

GitHub Actions / alma8

variable ‘inv_org’ set but not used [-Wunused-but-set-variable]

Check warning on line 172 in builtins/bvh/src/bvh/v2/bvh.h

View workflow job for this annotation

GitHub Actions / alma8

variable ‘inv_org’ set but not used [-Wunused-but-set-variable]

Check warning on line 172 in builtins/bvh/src/bvh/v2/bvh.h

View workflow job for this annotation

GitHub Actions / alma8

variable ‘inv_org’ set but not used [-Wunused-but-set-variable]

Check warning on line 172 in builtins/bvh/src/bvh/v2/bvh.h

View workflow job for this annotation

GitHub Actions / alma8

variable ‘inv_org’ set but not used [-Wunused-but-set-variable]

Check warning on line 172 in builtins/bvh/src/bvh/v2/bvh.h

View workflow job for this annotation

GitHub Actions / alma8

variable ‘inv_org’ set but not used [-Wunused-but-set-variable]

Check warning on line 172 in builtins/bvh/src/bvh/v2/bvh.h

View workflow job for this annotation

GitHub Actions / alma8

variable ‘inv_org’ set but not used [-Wunused-but-set-variable]

Check warning on line 172 in builtins/bvh/src/bvh/v2/bvh.h

View workflow job for this annotation

GitHub Actions / alma8

variable ‘inv_org’ set but not used [-Wunused-but-set-variable]

Check warning on line 172 in builtins/bvh/src/bvh/v2/bvh.h

View workflow job for this annotation

GitHub Actions / alma8

variable ‘inv_org’ set but not used [-Wunused-but-set-variable]

Check warning on line 172 in builtins/bvh/src/bvh/v2/bvh.h

View workflow job for this annotation

GitHub Actions / alma8

variable ‘inv_org’ set but not used [-Wunused-but-set-variable]

Check warning on line 172 in builtins/bvh/src/bvh/v2/bvh.h

View workflow job for this annotation

GitHub Actions / alma8

variable ‘inv_org’ set but not used [-Wunused-but-set-variable]

Check warning on line 172 in builtins/bvh/src/bvh/v2/bvh.h

View workflow job for this annotation

GitHub Actions / alma8

variable ‘inv_org’ set but not used [-Wunused-but-set-variable]

Check warning on line 172 in builtins/bvh/src/bvh/v2/bvh.h

View workflow job for this annotation

GitHub Actions / alma8

variable ‘inv_org’ set but not used [-Wunused-but-set-variable]
auto inv_dir_pad = ray.pad_inv_dir(inv_dir);
auto octant = ray.get_octant();

traverse_top_down<IsAnyHit>(start, stack, leaf_fn, [&] (const Node& left, const Node& right) {
inner_fn(left, right);
std::pair<Scalar, Scalar> intr_left, intr_right;
if constexpr (IsRobust) {
intr_left = left .intersect_robust(ray, inv_dir, inv_dir_pad_or_inv_org, octant);
intr_right = right.intersect_robust(ray, inv_dir, inv_dir_pad_or_inv_org, octant);
intr_left = left .intersect_robust(ray, inv_dir, inv_dir_pad, octant);
intr_right = right.intersect_robust(ray, inv_dir, inv_dir_pad, octant);
} else {
intr_left = left .intersect_fast(ray, inv_dir, inv_dir_pad_or_inv_org, octant);
intr_right = right.intersect_fast(ray, inv_dir, inv_dir_pad_or_inv_org, octant);
intr_left = left .intersect_fast(ray, inv_dir, inv_org, octant);
intr_right = right.intersect_fast(ray, inv_dir, inv_org, octant);
}
return std::make_tuple(
intr_left.first <= intr_left.second,
Expand Down Expand Up @@ -222,24 +227,26 @@
}

template <typename Node>
template <typename IndexType>
void Bvh<Node>::serialize(OutputStream& stream) const {
stream.write(nodes.size());
stream.write(prim_ids.size());
stream.write(static_cast<IndexType>(nodes.size()));
stream.write(static_cast<IndexType>(prim_ids.size()));
for (auto&& node : nodes)
node.serialize(stream);
for (auto&& prim_id : prim_ids)
stream.write(prim_id);
stream.write(static_cast<IndexType>(prim_id));
}

template <typename Node>
template <typename IndexType>
Bvh<Node> Bvh<Node>::deserialize(InputStream& stream) {
Bvh bvh;
bvh.nodes.resize(stream.read<size_t>());
bvh.prim_ids.resize(stream.read<size_t>());
bvh.nodes.resize(stream.read<IndexType>());
bvh.prim_ids.resize(stream.read<IndexType>());
for (auto& node : bvh.nodes)
node = Node::deserialize(stream);
for (auto& prim_id : bvh.prim_ids)
prim_id = stream.read<size_t>();
prim_id = stream.read<IndexType>();
return bvh;
}

Expand Down
19 changes: 19 additions & 0 deletions builtins/bvh/src/bvh/v2/c_api/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
add_library(bvh_c SHARED bvh.cpp)
target_link_libraries(bvh_c PRIVATE bvh)
target_compile_definitions(bvh_c PRIVATE -DBVH_BUILD_API)
target_include_directories(bvh_c INTERFACE
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/src>
$<INSTALL_INTERFACE:include>)
set_target_properties(bvh_c PROPERTIES
CXX_STANDARD 20
CXX_VISIBILITY_PRESET hidden
INTERPROCEDURAL_OPTIMIZATION_RELEASE ON)

if (BVH_C_API_STATIC_LINK_STDLIB)
# Link statically against standard C++ library
target_link_options(bvh_c PRIVATE $<$<C_COMPILER_ID:GNU,Clang>:-static-libstdc++>)
endif()

if (BVH_C_API_UNSAFE_CASTS)
target_compile_definitions(bvh_c PRIVATE -DBVH_C_API_UNSAFE_CASTS)
endif()
28 changes: 28 additions & 0 deletions builtins/bvh/src/bvh/v2/c_api/bvh.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#include <bvh/v2/c_api/bvh.h>
#include <bvh/v2/c_api/bvh_impl.h>
#include <bvh/v2/thread_pool.h>

namespace bvh::v2::c_api {

BVH_TYPES(float, 2, 2f)
BVH_TYPES(float, 3, 3f)
BVH_TYPES(double, 2, 2d)
BVH_TYPES(double, 3, 3d)

extern "C" {

BVH_EXPORT struct bvh_thread_pool* bvh_thread_pool_create(size_t thread_count) {
return reinterpret_cast<bvh_thread_pool*>(new bvh::v2::ThreadPool(thread_count));
}

BVH_EXPORT void bvh_thread_pool_destroy(bvh_thread_pool* thread_pool) {
return delete reinterpret_cast<bvh::v2::ThreadPool*>(thread_pool);
}

BVH_IMPL(float, 2, 2f)
BVH_IMPL(float, 3, 3f)
BVH_IMPL(double, 2, 2d)
BVH_IMPL(double, 3, 3d)

} // extern "C"
} // namespace bvh::v2::c_api
Loading
Loading