portableRT is a C++ library that enables your application to perform ray tracing using all available hardware through a single API. On CPUs, it uses Embree. On GPUs, the user can choose between a general GPGPU approach, ideal for older or compute-focused GPUs without dedicated ray tracing units, or make use of the hardware-accelerated ray tracing units (when available)
This first release is intentionally minimal. It performs only ray triangle intersections, yet it does so through every supported backend. On GPUs it uses dedicated ray tracing cores or general GPGPU if desired. On the CPU it runs in parallel with std::thread for both the Embree and generic CPU backends, while the SYCL backend relies on SYCL’s own parallel execution.
| Device Type | Recommended Backend |
|---|---|
| NVIDIA GPUs with RT cores | OptiX |
| AMD GPUs with Ray Accelerators | HIP |
| Intel GPUs with Ray Tracing Units (RTUs) | Embree SYCL |
| CPUs | Embree CPU |
| CPUs without embree installation | CPU |
| GPUs without dedicated ray tracing units (any) | SYCL |
Download the latest release from the releases page.
Then include it in your project like this:
cmake_minimum_required(VERSION 3.17)
project(my_project LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(portableRT CONFIG REQUIRED)
add_executable(my_project main.cpp)
target_link_libraries(my_project PRIVATE portableRT::portableRT)And that's it! You can now use portableRT with all the backends enabled in your project.
You can select a backend at runtime using the select_backend function, as well as list all the available backends with available_backends().
By calling set_tris(std::vector<std::array<float, 9>>) and providing an array of triangles, the acceleration structure will be built automatically.
backend->set_tris(triangles);By calling closest_hits(std::vector<Ray>) and providing an array of rays, the nearest hit for each ray will be computed and returned as an array of distances to the intersection (or infinity if the ray doesn't intersect any triangle). It can be called as a free function that doesn't relies on virtual dispatch (and the only overhead is just a function pointer indirection), or as a member function of the backend object.
std::vector<float> hits = prt::closest_hits({rays});If you're doing depth visualization for example and only care about the "t" component of the intersection, or you just need the primitive ID of the intersected triangle, you're covered. Use filter tags to save bandwidth and skip computing the other components!
Filter the intersection results by passing any combination of tags to closest_hits. Available tags:
filter::t: distance to the intersectionfilter::uv: barycentric coordinatesfilter::primitive_id: index of the intersected trianglefilter::p: intersection pointfilter::valid: whether the intersection is valid
auto hits = prt::closest_hits<filter::t, filter::primitive_id, filter::uv, filter::p, filter::valid>(rays);Filtering is currently unavailable for the moment in the HIP, SYCL and Embree SYCL backends.
#include <array>
#include <iostream>
#include <portableRT/portableRT.hpp>
int main() {
std::array<float, 9> vertices = {-1, -1, 0, 1, -1, 0, 0, 1, 0};
prt::Ray hit_ray;
hit_ray.origin = std::array<float, 3>{0, 0, -1};
hit_ray.direction = std::array<float, 3>{0, 0, 1};
prt::Ray miss_ray;
miss_ray.origin = std::array<float, 3>{-2, 0, -1};
miss_ray.direction = std::array<float, 3>{0, 0, 1};
for (auto backend : prt::available_backends()) {
std::cout << "Testing " << backend->name() << std::endl;
prt::select_backend(backend);
backend->set_tris({vertices});
auto hits1 = prt::closest_hits({hit_ray});
auto hits2 = prt::closest_hits({miss_ray});
std::cout << "Ray 1: "
<< (hits1[0].valid ? "hit" : "miss")
<< "\nRay 2: "
<< (hits2[0].valid ? "hit" : "miss")
<< std::endl;
}
return 0;
}You can build the project by yourself and even contribute!
git clone https://github.com/101001000/portableRT.git
cd portableRT
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build buildThe default configuration builds only the reference CPU backend for testing purposes.
To compile support for other devices, pass the corresponding CMake options mentioned below.
-
Download OptiX and extract it.
-
Configure CMake:
-DUSE_OPTIX=ON -DOptiX_ROOT=/path/to/OptiX-SDK-7.5.0-linux64-x86_64
-
Install HIP with ROCm (Version 6) (tested with ROCm 6.4.2).
-
Configure CMake:
-DUSE_HIP=ON -DHIP_ROOT=/path/to/rocm
-
Grab the SYCL‑enabled build: embree‑4.4.0.sycl.x86_64.linux.tar.gz.
You can reuse the same install for the CPU back‑end. -
Configure CMake:
-DUSE_EMBREE_SYCL=ON -Dembree_DIR=/path/to/embree-sycl/
-
Download the CPU build (with or without SYCL): embree‑4.4.0.x86_64.linux.tar.gz.
-
Configure CMake:
-DUSE_EMBREE_CPU=ON -Dembree_DIR=/path/to/embree/
Don’t forget
• Sourceembree_vars.shbefore using any Embree back‑end.
• For SYCL targets (Embree SYCL, generic SYCL), compile with DPC++ 6.0.1 or newer — e.g. intel/llvm v6.0.1.
