From e6f52588d67788f6eb1167d8f38a6a4274509010 Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Thu, 30 Oct 2025 16:29:10 +0000 Subject: [PATCH 01/62] Started on batch API query methods --- include/xdg/embree/ray_tracer.h | 25 +++++ include/xdg/gprt/ray_tracer.h | 19 ++++ include/xdg/ray_tracing_interface.h | 21 +++++ src/gprt/ray_tracer.cpp | 139 +++++++++++++++++++++++++++- 4 files changed, 203 insertions(+), 1 deletion(-) diff --git a/include/xdg/embree/ray_tracer.h b/include/xdg/embree/ray_tracer.h index 9c031d8f..a68e21a8 100644 --- a/include/xdg/embree/ray_tracer.h +++ b/include/xdg/embree/ray_tracer.h @@ -55,6 +55,31 @@ class EmbreeRayTracer : public RayTracer { HitOrientation orientation = HitOrientation::EXITING, std::vector* const exclude_primitives = nullptr) override; + // Array version of point_in_volume + void batch_point_in_volume(TreeID tree, + const Position* points, + const Direction* const* directions, // [num_points] array of Direction pointers + const size_t num_points, + uint8_t* results, + std::vector* exclude_primitives = nullptr) const override + { + fatal_error("Batch point_in_volume not yet implemented for EmbreeRayTracer"); + }; + + // Array version of ray_fire + void batch_ray_fire(TreeID tree, + const Position* origins, + const Direction* directions, + const size_t num_rays, + double* hitDistances, + MeshID* surfaceIDs, + const double dist_limit = INFTY, + HitOrientation orientation = HitOrientation::EXITING, + std::vector* const exclude_primitives = nullptr) override + { + fatal_error("Batch ray_fire not yet implemented for EmbreeRayTracer"); + }; + std::pair closest(TreeID scene, const Position& origin) override; diff --git a/include/xdg/gprt/ray_tracer.h b/include/xdg/gprt/ray_tracer.h index 8d24d107..741b79ce 100644 --- a/include/xdg/gprt/ray_tracer.h +++ b/include/xdg/gprt/ray_tracer.h @@ -90,6 +90,25 @@ class GPRTRayTracer : public RayTracer { HitOrientation orientation = HitOrientation::EXITING, std::vector* const exclude_primitives = nullptr) override; + // Array version of point_in_volume + void batch_point_in_volume(TreeID tree, + const Position* points, + const Direction* const* directions, // [num_points] array of Direction pointers + const size_t num_points, + uint8_t* results, + std::vector* exclude_primitives = nullptr) const override; + + // Array version of ray_fire + void batch_ray_fire(TreeID tree, + const Position* origins, + const Direction* directions, + const size_t num_rays, + double* hitDistances, + MeshID* surfaceIDs, + const double dist_limit = INFTY, + HitOrientation orientation = HitOrientation::EXITING, + std::vector* const exclude_primitives = nullptr) override; + std::pair closest(TreeID scene, const Position& origin) override {}; diff --git a/include/xdg/ray_tracing_interface.h b/include/xdg/ray_tracing_interface.h index 9d938978..4b9170d2 100644 --- a/include/xdg/ray_tracing_interface.h +++ b/include/xdg/ray_tracing_interface.h @@ -86,6 +86,27 @@ class RayTracer { HitOrientation orientation = HitOrientation::EXITING, std::vector* const exclude_primitives = nullptr) = 0; + // Array based queries + + // Array version of point_in_volume + virtual void batch_point_in_volume(TreeID tree, + const Position* points, + const Direction* const* directions, // [num_points] array of Direction pointers + const size_t num_points, + uint8_t* results, + std::vector* exclude_primitives = nullptr) const = 0; + + // Array version of ray_fire + virtual void batch_ray_fire(TreeID tree, + const Position* origin, + const Direction* direction, + const size_t num_rays, + double* hitDistances, + MeshID* surfaceIDs, + const double dist_limit = INFTY, + HitOrientation orientation = HitOrientation::EXITING, + std::vector* const exclude_primitives = nullptr) = 0; + /** * @brief Finds the element containing a given point using the global element tree. * diff --git a/src/gprt/ray_tracer.cpp b/src/gprt/ray_tracer.cpp index bb3ece22..9ece6751 100644 --- a/src/gprt/ray_tracer.cpp +++ b/src/gprt/ray_tracer.cpp @@ -326,7 +326,144 @@ std::pair GPRTRayTracer::ray_fire(SurfaceTreeID tree, if (exclude_primitives) exclude_primitives->push_back(primitive_id); return {distance, surface}; } - + +void GPRTRayTracer::batch_point_in_volume(TreeID tree, + const Position* points, + const Direction* const* directions, // [num_points] array of Direction pointers + const size_t num_points, + uint8_t* results, + std::vector* exclude_primitives) const +{ + if (num_points == 0) return; // no work to do. Early exit + + GPRTAccel volume = surface_volume_tree_to_accel_map.at(tree); + dblRayGenData* rayGenPIVData = gprtRayGenGetParameters(rayGenPointInVolProgram_); + + // Set a default direction to be used if no direction is provided + const Direction defaultDir = Direction{1. / std::sqrt(2.0), 1. / std::sqrt(2.0), 0.0}; + + // resize buffers to the number of points to be queried + gprtBufferResize(context_, rayInputBuffer_, num_points, false); + gprtBufferResize(context_, rayOutputBuffer_, num_points, false); + + // Since we have resized the ray input buffer, we need to update the geom_data->rayIn pointers in all geometries + for (auto const& [surf, geom] : surface_to_geometry_map_) { + DPTriangleGeomData* geom_data = gprtGeomGetParameters(geom); + geom_data->rayIn = gprtBufferGetDevicePointer(rayInputBuffer_); + } + + // TODO - handle exclude_primitives for batch version + + // Map the region start + gprtBufferMap(rayInputBuffer_); + dblRayInput* rayInput = gprtBufferGetHostPointer(rayInputBuffer_); + for (size_t i = 0; i < num_points; ++i) { + const auto& point = points[i]; + const auto& direction = directions[i]; + + const Direction* dptr = directions ? directions[i] : nullptr; // if directions array is empty. Set dptr to nullptr + const Direction directionUsed = dptr ? *dptr : defaultDir; // if dptr is nullptr use default direction, othewise dereference to actual Direction + rayInput[i].volume_accel = gprtAccelGetDeviceAddress(volume); + + rayInput[i].origin = {point.x, point.y, point.z}; + rayInput[i].direction = {directionUsed.x, directionUsed.y, directionUsed.z}; + rayInput[i].tMax = INFTY; // Set a large distance limit + rayInput[i].tMin = 0.0; + rayInput[i].volume_tree = tree; // Set the TreeID of the volume being queried + rayInput[i].hitOrientation = HitOrientation::ANY; // No orientation culling for point-in-volume check + rayInput[i].exclude_primitives = nullptr; // Not currently supported in batch version + } + + gprtBufferUnmap(rayInputBuffer_); // required to sync buffer back on GPU? + + gprtBuildShaderBindingTable(context_, GPRT_SBT_ALL); + + gprtRayGenLaunch1D(context_, rayGenPointInVolProgram_, num_points); + + // Retrieve the output from the ray output buffer + gprtBufferMap(rayOutputBuffer_); + dblRayOutput* rayOutput = gprtBufferGetHostPointer(rayOutputBuffer_); + for (size_t i = 0; i < num_points; ++i) { + auto piv = rayOutput[i].piv; // Point in volume check result + results[i] = static_cast(piv); + } + gprtBufferUnmap(rayOutputBuffer_); // required to sync buffer back on GPU? Maybe this second unmap isn't actually needed since we dont need to resyncrhonize after retrieving the data from device + + return; +} + + + +// Array version of ray_fire +void GPRTRayTracer::batch_ray_fire(TreeID tree, + const Position* origins, + const Direction* directions, + const size_t num_rays, + double* hitDistances, + MeshID* surfaceIDs, + const double dist_limit, + HitOrientation orientation, + std::vector* const exclude_primitives) +{ + if (num_rays == 0) return; // no work to do. Early exit + + GPRTAccel volume = surface_volume_tree_to_accel_map.at(tree); + dblRayGenData* rayGenData = gprtRayGenGetParameters(rayGenProgram_); + + // resize buffers to the number of points to be queried + gprtBufferResize(context_, rayInputBuffer_, num_rays, false); + gprtBufferResize(context_, rayOutputBuffer_, num_rays, false); + + // Since we have resized the ray input buffer, we need to update the geom_data->rayIn pointers in all geometries + for (auto const& [surf, geom] : surface_to_geometry_map_) { + DPTriangleGeomData* geom_data = gprtGeomGetParameters(geom); + geom_data->rayIn = gprtBufferGetDevicePointer(rayInputBuffer_); + } + + gprtBufferMap(rayInputBuffer_); + dblRayInput* rayInput = gprtBufferGetHostPointer(rayInputBuffer_); + for (size_t i = 0; i < num_rays; ++i) { + const auto& origin = origins[i]; + const auto& direction = directions[i]; + + rayInput[i].volume_accel = gprtAccelGetDeviceAddress(volume); + rayInput[i].origin = {origin.x, origin.y, origin.z}; + rayInput[i].direction = {direction.x, direction.y, direction.z}; + rayInput[i].tMax = dist_limit; + rayInput[i].tMin = 0.0; + rayInput[i].hitOrientation = orientation; // Set orientation for the ray + rayInput[i].volume_tree = tree; // Set the TreeID of the volume being queried + rayInput[i].exclude_primitives = nullptr; // Not currently supported in batch version + } + + gprtBufferUnmap(rayInputBuffer_); // required to sync buffer back on GPU? + + gprtBuildShaderBindingTable(context_, GPRT_SBT_ALL); + + // Launch the ray generation shader with push constants and buffer bindings + gprtRayGenLaunch1D(context_, rayGenProgram_, num_rays); + + // Retrieve the output from the ray output buffer + gprtBufferMap(rayOutputBuffer_); + dblRayOutput* rayOutput = gprtBufferGetHostPointer(rayOutputBuffer_); + // populate the result arrays + for (size_t i = 0; i < num_rays; ++i) { + const MeshID surfaceHit = rayOutput[i].surf_id; + if (surfaceHit == ID_NONE) { + hitDistances[i] = INFTY; + surfaceIDs[i] = ID_NONE; + } + else { + hitDistances[i] = rayOutput[i].distance; + surfaceIDs[i] = surfaceHit; + // TODO - handle exclude_primitives for batch version + } + } + gprtBufferUnmap(rayOutputBuffer_); // required to sync buffer back on GPU? Maybe this second unmap isn't actually needed since we dont need to resyncrhonize after retrieving the data from device + + return; +} + void GPRTRayTracer::create_global_surface_tree() { // Create a TLAS (Top-Level Acceleration Structure) for all the volumes From 433452484c1eea7016bb0572081537576e4b2ec9 Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Thu, 30 Oct 2025 16:29:43 +0000 Subject: [PATCH 02/62] Added a batch_ray_fire miniapp for extra testing --- include/xdg/xdg.h | 19 ++++ src/gprt/ray_tracer.cpp | 8 ++ src/xdg.cpp | 20 +++- tools/CMakeLists.txt | 1 + tools/batch_ray_fire.cpp | 220 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 266 insertions(+), 2 deletions(-) create mode 100644 tools/batch_ray_fire.cpp diff --git a/include/xdg/xdg.h b/include/xdg/xdg.h index 82c1f8f2..ac0511f3 100644 --- a/include/xdg/xdg.h +++ b/include/xdg/xdg.h @@ -75,6 +75,25 @@ std::pair ray_fire(MeshID volume, HitOrientation orientation = HitOrientation::EXITING, std::vector* const exclude_primitives = nullptr) const; +// Array version of point_in_volume +void batch_point_in_volume(MeshID volume, + const Position* points, + const Direction* const* directions, // [num_points] array of Direction pointers + const size_t num_points, + uint8_t* results, + std::vector* exclude_primitives = nullptr) const; + +// Array version of ray_fire +void batch_ray_fire(MeshID volume, + const Position* origins, + const Direction* directions, + const size_t num_rays, + double* hitDistances, + MeshID* surfaceIDs, + const double dist_limit = INFTY, + HitOrientation orientation = HitOrientation::EXITING, + std::vector* const exclude_primitives = nullptr); + std::pair closest(MeshID volume, const Position& origin) const; diff --git a/src/gprt/ray_tracer.cpp b/src/gprt/ray_tracer.cpp index 9ece6751..7c4c6071 100644 --- a/src/gprt/ray_tracer.cpp +++ b/src/gprt/ray_tracer.cpp @@ -374,11 +374,15 @@ void GPRTRayTracer::batch_point_in_volume(TreeID tree, rayInput[i].exclude_primitives = nullptr; // Not currently supported in batch version } + rayGenPIVData->ray = gprtBufferGetDevicePointer(rayInputBuffer_); + rayGenPIVData->out = gprtBufferGetDevicePointer(rayOutputBuffer_); + gprtBufferUnmap(rayInputBuffer_); // required to sync buffer back on GPU? gprtBuildShaderBindingTable(context_, GPRT_SBT_ALL); gprtRayGenLaunch1D(context_, rayGenPointInVolProgram_, num_points); + gprtGraphicsSynchronize(context_); // Retrieve the output from the ray output buffer gprtBufferMap(rayOutputBuffer_); @@ -436,12 +440,16 @@ void GPRTRayTracer::batch_ray_fire(TreeID tree, rayInput[i].exclude_primitives = nullptr; // Not currently supported in batch version } + rayGenData->ray = gprtBufferGetDevicePointer(rayInputBuffer_); + rayGenData->out = gprtBufferGetDevicePointer(rayOutputBuffer_); + gprtBufferUnmap(rayInputBuffer_); // required to sync buffer back on GPU? gprtBuildShaderBindingTable(context_, GPRT_SBT_ALL); // Launch the ray generation shader with push constants and buffer bindings gprtRayGenLaunch1D(context_, rayGenProgram_, num_rays); + gprtGraphicsSynchronize(context_); // Retrieve the output from the ray output buffer gprtBufferMap(rayOutputBuffer_); diff --git a/src/xdg.cpp b/src/xdg.cpp index 371a9a1c..4d525f21 100644 --- a/src/xdg.cpp +++ b/src/xdg.cpp @@ -235,8 +235,24 @@ XDG::ray_fire(MeshID volume, HitOrientation orientation, std::vector* const exclude_primitives) const { - TreeID scene = volume_to_surface_tree_map_.at(volume); - return ray_tracing_interface()->ray_fire(scene, origin, direction, dist_limit, orientation, exclude_primitives); + TreeID tree = volume_to_surface_tree_map_.at(volume); + return ray_tracing_interface()->ray_fire(tree, origin, direction, dist_limit, orientation, exclude_primitives); +} + +// Array version of ray_fire +void +XDG::batch_ray_fire(MeshID volume, + const Position* origins, + const Direction* directions, + const size_t num_rays, + double* hitDistances, + MeshID* surfaceIDs, + const double dist_limit, + HitOrientation orientation, + std::vector* const exclude_primitives) +{ + TreeID tree = volume_to_surface_tree_map_.at(volume); + return ray_tracing_interface()->batch_ray_fire(tree, origins, directions, num_rays, hitDistances, surfaceIDs, dist_limit, orientation, exclude_primitives); } std::pair XDG::closest(MeshID volume, diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index f23f871e..c9df4b8c 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -1,6 +1,7 @@ set(TOOL_NAMES particle_sim ray_fire +batch_ray_fire find_volume point_in_volume overlap_check diff --git a/tools/batch_ray_fire.cpp b/tools/batch_ray_fire.cpp new file mode 100644 index 00000000..d2cea1ad --- /dev/null +++ b/tools/batch_ray_fire.cpp @@ -0,0 +1,220 @@ +#include +#include +#include +#include +#include + +#include "xdg/error.h" +#include "xdg/mesh_manager_interface.h" +#include "xdg/moab/mesh_manager.h" +#include "xdg/vec3da.h" +#include "xdg/xdg.h" + +#include "argparse/argparse.hpp" + +using namespace xdg; + +// --------------------------- Helpers to determine what kind of batch to use --------------------------- +enum class BatchMode { + ORIGIN_BROADCAST, // 1 origin, many directions + DIRECTION_BROADCAST, // many origins, 1 direction + PAIRWISE // equal numbers of origins and directions +}; + +inline const char* to_string(BatchMode mode) { + switch (mode) { + case BatchMode::ORIGIN_BROADCAST: return "ORIGIN_BROADCAST"; + case BatchMode::DIRECTION_BROADCAST: return "DIRECTION_BROADCAST"; + case BatchMode::PAIRWISE: return "PAIRWISE"; + default: return "UNKNOWN"; + } +} + +inline BatchMode deduce_batch_mode(size_t num_origins, size_t num_directions) { + if (num_origins == 0 || num_directions == 0) { + throw std::runtime_error("At least one origin and one direction must be provided."); + } + + if (num_origins == 1 && num_directions > 1) { + return BatchMode::ORIGIN_BROADCAST; + } + else if (num_directions == 1 && num_origins > 1) { + return BatchMode::DIRECTION_BROADCAST; + } + else if (num_origins == num_directions) { + return BatchMode::PAIRWISE; + } + else { + throw std::runtime_error( + "Invalid combination: number of origins (" + std::to_string(num_origins) + + ") does not match number of directions (" + std::to_string(num_directions) + + ") for broadcast or pairwise mode." + ); + } +} +// ------------------------------------------------------------------------------------------------------ + +int main(int argc, char** argv) { + + argparse::ArgumentParser args("XDG Batch Ray Fire Tool", "1.0", argparse::default_arguments::help); + + args.add_argument("filename") + .help("Path to the input file"); + + args.add_argument("volume") + .help("Volume ID to query").scan<'i', int>(); + + args.add_argument("-l", "--list") + .default_value(false) + .implicit_value(true) + .help("List all volumes in the file and exit"); + + args.add_argument("-o", "-p", "--origin", "--position") + .default_value(std::vector{0.0, 0.0, 0.0}) + .help("Ray origin/position. Repeat to supply multiple origins.") + .scan<'g', double>().nargs(3).append(); + + args.add_argument("-d", "--direction") + .default_value(std::vector{0.0, 0.0, 1.0}) + .help("Ray direction. Repeat to supply multiple directions.") + .scan<'g', double>().nargs(3).append(); + + + args.add_argument("-m", "--mesh-library") + .help("Mesh library to use. One of (MOAB, LIBMESH)") + .default_value("MOAB"); + + args.add_argument("-r", "--rt-library") + .help("Ray tracing library to use. One of (EMBREE, GPRT)") + .default_value("GPRT"); + + // High-level rules in the description + args.add_description( + "This tool supports two modes of operation for batch ray firing: 'Broadcast' and 'Pairwise'\n\n" + "To use 'Broadcast' mode, provide one origin and many directions, or one direction and many origins:\n" + " --origin x y z --direction u1 v1 w1 --direction u2 v2 w2 ...\n" + " --direction u v w --origin x1 y1 z1 --origin x2 y2 z2 ...\n\n" + "To use 'Pairwise' mode, each origin is paired with a corresponding direction in order:\n" + " --origin x1 y1 z1 --direction u1 v1 w1 --origin x2 y2 z2 --direction u2 v2 w2 ...\n" + ); + + + try { + args.parse_args(argc, argv); + } + catch (const std::runtime_error& err) { + std::cout << err.what() << std::endl; + std::cout << args; + exit(0); + } + + std::string mesh_str = args.get("--mesh-library"); + std::string rt_str = args.get("--rt-library"); + + MeshLibrary mesh_lib; + if (mesh_str == "MOAB") mesh_lib = MeshLibrary::MOAB; + else if (mesh_str == "LIBMESH") fatal_error("LibMesh is not currently supported with GPRT"); + else fatal_error("Invalid mesh library '{}' specified", mesh_str); + + RTLibrary rt_lib; + if (rt_str == "EMBREE") rt_lib = RTLibrary::EMBREE; + else if (rt_str == "GPRT") rt_lib = RTLibrary::GPRT; + else fatal_error("Invalid ray tracing library '{}' specified", rt_str); + + // create a mesh manager + std::shared_ptr xdg = XDG::create(mesh_lib, rt_lib); + const auto& mm = xdg->mesh_manager(); + mm->load_file(args.get("filename")); + mm->init(); + mm->parse_metadata(); + + auto rti = xdg->ray_tracing_interface(); + + if (args.get("--list")) { + std::cout << "Volumes: " << std::endl; + for (auto volume : mm->volumes()) { + std::cout << volume << std::endl; + } + exit(0); + } + + MeshID volume = args.get("volume"); + xdg->prepare_volume_for_raytracing(volume); + + // Gather our inputs and determine which mode of operation the tool will be working in + auto flat_origins = args.get>("--origin"); + auto flat_directions = args.get>("--direction"); + + // group every 3 into Position / Direction + std::vector> args_origins; + for (size_t i = 0; i < flat_origins.size(); i += 3) { + args_origins.push_back({flat_origins[i], flat_origins[i+1], flat_origins[i+2]}); + } + + std::vector> args_directions; + for (size_t i = 0; i < flat_directions.size(); i += 3) { + args_directions.push_back({flat_directions[i], flat_directions[i+1], flat_directions[i+2]}); + } + + // helper lambdas to convert std::vector to xdg::Position and xdg::Direction types + auto vec_to_pos = [](const std::vector& v) { return Position{v[0], v[1], v[2]}; }; + auto vec_to_dir = [](const std::vector& v) { + Direction dir{v[0], v[1], v[2]}; + dir.normalize(); + return dir; + }; + + size_t num_orig = args_origins.size(); + size_t num_dirs = args_directions.size(); + + auto mode = deduce_batch_mode(num_orig, num_dirs); + std::cout << "Running XDG Batch Ray Fire in " << to_string(mode) << " mode" << std::endl; + std::vector origins; + std::vector directions; + + switch (mode) + { + case BatchMode::ORIGIN_BROADCAST: + origins.assign(num_dirs, vec_to_pos(args_origins[0])); + directions.reserve(num_dirs); + for (const auto& dir : args_directions) directions.push_back(vec_to_dir(dir)); + break; + case BatchMode::DIRECTION_BROADCAST: + directions.assign(num_orig, vec_to_dir(args_directions[0])); + origins.reserve(num_orig); + for (const auto& origin : args_origins) origins.push_back(vec_to_pos(origin)); + break; + case BatchMode::PAIRWISE: + origins.reserve(num_orig); + directions.reserve(num_dirs); + for (size_t i = 0; i < num_orig; ++i) + { + origins.push_back(vec_to_pos(args_origins[i])); + directions.push_back(vec_to_dir(args_directions[i])); + } + break; + + default: + fatal_error("You must provide either a single origin and many directions. " + "A single direction and many origins. Or an equal number of origins and directions."); + } + + size_t num_rays = origins.size(); // get number of rays to fire from now aligned arrays + + std::vector hitDistances(num_rays); + std::vector surfacesHit(num_rays); + + xdg->batch_ray_fire(volume, origins.data(), directions.data(), num_rays, hitDistances.data(), surfacesHit.data()); + + std::cout << std::endl << "Printing Batch Ray results..." << std::endl; + + for (size_t i = 0; i < num_rays; ++i) { + std::cout << "Ray[" << i << "] " + << "Origin=(" << origins[i].x << ", " << origins[i].y << ", " << origins[i].z << ") " + << "Dir=(" << directions[i].x << ", " << directions[i].y << ", " << directions[i].z << ") " + << "Distance=" << std::setprecision(17) << hitDistances[i] << " " + << "| Surface=" << surfacesHit[i] << "\n"; + } + + return 0; +} From 1dbc734ae9d5aa463b06d6e613e31cc530b6d5a6 Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Tue, 4 Nov 2025 16:54:48 +0000 Subject: [PATCH 03/62] Added test_cases for batch_ray_fire --- tests/test_ray_fire.cpp | 83 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 82 insertions(+), 1 deletion(-) diff --git a/tests/test_ray_fire.cpp b/tests/test_ray_fire.cpp index be816c36..e0cc5768 100644 --- a/tests/test_ray_fire.cpp +++ b/tests/test_ray_fire.cpp @@ -109,4 +109,85 @@ TEMPLATE_TEST_CASE("Ray Fire on MeshMock (per-backend sections)", "[rayfire][moc intersection = rti->ray_fire(volume_tree, origin, direction, INFTY, HitOrientation::EXITING, &exclude_primitives); REQUIRE(intersection.second == ID_NONE); } -} \ No newline at end of file +} + +TEST_CASE("Batch API Ray Fire on MeshMock", "[rayfire][mock][batch]") { + auto rt_backend = GENERATE(RTLibrary::EMBREE, RTLibrary::GPRT); + + DYNAMIC_SECTION(fmt::format("Backend = {}", rt_backend)) { + check_ray_tracer_supported(rt_backend); + if (rt_backend == RTLibrary::EMBREE) { + SKIP("Skipping batch query mechanics test for Embree: batch API not implemented."); + } + + auto rti = create_raytracer(rt_backend); + REQUIRE(rti); + + auto mm = std::make_shared(false); + mm->init(); + REQUIRE(mm->mesh_library() == MeshLibrary::MOCK); + + auto [volume_tree, element_tree] = rti->register_volume(mm, mm->volumes()[0]); + REQUIRE(volume_tree != ID_NONE); + REQUIRE(element_tree == ID_NONE); + + rti->init(); + + // Create a set of 64 rays to be used throughout this test_case + std::vector origins; + std::vector directions; + origins.reserve(64); + directions.reserve(64); + for (int i = 0; i < 64; ++i) { + int axis = i % 3; + double s = (i % 2) ? 1.0 : -1.0; + origins.push_back({0,0,0}); + directions.push_back(axis == 0 ? Direction{s,0,0} + : axis == 1 ? Direction{0,s,0} + : Direction{0,0,s}); + } + + // Store results of scalar ray_fires to verify batch against scalar + std::vector scalar_ray_fire_distances(origins.size(), INFTY); + std::vector scalar_ray_fire_surface_id(origins.size(), ID_NONE); + for (size_t i = 0; i < origins.size(); ++i) { + auto [distance, surfID] = rti->ray_fire(volume_tree, origins[i], directions[i], INFTY, HitOrientation::EXITING); + scalar_ray_fire_distances[i] = distance; + scalar_ray_fire_surface_id[i] = surfID; + } + + SECTION("N=0 no-op") { + rti->batch_ray_fire(volume_tree, nullptr, nullptr, 0, nullptr, nullptr, + INFTY, HitOrientation::EXITING, nullptr); + SUCCEED("N=0 completed without error"); + } + + SECTION("N=1 equals scalar") { + double hd; + MeshID sid = ID_NONE; + + rti->batch_ray_fire(volume_tree, &origins[0], &directions[0], 1, &hd, + &sid, INFTY, HitOrientation::EXITING, nullptr); + + REQUIRE(sid != ID_NONE); // expect a hit + + // Ensure that hit matches scalar ray_fire + REQUIRE_THAT(hd, Catch::Matchers::WithinAbs(scalar_ray_fire_distances[0], 1e-6)); + REQUIRE(sid == scalar_ray_fire_surface_id[0]); + } + + SECTION("N=64") { + std::vector hd(origins.size(), -1.0); + std::vector sid(origins.size(), ID_NONE); + rti->batch_ray_fire(volume_tree, origins.data(), directions.data(), + origins.size(), hd.data(), sid.data(), + INFTY, HitOrientation::EXITING, nullptr); + + // Ensure that hits match scalar ray_fires + for (size_t i = 0; i < origins.size(); ++i) { + REQUIRE_THAT(hd[i], Catch::Matchers::WithinAbs(scalar_ray_fire_distances[i], 1e-6)); + REQUIRE(sid[i] == scalar_ray_fire_surface_id[i]); + } + } + } +} From b388e66a0406263b2e0127548bfd1aacfcdd8cd3 Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Wed, 5 Nov 2025 14:48:07 +0000 Subject: [PATCH 04/62] Implemented batch_point_in_volume() --- include/xdg/embree/ray_tracer.h | 3 ++- include/xdg/gprt/ray_tracer.h | 3 ++- include/xdg/ray_tracing_interface.h | 3 ++- include/xdg/xdg.h | 3 ++- src/gprt/ray_tracer.cpp | 42 +++++++++++++++++------------ src/xdg.cpp | 12 +++++++++ 6 files changed, 45 insertions(+), 21 deletions(-) diff --git a/include/xdg/embree/ray_tracer.h b/include/xdg/embree/ray_tracer.h index a68e21a8..d3d912f1 100644 --- a/include/xdg/embree/ray_tracer.h +++ b/include/xdg/embree/ray_tracer.h @@ -58,9 +58,10 @@ class EmbreeRayTracer : public RayTracer { // Array version of point_in_volume void batch_point_in_volume(TreeID tree, const Position* points, - const Direction* const* directions, // [num_points] array of Direction pointers + const Direction* directions, // [num_points] array of Direction pointers const size_t num_points, uint8_t* results, + const uint8_t* has_dir = nullptr, std::vector* exclude_primitives = nullptr) const override { fatal_error("Batch point_in_volume not yet implemented for EmbreeRayTracer"); diff --git a/include/xdg/gprt/ray_tracer.h b/include/xdg/gprt/ray_tracer.h index 741b79ce..4ad035c3 100644 --- a/include/xdg/gprt/ray_tracer.h +++ b/include/xdg/gprt/ray_tracer.h @@ -93,9 +93,10 @@ class GPRTRayTracer : public RayTracer { // Array version of point_in_volume void batch_point_in_volume(TreeID tree, const Position* points, - const Direction* const* directions, // [num_points] array of Direction pointers + const Direction* directions, // [num_points] array of Direction pointers const size_t num_points, uint8_t* results, + const uint8_t* has_dir = nullptr, std::vector* exclude_primitives = nullptr) const override; // Array version of ray_fire diff --git a/include/xdg/ray_tracing_interface.h b/include/xdg/ray_tracing_interface.h index 4b9170d2..d7c4fb2b 100644 --- a/include/xdg/ray_tracing_interface.h +++ b/include/xdg/ray_tracing_interface.h @@ -91,9 +91,10 @@ class RayTracer { // Array version of point_in_volume virtual void batch_point_in_volume(TreeID tree, const Position* points, - const Direction* const* directions, // [num_points] array of Direction pointers + const Direction* directions, // [num_points] array of Direction pointers const size_t num_points, uint8_t* results, + const uint8_t* has_dir = nullptr, std::vector* exclude_primitives = nullptr) const = 0; // Array version of ray_fire diff --git a/include/xdg/xdg.h b/include/xdg/xdg.h index ac0511f3..02fb5a6a 100644 --- a/include/xdg/xdg.h +++ b/include/xdg/xdg.h @@ -78,9 +78,10 @@ std::pair ray_fire(MeshID volume, // Array version of point_in_volume void batch_point_in_volume(MeshID volume, const Position* points, - const Direction* const* directions, // [num_points] array of Direction pointers + const Direction* directions, // [num_points] array of Direction pointers const size_t num_points, uint8_t* results, + const uint8_t* has_dir = nullptr, std::vector* exclude_primitives = nullptr) const; // Array version of ray_fire diff --git a/src/gprt/ray_tracer.cpp b/src/gprt/ray_tracer.cpp index 7c4c6071..1d39424d 100644 --- a/src/gprt/ray_tracer.cpp +++ b/src/gprt/ray_tracer.cpp @@ -329,18 +329,15 @@ std::pair GPRTRayTracer::ray_fire(SurfaceTreeID tree, void GPRTRayTracer::batch_point_in_volume(TreeID tree, const Position* points, - const Direction* const* directions, // [num_points] array of Direction pointers + const Direction* directions, // [num_points] array of Direction pointers const size_t num_points, uint8_t* results, + const uint8_t* has_dir, std::vector* exclude_primitives) const { if (num_points == 0) return; // no work to do. Early exit GPRTAccel volume = surface_volume_tree_to_accel_map.at(tree); - dblRayGenData* rayGenPIVData = gprtRayGenGetParameters(rayGenPointInVolProgram_); - - // Set a default direction to be used if no direction is provided - const Direction defaultDir = Direction{1. / std::sqrt(2.0), 1. / std::sqrt(2.0), 0.0}; // resize buffers to the number of points to be queried gprtBufferResize(context_, rayInputBuffer_, num_points, false); @@ -352,20 +349,32 @@ void GPRTRayTracer::batch_point_in_volume(TreeID tree, geom_data->rayIn = gprtBufferGetDevicePointer(rayInputBuffer_); } + // Refresh raygen IO after resize + dblRayGenData* rayGenPIVData = gprtRayGenGetParameters(rayGenPointInVolProgram_); + rayGenPIVData->ray = gprtBufferGetDevicePointer(rayInputBuffer_); + rayGenPIVData->out = gprtBufferGetDevicePointer(rayOutputBuffer_); + // TODO - handle exclude_primitives for batch version + // Set a default direction to be used if no direction is provided + const Direction defaultDir = Direction{1. / std::sqrt(2.0), 1. / std::sqrt(2.0), 0.0}; + // Map the region start gprtBufferMap(rayInputBuffer_); dblRayInput* rayInput = gprtBufferGetHostPointer(rayInputBuffer_); + const auto volumeAddr = gprtAccelGetDeviceAddress(volume); for (size_t i = 0; i < num_points; ++i) { - const auto& point = points[i]; - const auto& direction = directions[i]; + Direction directionUsed = + (!directions || (has_dir && !has_dir[i])) ? defaultDir : directions[i]; - const Direction* dptr = directions ? directions[i] : nullptr; // if directions array is empty. Set dptr to nullptr - const Direction directionUsed = dptr ? *dptr : defaultDir; // if dptr is nullptr use default direction, othewise dereference to actual Direction - rayInput[i].volume_accel = gprtAccelGetDeviceAddress(volume); + // Catch directions with zero length + const double l2 = directionUsed.x*directionUsed.x + + directionUsed.y*directionUsed.y + + directionUsed.z*directionUsed.z; + if (l2 == 0.0) directionUsed = defaultDir; - rayInput[i].origin = {point.x, point.y, point.z}; + rayInput[i].volume_accel = volumeAddr; + rayInput[i].origin = {points[i].x, points[i].y, points[i].z}; rayInput[i].direction = {directionUsed.x, directionUsed.y, directionUsed.z}; rayInput[i].tMax = INFTY; // Set a large distance limit rayInput[i].tMin = 0.0; @@ -374,12 +383,10 @@ void GPRTRayTracer::batch_point_in_volume(TreeID tree, rayInput[i].exclude_primitives = nullptr; // Not currently supported in batch version } - rayGenPIVData->ray = gprtBufferGetDevicePointer(rayInputBuffer_); - rayGenPIVData->out = gprtBufferGetDevicePointer(rayOutputBuffer_); - gprtBufferUnmap(rayInputBuffer_); // required to sync buffer back on GPU? - - gprtBuildShaderBindingTable(context_, GPRT_SBT_ALL); + + // rebuild SBT geom and raygen only + gprtBuildShaderBindingTable(context_, static_cast(GPRT_SBT_GEOM | GPRT_SBT_RAYGEN)); gprtRayGenLaunch1D(context_, rayGenPointInVolProgram_, num_points); gprtGraphicsSynchronize(context_); @@ -445,7 +452,8 @@ void GPRTRayTracer::batch_ray_fire(TreeID tree, gprtBufferUnmap(rayInputBuffer_); // required to sync buffer back on GPU? - gprtBuildShaderBindingTable(context_, GPRT_SBT_ALL); + // rebuild SBT geom and raygen only + gprtBuildShaderBindingTable(context_, static_cast(GPRT_SBT_GEOM | GPRT_SBT_RAYGEN)); // Launch the ray generation shader with push constants and buffer bindings gprtRayGenLaunch1D(context_, rayGenProgram_, num_rays); diff --git a/src/xdg.cpp b/src/xdg.cpp index 4d525f21..a6470169 100644 --- a/src/xdg.cpp +++ b/src/xdg.cpp @@ -110,6 +110,18 @@ bool XDG::point_in_volume(MeshID volume, return ray_tracing_interface()->point_in_volume(tree, point, direction, exclude_primitives); } +void XDG::batch_point_in_volume(MeshID volume, + const Position* points, + const Direction* directions, // [num_points] array of Direction pointers + const size_t num_points, + uint8_t* results, + const uint8_t* has_dir, + std::vector* exclude_primitives) const +{ + TreeID tree = volume_to_surface_tree_map_.at(volume); + ray_tracing_interface()->batch_point_in_volume(tree, points, directions, num_points, results, has_dir, exclude_primitives); +} + MeshID XDG::find_volume(const Position& point, const Direction& direction) const { From c9af01e755b25100c1cdd28b79211bebbad98b4b Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Wed, 5 Nov 2025 15:51:14 +0000 Subject: [PATCH 05/62] Added a batch_point in volume miniapp for extra testing --- tests/test_point_in_volume.cpp | 70 ++++++++++++ tools/CMakeLists.txt | 1 + tools/batch_point_in_volume.cpp | 196 ++++++++++++++++++++++++++++++++ tools/batch_ray_fire.cpp | 28 +++-- 4 files changed, 285 insertions(+), 10 deletions(-) create mode 100644 tools/batch_point_in_volume.cpp diff --git a/tests/test_point_in_volume.cpp b/tests/test_point_in_volume.cpp index ae34e823..1bf2f6c8 100644 --- a/tests/test_point_in_volume.cpp +++ b/tests/test_point_in_volume.cpp @@ -78,3 +78,73 @@ TEMPLATE_TEST_CASE("Point-in-volume on MeshMock", "[piv][mock]", REQUIRE(result == false); } } + +TEST_CASE("Batch API Point-in-volume on MeshMock", "[piv][mock][batch]") { + auto rt_backend = GENERATE(RTLibrary::EMBREE, RTLibrary::GPRT); + + DYNAMIC_SECTION(fmt::format("Backend = {}", rt_backend)) { + check_ray_tracer_supported(rt_backend); + if (rt_backend == RTLibrary::EMBREE) { + SKIP("Skipping PIV batch for Embree: batch API not implemented yet"); + } + + auto rti = create_raytracer(rt_backend); + REQUIRE(rti); + + auto mm = std::make_shared(false); + mm->init(); + REQUIRE(mm->mesh_library() == MeshLibrary::MOCK); + + auto [volume_tree, element_tree] = rti->register_volume(mm, mm->volumes()[0]); + REQUIRE(volume_tree != ID_NONE); + REQUIRE(element_tree == ID_NONE); + + rti->init(); + + // Build 64 points: alternate inside/outside with some null directions + const size_t num_points = 64; + std::vector points(num_points); + std::vector directions(num_points); // contiguous directions (ignored if has_dir[i]==0) + std::vector has_dir(num_points, 0); // mask: 1 => use directions[i], 0 => use default + + for (int i = 0; i < num_points; ++i) { + // even i: origin (inside); odd i: just outside +X + points[i] = (i % 2 == 0) ? Position{0,0,0} : Position{5.1,0,0}; + + // every 3rd ray has no direction has_dir == 0; others alternate ±X with has_dir == 1 + if (i % 3 != 0) { + directions[i] = (i % 2 == 0) ? Direction{1,0,0} : Direction{-1,0,0}; + has_dir[i] = 1; + } else { + has_dir[i] = 0; // mask out direction and use default + } + } + + // Store results of scalar point_in_volume calls to verify batch against scalar + std::vector truth(num_points, 0); + for (size_t i = 0; i < num_points; ++i) { + const Direction* dptr = has_dir[i] ? &directions[i] : nullptr; + truth[i] = static_cast(rti->point_in_volume(volume_tree, points[i], dptr)); + } + + SECTION("N=0 no-op") { + rti->batch_point_in_volume(volume_tree, nullptr, nullptr, 0, nullptr, nullptr); + SUCCEED("N=0 completed without error"); + } + + SECTION("N=1") { + uint8_t result = 0xFF; // sentinel + rti->batch_point_in_volume(volume_tree, &points[0], &directions[0], 1, &result, &has_dir[0]); + REQUIRE((result == 0 || result == 1)); + REQUIRE(result == truth[0]); + } + + SECTION("N=64") { + std::vector results(num_points, 0xFF); + rti->batch_point_in_volume(volume_tree, points.data(), directions.data(), num_points, results.data(), has_dir.data()); + for (size_t i = 0; i < points.size(); ++i) { + REQUIRE(results[i] == truth[i]); + } + } + } +} diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index c9df4b8c..a3fa0017 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -4,6 +4,7 @@ ray_fire batch_ray_fire find_volume point_in_volume +batch_point_in_volume overlap_check walk_elements tally_segments diff --git a/tools/batch_point_in_volume.cpp b/tools/batch_point_in_volume.cpp new file mode 100644 index 00000000..13a35c81 --- /dev/null +++ b/tools/batch_point_in_volume.cpp @@ -0,0 +1,196 @@ +#include +#include +#include +#include +#include +#include + +#include "xdg/error.h" +#include "xdg/mesh_manager_interface.h" +#include "xdg/moab/mesh_manager.h" +#include "xdg/vec3da.h" +#include "xdg/xdg.h" + +#include "argparse/argparse.hpp" + +using namespace xdg; + +int main(int argc, char** argv) { + + argparse::ArgumentParser args("XDG Batch Point In Volume Tool", "1.0", argparse::default_arguments::help); + + args.add_argument("filename") + .help("Path to the input file"); + + args.add_argument("volume") + .help("Volume ID to query").scan<'i', int>(); + + args.add_argument("-l", "--list") + .default_value(false) + .implicit_value(true) + .help("List all volumes in the file and exit"); + + args.add_argument("-o", "-p", "--origin", "--position") + .default_value(std::vector{0.0, 0.0, 0.0}) + .help("Ray origin/position. Repeat to supply multiple origins.") + .scan<'g', double>().nargs(3).append(); + + args.add_argument("-d", "--direction") + .default_value(std::vector{0.0, 0.0, 1.0}) + .help("Ray direction. Repeat to supply multiple directions.") + .scan<'g', double>().nargs(3).append(); + + + args.add_argument("-m", "--mesh-library") + .help("Mesh library to use. One of (MOAB, LIBMESH)") + .default_value("MOAB"); + + args.add_argument("-r", "--rt-library") + .help("Ray tracing library to use. One of (EMBREE, GPRT)") + .default_value("GPRT"); + + // High-level rules in the description + args.add_description( + "Directions are completely optional for this tool but the number provided will effect how the program runs: \n\n" + " Only points (mask all, device default dir used)\n" + " --origin 0 0 0 --origin 5.1 0 0 --origin 0 0 0\n\n" + " One direction (broadcast to all)\n" + " --origin 0 0 0 --origin 5.1 0 0 --direction 1 0 0\n\n" + " Several directions. Match to points and mask remainder\n" + " --origin 0 0 0 --origin 5.1 0 0 --origin 4.999999 0 0 \\\n" + " --direction 1 0 0 --direction -1 0 0\n" + ); + + try { + args.parse_args(argc, argv); + } + catch (const std::runtime_error& err) { + std::cout << err.what() << std::endl; + std::cout << args; + exit(0); + } + + std::string mesh_str = args.get("--mesh-library"); + std::string rt_str = args.get("--rt-library"); + + MeshLibrary mesh_lib; + if (mesh_str == "MOAB") mesh_lib = MeshLibrary::MOAB; + else if (mesh_str == "LIBMESH") fatal_error("LibMesh is not currently supported with GPRT"); + else fatal_error("Invalid mesh library '{}' specified", mesh_str); + + RTLibrary rt_lib; + if (rt_str == "EMBREE") rt_lib = RTLibrary::EMBREE; + else if (rt_str == "GPRT") rt_lib = RTLibrary::GPRT; + else fatal_error("Invalid ray tracing library '{}' specified", rt_str); + + // create a mesh manager + std::shared_ptr xdg = XDG::create(mesh_lib, rt_lib); + const auto& mm = xdg->mesh_manager(); + mm->load_file(args.get("filename")); + mm->init(); + mm->parse_metadata(); + + auto rti = xdg->ray_tracing_interface(); + + if (args.get("--list")) { + std::cout << "Volumes: " << std::endl; + for (auto volume : mm->volumes()) { + std::cout << volume << std::endl; + } + exit(0); + } + + MeshID volume = args.get("volume"); + xdg->prepare_volume_for_raytracing(volume); + + // Gather our inputs and determine which mode of operation the tool will be working in + auto flat_origins = args.get>("--origin"); + auto flat_directions = args.get>("--direction"); + + if (flat_origins.empty()) { + fatal_error("You must supply at least one --origin x y z"); + } + if (flat_origins.size() % 3 != 0) { + fatal_error("Origins must be supplied in groups of 3 numbers."); + } + + // group every 3 into Position / Direction + std::vector> args_origins; + for (size_t i = 0; i < flat_origins.size(); i += 3) { + args_origins.push_back({flat_origins[i], flat_origins[i+1], flat_origins[i+2]}); + } + + std::vector> args_directions; + for (size_t i = 0; i < flat_directions.size(); i += 3) { + args_directions.push_back({flat_directions[i], flat_directions[i+1], flat_directions[i+2]}); + } + + // helper lambdas to convert std::vector to xdg::Position and xdg::Direction types + auto vec_to_pos = [](const std::vector& v) { return Position{v[0], v[1], v[2]}; }; + auto vec_to_dir = [](const std::vector& v) { + Direction dir{v[0], v[1], v[2]}; + dir.normalize(); + return dir; + }; + + const size_t N = args_origins.size(); + size_t num_dirs = args_directions.size(); + + std::vector origins; + origins.reserve(N); + for (const auto& o : args_origins) origins.push_back(vec_to_pos(o)); + + std::vector directions; + std::vector has_dir; // mask to indicate which rays have directions + const Direction* directions_ptr = nullptr; + const uint8_t* has_dir_ptr = nullptr; + + if (num_dirs == 0) { + // No directions let batch API set default direction per point + directions_ptr = nullptr; + has_dir_ptr = nullptr; + } else if (num_dirs == 1) { + // Broadcast one direction to all points (no mask needed) + directions.assign(N, vec_to_dir(args_directions[0])); + directions_ptr = directions.data(); + has_dir_ptr = nullptr; + } else if (num_dirs < N) { + // First k get explicit directions; rest fall back to default via mask + const size_t k = num_dirs; + directions.resize(N); + has_dir.assign(N, 0); + for (size_t i = 0; i < k; ++i) { + directions[i] = vec_to_dir(args_directions[i]); + has_dir[i] = 1; + } + directions_ptr = directions.data(); + has_dir_ptr = has_dir.data(); + } else { + // ≥ N directions → use first N pairwise (no mask needed) + directions.reserve(N); + for (size_t i = 0; i < N; ++i) directions.push_back(vec_to_dir(args_directions[i])); + directions_ptr = directions.data(); + has_dir_ptr = nullptr; + } + + std::vector results(N, 0xFF); + + xdg->batch_point_in_volume(volume, + origins.data(), + directions.data(), + N, + results.data(), + has_dir.data()); + + std::cout << std::endl << "Printing Batch point in volume results..." << std::endl; + + std::cout << "\nPrinting Batch point-in-volume results...\n"; + for (size_t i = 0; i < N; ++i) { + const auto& p = origins[i]; + std::cout << "Point (" << p.x << ", " << p.y << ", " << p.z << ") " + << (results[i] ? "is in " : "is NOT in ") + << "Volume " << volume << "\n"; + } + + return 0; +} diff --git a/tools/batch_ray_fire.cpp b/tools/batch_ray_fire.cpp index d2cea1ad..3400bfc5 100644 --- a/tools/batch_ray_fire.cpp +++ b/tools/batch_ray_fire.cpp @@ -12,9 +12,6 @@ #include "argparse/argparse.hpp" -using namespace xdg; - -// --------------------------- Helpers to determine what kind of batch to use --------------------------- enum class BatchMode { ORIGIN_BROADCAST, // 1 origin, many directions DIRECTION_BROADCAST, // many origins, 1 direction @@ -52,7 +49,8 @@ inline BatchMode deduce_batch_mode(size_t num_origins, size_t num_directions) { ); } } -// ------------------------------------------------------------------------------------------------------ + +using namespace xdg; int main(int argc, char** argv) { @@ -90,12 +88,12 @@ int main(int argc, char** argv) { // High-level rules in the description args.add_description( - "This tool supports two modes of operation for batch ray firing: 'Broadcast' and 'Pairwise'\n\n" - "To use 'Broadcast' mode, provide one origin and many directions, or one direction and many origins:\n" - " --origin x y z --direction u1 v1 w1 --direction u2 v2 w2 ...\n" - " --direction u v w --origin x1 y1 z1 --origin x2 y2 z2 ...\n\n" - "To use 'Pairwise' mode, each origin is paired with a corresponding direction in order:\n" - " --origin x1 y1 z1 --direction u1 v1 w1 --origin x2 y2 z2 --direction u2 v2 w2 ...\n" + "This tool supports two modes of operation for batch ray firing: 'Broadcast' and 'Pairwise'\n\n" + "To use 'Broadcast' mode, provide one origin and many directions, or one direction and many origins:\n" + " --origin x y z --direction u1 v1 w1 --direction u2 v2 w2 ...\n" + " --direction u v w --origin x1 y1 z1 --origin x2 y2 z2 ...\n\n" + "To use 'Pairwise' mode, each origin is paired with a corresponding direction in order:\n" + " --origin x1 y1 z1 --direction u1 v1 w1 --origin x2 y2 z2 --direction u2 v2 w2 ...\n" ); @@ -145,6 +143,16 @@ int main(int argc, char** argv) { auto flat_origins = args.get>("--origin"); auto flat_directions = args.get>("--direction"); + if (flat_origins.empty()) { + fatal_error("You must supply at least one --origin x y z"); + } + if (flat_origins.size() % 3 != 0) { + fatal_error("Origins must be supplied in groups of 3 numbers."); + } + if (flat_directions.size() % 3 != 0) { + fatal_error("Directions must be supplied in groups of 3 numbers."); + } + // group every 3 into Position / Direction std::vector> args_origins; for (size_t i = 0; i < flat_origins.size(); i += 3) { From 102501393e68362b997051cf978bc367de9d21c7 Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Wed, 5 Nov 2025 16:32:24 +0000 Subject: [PATCH 06/62] Added stress test for batch_ray_fire which calls over a batch of 10M rays --- tests/test_ray_fire.cpp | 116 +++++++++++++++++++++++++--------------- 1 file changed, 74 insertions(+), 42 deletions(-) diff --git a/tests/test_ray_fire.cpp b/tests/test_ray_fire.cpp index e0cc5768..7e616be6 100644 --- a/tests/test_ray_fire.cpp +++ b/tests/test_ray_fire.cpp @@ -11,6 +11,8 @@ #include "mesh_mock.h" #include "util.h" +#include + using namespace xdg; using namespace xdg::test; @@ -133,60 +135,90 @@ TEST_CASE("Batch API Ray Fire on MeshMock", "[rayfire][mock][batch]") { rti->init(); - // Create a set of 64 rays to be used throughout this test_case - std::vector origins; - std::vector directions; - origins.reserve(64); - directions.reserve(64); - for (int i = 0; i < 64; ++i) { - int axis = i % 3; - double s = (i % 2) ? 1.0 : -1.0; - origins.push_back({0,0,0}); - directions.push_back(axis == 0 ? Direction{s,0,0} - : axis == 1 ? Direction{0,s,0} - : Direction{0,0,s}); - } - - // Store results of scalar ray_fires to verify batch against scalar - std::vector scalar_ray_fire_distances(origins.size(), INFTY); - std::vector scalar_ray_fire_surface_id(origins.size(), ID_NONE); - for (size_t i = 0; i < origins.size(); ++i) { - auto [distance, surfID] = rti->ray_fire(volume_tree, origins[i], directions[i], INFTY, HitOrientation::EXITING); - scalar_ray_fire_distances[i] = distance; - scalar_ray_fire_surface_id[i] = surfID; - } + // Helper to synthesize origins/directions like your 64-ray pattern, extended to N + auto make_rays = [](size_t N, std::vector& origins, std::vector& directions) { + origins.clear(); directions.clear(); + origins.reserve(N); directions.reserve(N); + for (size_t i = 0; i < N; ++i) { + int axis = int(i % 3); + double s = (i % 2) ? 1.0 : -1.0; + origins.push_back({0.0, 0.0, 0.0}); + if (axis == 0) directions.push_back({s, 0.0, 0.0}); + else if (axis == 1) directions.push_back({0.0, s, 0.0}); + else directions.push_back({0.0, 0.0, s}); + } + }; + // ---- N = 0 ---- SECTION("N=0 no-op") { rti->batch_ray_fire(volume_tree, nullptr, nullptr, 0, nullptr, nullptr, INFTY, HitOrientation::EXITING, nullptr); SUCCEED("N=0 completed without error"); } + // ---- N = 1 ---- SECTION("N=1 equals scalar") { - double hd; - MeshID sid = ID_NONE; + std::vector origins; + std::vector directions; + make_rays(1, origins, directions); + + auto [dist_scalar, id_scalar] = rti->ray_fire(volume_tree, origins[0], directions[0], INFTY, HitOrientation::EXITING); - rti->batch_ray_fire(volume_tree, &origins[0], &directions[0], 1, &hd, - &sid, INFTY, HitOrientation::EXITING, nullptr); + double dist_batch = -1.0; + MeshID id_batch = ID_NONE; + rti->batch_ray_fire(volume_tree, origins.data(), directions.data(), 1, + &dist_batch, &id_batch, INFTY, HitOrientation::EXITING, nullptr); - REQUIRE(sid != ID_NONE); // expect a hit - - // Ensure that hit matches scalar ray_fire - REQUIRE_THAT(hd, Catch::Matchers::WithinAbs(scalar_ray_fire_distances[0], 1e-6)); - REQUIRE(sid == scalar_ray_fire_surface_id[0]); + REQUIRE(id_batch == id_scalar); + REQUIRE_THAT(dist_batch, Catch::Matchers::WithinAbs(dist_scalar, 1e-6)); } - SECTION("N=64") { - std::vector hd(origins.size(), -1.0); - std::vector sid(origins.size(), ID_NONE); - rti->batch_ray_fire(volume_tree, origins.data(), directions.data(), - origins.size(), hd.data(), sid.data(), - INFTY, HitOrientation::EXITING, nullptr); - - // Ensure that hits match scalar ray_fires - for (size_t i = 0; i < origins.size(); ++i) { - REQUIRE_THAT(hd[i], Catch::Matchers::WithinAbs(scalar_ray_fire_distances[i], 1e-6)); - REQUIRE(sid[i] == scalar_ray_fire_surface_id[i]); + // ---- N = 64 ---- + SECTION("N=64 matches scalar for all") { + std::vector origins; + std::vector directions; + make_rays(64, origins, directions); + + std::vector dist_scalar(64, INFTY); + std::vector id_scalar(64, ID_NONE); + for (size_t i = 0; i < 64; ++i) { + auto [d, id] = rti->ray_fire(volume_tree, origins[i], directions[i], INFTY, HitOrientation::EXITING); + dist_scalar[i] = d; id_scalar[i] = id; + } + + std::vector dist_batch(64, -1.0); + std::vector id_batch(64, ID_NONE); + rti->batch_ray_fire(volume_tree, origins.data(), directions.data(), origins.size(), + dist_batch.data(), id_batch.data(), INFTY, HitOrientation::EXITING, nullptr); + + for (size_t i = 0; i < 64; ++i) { + REQUIRE(id_batch[i] == id_scalar[i]); + REQUIRE_THAT(dist_batch[i], Catch::Matchers::WithinAbs(dist_scalar[i], 1e-6)); + } + } + + // ---- N = 10,000,000 ---- + SECTION("N=10,000,000 batch with basic sanity checks") { + const size_t N = 10000000; + std::vector origins; + std::vector directions; + make_rays(N, origins, directions); + + // Batch compute + std::vector dist_batch(N, -1.0); + std::vector id_batch(N, ID_NONE); + auto t0 = std::chrono::high_resolution_clock::now(); + rti->batch_ray_fire(volume_tree, origins.data(), directions.data(), N, + dist_batch.data(), id_batch.data(), INFTY, HitOrientation::EXITING, nullptr); + auto t1 = std::chrono::high_resolution_clock::now(); + std::chrono::duration batch_time = t1 - t0; + printf("Completed batch ray fire for N=%zu rays, in %f seconds\n", N, batch_time.count()); + + // Basic sanity checks over 100 rays + for (size_t i = 0; i < N; i += N/100) { + REQUIRE(id_batch[i] != ID_NONE); + REQUIRE(std::isfinite(dist_batch[i])); + REQUIRE(dist_batch[i] >= 0.0); } } } From 0daa26530b1703a1ebde0b634a93b0e8e81e9b4e Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Thu, 6 Nov 2025 11:52:00 +0000 Subject: [PATCH 07/62] Added a catch2 microbenchmark to the large number of rays stress test --- tests/test_ray_fire.cpp | 26 +++++++++++++------------- tools/batch_ray_fire.cpp | 3 +-- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/tests/test_ray_fire.cpp b/tests/test_ray_fire.cpp index 7e616be6..91f107c2 100644 --- a/tests/test_ray_fire.cpp +++ b/tests/test_ray_fire.cpp @@ -3,7 +3,7 @@ #include #include #include - +#include // xdg includes #include "xdg/constants.h" @@ -137,8 +137,10 @@ TEST_CASE("Batch API Ray Fire on MeshMock", "[rayfire][mock][batch]") { // Helper to synthesize origins/directions like your 64-ray pattern, extended to N auto make_rays = [](size_t N, std::vector& origins, std::vector& directions) { - origins.clear(); directions.clear(); - origins.reserve(N); directions.reserve(N); + origins.clear(); + directions.clear(); + origins.reserve(N); + directions.reserve(N); for (size_t i = 0; i < N; ++i) { int axis = int(i % 3); double s = (i % 2) ? 1.0 : -1.0; @@ -197,9 +199,9 @@ TEST_CASE("Batch API Ray Fire on MeshMock", "[rayfire][mock][batch]") { } } - // ---- N = 10,000,000 ---- - SECTION("N=10,000,000 batch with basic sanity checks") { - const size_t N = 10000000; + // ---- N = 100,000 ---- + SECTION("N=100,00 batch with basic sanity checks") { + const size_t N = 100000; std::vector origins; std::vector directions; make_rays(N, origins, directions); @@ -207,13 +209,11 @@ TEST_CASE("Batch API Ray Fire on MeshMock", "[rayfire][mock][batch]") { // Batch compute std::vector dist_batch(N, -1.0); std::vector id_batch(N, ID_NONE); - auto t0 = std::chrono::high_resolution_clock::now(); - rti->batch_ray_fire(volume_tree, origins.data(), directions.data(), N, - dist_batch.data(), id_batch.data(), INFTY, HitOrientation::EXITING, nullptr); - auto t1 = std::chrono::high_resolution_clock::now(); - std::chrono::duration batch_time = t1 - t0; - printf("Completed batch ray fire for N=%zu rays, in %f seconds\n", N, batch_time.count()); - + BENCHMARK("Batch ray fire with N = 100,000") + { + return rti->batch_ray_fire(volume_tree, origins.data(), directions.data(), N, + dist_batch.data(), id_batch.data(), INFTY, HitOrientation::EXITING, nullptr); + }; // Basic sanity checks over 100 rays for (size_t i = 0; i < N; i += N/100) { REQUIRE(id_batch[i] != ID_NONE); diff --git a/tools/batch_ray_fire.cpp b/tools/batch_ray_fire.cpp index 3400bfc5..23ddee4c 100644 --- a/tools/batch_ray_fire.cpp +++ b/tools/batch_ray_fire.cpp @@ -96,7 +96,6 @@ int main(int argc, char** argv) { " --origin x1 y1 z1 --direction u1 v1 w1 --origin x2 y2 z2 --direction u2 v2 w2 ...\n" ); - try { args.parse_args(argc, argv); } @@ -107,7 +106,7 @@ int main(int argc, char** argv) { } std::string mesh_str = args.get("--mesh-library"); - std::string rt_str = args.get("--rt-library"); + std::string rt_str = args.get("--rt-library"); MeshLibrary mesh_lib; if (mesh_str == "MOAB") mesh_lib = MeshLibrary::MOAB; From f7a49bac4833b952dd3013e8a834f8e48e9a87a6 Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Fri, 7 Nov 2025 13:20:15 +0000 Subject: [PATCH 08/62] Rebased changes from #170 --- include/xdg/embree/ray_tracer.h | 2 +- include/xdg/gprt/ray_tracer.h | 5 +- include/xdg/ray_tracing_interface.h | 2 +- src/gprt/ray_tracer.cpp | 105 +++++++++++----------------- 4 files changed, 45 insertions(+), 69 deletions(-) diff --git a/include/xdg/embree/ray_tracer.h b/include/xdg/embree/ray_tracer.h index d3d912f1..7970550d 100644 --- a/include/xdg/embree/ray_tracer.h +++ b/include/xdg/embree/ray_tracer.h @@ -62,7 +62,7 @@ class EmbreeRayTracer : public RayTracer { const size_t num_points, uint8_t* results, const uint8_t* has_dir = nullptr, - std::vector* exclude_primitives = nullptr) const override + std::vector* exclude_primitives = nullptr) override { fatal_error("Batch point_in_volume not yet implemented for EmbreeRayTracer"); }; diff --git a/include/xdg/gprt/ray_tracer.h b/include/xdg/gprt/ray_tracer.h index 4ad035c3..7f4be94f 100644 --- a/include/xdg/gprt/ray_tracer.h +++ b/include/xdg/gprt/ray_tracer.h @@ -36,7 +36,6 @@ struct gprtRayHit { bool is_valid() const { return capacity > 0 && ray && hit && devRayAddr && devHitAddr; } }; - class GPRTRayTracer : public RayTracer { public: GPRTRayTracer(); @@ -97,7 +96,7 @@ class GPRTRayTracer : public RayTracer { const size_t num_points, uint8_t* results, const uint8_t* has_dir = nullptr, - std::vector* exclude_primitives = nullptr) const override; + std::vector* exclude_primitives = nullptr) override; // Array version of ray_fire void batch_ray_fire(TreeID tree, @@ -122,7 +121,7 @@ class GPRTRayTracer : public RayTracer { } private: - void check_ray_buffer_capacity(size_t N); + void check_ray_buffer_capacity(const size_t N); // GPRT objects GPRTContext context_; diff --git a/include/xdg/ray_tracing_interface.h b/include/xdg/ray_tracing_interface.h index d7c4fb2b..ff3eff50 100644 --- a/include/xdg/ray_tracing_interface.h +++ b/include/xdg/ray_tracing_interface.h @@ -95,7 +95,7 @@ class RayTracer { const size_t num_points, uint8_t* results, const uint8_t* has_dir = nullptr, - std::vector* exclude_primitives = nullptr) const = 0; + std::vector* exclude_primitives = nullptr) = 0; // Array version of ray_fire virtual void batch_ray_fire(TreeID tree, diff --git a/src/gprt/ray_tracer.cpp b/src/gprt/ray_tracer.cpp index 1d39424d..de1ae07a 100644 --- a/src/gprt/ray_tracer.cpp +++ b/src/gprt/ray_tracer.cpp @@ -333,26 +333,14 @@ void GPRTRayTracer::batch_point_in_volume(TreeID tree, const size_t num_points, uint8_t* results, const uint8_t* has_dir, - std::vector* exclude_primitives) const + std::vector* exclude_primitives) { if (num_points == 0) return; // no work to do. Early exit GPRTAccel volume = surface_volume_tree_to_accel_map.at(tree); - - // resize buffers to the number of points to be queried - gprtBufferResize(context_, rayInputBuffer_, num_points, false); - gprtBufferResize(context_, rayOutputBuffer_, num_points, false); - - // Since we have resized the ray input buffer, we need to update the geom_data->rayIn pointers in all geometries - for (auto const& [surf, geom] : surface_to_geometry_map_) { - DPTriangleGeomData* geom_data = gprtGeomGetParameters(geom); - geom_data->rayIn = gprtBufferGetDevicePointer(rayInputBuffer_); - } - - // Refresh raygen IO after resize - dblRayGenData* rayGenPIVData = gprtRayGenGetParameters(rayGenPointInVolProgram_); - rayGenPIVData->ray = gprtBufferGetDevicePointer(rayInputBuffer_); - rayGenPIVData->out = gprtBufferGetDevicePointer(rayOutputBuffer_); + auto rayGen = rayGenPrograms_.at(RayGenType::POINT_IN_VOLUME); + dblRayGenData* rayGenData = gprtRayGenGetParameters(rayGen); + check_ray_buffer_capacity(num_points); // TODO - handle exclude_primitives for batch version @@ -360,8 +348,8 @@ void GPRTRayTracer::batch_point_in_volume(TreeID tree, const Direction defaultDir = Direction{1. / std::sqrt(2.0), 1. / std::sqrt(2.0), 0.0}; // Map the region start - gprtBufferMap(rayInputBuffer_); - dblRayInput* rayInput = gprtBufferGetHostPointer(rayInputBuffer_); + gprtBufferMap(rayHitBuffers_.ray); + dblRay* ray = gprtBufferGetHostPointer(rayHitBuffers_.ray); const auto volumeAddr = gprtAccelGetDeviceAddress(volume); for (size_t i = 0; i < num_points; ++i) { Direction directionUsed = @@ -373,32 +361,32 @@ void GPRTRayTracer::batch_point_in_volume(TreeID tree, + directionUsed.z*directionUsed.z; if (l2 == 0.0) directionUsed = defaultDir; - rayInput[i].volume_accel = volumeAddr; - rayInput[i].origin = {points[i].x, points[i].y, points[i].z}; - rayInput[i].direction = {directionUsed.x, directionUsed.y, directionUsed.z}; - rayInput[i].tMax = INFTY; // Set a large distance limit - rayInput[i].tMin = 0.0; - rayInput[i].volume_tree = tree; // Set the TreeID of the volume being queried - rayInput[i].hitOrientation = HitOrientation::ANY; // No orientation culling for point-in-volume check - rayInput[i].exclude_primitives = nullptr; // Not currently supported in batch version + ray[i].volume_accel = volumeAddr; + ray[i].origin = {points[i].x, points[i].y, points[i].z}; + ray[i].direction = {directionUsed.x, directionUsed.y, directionUsed.z}; + ray[i].tMax = INFTY; // Set a large distance limit + ray[i].tMin = 0.0; + ray[i].volume_tree = tree; // Set the TreeID of the volume being queried + ray[i].hitOrientation = HitOrientation::ANY; // No orientation culling for point-in-volume check + ray[i].exclude_primitives = nullptr; // Not currently supported in batch version } - gprtBufferUnmap(rayInputBuffer_); // required to sync buffer back on GPU? + gprtBufferUnmap(rayHitBuffers_.ray); // required to sync buffer back on GPU? // rebuild SBT geom and raygen only gprtBuildShaderBindingTable(context_, static_cast(GPRT_SBT_GEOM | GPRT_SBT_RAYGEN)); - gprtRayGenLaunch1D(context_, rayGenPointInVolProgram_, num_points); + gprtRayGenLaunch1D(context_, rayGen, num_points); gprtGraphicsSynchronize(context_); // Retrieve the output from the ray output buffer - gprtBufferMap(rayOutputBuffer_); - dblRayOutput* rayOutput = gprtBufferGetHostPointer(rayOutputBuffer_); + gprtBufferMap(rayHitBuffers_.hit); + dblHit* hit = gprtBufferGetHostPointer(rayHitBuffers_.hit); for (size_t i = 0; i < num_points; ++i) { - auto piv = rayOutput[i].piv; // Point in volume check result + auto piv = hit[i].piv; // Point in volume check result results[i] = static_cast(piv); } - gprtBufferUnmap(rayOutputBuffer_); // required to sync buffer back on GPU? Maybe this second unmap isn't actually needed since we dont need to resyncrhonize after retrieving the data from device + gprtBufferUnmap(rayHitBuffers_.hit); // required to sync buffer back on GPU? Maybe this second unmap isn't actually needed since we dont need to resyncrhonize after retrieving the data from device return; } @@ -419,63 +407,52 @@ void GPRTRayTracer::batch_ray_fire(TreeID tree, if (num_rays == 0) return; // no work to do. Early exit GPRTAccel volume = surface_volume_tree_to_accel_map.at(tree); - dblRayGenData* rayGenData = gprtRayGenGetParameters(rayGenProgram_); - - // resize buffers to the number of points to be queried - gprtBufferResize(context_, rayInputBuffer_, num_rays, false); - gprtBufferResize(context_, rayOutputBuffer_, num_rays, false); - - // Since we have resized the ray input buffer, we need to update the geom_data->rayIn pointers in all geometries - for (auto const& [surf, geom] : surface_to_geometry_map_) { - DPTriangleGeomData* geom_data = gprtGeomGetParameters(geom); - geom_data->rayIn = gprtBufferGetDevicePointer(rayInputBuffer_); - } + auto rayGen = rayGenPrograms_.at(RayGenType::RAY_FIRE); + dblRayGenData* rayGenData = gprtRayGenGetParameters(rayGen); + check_ray_buffer_capacity(num_rays); - gprtBufferMap(rayInputBuffer_); - dblRayInput* rayInput = gprtBufferGetHostPointer(rayInputBuffer_); + gprtBufferMap(rayHitBuffers_.ray); + dblRay* ray = gprtBufferGetHostPointer(rayHitBuffers_.ray); for (size_t i = 0; i < num_rays; ++i) { const auto& origin = origins[i]; const auto& direction = directions[i]; - rayInput[i].volume_accel = gprtAccelGetDeviceAddress(volume); - rayInput[i].origin = {origin.x, origin.y, origin.z}; - rayInput[i].direction = {direction.x, direction.y, direction.z}; - rayInput[i].tMax = dist_limit; - rayInput[i].tMin = 0.0; - rayInput[i].hitOrientation = orientation; // Set orientation for the ray - rayInput[i].volume_tree = tree; // Set the TreeID of the volume being queried - rayInput[i].exclude_primitives = nullptr; // Not currently supported in batch version + ray[i].volume_accel = gprtAccelGetDeviceAddress(volume); + ray[i].origin = {origin.x, origin.y, origin.z}; + ray[i].direction = {direction.x, direction.y, direction.z}; + ray[i].tMax = dist_limit; + ray[i].tMin = 0.0; + ray[i].hitOrientation = orientation; // Set orientation for the ray + ray[i].volume_tree = tree; // Set the TreeID of the volume being queried + ray[i].exclude_primitives = nullptr; // Not currently supported in batch version } - rayGenData->ray = gprtBufferGetDevicePointer(rayInputBuffer_); - rayGenData->out = gprtBufferGetDevicePointer(rayOutputBuffer_); - - gprtBufferUnmap(rayInputBuffer_); // required to sync buffer back on GPU? + gprtBufferUnmap(rayHitBuffers_.ray); // required to sync buffer back on GPU? // rebuild SBT geom and raygen only gprtBuildShaderBindingTable(context_, static_cast(GPRT_SBT_GEOM | GPRT_SBT_RAYGEN)); // Launch the ray generation shader with push constants and buffer bindings - gprtRayGenLaunch1D(context_, rayGenProgram_, num_rays); + gprtRayGenLaunch1D(context_, rayGen, num_rays); gprtGraphicsSynchronize(context_); // Retrieve the output from the ray output buffer - gprtBufferMap(rayOutputBuffer_); - dblRayOutput* rayOutput = gprtBufferGetHostPointer(rayOutputBuffer_); + gprtBufferMap(rayHitBuffers_.hit); + dblHit* hit = gprtBufferGetHostPointer(rayHitBuffers_.hit); // populate the result arrays for (size_t i = 0; i < num_rays; ++i) { - const MeshID surfaceHit = rayOutput[i].surf_id; + const MeshID surfaceHit = hit[i].surf_id; if (surfaceHit == ID_NONE) { hitDistances[i] = INFTY; surfaceIDs[i] = ID_NONE; } else { - hitDistances[i] = rayOutput[i].distance; + hitDistances[i] = hit[i].distance; surfaceIDs[i] = surfaceHit; // TODO - handle exclude_primitives for batch version } } - gprtBufferUnmap(rayOutputBuffer_); // required to sync buffer back on GPU? Maybe this second unmap isn't actually needed since we dont need to resyncrhonize after retrieving the data from device + gprtBufferUnmap(rayHitBuffers_.hit); // required to sync buffer back on GPU? Maybe this second unmap isn't actually needed since we dont need to resyncrhonize after retrieving the data from device return; } @@ -494,7 +471,7 @@ void GPRTRayTracer::create_global_surface_tree() global_surface_accel_ = global_accel; } -void GPRTRayTracer::check_ray_buffer_capacity(size_t N) +void GPRTRayTracer::check_ray_buffer_capacity(const size_t N) { if (N <= rayHitBuffers_.capacity) return; // current capacity is sufficient From 742b1f0ed538974d0386ec4f095367208a0f55aa Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Mon, 10 Nov 2025 11:00:49 +0000 Subject: [PATCH 09/62] Overloading ray_fire/point_in_volume for batch calls --- include/xdg/embree/ray_tracer.h | 35 +++++++++--------- include/xdg/gprt/ray_tracer.h | 36 +++++++++---------- include/xdg/ray_tracing_interface.h | 55 +++++++++++++++++++---------- include/xdg/xdg.h | 53 +++++++++++++++++---------- src/gprt/ray_tracer.cpp | 22 ++++++------ src/xdg.cpp | 24 ++++++------- tests/test_point_in_volume.cpp | 6 ++-- tests/test_ray_fire.cpp | 16 ++++----- tools/batch_point_in_volume.cpp | 12 +++---- tools/batch_ray_fire.cpp | 2 +- 10 files changed, 145 insertions(+), 116 deletions(-) diff --git a/include/xdg/embree/ray_tracer.h b/include/xdg/embree/ray_tracer.h index 7970550d..463bd633 100644 --- a/include/xdg/embree/ray_tracer.h +++ b/include/xdg/embree/ray_tracer.h @@ -47,16 +47,8 @@ class EmbreeRayTracer : public RayTracer { const Direction* direction = nullptr, const std::vector* exclude_primitives = nullptr) const override; - - std::pair ray_fire(TreeID scene, - const Position& origin, - const Direction& direction, - const double dist_limit = INFTY, - HitOrientation orientation = HitOrientation::EXITING, - std::vector* const exclude_primitives = nullptr) override; - // Array version of point_in_volume - void batch_point_in_volume(TreeID tree, + void point_in_volume(TreeID tree, const Position* points, const Direction* directions, // [num_points] array of Direction pointers const size_t num_points, @@ -67,16 +59,23 @@ class EmbreeRayTracer : public RayTracer { fatal_error("Batch point_in_volume not yet implemented for EmbreeRayTracer"); }; + std::pair ray_fire(TreeID scene, + const Position& origin, + const Direction& direction, + const double dist_limit = INFTY, + HitOrientation orientation = HitOrientation::EXITING, + std::vector* const exclude_primitives = nullptr) override; + // Array version of ray_fire - void batch_ray_fire(TreeID tree, - const Position* origins, - const Direction* directions, - const size_t num_rays, - double* hitDistances, - MeshID* surfaceIDs, - const double dist_limit = INFTY, - HitOrientation orientation = HitOrientation::EXITING, - std::vector* const exclude_primitives = nullptr) override + void ray_fire(TreeID tree, + const Position* origins, + const Direction* directions, + const size_t num_rays, + double* hitDistances, + MeshID* surfaceIDs, + const double dist_limit = INFTY, + HitOrientation orientation = HitOrientation::EXITING, + std::vector* const exclude_primitives = nullptr) override { fatal_error("Batch ray_fire not yet implemented for EmbreeRayTracer"); }; diff --git a/include/xdg/gprt/ray_tracer.h b/include/xdg/gprt/ray_tracer.h index 7f4be94f..be51a54a 100644 --- a/include/xdg/gprt/ray_tracer.h +++ b/include/xdg/gprt/ray_tracer.h @@ -82,6 +82,14 @@ class GPRTRayTracer : public RayTracer { const Direction* direction = nullptr, const std::vector* exclude_primitives = nullptr) const override; + void point_in_volume(TreeID tree, + const Position* points, + const Direction* directions, // [num_points] array of Direction pointers + const size_t num_points, + uint8_t* results, + const uint8_t* has_dir = nullptr, + std::vector* exclude_primitives = nullptr) override; + std::pair ray_fire(TreeID scene, const Position& origin, const Direction& direction, @@ -89,25 +97,15 @@ class GPRTRayTracer : public RayTracer { HitOrientation orientation = HitOrientation::EXITING, std::vector* const exclude_primitives = nullptr) override; - // Array version of point_in_volume - void batch_point_in_volume(TreeID tree, - const Position* points, - const Direction* directions, // [num_points] array of Direction pointers - const size_t num_points, - uint8_t* results, - const uint8_t* has_dir = nullptr, - std::vector* exclude_primitives = nullptr) override; - - // Array version of ray_fire - void batch_ray_fire(TreeID tree, - const Position* origins, - const Direction* directions, - const size_t num_rays, - double* hitDistances, - MeshID* surfaceIDs, - const double dist_limit = INFTY, - HitOrientation orientation = HitOrientation::EXITING, - std::vector* const exclude_primitives = nullptr) override; + void ray_fire(TreeID tree, + const Position* origins, + const Direction* directions, + const size_t num_rays, + double* hitDistances, + MeshID* surfaceIDs, + const double dist_limit = INFTY, + HitOrientation orientation = HitOrientation::EXITING, + std::vector* const exclude_primitives = nullptr) override; std::pair closest(TreeID scene, const Position& origin) override {}; diff --git a/include/xdg/ray_tracing_interface.h b/include/xdg/ray_tracing_interface.h index ff3eff50..6413f7ed 100644 --- a/include/xdg/ray_tracing_interface.h +++ b/include/xdg/ray_tracing_interface.h @@ -87,26 +87,43 @@ class RayTracer { std::vector* const exclude_primitives = nullptr) = 0; // Array based queries - + // Array version of point_in_volume - virtual void batch_point_in_volume(TreeID tree, - const Position* points, - const Direction* directions, // [num_points] array of Direction pointers - const size_t num_points, - uint8_t* results, - const uint8_t* has_dir = nullptr, - std::vector* exclude_primitives = nullptr) = 0; - - // Array version of ray_fire - virtual void batch_ray_fire(TreeID tree, - const Position* origin, - const Direction* direction, - const size_t num_rays, - double* hitDistances, - MeshID* surfaceIDs, - const double dist_limit = INFTY, - HitOrientation orientation = HitOrientation::EXITING, - std::vector* const exclude_primitives = nullptr) = 0; + virtual void point_in_volume(TreeID tree, + const Position* points, + const Direction* directions, // [num_points] array of Direction pointers + const size_t num_points, + uint8_t* results, + const uint8_t* has_dir = nullptr, + std::vector* exclude_primitives = nullptr) = 0; + + /** + * @brief Array based version of ray_fire query + * + * This method performs a set of ray fire queries on a batch of rays defined by their origins and directions. + * It computes the intersection distances and surface IDs for each ray in the batch. With GPRT ray tracing + * this launches the RT pipeline with the number of rays provided. + * + * @param tree The TreeID of the volume we are querying against + * @param origin An array of Position objects representing the starting points of the rays + * @param direction An array of Direction objects representing the directions of the rays + * @param num_rays The number of rays to be processed in the batch + * @param hitDistances An output array to store the computed intersection distances for each ray + * @param surfaceIDs An output array to store the MeshIDs of the surfaces hit by each ray + * @param dist_limit The maximum distance to consider for intersections + * @param orientation Flag to consider whether Entering/Exiting hits should be rejected + * @param exclude_primitives An optional vector of surface element MeshIDs to exclude from intersection tests + * @return Void. Outputs stored in hitDistances and surfaceIDs arrays + */ + virtual void ray_fire(TreeID tree, + const Position* origin, + const Direction* direction, + const size_t num_rays, + double* hitDistances, + MeshID* surfaceIDs, + const double dist_limit = INFTY, + HitOrientation orientation = HitOrientation::EXITING, + std::vector* const exclude_primitives = nullptr) = 0; /** * @brief Finds the element containing a given point using the global element tree. diff --git a/include/xdg/xdg.h b/include/xdg/xdg.h index 02fb5a6a..275cdf87 100644 --- a/include/xdg/xdg.h +++ b/include/xdg/xdg.h @@ -76,24 +76,41 @@ std::pair ray_fire(MeshID volume, std::vector* const exclude_primitives = nullptr) const; // Array version of point_in_volume -void batch_point_in_volume(MeshID volume, - const Position* points, - const Direction* directions, // [num_points] array of Direction pointers - const size_t num_points, - uint8_t* results, - const uint8_t* has_dir = nullptr, - std::vector* exclude_primitives = nullptr) const; - -// Array version of ray_fire -void batch_ray_fire(MeshID volume, - const Position* origins, - const Direction* directions, - const size_t num_rays, - double* hitDistances, - MeshID* surfaceIDs, - const double dist_limit = INFTY, - HitOrientation orientation = HitOrientation::EXITING, - std::vector* const exclude_primitives = nullptr); +void point_in_volume(MeshID volume, + const Position* points, + const Direction* directions, // [num_points] array of Direction pointers + const size_t num_points, + uint8_t* results, + const uint8_t* has_dir = nullptr, + std::vector* exclude_primitives = nullptr) const; + +/** + * @brief Array based version of ray_fire query + * + * This method performs a set of ray fire queries on a batch of rays defined by their origins and directions. + * It computes the intersection distances and surface IDs for each ray in the batch. With GPRT ray tracing + * this launches the RT pipeline with the number of rays provided. + * + * @param volume The MeshID of the volume we are querying against + * @param origin An array of Position objects representing the starting points of the rays + * @param direction An array of Direction objects representing the directions of the rays + * @param num_rays The number of rays to be processed in the batch + * @param hitDistances An output array to store the computed intersection distances for each ray + * @param surfaceIDs An output array to store the MeshIDs of the surfaces hit by each ray + * @param dist_limit The maximum distance to consider for intersections + * @param orientation Flag to consider whether Entering/Exiting hits should be rejected + * @param exclude_primitives An optional vector of surface element MeshIDs to exclude from intersection tests + * @return Void. Outputs stored in hitDistances and surfaceIDs arrays + */ +void ray_fire(MeshID volume, + const Position* origins, + const Direction* directions, + const size_t num_rays, + double* hitDistances, + MeshID* surfaceIDs, + const double dist_limit = INFTY, + HitOrientation orientation = HitOrientation::EXITING, + std::vector* const exclude_primitives = nullptr); std::pair closest(MeshID volume, const Position& origin) const; diff --git a/src/gprt/ray_tracer.cpp b/src/gprt/ray_tracer.cpp index de1ae07a..0c8dcf08 100644 --- a/src/gprt/ray_tracer.cpp +++ b/src/gprt/ray_tracer.cpp @@ -327,7 +327,7 @@ std::pair GPRTRayTracer::ray_fire(SurfaceTreeID tree, return {distance, surface}; } -void GPRTRayTracer::batch_point_in_volume(TreeID tree, +void GPRTRayTracer::point_in_volume(TreeID tree, const Position* points, const Direction* directions, // [num_points] array of Direction pointers const size_t num_points, @@ -391,18 +391,16 @@ void GPRTRayTracer::batch_point_in_volume(TreeID tree, return; } - - // Array version of ray_fire -void GPRTRayTracer::batch_ray_fire(TreeID tree, - const Position* origins, - const Direction* directions, - const size_t num_rays, - double* hitDistances, - MeshID* surfaceIDs, - const double dist_limit, - HitOrientation orientation, - std::vector* const exclude_primitives) +void GPRTRayTracer::ray_fire(TreeID tree, + const Position* origins, + const Direction* directions, + const size_t num_rays, + double* hitDistances, + MeshID* surfaceIDs, + const double dist_limit, + HitOrientation orientation, + std::vector* const exclude_primitives) { if (num_rays == 0) return; // no work to do. Early exit diff --git a/src/xdg.cpp b/src/xdg.cpp index a6470169..fc336133 100644 --- a/src/xdg.cpp +++ b/src/xdg.cpp @@ -110,7 +110,7 @@ bool XDG::point_in_volume(MeshID volume, return ray_tracing_interface()->point_in_volume(tree, point, direction, exclude_primitives); } -void XDG::batch_point_in_volume(MeshID volume, +void XDG::point_in_volume(MeshID volume, const Position* points, const Direction* directions, // [num_points] array of Direction pointers const size_t num_points, @@ -119,7 +119,7 @@ void XDG::batch_point_in_volume(MeshID volume, std::vector* exclude_primitives) const { TreeID tree = volume_to_surface_tree_map_.at(volume); - ray_tracing_interface()->batch_point_in_volume(tree, points, directions, num_points, results, has_dir, exclude_primitives); + ray_tracing_interface()->point_in_volume(tree, points, directions, num_points, results, has_dir, exclude_primitives); } MeshID XDG::find_volume(const Position& point, @@ -253,18 +253,18 @@ XDG::ray_fire(MeshID volume, // Array version of ray_fire void -XDG::batch_ray_fire(MeshID volume, - const Position* origins, - const Direction* directions, - const size_t num_rays, - double* hitDistances, - MeshID* surfaceIDs, - const double dist_limit, - HitOrientation orientation, - std::vector* const exclude_primitives) +XDG::ray_fire(MeshID volume, + const Position* origins, + const Direction* directions, + const size_t num_rays, + double* hitDistances, + MeshID* surfaceIDs, + const double dist_limit, + HitOrientation orientation, + std::vector* const exclude_primitives) { TreeID tree = volume_to_surface_tree_map_.at(volume); - return ray_tracing_interface()->batch_ray_fire(tree, origins, directions, num_rays, hitDistances, surfaceIDs, dist_limit, orientation, exclude_primitives); + return ray_tracing_interface()->ray_fire(tree, origins, directions, num_rays, hitDistances, surfaceIDs, dist_limit, orientation, exclude_primitives); } std::pair XDG::closest(MeshID volume, diff --git a/tests/test_point_in_volume.cpp b/tests/test_point_in_volume.cpp index 1bf2f6c8..7adcf823 100644 --- a/tests/test_point_in_volume.cpp +++ b/tests/test_point_in_volume.cpp @@ -128,20 +128,20 @@ TEST_CASE("Batch API Point-in-volume on MeshMock", "[piv][mock][batch]") { } SECTION("N=0 no-op") { - rti->batch_point_in_volume(volume_tree, nullptr, nullptr, 0, nullptr, nullptr); + rti->point_in_volume(volume_tree, nullptr, nullptr, 0, nullptr, nullptr); SUCCEED("N=0 completed without error"); } SECTION("N=1") { uint8_t result = 0xFF; // sentinel - rti->batch_point_in_volume(volume_tree, &points[0], &directions[0], 1, &result, &has_dir[0]); + rti->point_in_volume(volume_tree, &points[0], &directions[0], 1, &result, &has_dir[0]); REQUIRE((result == 0 || result == 1)); REQUIRE(result == truth[0]); } SECTION("N=64") { std::vector results(num_points, 0xFF); - rti->batch_point_in_volume(volume_tree, points.data(), directions.data(), num_points, results.data(), has_dir.data()); + rti->point_in_volume(volume_tree, points.data(), directions.data(), num_points, results.data(), has_dir.data()); for (size_t i = 0; i < points.size(); ++i) { REQUIRE(results[i] == truth[i]); } diff --git a/tests/test_ray_fire.cpp b/tests/test_ray_fire.cpp index 91f107c2..0d609f40 100644 --- a/tests/test_ray_fire.cpp +++ b/tests/test_ray_fire.cpp @@ -153,8 +153,8 @@ TEST_CASE("Batch API Ray Fire on MeshMock", "[rayfire][mock][batch]") { // ---- N = 0 ---- SECTION("N=0 no-op") { - rti->batch_ray_fire(volume_tree, nullptr, nullptr, 0, nullptr, nullptr, - INFTY, HitOrientation::EXITING, nullptr); + rti->ray_fire(volume_tree, nullptr, nullptr, 0, nullptr, nullptr, + INFTY, HitOrientation::EXITING, nullptr); SUCCEED("N=0 completed without error"); } @@ -168,8 +168,8 @@ TEST_CASE("Batch API Ray Fire on MeshMock", "[rayfire][mock][batch]") { double dist_batch = -1.0; MeshID id_batch = ID_NONE; - rti->batch_ray_fire(volume_tree, origins.data(), directions.data(), 1, - &dist_batch, &id_batch, INFTY, HitOrientation::EXITING, nullptr); + rti->ray_fire(volume_tree, origins.data(), directions.data(), 1, + &dist_batch, &id_batch, INFTY, HitOrientation::EXITING, nullptr); REQUIRE(id_batch == id_scalar); REQUIRE_THAT(dist_batch, Catch::Matchers::WithinAbs(dist_scalar, 1e-6)); @@ -190,8 +190,8 @@ TEST_CASE("Batch API Ray Fire on MeshMock", "[rayfire][mock][batch]") { std::vector dist_batch(64, -1.0); std::vector id_batch(64, ID_NONE); - rti->batch_ray_fire(volume_tree, origins.data(), directions.data(), origins.size(), - dist_batch.data(), id_batch.data(), INFTY, HitOrientation::EXITING, nullptr); + rti->ray_fire(volume_tree, origins.data(), directions.data(), origins.size(), + dist_batch.data(), id_batch.data(), INFTY, HitOrientation::EXITING, nullptr); for (size_t i = 0; i < 64; ++i) { REQUIRE(id_batch[i] == id_scalar[i]); @@ -211,8 +211,8 @@ TEST_CASE("Batch API Ray Fire on MeshMock", "[rayfire][mock][batch]") { std::vector id_batch(N, ID_NONE); BENCHMARK("Batch ray fire with N = 100,000") { - return rti->batch_ray_fire(volume_tree, origins.data(), directions.data(), N, - dist_batch.data(), id_batch.data(), INFTY, HitOrientation::EXITING, nullptr); + return rti->ray_fire(volume_tree, origins.data(), directions.data(), N, + dist_batch.data(), id_batch.data(), INFTY, HitOrientation::EXITING, nullptr); }; // Basic sanity checks over 100 rays for (size_t i = 0; i < N; i += N/100) { diff --git a/tools/batch_point_in_volume.cpp b/tools/batch_point_in_volume.cpp index 13a35c81..ffd03f91 100644 --- a/tools/batch_point_in_volume.cpp +++ b/tools/batch_point_in_volume.cpp @@ -175,12 +175,12 @@ int main(int argc, char** argv) { std::vector results(N, 0xFF); - xdg->batch_point_in_volume(volume, - origins.data(), - directions.data(), - N, - results.data(), - has_dir.data()); + xdg->point_in_volume(volume, + origins.data(), + directions.data(), + N, + results.data(), + has_dir.data()); std::cout << std::endl << "Printing Batch point in volume results..." << std::endl; diff --git a/tools/batch_ray_fire.cpp b/tools/batch_ray_fire.cpp index 23ddee4c..27c37dfc 100644 --- a/tools/batch_ray_fire.cpp +++ b/tools/batch_ray_fire.cpp @@ -211,7 +211,7 @@ int main(int argc, char** argv) { std::vector hitDistances(num_rays); std::vector surfacesHit(num_rays); - xdg->batch_ray_fire(volume, origins.data(), directions.data(), num_rays, hitDistances.data(), surfacesHit.data()); + xdg->ray_fire(volume, origins.data(), directions.data(), num_rays, hitDistances.data(), surfacesHit.data()); std::cout << std::endl << "Printing Batch Ray results..." << std::endl; From 8cdab3140cce76db35b08480f46a105ef9336e2f Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Mon, 10 Nov 2025 11:48:41 +0000 Subject: [PATCH 10/62] Added method descriptions for the two overloads --- include/xdg/ray_tracing_interface.h | 72 ++++++++++++++++++++++------- include/xdg/xdg.h | 63 +++++++++++++++++++++---- 2 files changed, 109 insertions(+), 26 deletions(-) diff --git a/include/xdg/ray_tracing_interface.h b/include/xdg/ray_tracing_interface.h index 6413f7ed..354afb68 100644 --- a/include/xdg/ray_tracing_interface.h +++ b/include/xdg/ray_tracing_interface.h @@ -73,12 +73,39 @@ class RayTracer { */ virtual void create_global_element_tree() = 0; - // Query Methods + /** + * @brief Check whether a point lies in a specified volume + * + * This method performs a check to see whether a given point is inside a volume provided. + * It computes this by firing a ray from the point and checking whether or not the ray is Entering or Exiting + * the volume boundary. If no direction is provided, a default direction will be used. + * + * @param[in] tree The TreeID of the volume we are querying against + * @param[in] point A Position object representing the starting points of the rays + * @param[in] direction Direction object to launch a ray in a specified direction + * @param[in] exclude_primitives (optional) vector of surface element MeshIDs to exclude from intersection tests + * @return Boolean result of point in volume check + */ virtual bool point_in_volume(TreeID tree, const Position& point, const Direction* direction = nullptr, const std::vector* exclude_primitives = nullptr) const = 0; - + + /** + * @brief Fire a ray against a given volume and return the first hit + * + * This method fires a ray from a given origin in a specified direction against the surfaces of a volume. + * It returns the distance to the closest hit and the MeshID of the surface hit. The user can specify + * a distance limit and whether Entering/Exiting hits should be rejected. + * + * @param[in] tree The TreeID of the volume we are querying against + * @param[in] origin An array of Position objects representing the starting points of the rays + * @param[in] direction (optional) Direction object to launch a ray in a specified direction + * @param[in] dist_limit (optional) maximum distance to consider for intersections + * @param[in] orientation (optional) flag to consider whether Entering/Exiting hits should be rejected. Defaults to EXITING + * @param[in] exclude_primitives (optional) vector of surface element MeshIDs to exclude from intersection tests + * @return A pair containing the distance to the closest hit and the MeshID of the surface hit + */ virtual std::pair ray_fire(TreeID tree, const Position& origin, const Direction& direction, @@ -86,9 +113,22 @@ class RayTracer { HitOrientation orientation = HitOrientation::EXITING, std::vector* const exclude_primitives = nullptr) = 0; - // Array based queries - - // Array version of point_in_volume + /** + * @brief Array based version of point_in_volume query + * + * This method performs a set of point_in_volume queries on a batch of rays defined by their origins and directions. + * It computes whether or not a point lies in a given volume for each point in the batch. With GPRT ray tracing + * this launches the RT pipeline with the number of rays provided. + * + * @param[in] tree The TreeID of the volume we are querying against + * @param[in] points An array of Position objects representing the starting points of the rays + * @param[in] directions An array of Direction objects representing the directions of the rays + * @param[in] num_points The number of points to be processed in the batch + * @param[out] results An output array to store the computed results for each point (1 if inside volume, 0 if outside) + * @param[in] has_dir (optional) array to mask which points have valid directions + * @param[in] exclude_primitives (optional) vector of surface element MeshIDs to exclude from intersection tests + * @return Void. Outputs stored in results array + */ virtual void point_in_volume(TreeID tree, const Position* points, const Direction* directions, // [num_points] array of Direction pointers @@ -104,20 +144,20 @@ class RayTracer { * It computes the intersection distances and surface IDs for each ray in the batch. With GPRT ray tracing * this launches the RT pipeline with the number of rays provided. * - * @param tree The TreeID of the volume we are querying against - * @param origin An array of Position objects representing the starting points of the rays - * @param direction An array of Direction objects representing the directions of the rays - * @param num_rays The number of rays to be processed in the batch - * @param hitDistances An output array to store the computed intersection distances for each ray - * @param surfaceIDs An output array to store the MeshIDs of the surfaces hit by each ray - * @param dist_limit The maximum distance to consider for intersections - * @param orientation Flag to consider whether Entering/Exiting hits should be rejected - * @param exclude_primitives An optional vector of surface element MeshIDs to exclude from intersection tests + * @param[in] tree The TreeID of the volume we are querying against + * @param[in] origins An array of Position objects representing the starting points of the rays + * @param[in] directions An array of Direction objects representing the directions of the rays + * @param[in] num_rays The number of rays to be processed in the batch + * @param[out] hitDistances An output array to store the computed intersection distances for each ray + * @param[out] surfaceIDs An output array to store the MeshIDs of the surfaces hit by each ray + * @param[in] dist_limit (optional) maximum distance to consider for intersections + * @param[in] orientation (optional) flag to consider whether Entering/Exiting hits should be rejected. Defaults to EXITING + * @param[in] exclude_primitives (optional) vector of surface element MeshIDs to exclude from intersection tests * @return Void. Outputs stored in hitDistances and surfaceIDs arrays */ virtual void ray_fire(TreeID tree, - const Position* origin, - const Direction* direction, + const Position* origins, + const Direction* directions, const size_t num_rays, double* hitDistances, MeshID* surfaceIDs, diff --git a/include/xdg/xdg.h b/include/xdg/xdg.h index 275cdf87..bda8f4d4 100644 --- a/include/xdg/xdg.h +++ b/include/xdg/xdg.h @@ -63,11 +63,39 @@ next_element(MeshID current_element, const Position& r, const Direction& u) const; +/** + * @brief Check whether a point lies in a specified volume + * + * This method performs a check to see whether a given point is inside a volume provided. + * It computes this by firing a ray from the point and checking whether or not the ray is Entering or Exiting + * the volume boundary. If no direction is provided, a default direction will be used. + * + * @param[in] volume The MeshID of the volume we are querying against + * @param[in] point A Position object representing the starting points of the rays + * @param[in] direction Direction object to launch a ray in a specified direction + * @param[in] exclude_primitives (optional) vector of surface element MeshIDs to exclude from intersection tests + * @return Boolean result of point in volume check + */ bool point_in_volume(MeshID volume, const Position point, const Direction* direction = nullptr, const std::vector* exclude_primitives = nullptr) const; +/** + * @brief Fire a ray against a given volume and return the first hit + * + * This method fires a ray from a given origin in a specified direction against the surfaces of a volume. + * It returns the distance to the closest hit and the MeshID of the surface hit. The user can specify + * a distance limit and whether Entering/Exiting hits should be rejected. + * + * @param[in] volume The MeshID of the volume we are querying against + * @param[in] origin An array of Position objects representing the starting points of the rays + * @param[in] direction (optional) Direction object to launch a ray in a specified direction + * @param[in] dist_limit (optional) maximum distance to consider for intersections + * @param[in] orientation (optional) flag to consider whether Entering/Exiting hits should be rejected. Defaults to EXITING + * @param[in] exclude_primitives (optional) vector of surface element MeshIDs to exclude from intersection tests + * @return A pair containing the distance to the closest hit and the MeshID of the surface hit + */ std::pair ray_fire(MeshID volume, const Position& origin, const Direction& direction, @@ -75,7 +103,22 @@ std::pair ray_fire(MeshID volume, HitOrientation orientation = HitOrientation::EXITING, std::vector* const exclude_primitives = nullptr) const; -// Array version of point_in_volume +/** + * @brief Array based version of point_in_volume query + * + * This method performs a set of point_in_volume queries on a batch of rays defined by their origins and directions. + * It computes whether or not a point lies in a given volume for each point in the batch. With GPRT ray tracing + * this launches the RT pipeline with the number of rays provided. + * + * @param[in] volume The MeshID of the volume we are querying against + * @param[in] points An array of Position objects representing the starting points of the rays + * @param[in] directions An array of Direction objects representing the directions of the rays + * @param[in] num_points The number of points to be processed in the batch + * @param[out] results An output array to store the computed results for each point (1 if inside volume, 0 if outside) + * @param[in] has_dir (optional) array to mask which points have valid directions + * @param[in] exclude_primitives (optional) vector of surface element MeshIDs to exclude from intersection tests + * @return Void. Outputs stored in results array + */ void point_in_volume(MeshID volume, const Position* points, const Direction* directions, // [num_points] array of Direction pointers @@ -91,15 +134,15 @@ void point_in_volume(MeshID volume, * It computes the intersection distances and surface IDs for each ray in the batch. With GPRT ray tracing * this launches the RT pipeline with the number of rays provided. * - * @param volume The MeshID of the volume we are querying against - * @param origin An array of Position objects representing the starting points of the rays - * @param direction An array of Direction objects representing the directions of the rays - * @param num_rays The number of rays to be processed in the batch - * @param hitDistances An output array to store the computed intersection distances for each ray - * @param surfaceIDs An output array to store the MeshIDs of the surfaces hit by each ray - * @param dist_limit The maximum distance to consider for intersections - * @param orientation Flag to consider whether Entering/Exiting hits should be rejected - * @param exclude_primitives An optional vector of surface element MeshIDs to exclude from intersection tests + * @param[in] tree The TreeID of the volume we are querying against + * @param[in] origins An array of Position objects representing the starting points of the rays + * @param[in] directions An array of Direction objects representing the directions of the rays + * @param[in] num_rays The number of rays to be processed in the batch + * @param[out] hitDistances An output array to store the computed intersection distances for each ray + * @param[out] surfaceIDs An output array to store the MeshIDs of the surfaces hit by each ray + * @param[in] dist_limit (optional) maximum distance to consider for intersections + * @param[in] orientation (optional) flag to consider whether Entering/Exiting hits should be rejected. Defaults to EXITING + * @param[in] exclude_primitives (optional) vector of surface element MeshIDs to exclude from intersection tests * @return Void. Outputs stored in hitDistances and surfaceIDs arrays */ void ray_fire(MeshID volume, From 081abc151fd59a7662ae50ec1b47e45148a73b39 Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Tue, 11 Nov 2025 11:33:44 +0000 Subject: [PATCH 11/62] Refactor batch based point_in_volume tests --- src/gprt/ray_tracer.cpp | 10 ++++- tests/test_point_in_volume.cpp | 71 ++++++++++++++++++---------------- tests/test_ray_fire.cpp | 20 +++++----- 3 files changed, 57 insertions(+), 44 deletions(-) diff --git a/src/gprt/ray_tracer.cpp b/src/gprt/ray_tracer.cpp index 0c8dcf08..2aa7caf1 100644 --- a/src/gprt/ray_tracer.cpp +++ b/src/gprt/ray_tracer.cpp @@ -222,9 +222,17 @@ bool GPRTRayTracer::point_in_volume(SurfaceTreeID tree, auto rayGen = rayGenPrograms_.at(RayGenType::POINT_IN_VOLUME); dblRayGenData* rayGenPIVData = gprtRayGenGetParameters(rayGen); + const Direction defaultDir = Direction{1. / std::sqrt(2.0), 1. / std::sqrt(2.0), 0.0}; + // Use provided direction or if Direction == nulptr use default direction Direction directionUsed = (direction != nullptr) ? Direction{direction->x, direction->y, direction->z} - : Direction{1. / std::sqrt(2.0), 1. / std::sqrt(2.0), 0.0}; + : defaultDir; + + // Catch directions with zero length + const double l2 = directionUsed.x*directionUsed.x + + directionUsed.y*directionUsed.y + + directionUsed.z*directionUsed.z; + if (l2 == 0.0) directionUsed = defaultDir; gprtBufferMap(rayHitBuffers_.ray); // Update the ray input buffer dblRay* ray = gprtBufferGetHostPointer(rayHitBuffers_.ray); diff --git a/tests/test_point_in_volume.cpp b/tests/test_point_in_volume.cpp index 7adcf823..4a92873a 100644 --- a/tests/test_point_in_volume.cpp +++ b/tests/test_point_in_volume.cpp @@ -101,31 +101,10 @@ TEST_CASE("Batch API Point-in-volume on MeshMock", "[piv][mock][batch]") { rti->init(); - // Build 64 points: alternate inside/outside with some null directions - const size_t num_points = 64; - std::vector points(num_points); - std::vector directions(num_points); // contiguous directions (ignored if has_dir[i]==0) - std::vector has_dir(num_points, 0); // mask: 1 => use directions[i], 0 => use default - - for (int i = 0; i < num_points; ++i) { - // even i: origin (inside); odd i: just outside +X - points[i] = (i % 2 == 0) ? Position{0,0,0} : Position{5.1,0,0}; - - // every 3rd ray has no direction has_dir == 0; others alternate ±X with has_dir == 1 - if (i % 3 != 0) { - directions[i] = (i % 2 == 0) ? Direction{1,0,0} : Direction{-1,0,0}; - has_dir[i] = 1; - } else { - has_dir[i] = 0; // mask out direction and use default - } - } - - // Store results of scalar point_in_volume calls to verify batch against scalar - std::vector truth(num_points, 0); - for (size_t i = 0; i < num_points; ++i) { - const Direction* dptr = has_dir[i] ? &directions[i] : nullptr; - truth[i] = static_cast(rti->point_in_volume(volume_tree, points[i], dptr)); - } + std::vector points; + std::vector directions; + std::vector has_dir; + size_t N; SECTION("N=0 no-op") { rti->point_in_volume(volume_tree, nullptr, nullptr, 0, nullptr, nullptr); @@ -133,18 +112,44 @@ TEST_CASE("Batch API Point-in-volume on MeshMock", "[piv][mock][batch]") { } SECTION("N=1") { - uint8_t result = 0xFF; // sentinel - rti->point_in_volume(volume_tree, &points[0], &directions[0], 1, &result, &has_dir[0]); - REQUIRE((result == 0 || result == 1)); - REQUIRE(result == truth[0]); + N = 1; + make_points(N, points, directions, has_dir); + + auto scalar_result = rti->point_in_volume(volume_tree, points[0], &directions[0]); + + std::vector batch_result(N, 0xFF); + rti->point_in_volume(volume_tree, &points[0], &directions[0], N, &batch_result[0], nullptr); + REQUIRE(batch_result[0] == static_cast(scalar_result)); } SECTION("N=64") { - std::vector results(num_points, 0xFF); - rti->point_in_volume(volume_tree, points.data(), directions.data(), num_points, results.data(), has_dir.data()); + N = 64; + make_points(N, points, directions, has_dir); + + // Store results of scalar point_in_volume calls to verify batch against scalar + std::vector scalar_results(N, 0); + for (size_t i = 0; i < N; ++i) { + const Direction* dptr = has_dir[i] ? &directions[i] : nullptr; + scalar_results[i] = static_cast(rti->point_in_volume(volume_tree, points[i], dptr)); + } + + std::vector batch_results(N, 0xFF); + rti->point_in_volume(volume_tree, points.data(), directions.data(), N, batch_results.data(), has_dir.data()); for (size_t i = 0; i < points.size(); ++i) { - REQUIRE(results[i] == truth[i]); + REQUIRE(batch_results[i] == scalar_results[i]); } } + + // ---- N = 100,000 ---- + SECTION("N=100,00 batch with basic sanity checks") { + N = 100000; + make_points(N, points, directions, has_dir); + + std::vector batch_results(N, 0xFF); + BENCHMARK("Batch point_in_volume with N = 100,000") + { + return rti->point_in_volume(volume_tree, points.data(), directions.data(), N, batch_results.data(), has_dir.data()); + }; + } } -} +} \ No newline at end of file diff --git a/tests/test_ray_fire.cpp b/tests/test_ray_fire.cpp index 0d609f40..4d12fce8 100644 --- a/tests/test_ray_fire.cpp +++ b/tests/test_ray_fire.cpp @@ -135,7 +135,7 @@ TEST_CASE("Batch API Ray Fire on MeshMock", "[rayfire][mock][batch]") { rti->init(); - // Helper to synthesize origins/directions like your 64-ray pattern, extended to N + // Helper to synthesize origins/directions for N rays auto make_rays = [](size_t N, std::vector& origins, std::vector& directions) { origins.clear(); directions.clear(); @@ -151,6 +151,10 @@ TEST_CASE("Batch API Ray Fire on MeshMock", "[rayfire][mock][batch]") { } }; + std::vector origins; + std::vector directions; + size_t N; + // ---- N = 0 ---- SECTION("N=0 no-op") { rti->ray_fire(volume_tree, nullptr, nullptr, 0, nullptr, nullptr, @@ -160,9 +164,8 @@ TEST_CASE("Batch API Ray Fire on MeshMock", "[rayfire][mock][batch]") { // ---- N = 1 ---- SECTION("N=1 equals scalar") { - std::vector origins; - std::vector directions; - make_rays(1, origins, directions); + N = 1; + make_rays(N, origins, directions); auto [dist_scalar, id_scalar] = rti->ray_fire(volume_tree, origins[0], directions[0], INFTY, HitOrientation::EXITING); @@ -177,9 +180,8 @@ TEST_CASE("Batch API Ray Fire on MeshMock", "[rayfire][mock][batch]") { // ---- N = 64 ---- SECTION("N=64 matches scalar for all") { - std::vector origins; - std::vector directions; - make_rays(64, origins, directions); + N = 64; + make_rays(N, origins, directions); std::vector dist_scalar(64, INFTY); std::vector id_scalar(64, ID_NONE); @@ -201,9 +203,7 @@ TEST_CASE("Batch API Ray Fire on MeshMock", "[rayfire][mock][batch]") { // ---- N = 100,000 ---- SECTION("N=100,00 batch with basic sanity checks") { - const size_t N = 100000; - std::vector origins; - std::vector directions; + N = 100000; make_rays(N, origins, directions); // Batch compute From e9af504ca4e9f82e64bff1ae23488413bfaa655c Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Wed, 12 Nov 2025 17:11:34 +0000 Subject: [PATCH 12/62] Dropped dir mask in batch PIV calls + made dirs optional --- include/xdg/embree/ray_tracer.h | 3 +-- include/xdg/gprt/ray_tracer.h | 3 +-- include/xdg/ray_tracing_interface.h | 17 ++++++------ include/xdg/xdg.h | 17 ++++++------ src/gprt/ray_tracer.cpp | 32 +++++++++++------------ src/xdg.cpp | 5 ++-- tests/test_point_in_volume.cpp | 40 ++++++++++++++++++++--------- tools/batch_point_in_volume.cpp | 3 +-- 8 files changed, 65 insertions(+), 55 deletions(-) diff --git a/include/xdg/embree/ray_tracer.h b/include/xdg/embree/ray_tracer.h index 463bd633..7045a49b 100644 --- a/include/xdg/embree/ray_tracer.h +++ b/include/xdg/embree/ray_tracer.h @@ -50,10 +50,9 @@ class EmbreeRayTracer : public RayTracer { // Array version of point_in_volume void point_in_volume(TreeID tree, const Position* points, - const Direction* directions, // [num_points] array of Direction pointers const size_t num_points, uint8_t* results, - const uint8_t* has_dir = nullptr, + const Direction* directions = nullptr, std::vector* exclude_primitives = nullptr) override { fatal_error("Batch point_in_volume not yet implemented for EmbreeRayTracer"); diff --git a/include/xdg/gprt/ray_tracer.h b/include/xdg/gprt/ray_tracer.h index be51a54a..2f637665 100644 --- a/include/xdg/gprt/ray_tracer.h +++ b/include/xdg/gprt/ray_tracer.h @@ -84,10 +84,9 @@ class GPRTRayTracer : public RayTracer { void point_in_volume(TreeID tree, const Position* points, - const Direction* directions, // [num_points] array of Direction pointers const size_t num_points, uint8_t* results, - const uint8_t* has_dir = nullptr, + const Direction* directions = nullptr, std::vector* exclude_primitives = nullptr) override; std::pair ray_fire(TreeID scene, diff --git a/include/xdg/ray_tracing_interface.h b/include/xdg/ray_tracing_interface.h index 354afb68..ad62bddf 100644 --- a/include/xdg/ray_tracing_interface.h +++ b/include/xdg/ray_tracing_interface.h @@ -79,10 +79,11 @@ class RayTracer { * This method performs a check to see whether a given point is inside a volume provided. * It computes this by firing a ray from the point and checking whether or not the ray is Entering or Exiting * the volume boundary. If no direction is provided, a default direction will be used. + * Note - zero length direction vectors are not explicitly checked for internally and should be avoided to avoid causing undefined behavior. * * @param[in] tree The TreeID of the volume we are querying against - * @param[in] point A Position object representing the starting points of the rays - * @param[in] direction Direction object to launch a ray in a specified direction + * @param[in] point The point to be queried + * @param[in] direction (optional) direction to launch a ray in a specified direction - must be non-zero length * @param[in] exclude_primitives (optional) vector of surface element MeshIDs to exclude from intersection tests * @return Boolean result of point in volume check */ @@ -97,7 +98,8 @@ class RayTracer { * This method fires a ray from a given origin in a specified direction against the surfaces of a volume. * It returns the distance to the closest hit and the MeshID of the surface hit. The user can specify * a distance limit and whether Entering/Exiting hits should be rejected. - * + * Note - zero length direction vectors are not explicitly checked for internally and should be avoided to avoid causing undefined behavior. + * * @param[in] tree The TreeID of the volume we are querying against * @param[in] origin An array of Position objects representing the starting points of the rays * @param[in] direction (optional) Direction object to launch a ray in a specified direction @@ -121,22 +123,19 @@ class RayTracer { * this launches the RT pipeline with the number of rays provided. * * @param[in] tree The TreeID of the volume we are querying against - * @param[in] points An array of Position objects representing the starting points of the rays - * @param[in] directions An array of Direction objects representing the directions of the rays + * @param[in] points An array of points to query * @param[in] num_points The number of points to be processed in the batch * @param[out] results An output array to store the computed results for each point (1 if inside volume, 0 if outside) - * @param[in] has_dir (optional) array to mask which points have valid directions + * @param[in] directions (optional) array of directions to launch rays in explicit directions per point - these must be non-zero length * @param[in] exclude_primitives (optional) vector of surface element MeshIDs to exclude from intersection tests * @return Void. Outputs stored in results array */ virtual void point_in_volume(TreeID tree, const Position* points, - const Direction* directions, // [num_points] array of Direction pointers const size_t num_points, uint8_t* results, - const uint8_t* has_dir = nullptr, + const Direction* directions = nullptr, std::vector* exclude_primitives = nullptr) = 0; - /** * @brief Array based version of ray_fire query * diff --git a/include/xdg/xdg.h b/include/xdg/xdg.h index bda8f4d4..5a8a692c 100644 --- a/include/xdg/xdg.h +++ b/include/xdg/xdg.h @@ -69,10 +69,11 @@ next_element(MeshID current_element, * This method performs a check to see whether a given point is inside a volume provided. * It computes this by firing a ray from the point and checking whether or not the ray is Entering or Exiting * the volume boundary. If no direction is provided, a default direction will be used. + * Note - zero length direction vectors are not explicitly checked for internally and should be avoided to avoid causing undefined behavior. * - * @param[in] volume The MeshID of the volume we are querying against - * @param[in] point A Position object representing the starting points of the rays - * @param[in] direction Direction object to launch a ray in a specified direction + * @param[in] tree The TreeID of the volume we are querying against + * @param[in] point The point to be queried + * @param[in] direction (optional) direction to launch a ray in a specified direction - must be non-zero length * @param[in] exclude_primitives (optional) vector of surface element MeshIDs to exclude from intersection tests * @return Boolean result of point in volume check */ @@ -110,21 +111,19 @@ std::pair ray_fire(MeshID volume, * It computes whether or not a point lies in a given volume for each point in the batch. With GPRT ray tracing * this launches the RT pipeline with the number of rays provided. * - * @param[in] volume The MeshID of the volume we are querying against - * @param[in] points An array of Position objects representing the starting points of the rays - * @param[in] directions An array of Direction objects representing the directions of the rays + * @param[in] tree The TreeID of the volume we are querying against + * @param[in] points An array of points to query * @param[in] num_points The number of points to be processed in the batch * @param[out] results An output array to store the computed results for each point (1 if inside volume, 0 if outside) - * @param[in] has_dir (optional) array to mask which points have valid directions + * @param[in] directions (optional) array of directions to launch rays in explicit directions per point - these must be non-zero length * @param[in] exclude_primitives (optional) vector of surface element MeshIDs to exclude from intersection tests * @return Void. Outputs stored in results array */ void point_in_volume(MeshID volume, const Position* points, - const Direction* directions, // [num_points] array of Direction pointers const size_t num_points, uint8_t* results, - const uint8_t* has_dir = nullptr, + const Direction* directions = nullptr, std::vector* exclude_primitives = nullptr) const; /** diff --git a/src/gprt/ray_tracer.cpp b/src/gprt/ray_tracer.cpp index 2aa7caf1..11f08caf 100644 --- a/src/gprt/ray_tracer.cpp +++ b/src/gprt/ray_tracer.cpp @@ -337,10 +337,9 @@ std::pair GPRTRayTracer::ray_fire(SurfaceTreeID tree, void GPRTRayTracer::point_in_volume(TreeID tree, const Position* points, - const Direction* directions, // [num_points] array of Direction pointers const size_t num_points, uint8_t* results, - const uint8_t* has_dir, + const Direction* directions, std::vector* exclude_primitives) { if (num_points == 0) return; // no work to do. Early exit @@ -359,24 +358,25 @@ void GPRTRayTracer::point_in_volume(TreeID tree, gprtBufferMap(rayHitBuffers_.ray); dblRay* ray = gprtBufferGetHostPointer(rayHitBuffers_.ray); const auto volumeAddr = gprtAccelGetDeviceAddress(volume); - for (size_t i = 0; i < num_points; ++i) { - Direction directionUsed = - (!directions || (has_dir && !has_dir[i])) ? defaultDir : directions[i]; - - // Catch directions with zero length - const double l2 = directionUsed.x*directionUsed.x - + directionUsed.y*directionUsed.y - + directionUsed.z*directionUsed.z; - if (l2 == 0.0) directionUsed = defaultDir; + // Common ray params + for (size_t i = 0; i < num_points; ++i) { ray[i].volume_accel = volumeAddr; ray[i].origin = {points[i].x, points[i].y, points[i].z}; - ray[i].direction = {directionUsed.x, directionUsed.y, directionUsed.z}; - ray[i].tMax = INFTY; // Set a large distance limit + ray[i].tMax = INFTY; ray[i].tMin = 0.0; - ray[i].volume_tree = tree; // Set the TreeID of the volume being queried - ray[i].hitOrientation = HitOrientation::ANY; // No orientation culling for point-in-volume check - ray[i].exclude_primitives = nullptr; // Not currently supported in batch version + ray[i].volume_tree = tree; + ray[i].hitOrientation = HitOrientation::ANY; + ray[i].exclude_primitives = nullptr; + } + + // Directions + if (!directions) { + for (size_t i = 0; i < num_points; ++i) + ray[i].direction = double3{ defaultDir.x, defaultDir.y, defaultDir.z }; + } else { + for (size_t i = 0; i < num_points; ++i) + ray[i].direction = double3{ directions[i].x, directions[i].y, directions[i].z }; } gprtBufferUnmap(rayHitBuffers_.ray); // required to sync buffer back on GPU? diff --git a/src/xdg.cpp b/src/xdg.cpp index fc336133..d34cf3be 100644 --- a/src/xdg.cpp +++ b/src/xdg.cpp @@ -112,14 +112,13 @@ bool XDG::point_in_volume(MeshID volume, void XDG::point_in_volume(MeshID volume, const Position* points, - const Direction* directions, // [num_points] array of Direction pointers const size_t num_points, uint8_t* results, - const uint8_t* has_dir, + const Direction* directions, std::vector* exclude_primitives) const { TreeID tree = volume_to_surface_tree_map_.at(volume); - ray_tracing_interface()->point_in_volume(tree, points, directions, num_points, results, has_dir, exclude_primitives); + ray_tracing_interface()->point_in_volume(tree, points, num_points, results, directions, exclude_primitives); } MeshID XDG::find_volume(const Position& point, diff --git a/tests/test_point_in_volume.cpp b/tests/test_point_in_volume.cpp index 4a92873a..dd35b085 100644 --- a/tests/test_point_in_volume.cpp +++ b/tests/test_point_in_volume.cpp @@ -10,7 +10,21 @@ #include "mesh_mock.h" using namespace xdg; -using namespace xdg::test; + +static void make_points(size_t N, + std::vector& points, + std::vector& directions) +{ + points.resize(N); + directions.resize(N); + for (size_t i = 0; i < N; ++i) { + // evens inside (origin), odds just outside +X + points[i] = (i % 2 == 0) ? xdg::Position{0,0,0} : xdg::Position{5.1,0,0}; + // alternate ±X directions + directions[i] = (i % 2 == 0) ? xdg::Direction{ 1,0,0} + : xdg::Direction{-1,0,0}; + } +} // ---------- single test, sections per backend -------------------------------- @@ -107,34 +121,36 @@ TEST_CASE("Batch API Point-in-volume on MeshMock", "[piv][mock][batch]") { size_t N; SECTION("N=0 no-op") { - rti->point_in_volume(volume_tree, nullptr, nullptr, 0, nullptr, nullptr); + rti->point_in_volume(volume_tree, + nullptr, /*points*/ + 0, /*num_points*/ + nullptr /*results*/); SUCCEED("N=0 completed without error"); } SECTION("N=1") { N = 1; - make_points(N, points, directions, has_dir); + make_points(N, points, directions); - auto scalar_result = rti->point_in_volume(volume_tree, points[0], &directions[0]); + auto scalar_result = static_cast(rti->point_in_volume(volume_tree, points[0], &directions[0])); std::vector batch_result(N, 0xFF); - rti->point_in_volume(volume_tree, &points[0], &directions[0], N, &batch_result[0], nullptr); - REQUIRE(batch_result[0] == static_cast(scalar_result)); + rti->point_in_volume(volume_tree, points.data(), N, batch_result.data(), directions.data()); + REQUIRE(batch_result[0] == scalar_result); } SECTION("N=64") { N = 64; - make_points(N, points, directions, has_dir); + make_points(N, points, directions); // Store results of scalar point_in_volume calls to verify batch against scalar std::vector scalar_results(N, 0); for (size_t i = 0; i < N; ++i) { - const Direction* dptr = has_dir[i] ? &directions[i] : nullptr; - scalar_results[i] = static_cast(rti->point_in_volume(volume_tree, points[i], dptr)); + scalar_results[i] = static_cast(rti->point_in_volume(volume_tree, points[i], &directions[i])); } std::vector batch_results(N, 0xFF); - rti->point_in_volume(volume_tree, points.data(), directions.data(), N, batch_results.data(), has_dir.data()); + rti->point_in_volume(volume_tree, points.data(), N, batch_results.data(), directions.data()); for (size_t i = 0; i < points.size(); ++i) { REQUIRE(batch_results[i] == scalar_results[i]); } @@ -143,12 +159,12 @@ TEST_CASE("Batch API Point-in-volume on MeshMock", "[piv][mock][batch]") { // ---- N = 100,000 ---- SECTION("N=100,00 batch with basic sanity checks") { N = 100000; - make_points(N, points, directions, has_dir); + make_points(N, points, directions); std::vector batch_results(N, 0xFF); BENCHMARK("Batch point_in_volume with N = 100,000") { - return rti->point_in_volume(volume_tree, points.data(), directions.data(), N, batch_results.data(), has_dir.data()); + return rti->point_in_volume(volume_tree, points.data(), N, batch_results.data(), directions.data()); }; } } diff --git a/tools/batch_point_in_volume.cpp b/tools/batch_point_in_volume.cpp index ffd03f91..69dadd57 100644 --- a/tools/batch_point_in_volume.cpp +++ b/tools/batch_point_in_volume.cpp @@ -177,10 +177,9 @@ int main(int argc, char** argv) { xdg->point_in_volume(volume, origins.data(), - directions.data(), N, results.data(), - has_dir.data()); + directions.data()); std::cout << std::endl << "Printing Batch point in volume results..." << std::endl; From 0f0811cce1b7f07dbbbca7d75d9090c1ed5e1107 Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Wed, 12 Nov 2025 18:11:00 +0000 Subject: [PATCH 13/62] Added a new ray-benchmark tool to compare embree vs gprt ray throughput --- tools/CMakeLists.txt | 1 + tools/ray-benchmark.cpp | 160 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 161 insertions(+) create mode 100644 tools/ray-benchmark.cpp diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index a3fa0017..6fde5577 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -8,6 +8,7 @@ batch_point_in_volume overlap_check walk_elements tally_segments +ray-benchmark ) #=============================================================================== diff --git a/tools/ray-benchmark.cpp b/tools/ray-benchmark.cpp new file mode 100644 index 00000000..9b63ab87 --- /dev/null +++ b/tools/ray-benchmark.cpp @@ -0,0 +1,160 @@ +#include +#include +#include +#include +#include +#include +#include + + +#include "xdg/error.h" +#include "xdg/mesh_manager_interface.h" +#include "xdg/moab/mesh_manager.h" +#include "xdg/vec3da.h" +#include "xdg/xdg.h" + +#include "argparse/argparse.hpp" + +using namespace xdg; + +inline Direction random_unit_dir(std::mt19937_64 &rng) { + std::uniform_real_distribution U(-1.0, 1.0); + double x1, x2, s; + do { + x1 = U(rng); + x2 = U(rng); + s = x1*x1 + x2*x2; + } while (s <= 0.0 || s >= 1.0); + const double t = 2.0 * std::sqrt(1.0 - s); + return { x1 * t, x2 * t, 1.0 - 2.0 * s }; // already unit length +} + +inline void generate_dirs(std::vector &out, uint64_t seed = 12345) { + std::mt19937_64 rng(seed); + for (auto &d : out) d = random_unit_dir(rng); +} + +int main(int argc, char** argv) { + + argparse::ArgumentParser args("XDG Ray Tracing throughput benchmarking tool", "1.0", argparse::default_arguments::help); + + args.add_argument("filename") + .help("Path to the input file"); + + args.add_argument("volume") + .help("Volume ID to query") + .scan<'i', int>(); + + args.add_argument("-n", "--num-rays") + .default_value(10'000'000) + .help("Number of rays to be cast for the benchmark (default - 10 million)") + .scan<'u', std::size_t>(); + + args.add_argument("-s", "--seed") + .default_value(12345) + .help("Seed for random number generator (default - 12345)") + .scan<'u', uint64_t>(); + + args.add_argument("-o", "-p", "--origin", "--position") + .default_value(std::vector{0.0, 0.0, 0.0}) + .help("Ray origin/position (default - {0.0, 0.0, 0.0} )") + .scan<'g', double>().nargs(3); + + args.add_argument("-m", "--mesh-library") + .help("Mesh library to use. One of (MOAB, LIBMESH)") + .default_value("MOAB"); + + args.add_argument("-r", "--rt-library") + .help("Ray tracing library to use. One of (EMBREE, GPRT)") + .default_value("EMBREE"); + + args.add_argument("-l", "--list") + .default_value(false) + .implicit_value(true) + .help("List all volumes in the file and exit"); + + args.add_description( + "This tool supports can be used to benchmark XDG ray tracing throughput on a given mesh against" + "a given volume \n." + "A single origin/seed point is provided and ray directions are randomly generated in 360 degrees from that position" + ); + + try { + args.parse_args(argc, argv); + } + catch (const std::runtime_error& err) { + std::cout << err.what() << std::endl; + std::cout << args; + exit(0); + } + + std::string mesh_str = args.get("--mesh-library"); + std::string rt_str = args.get("--rt-library"); + + RTLibrary rt_lib; + if (rt_str == "EMBREE") + rt_lib = RTLibrary::EMBREE; + else if (rt_str == "GPRT") + rt_lib = RTLibrary::GPRT; + else + fatal_error("Invalid ray tracing library '{}' specified", rt_str); + + MeshLibrary mesh_lib; + if (mesh_str == "MOAB") + mesh_lib = MeshLibrary::MOAB; + else if (mesh_str == "LIBMESH") { + mesh_lib = MeshLibrary::LIBMESH; + if (rt_lib == RTLibrary::GPRT) + fatal_error("LibMesh is not currently supported with GPRT"); + } + else + fatal_error("Invalid mesh library '{}' specified", mesh_str); + + // create xdg instance + std::shared_ptr xdg = XDG::create(mesh_lib, rt_lib); + const auto& mm = xdg->mesh_manager(); + mm->load_file(args.get("filename")); + mm->init(); + mm->parse_metadata(); + + // Generate a set of random rays + size_t N = args.get("--num-rays"); + uint64_t seed = args.get("--seed"); + Position origin = args.get>("--origin"); + std::vector origins(N, origin); + std::vector directions(N); + generate_dirs(directions, seed); + + MeshID volume = args.get("volume"); + xdg->prepare_raytracer(); + xdg->prepare_volume_for_raytracing(volume); + auto rti = xdg->ray_tracing_interface(); + + std::vector hitDistances(N, -1.0); + std::vector hitElements(N, ID_NONE); + + std::cout << "Starting ray fire benchmark with " << N << " rays" << " using " + << rt_str << ": \n" << std::endl; + auto start = std::chrono::high_resolution_clock::now(); + + if (rt_lib == RTLibrary::GPRT) { + // GPRT backend supports batch ray fire + xdg->ray_fire(volume, origins.data(), directions.data(), N, hitDistances.data(), hitElements.data()); + } else { + for (size_t i = 0; i < N; ++i) { + auto result = xdg->ray_fire(volume, origin, directions[i]); + } + } + + auto end = std::chrono::high_resolution_clock::now(); + std::chrono::duration elapsed = end - start; + double rays_per_second = static_cast(N) / elapsed.count(); + + std::cout << "----------------------------------------" << std::endl; + std::cout << "Completed " << N << " rays in " << elapsed.count() << " seconds." << std::endl; + std::cout << "Ray tracing throughput: " << rays_per_second << " rays/second." << std::endl; + std::cout << "---------------------------------------- \n" << std::endl; + + + return 0; +} \ No newline at end of file From 50d0e07801e85eb330a750c47adac1586e64744e Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Wed, 12 Nov 2025 18:11:20 +0000 Subject: [PATCH 14/62] Removed performance benchmarking in unit tests --- tests/test_point_in_volume.cpp | 12 ------------ tests/test_ray_fire.cpp | 22 ---------------------- 2 files changed, 34 deletions(-) diff --git a/tests/test_point_in_volume.cpp b/tests/test_point_in_volume.cpp index dd35b085..2ee445f5 100644 --- a/tests/test_point_in_volume.cpp +++ b/tests/test_point_in_volume.cpp @@ -155,17 +155,5 @@ TEST_CASE("Batch API Point-in-volume on MeshMock", "[piv][mock][batch]") { REQUIRE(batch_results[i] == scalar_results[i]); } } - - // ---- N = 100,000 ---- - SECTION("N=100,00 batch with basic sanity checks") { - N = 100000; - make_points(N, points, directions); - - std::vector batch_results(N, 0xFF); - BENCHMARK("Batch point_in_volume with N = 100,000") - { - return rti->point_in_volume(volume_tree, points.data(), N, batch_results.data(), directions.data()); - }; - } } } \ No newline at end of file diff --git a/tests/test_ray_fire.cpp b/tests/test_ray_fire.cpp index 4d12fce8..08814084 100644 --- a/tests/test_ray_fire.cpp +++ b/tests/test_ray_fire.cpp @@ -3,7 +3,6 @@ #include #include #include -#include // xdg includes #include "xdg/constants.h" @@ -200,26 +199,5 @@ TEST_CASE("Batch API Ray Fire on MeshMock", "[rayfire][mock][batch]") { REQUIRE_THAT(dist_batch[i], Catch::Matchers::WithinAbs(dist_scalar[i], 1e-6)); } } - - // ---- N = 100,000 ---- - SECTION("N=100,00 batch with basic sanity checks") { - N = 100000; - make_rays(N, origins, directions); - - // Batch compute - std::vector dist_batch(N, -1.0); - std::vector id_batch(N, ID_NONE); - BENCHMARK("Batch ray fire with N = 100,000") - { - return rti->ray_fire(volume_tree, origins.data(), directions.data(), N, - dist_batch.data(), id_batch.data(), INFTY, HitOrientation::EXITING, nullptr); - }; - // Basic sanity checks over 100 rays - for (size_t i = 0; i < N; i += N/100) { - REQUIRE(id_batch[i] != ID_NONE); - REQUIRE(std::isfinite(dist_batch[i])); - REQUIRE(dist_batch[i] >= 0.0); - } - } } } From a2092ec402a830fc4e686b84b582efa65227c43b Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Tue, 18 Nov 2025 19:10:49 +0000 Subject: [PATCH 15/62] Removed the leftover extra call to rebuild SBT --- src/gprt/ray_tracer.cpp | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/gprt/ray_tracer.cpp b/src/gprt/ray_tracer.cpp index 11f08caf..fcb971f9 100644 --- a/src/gprt/ray_tracer.cpp +++ b/src/gprt/ray_tracer.cpp @@ -381,9 +381,6 @@ void GPRTRayTracer::point_in_volume(TreeID tree, gprtBufferUnmap(rayHitBuffers_.ray); // required to sync buffer back on GPU? - // rebuild SBT geom and raygen only - gprtBuildShaderBindingTable(context_, static_cast(GPRT_SBT_GEOM | GPRT_SBT_RAYGEN)); - gprtRayGenLaunch1D(context_, rayGen, num_points); gprtGraphicsSynchronize(context_); @@ -419,11 +416,12 @@ void GPRTRayTracer::ray_fire(TreeID tree, gprtBufferMap(rayHitBuffers_.ray); dblRay* ray = gprtBufferGetHostPointer(rayHitBuffers_.ray); + const auto volAddr = gprtAccelGetDeviceAddress(volume); for (size_t i = 0; i < num_rays; ++i) { const auto& origin = origins[i]; const auto& direction = directions[i]; - ray[i].volume_accel = gprtAccelGetDeviceAddress(volume); + ray[i].volume_accel = volAddr; ray[i].origin = {origin.x, origin.y, origin.z}; ray[i].direction = {direction.x, direction.y, direction.z}; ray[i].tMax = dist_limit; @@ -434,10 +432,7 @@ void GPRTRayTracer::ray_fire(TreeID tree, } gprtBufferUnmap(rayHitBuffers_.ray); // required to sync buffer back on GPU? - - // rebuild SBT geom and raygen only - gprtBuildShaderBindingTable(context_, static_cast(GPRT_SBT_GEOM | GPRT_SBT_RAYGEN)); - + // Launch the ray generation shader with push constants and buffer bindings gprtRayGenLaunch1D(context_, rayGen, num_rays); gprtGraphicsSynchronize(context_); From eb5d3e9e32d3b1bc25cece98a4cb4d7cf80b41e0 Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Wed, 19 Nov 2025 11:35:35 +0000 Subject: [PATCH 16/62] Update AABB population program to properly distribute threads --- src/gprt/dbl_deviceCode.slang | 7 ++++++- src/gprt/ray_tracer.cpp | 7 +++++-- tools/ray-benchmark.cpp | 5 ++++- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/gprt/dbl_deviceCode.slang b/src/gprt/dbl_deviceCode.slang index ff082a45..0034e534 100644 --- a/src/gprt/dbl_deviceCode.slang +++ b/src/gprt/dbl_deviceCode.slang @@ -104,10 +104,15 @@ void point_in_volume(uniform dblRayGenData record, uniform DPTriangleGeomData me // ------------------------------------------------- Compute Shaders ------------------------------------------------- /* A shader to compute and store AABB min/maxes in single precision using double precision coords*/ [shader("compute")] -[numthreads(1, 1, 1)] +[numthreads(64, 1, 1)] void populate_aabbs(uint3 DispatchThreadID: SV_DispatchThreadID, uniform DPTriangleGeomData record) { int primID = DispatchThreadID.x; + + // guard against more threads than primitives + if (primID >= record.num_faces) + return; + int3 indices = record.index[primID]; double3 A = record.vertex[indices[0]]; double3 B = record.vertex[indices[1]]; diff --git a/src/gprt/ray_tracer.cpp b/src/gprt/ray_tracer.cpp index fcb971f9..0cd21f06 100644 --- a/src/gprt/ray_tracer.cpp +++ b/src/gprt/ray_tracer.cpp @@ -164,8 +164,11 @@ GPRTRayTracer::create_surface_tree(const std::shared_ptr& mesh_mana geom_data->normals = gprtBufferGetDevicePointer(normal_buffer); geom_data->primitive_refs = gprtBufferGetDevicePointer(primitive_refs_buffer); geom_data->num_faces = num_faces; - - gprtComputeLaunch(aabbPopulationProgram_, {num_faces, 1, 1}, {1, 1, 1}, *geom_data); + + constexpr uint32_t threadsPerGroup = 64; // must match [numthreads(64,1,1)] + uint32_t numGroupsX = (num_faces + threadsPerGroup - 1) / threadsPerGroup; + + gprtComputeLaunch(aabbPopulationProgram_, {numGroupsX, 1, 1}, {threadsPerGroup, 1, 1}, *geom_data); GPRTAccel blas = gprtAABBAccelCreate(context_, triangleGeom, buildParams_.buildMode); diff --git a/tools/ray-benchmark.cpp b/tools/ray-benchmark.cpp index 9b63ab87..b2e946c4 100644 --- a/tools/ray-benchmark.cpp +++ b/tools/ray-benchmark.cpp @@ -115,7 +115,7 @@ int main(int argc, char** argv) { const auto& mm = xdg->mesh_manager(); mm->load_file(args.get("filename")); mm->init(); - mm->parse_metadata(); + // mm->parse_metadata(); // Generate a set of random rays size_t N = args.get("--num-rays"); @@ -133,6 +133,9 @@ int main(int argc, char** argv) { std::vector hitDistances(N, -1.0); std::vector hitElements(N, ID_NONE); + std::cout << "Volume ID: " << volume << " with: " << mm->num_volume_faces(volume) + << " faces" << std::endl; + std::cout << "Starting ray fire benchmark with " << N << " rays" << " using " << rt_str << ": \n" << std::endl; auto start = std::chrono::high_resolution_clock::now(); From 00bc00e2c0ebd5b8d80182dff9d401bbc1cf7e94 Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Wed, 19 Nov 2025 11:46:35 +0000 Subject: [PATCH 17/62] Move timer to exclude memory transfer around raygen launch --- src/gprt/ray_tracer.cpp | 18 ++++++++++++++++++ tools/ray-benchmark.cpp | 18 +----------------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/gprt/ray_tracer.cpp b/src/gprt/ray_tracer.cpp index 0cd21f06..679232aa 100644 --- a/src/gprt/ray_tracer.cpp +++ b/src/gprt/ray_tracer.cpp @@ -1,6 +1,7 @@ #include "xdg/gprt/ray_tracer.h" #include "gprt/gprt.h" +#include namespace xdg { GPRTRayTracer::GPRTRayTracer() @@ -436,6 +437,17 @@ void GPRTRayTracer::ray_fire(TreeID tree, gprtBufferUnmap(rayHitBuffers_.ray); // required to sync buffer back on GPU? + std::cout << "Starting ray fire benchmark with " << num_rays << " rays" << " using " + << "GPRT" << ": \n" << std::endl; + auto start = std::chrono::high_resolution_clock::now(); + + // Launch the ray generation shader with push constants and buffer bindings + gprtRayGenLaunch1D(context_, rayGen, num_rays); + gprtGraphicsSynchronize(context_); + auto end = std::chrono::high_resolution_clock::now(); + std::chrono::duration elapsed = end - start; + double rays_per_second = static_cast(num_rays) / elapsed.count(); + // Launch the ray generation shader with push constants and buffer bindings gprtRayGenLaunch1D(context_, rayGen, num_rays); gprtGraphicsSynchronize(context_); @@ -458,6 +470,12 @@ void GPRTRayTracer::ray_fire(TreeID tree, } gprtBufferUnmap(rayHitBuffers_.hit); // required to sync buffer back on GPU? Maybe this second unmap isn't actually needed since we dont need to resyncrhonize after retrieving the data from device + std::cout << "----------------------------------------" << std::endl; + std::cout << "Completed " << num_rays << " rays in " << elapsed.count() << " seconds." << std::endl; + std::cout << "Ray tracing throughput: " << rays_per_second << " rays/second." << std::endl; + std::cout << "---------------------------------------- \n" << std::endl; + + return; } diff --git a/tools/ray-benchmark.cpp b/tools/ray-benchmark.cpp index b2e946c4..db1747d9 100644 --- a/tools/ray-benchmark.cpp +++ b/tools/ray-benchmark.cpp @@ -133,12 +133,6 @@ int main(int argc, char** argv) { std::vector hitDistances(N, -1.0); std::vector hitElements(N, ID_NONE); - std::cout << "Volume ID: " << volume << " with: " << mm->num_volume_faces(volume) - << " faces" << std::endl; - - std::cout << "Starting ray fire benchmark with " << N << " rays" << " using " - << rt_str << ": \n" << std::endl; - auto start = std::chrono::high_resolution_clock::now(); if (rt_lib == RTLibrary::GPRT) { // GPRT backend supports batch ray fire @@ -148,16 +142,6 @@ int main(int argc, char** argv) { auto result = xdg->ray_fire(volume, origin, directions[i]); } } - - auto end = std::chrono::high_resolution_clock::now(); - std::chrono::duration elapsed = end - start; - double rays_per_second = static_cast(N) / elapsed.count(); - - std::cout << "----------------------------------------" << std::endl; - std::cout << "Completed " << N << " rays in " << elapsed.count() << " seconds." << std::endl; - std::cout << "Ray tracing throughput: " << rays_per_second << " rays/second." << std::endl; - std::cout << "---------------------------------------- \n" << std::endl; - - + return 0; } \ No newline at end of file From 59f59d3b0f65eeafeb86894e6d36b6bb1901313f Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Wed, 19 Nov 2025 15:35:47 +0000 Subject: [PATCH 18/62] Ensured timer still setup for Embree ray benchamrking --- src/gprt/ray_tracer.cpp | 2 +- tools/ray-benchmark.cpp | 22 +++++++++++++++++++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/gprt/ray_tracer.cpp b/src/gprt/ray_tracer.cpp index 679232aa..2d02824c 100644 --- a/src/gprt/ray_tracer.cpp +++ b/src/gprt/ray_tracer.cpp @@ -438,7 +438,7 @@ void GPRTRayTracer::ray_fire(TreeID tree, gprtBufferUnmap(rayHitBuffers_.ray); // required to sync buffer back on GPU? std::cout << "Starting ray fire benchmark with " << num_rays << " rays" << " using " - << "GPRT" << ": \n" << std::endl; + << "GPRT (FP64)" << ": \n" << std::endl; auto start = std::chrono::high_resolution_clock::now(); // Launch the ray generation shader with push constants and buffer bindings diff --git a/tools/ray-benchmark.cpp b/tools/ray-benchmark.cpp index db1747d9..4c116f5f 100644 --- a/tools/ray-benchmark.cpp +++ b/tools/ray-benchmark.cpp @@ -133,15 +133,31 @@ int main(int argc, char** argv) { std::vector hitDistances(N, -1.0); std::vector hitElements(N, ID_NONE); - if (rt_lib == RTLibrary::GPRT) { - // GPRT backend supports batch ray fire + // GPRT backend supports batch ray fire (timer placed around raygen launch) xdg->ray_fire(volume, origins.data(), directions.data(), N, hitDistances.data(), hitElements.data()); } else { + std::cout << "Volume ID: " << volume << " with: " << mm->num_volume_faces(volume) + << " faces" << std::endl; + + std::cout << "Starting ray fire benchmark with " << N << " rays" << " using " + << rt_str << ": \n" << std::endl; + auto start = std::chrono::high_resolution_clock::now(); for (size_t i = 0; i < N; ++i) { auto result = xdg->ray_fire(volume, origin, directions[i]); } + auto end = std::chrono::high_resolution_clock::now(); + std::chrono::duration elapsed = end - start; + double rays_per_second = static_cast(N) / elapsed.count(); + + std::cout << "----------------------------------------" << std::endl; + std::cout << "Completed " << N << " rays in " << elapsed.count() << " seconds." << std::endl; + std::cout << "Ray tracing throughput: " << rays_per_second << " rays/second." << std::endl; + std::cout << "---------------------------------------- \n" << std::endl; } - + + + + return 0; } \ No newline at end of file From 8ac274466a075439232b50642950aa1df1bbe2e7 Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Tue, 25 Nov 2025 16:23:06 +0000 Subject: [PATCH 19/62] Add default stubs for GPU specific ray tracing methods. Cleanup embree overrides --- include/xdg/embree/ray_tracer.h | 24 ----------- include/xdg/ray_tracing_interface.h | 67 +++++++++++++++++++++++------ 2 files changed, 53 insertions(+), 38 deletions(-) diff --git a/include/xdg/embree/ray_tracer.h b/include/xdg/embree/ray_tracer.h index 7045a49b..9c031d8f 100644 --- a/include/xdg/embree/ray_tracer.h +++ b/include/xdg/embree/ray_tracer.h @@ -47,16 +47,6 @@ class EmbreeRayTracer : public RayTracer { const Direction* direction = nullptr, const std::vector* exclude_primitives = nullptr) const override; - // Array version of point_in_volume - void point_in_volume(TreeID tree, - const Position* points, - const size_t num_points, - uint8_t* results, - const Direction* directions = nullptr, - std::vector* exclude_primitives = nullptr) override - { - fatal_error("Batch point_in_volume not yet implemented for EmbreeRayTracer"); - }; std::pair ray_fire(TreeID scene, const Position& origin, @@ -65,20 +55,6 @@ class EmbreeRayTracer : public RayTracer { HitOrientation orientation = HitOrientation::EXITING, std::vector* const exclude_primitives = nullptr) override; - // Array version of ray_fire - void ray_fire(TreeID tree, - const Position* origins, - const Direction* directions, - const size_t num_rays, - double* hitDistances, - MeshID* surfaceIDs, - const double dist_limit = INFTY, - HitOrientation orientation = HitOrientation::EXITING, - std::vector* const exclude_primitives = nullptr) override - { - fatal_error("Batch ray_fire not yet implemented for EmbreeRayTracer"); - }; - std::pair closest(TreeID scene, const Position& origin) override; diff --git a/include/xdg/ray_tracing_interface.h b/include/xdg/ray_tracing_interface.h index ad62bddf..b65780f7 100644 --- a/include/xdg/ray_tracing_interface.h +++ b/include/xdg/ray_tracing_interface.h @@ -5,6 +5,7 @@ #include #include +#include "xdg/error.h" #include "xdg/constants.h" #include "xdg/embree_interface.h" #include "xdg/mesh_manager_interface.h" @@ -115,6 +116,47 @@ class RayTracer { HitOrientation orientation = HitOrientation::EXITING, std::vector* const exclude_primitives = nullptr) = 0; + /** + * @brief Finds the element containing a given point using the global element tree. + * + * This method searches for the element that contains the specified point using + * the global element tree. It is a convenience wrapper around the tree-specific + * find_element method. + * + * @param point The Position to search for + * @return The MeshID of the containing element, or ID_NONE if no element contains the point + */ + virtual MeshID find_element(const Position& point) const = 0; + + /** + * @brief Finds the element containing a given point using a specific tree. + * + * This method searches for the element that contains the specified point using + * the provided tree. It is a more specific version of the global find_element + * method. + */ + virtual MeshID find_element(TreeID tree, const Position& point) const = 0; + + virtual std::pair closest(TreeID tree, + const Position& origin) = 0; + + virtual bool occluded(TreeID tree, + const Position& origin, + const Direction& direction, + double& dist) const = 0; + + virtual RTLibrary library() const = 0; + + + // Generic Accessors + int num_registered_trees() const { return surface_trees_.size() + element_trees_.size(); }; + int num_registered_surface_trees() const { return surface_trees_.size(); }; + int num_registered_element_trees() const { return element_trees_.size(); }; + + + // GPU Ray Tracing Support + + /** * @brief Array based version of point_in_volume query * @@ -135,7 +177,10 @@ class RayTracer { const size_t num_points, uint8_t* results, const Direction* directions = nullptr, - std::vector* exclude_primitives = nullptr) = 0; + std::vector* exclude_primitives = nullptr) + { + fatal_error("GPU ray tracing not supported with this RayTracer backend"); + } /** * @brief Array based version of ray_fire query * @@ -162,8 +207,10 @@ class RayTracer { MeshID* surfaceIDs, const double dist_limit = INFTY, HitOrientation orientation = HitOrientation::EXITING, - std::vector* const exclude_primitives = nullptr) = 0; - + std::vector* const exclude_primitives = nullptr) + { + fatal_error("GPU ray tracing not supported with this RayTracer backend"); + } /** * @brief Finds the element containing a given point using the global element tree. * @@ -183,17 +230,9 @@ class RayTracer { * the provided tree. It is a more specific version of the global find_element * method. */ - virtual MeshID find_element(TreeID tree, const Position& point) const = 0; - - virtual std::pair closest(TreeID tree, - const Position& origin) = 0; - - virtual bool occluded(TreeID tree, - const Position& origin, - const Direction& direction, - double& dist) const = 0; - - virtual RTLibrary library() const = 0; + virtual void check_rayhit_buffer_capacity(const size_t num_rays) { + fatal_error("GPU ray tracing not supported with this RayTracer backend"); + } // Generic Accessors From 7bc2ec79411e71196d2c31c967c03007fb9e7d79 Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Tue, 25 Nov 2025 16:40:17 +0000 Subject: [PATCH 20/62] Refactor raygen launching functions to make use of PushConstants for constants across all rays --- include/xdg/gprt/shared_structs.h | 8 ++-- src/gprt/dbl_deviceCode.slang | 23 +++++------ src/gprt/ray_tracer.cpp | 68 +++++++++++++++++-------------- 3 files changed, 51 insertions(+), 48 deletions(-) diff --git a/include/xdg/gprt/shared_structs.h b/include/xdg/gprt/shared_structs.h index 3855775a..fdd694f6 100644 --- a/include/xdg/gprt/shared_structs.h +++ b/include/xdg/gprt/shared_structs.h @@ -11,13 +11,8 @@ struct dblRay { double3 origin; double3 direction; - double tMin; // Minimum distance for ray intersection - double tMax; // Maximum distance for ray intersection int32_t* exclude_primitives; // Optional for excluding primitives int32_t exclude_count; // Number of excluded primitives - xdg::HitOrientation hitOrientation; - int volume_tree; // TreeID of the volume being queried - SurfaceAccelerationStructure volume_accel; // The volume accel }; struct dblHit @@ -57,4 +52,7 @@ struct dblRayGenData { struct dblRayFirePushConstants { double tMax; double tMin; + SurfaceAccelerationStructure volume_accel; + int volume_tree; + xdg::HitOrientation hitOrientation; }; diff --git a/src/gprt/dbl_deviceCode.slang b/src/gprt/dbl_deviceCode.slang index 0034e534..fca4fe05 100644 --- a/src/gprt/dbl_deviceCode.slang +++ b/src/gprt/dbl_deviceCode.slang @@ -58,10 +58,10 @@ void ray_fire(uniform dblRayGenData record, uniform DPTriangleGeomData mesh) { RayDesc rayDesc; rayDesc.Origin = float3(record.ray[rayID].origin); rayDesc.Direction = normalize(float3(record.ray[rayID].direction)); - rayDesc.TMin = float(record.ray[rayID].tMin); - rayDesc.TMax = float(record.ray[rayID].tMax); + rayDesc.TMin = float(PC.tMin); + rayDesc.TMax = float(PC.tMax); - SurfaceAccelerationStructure world = record.ray[rayID].volume_accel; + SurfaceAccelerationStructure world = PC.volume_accel; // Pass the ray's origin and direction to the payload payload.distance = -1.0f; @@ -85,10 +85,10 @@ void point_in_volume(uniform dblRayGenData record, uniform DPTriangleGeomData me RayDesc rayDesc; rayDesc.Origin = float3(record.ray[rayID].origin); rayDesc.Direction = float3(normalize(record.ray[rayID].direction)); - rayDesc.TMin = float(record.ray[rayID].tMin); - rayDesc.TMax = float(record.ray[rayID].tMax); + rayDesc.TMin = float(PC.tMin); + rayDesc.TMax = float(PC.tMax); - SurfaceAccelerationStructure world = record.ray[rayID].volume_accel; + SurfaceAccelerationStructure world = PC.volume_accel; // Pass the ray's origin and direction to the payload payload.surf_id = -1; @@ -105,8 +105,7 @@ void point_in_volume(uniform dblRayGenData record, uniform DPTriangleGeomData me /* A shader to compute and store AABB min/maxes in single precision using double precision coords*/ [shader("compute")] [numthreads(64, 1, 1)] -void -populate_aabbs(uint3 DispatchThreadID: SV_DispatchThreadID, uniform DPTriangleGeomData record) { +void populate_aabbs(uint3 DispatchThreadID: SV_DispatchThreadID, uniform DPTriangleGeomData record) { int primID = DispatchThreadID.x; // guard against more threads than primitives @@ -163,8 +162,8 @@ void DPTrianglePluckerIntersection(uniform DPTriangleGeomData record) double3 origin = record.ray[rayID].origin; double3 direction = record.ray[rayID].direction; - double tMin = record.ray[rayID].tMin; - double tMax = record.ray[rayID].tMax; + double tMin = PC.tMin; + double tMax = PC.tMax; const double3 raya = direction; const double3 rayb = cross(direction, origin); @@ -234,7 +233,7 @@ void DPTrianglePluckerIntersection(uniform DPTriangleGeomData record) double3 norm = record.normals[primID]; // recover double precision normal. TODO - Should we calculate from vertices instead? // sense adjustment of normal - if (record.ray[rayID].volume_tree == record.reverse_tree) + if (PC.volume_tree == record.reverse_tree) { norm = -norm; } @@ -243,7 +242,7 @@ void DPTrianglePluckerIntersection(uniform DPTriangleGeomData record) uint hit_kind = norm_dot_dir < 0 ? HIT_KIND_TRIANGLE_FRONT_FACE : HIT_KIND_TRIANGLE_BACK_FACE; - xdg::HitOrientation hitOrientation = record.ray[rayID].hitOrientation; + xdg::HitOrientation hitOrientation = PC.hitOrientation; if (orientation_cull(direction, norm, hitOrientation)) { diff --git a/src/gprt/ray_tracer.cpp b/src/gprt/ray_tracer.cpp index 2d02824c..892db556 100644 --- a/src/gprt/ray_tracer.cpp +++ b/src/gprt/ray_tracer.cpp @@ -240,13 +240,8 @@ bool GPRTRayTracer::point_in_volume(SurfaceTreeID tree, gprtBufferMap(rayHitBuffers_.ray); // Update the ray input buffer dblRay* ray = gprtBufferGetHostPointer(rayHitBuffers_.ray); - ray[0].volume_accel = gprtAccelGetDeviceAddress(volume); ray[0].origin = {point.x, point.y, point.z}; ray[0].direction = {directionUsed.x, directionUsed.y, directionUsed.z}; - ray[0].tMax = INFTY; // Set a large distance limit - ray[0].tMin = 0.0; - ray[0].volume_tree = tree; // Set the TreeID of the volume being queried - ray[0].hitOrientation = HitOrientation::ANY; // No orientation culling for point-in-volume check if (exclude_primitives) { if (!exclude_primitives->empty()) gprtBufferResize(context_, excludePrimitivesBuffer_, exclude_primitives->size(), false); @@ -264,7 +259,14 @@ bool GPRTRayTracer::point_in_volume(SurfaceTreeID tree, } gprtBufferUnmap(rayHitBuffers_.ray); // required to sync buffer back on GPU? - gprtRayGenLaunch1D(context_, rayGen, 1); // Launch raygen shader (entry point to RT pipeline) + dblRayFirePushConstants pushConstants; + pushConstants.hitOrientation = HitOrientation::ANY; + pushConstants.tMax = INFTY; + pushConstants.tMin = 0.0; + pushConstants.volume_accel = gprtAccelGetDeviceAddress(volume); + pushConstants.volume_tree = tree; + + gprtRayGenLaunch1D(context_, rayGen, 1, pushConstants); // Launch raygen shader (entry point to RT pipeline) gprtGraphicsSynchronize(context_); // Ensure all GPU operations are complete before returning control flow to CPU // Retrieve the hit from the dblHit buffer @@ -297,13 +299,8 @@ std::pair GPRTRayTracer::ray_fire(SurfaceTreeID tree, gprtBufferMap(rayHitBuffers_.ray); // Update the ray input buffer dblRay* ray = gprtBufferGetHostPointer(rayHitBuffers_.ray); - ray[0].volume_accel = gprtAccelGetDeviceAddress(volume); ray[0].origin = {origin.x, origin.y, origin.z}; ray[0].direction = {direction.x, direction.y, direction.z}; - ray[0].tMax = dist_limit; - ray[0].tMin = 0.0; - ray[0].hitOrientation = orientation; // Set orientation for the ray - ray[0].volume_tree = tree; // Set the TreeID of the volume being queried if (exclude_primitives) { if (!exclude_primitives->empty()) gprtBufferResize(context_, excludePrimitivesBuffer_, exclude_primitives->size(), false); @@ -321,7 +318,15 @@ std::pair GPRTRayTracer::ray_fire(SurfaceTreeID tree, } gprtBufferUnmap(rayHitBuffers_.ray); // required to sync buffer back on GPU? - gprtRayGenLaunch1D(context_, rayGen, 1); // Launch raygen shader (entry point to RT pipeline) + // Set push constants (same for every ray) + dblRayFirePushConstants pushConstants; + pushConstants.hitOrientation = orientation; + pushConstants.tMax = dist_limit; + pushConstants.tMin = 0.0; + pushConstants.volume_accel = gprtAccelGetDeviceAddress(volume); + pushConstants.volume_tree = tree; + + gprtRayGenLaunch1D(context_, rayGen, 1, pushConstants); // Launch raygen shader (entry point to RT pipeline) gprtGraphicsSynchronize(context_); // Ensure all GPU operations are complete before returning control flow to CPU // Retrieve the hit from the dblHit buffer @@ -361,16 +366,10 @@ void GPRTRayTracer::point_in_volume(TreeID tree, // Map the region start gprtBufferMap(rayHitBuffers_.ray); dblRay* ray = gprtBufferGetHostPointer(rayHitBuffers_.ray); - const auto volumeAddr = gprtAccelGetDeviceAddress(volume); // Common ray params for (size_t i = 0; i < num_points; ++i) { - ray[i].volume_accel = volumeAddr; ray[i].origin = {points[i].x, points[i].y, points[i].z}; - ray[i].tMax = INFTY; - ray[i].tMin = 0.0; - ray[i].volume_tree = tree; - ray[i].hitOrientation = HitOrientation::ANY; ray[i].exclude_primitives = nullptr; } @@ -385,7 +384,15 @@ void GPRTRayTracer::point_in_volume(TreeID tree, gprtBufferUnmap(rayHitBuffers_.ray); // required to sync buffer back on GPU? - gprtRayGenLaunch1D(context_, rayGen, num_points); + // Set push constants (same for every ray) + dblRayFirePushConstants pushConstants; + pushConstants.hitOrientation = HitOrientation::ANY; + pushConstants.tMax = INFTY; + pushConstants.tMin = 0.0; + pushConstants.volume_accel = gprtAccelGetDeviceAddress(volume); + pushConstants.volume_tree = tree; + + gprtRayGenLaunch1D(context_, rayGen, num_points, pushConstants); gprtGraphicsSynchronize(context_); // Retrieve the output from the ray output buffer @@ -420,37 +427,36 @@ void GPRTRayTracer::ray_fire(TreeID tree, gprtBufferMap(rayHitBuffers_.ray); dblRay* ray = gprtBufferGetHostPointer(rayHitBuffers_.ray); - const auto volAddr = gprtAccelGetDeviceAddress(volume); + // Set per ray values for (size_t i = 0; i < num_rays; ++i) { const auto& origin = origins[i]; const auto& direction = directions[i]; - ray[i].volume_accel = volAddr; ray[i].origin = {origin.x, origin.y, origin.z}; ray[i].direction = {direction.x, direction.y, direction.z}; - ray[i].tMax = dist_limit; - ray[i].tMin = 0.0; - ray[i].hitOrientation = orientation; // Set orientation for the ray - ray[i].volume_tree = tree; // Set the TreeID of the volume being queried ray[i].exclude_primitives = nullptr; // Not currently supported in batch version } gprtBufferUnmap(rayHitBuffers_.ray); // required to sync buffer back on GPU? - + + // Set push constants (same for every ray) + dblRayFirePushConstants pushConstants; + pushConstants.hitOrientation = orientation; + pushConstants.tMax = dist_limit; + pushConstants.tMin = 0.0; + pushConstants.volume_accel = gprtAccelGetDeviceAddress(volume); + pushConstants.volume_tree = tree; + std::cout << "Starting ray fire benchmark with " << num_rays << " rays" << " using " << "GPRT (FP64)" << ": \n" << std::endl; auto start = std::chrono::high_resolution_clock::now(); // Launch the ray generation shader with push constants and buffer bindings - gprtRayGenLaunch1D(context_, rayGen, num_rays); + gprtRayGenLaunch1D(context_, rayGen, num_rays, pushConstants); gprtGraphicsSynchronize(context_); auto end = std::chrono::high_resolution_clock::now(); std::chrono::duration elapsed = end - start; double rays_per_second = static_cast(num_rays) / elapsed.count(); - - // Launch the ray generation shader with push constants and buffer bindings - gprtRayGenLaunch1D(context_, rayGen, num_rays); - gprtGraphicsSynchronize(context_); // Retrieve the output from the ray output buffer gprtBufferMap(rayHitBuffers_.hit); From 605e573c227ded3cf1b6a1fde75d763ac9b41763 Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Tue, 25 Nov 2025 16:44:52 +0000 Subject: [PATCH 21/62] Added methods for pre-packing rays and performing ray_fire on those rays --- include/xdg/gprt/ray_tracer.h | 25 +++++++++-- include/xdg/gprt/shared_structs.h | 8 ++++ include/xdg/ray_tracing_interface.h | 57 ++++++++++++++++------- include/xdg/xdg.h | 15 +++++++ src/gprt/dbl_deviceCode.slang | 26 ++++++++++- src/gprt/ray_tracer.cpp | 70 +++++++++++++++++++++++++++-- src/xdg.cpp | 17 +++++++ 7 files changed, 194 insertions(+), 24 deletions(-) diff --git a/include/xdg/gprt/ray_tracer.h b/include/xdg/gprt/ray_tracer.h index 2f637665..03924563 100644 --- a/include/xdg/gprt/ray_tracer.h +++ b/include/xdg/gprt/ray_tracer.h @@ -95,7 +95,6 @@ class GPRTRayTracer : public RayTracer { const double dist_limit = INFTY, HitOrientation orientation = HitOrientation::EXITING, std::vector* const exclude_primitives = nullptr) override; - void ray_fire(TreeID tree, const Position* origins, const Direction* directions, @@ -106,6 +105,11 @@ class GPRTRayTracer : public RayTracer { HitOrientation orientation = HitOrientation::EXITING, std::vector* const exclude_primitives = nullptr) override; + void ray_fire_packed(TreeID tree, + const size_t num_rays, + const double dist_limit = INFTY, + HitOrientation orientation = HitOrientation::EXITING) override; + std::pair closest(TreeID scene, const Position& origin) override {}; @@ -116,9 +120,23 @@ class GPRTRayTracer : public RayTracer { fatal_error("Occlusion queries are not currently supported with GPRT ray tracer"); return false; } - + + // Check to see if buffers large enough and resize if not + void check_rayhit_buffer_capacity(const size_t N) override; + + // Method to expose device ray and hit buffers for external population + DeviceRayHitBuffers get_device_rayhit_buffers(const size_t N) override; + + void pack_external_rays(void* origins_device_ptr, + void* directions_device_ptr, + size_t num_rays) override; + + GPRTContext context() + { + return context_; + } + private: - void check_ray_buffer_capacity(const size_t N); // GPRT objects GPRTContext context_; @@ -132,6 +150,7 @@ class GPRTRayTracer : public RayTracer { GPRTMissOf missProgram_; GPRTComputeOf aabbPopulationProgram_; // packRaysProgam_; // 0; } + }; /** - * @brief Finds the element containing a given point using a specific tree. - * - * This method searches for the element that contains the specified point using - * the provided tree. It is a more specific version of the global find_element - * method. + * @brief Check whether the current ray buffer capacity is sufficient for the number of rays requested + * @param[in] num_rays The number of rays to be processed */ virtual void check_rayhit_buffer_capacity(const size_t num_rays) { fatal_error("GPU ray tracing not supported with this RayTracer backend"); } + /** + * @brief return device pointers to ray and hit buffers for GPU ray tracing + * @return DeviceRayHitBuffers struct containing device pointers to ray and hit buffers + */ + virtual DeviceRayHitBuffers get_device_rayhit_buffers(const size_t num_rays) { + fatal_error("GPU ray tracing not supported with this RayTracer backend"); + return {}; + } - // Generic Accessors - int num_registered_trees() const { return surface_trees_.size() + element_trees_.size(); }; - int num_registered_surface_trees() const { return surface_trees_.size(); }; - int num_registered_element_trees() const { return element_trees_.size(); }; + virtual void pack_external_rays(void* origins_device_ptr, + void* directions_device_ptr, + size_t num_rays) { + fatal_error("GPU ray tracing not supported with this RayTracer backend"); + return; + } protected: // Common functions across RayTracers diff --git a/include/xdg/xdg.h b/include/xdg/xdg.h index 5a8a692c..76922870 100644 --- a/include/xdg/xdg.h +++ b/include/xdg/xdg.h @@ -154,6 +154,11 @@ void ray_fire(MeshID volume, HitOrientation orientation = HitOrientation::EXITING, std::vector* const exclude_primitives = nullptr); +void ray_fire_packed(MeshID volume, + const size_t num_rays, + const double dist_limit = INFTY, + HitOrientation orientation = HitOrientation::EXITING); + std::pair closest(MeshID volume, const Position& origin) const; @@ -184,6 +189,15 @@ Direction surface_normal(MeshID surface, ray_tracing_interface_ = ray_tracing_interface; } + RayTracer::DeviceRayHitBuffers get_device_rayhit_buffers(const size_t requiredCapacity) + { + return ray_tracing_interface()->get_device_rayhit_buffers(requiredCapacity); + } + + void pack_external_rays(void* origins_device_ptr, + void* directions_device_ptr, + size_t num_rays); + // Accessors const std::shared_ptr& ray_tracing_interface() const { return ray_tracing_interface_; @@ -192,6 +206,7 @@ Direction surface_normal(MeshID surface, const std::shared_ptr& mesh_manager() const { return mesh_manager_; } + // Private methods private: double _triangle_volume_contribution(const PrimitiveRef& triangle) const; diff --git a/src/gprt/dbl_deviceCode.slang b/src/gprt/dbl_deviceCode.slang index fca4fe05..0732f92e 100644 --- a/src/gprt/dbl_deviceCode.slang +++ b/src/gprt/dbl_deviceCode.slang @@ -125,8 +125,32 @@ void populate_aabbs(uint3 DispatchThreadID: SV_DispatchThreadID, uniform DPTrian record.aabbs[2 * primID + 1] = fpaabbmax; } -// ------------------------------------------------ CUSTOM INTERSECTION SHADERS ------------------------------------------------ +// TODO - does threads per work group matter at all? +[shader("compute")] +[numthreads(256, 1, 1)] +void pack_external_rays(uint3 DispatchThreadID: SV_DispatchThreadID, uniform ExternalRayParams extParams) { + + // guard against more threads than primitives - Will this ever happen when performing operations on every ray? Probably not + // if (rayID >= extParams.num_rays) + // return; + + // Global thread index (we only use the x-dimension) + uint idx = DispatchThreadID.x; + uint stride = extParams.total_threads; // Groups * 256 + // Grid-stride loop: each thread handles ray idx, idx+stride, idx+2*stride, ... + for (; idx < extParams.num_rays; idx += stride) + { + dblRay r; + r.origin = extParams.origins[idx]; + r.direction = extParams.directions[idx]; + r.exclude_primitives = nullptr; + r.exclude_count = 0; + + extParams.xdgRays[idx] = r; + } +} +// ------------------------------------------------ CUSTOM INTERSECTION SHADERS ------------------------------------------------ /* 1D ray generation intersection with a double precision triangle using the Plucker intersection algorithm*/ [shader("intersection")] diff --git a/src/gprt/ray_tracer.cpp b/src/gprt/ray_tracer.cpp index 892db556..e7093401 100644 --- a/src/gprt/ray_tracer.cpp +++ b/src/gprt/ray_tracer.cpp @@ -77,6 +77,7 @@ void GPRTRayTracer::setup_shaders() missProgram_ = gprtMissCreate(context_, module_, "ray_fire_miss"); aabbPopulationProgram_ = gprtComputeCreate(context_, module_, "populate_aabbs"); + packRaysProgam_ = gprtComputeCreate(context_, module_, "pack_external_rays"); // Create a "triangle" geometry type and set its closest-hit program trianglesGeomType_ = gprtGeomTypeCreate(context_, GPRT_AABBS); @@ -356,7 +357,7 @@ void GPRTRayTracer::point_in_volume(TreeID tree, GPRTAccel volume = surface_volume_tree_to_accel_map.at(tree); auto rayGen = rayGenPrograms_.at(RayGenType::POINT_IN_VOLUME); dblRayGenData* rayGenData = gprtRayGenGetParameters(rayGen); - check_ray_buffer_capacity(num_points); + check_rayhit_buffer_capacity(num_points); // TODO - handle exclude_primitives for batch version @@ -423,7 +424,7 @@ void GPRTRayTracer::ray_fire(TreeID tree, GPRTAccel volume = surface_volume_tree_to_accel_map.at(tree); auto rayGen = rayGenPrograms_.at(RayGenType::RAY_FIRE); dblRayGenData* rayGenData = gprtRayGenGetParameters(rayGen); - check_ray_buffer_capacity(num_rays); + check_rayhit_buffer_capacity(num_rays); gprtBufferMap(rayHitBuffers_.ray); dblRay* ray = gprtBufferGetHostPointer(rayHitBuffers_.ray); @@ -485,6 +486,31 @@ void GPRTRayTracer::ray_fire(TreeID tree, return; } +void +GPRTRayTracer::ray_fire_packed(TreeID tree, + const size_t num_rays, + const double dist_limit, + HitOrientation orientation) +{ + if (num_rays == 0) return; // no work to do. Early exit + + check_rayhit_buffer_capacity(num_rays); + + GPRTAccel volume = surface_volume_tree_to_accel_map.at(tree); + auto rayGen = rayGenPrograms_.at(RayGenType::RAY_FIRE); + + dblRayFirePushConstants pushConstants; + pushConstants.volume_accel = gprtAccelGetDeviceAddress(volume); + pushConstants.tMax = dist_limit; + pushConstants.tMin = 0.0; + pushConstants.hitOrientation = orientation; // Set orientation for the ray + pushConstants.volume_tree = tree; // Set the TreeID of the volume being queried + + gprtRayGenLaunch1D(context_, rayGen, num_rays, pushConstants); + gprtGraphicsSynchronize(context_); + return; +} + void GPRTRayTracer::create_global_surface_tree() { // Create a TLAS (Top-Level Acceleration Structure) for all the volumes @@ -499,7 +525,7 @@ void GPRTRayTracer::create_global_surface_tree() global_surface_accel_ = global_accel; } -void GPRTRayTracer::check_ray_buffer_capacity(const size_t N) +void GPRTRayTracer::check_rayhit_buffer_capacity(const size_t N) { if (N <= rayHitBuffers_.capacity) return; // current capacity is sufficient @@ -526,4 +552,42 @@ void GPRTRayTracer::check_ray_buffer_capacity(const size_t N) gprtBuildShaderBindingTable(context_, static_cast(GPRT_SBT_GEOM | GPRT_SBT_RAYGEN)); } +RayTracer::DeviceRayHitBuffers GPRTRayTracer::get_device_rayhit_buffers(const size_t N) +{ + check_rayhit_buffer_capacity(N); + DeviceRayHitBuffers buffers; + buffers.rays = rayHitBuffers_.devRayAddr; + buffers.hits = rayHitBuffers_.devHitAddr; + buffers.capacity = rayHitBuffers_.capacity; + return buffers; +} + +void GPRTRayTracer::pack_external_rays(void* origins_device_ptr, + void* directions_device_ptr, + size_t num_rays) +{ + if (num_rays == 0) return; + + check_rayhit_buffer_capacity(num_rays); + ExternalRayParams params = {}; + params.num_rays = num_rays; + + // Workgroup setup + constexpr int threadsPerGroup = 256; + const int neededGroups = (params.num_rays + threadsPerGroup - 1) / threadsPerGroup; + const int groups = std::min(neededGroups, WORKGROUP_LIMIT); + + params.xdgRays = rayHitBuffers_.devRayAddr; // dblRay* + params.origins = static_cast(origins_device_ptr); + params.directions = static_cast(directions_device_ptr); + params.total_threads = groups * threadsPerGroup; + + gprtComputeLaunch(packRaysProgam_, + { groups, 1, 1 }, + { threadsPerGroup, 1, 1 }, + params); + gprtComputeSynchronize(context_); +} + } // namespace xdg + diff --git a/src/xdg.cpp b/src/xdg.cpp index d34cf3be..b6e816b4 100644 --- a/src/xdg.cpp +++ b/src/xdg.cpp @@ -266,6 +266,16 @@ XDG::ray_fire(MeshID volume, return ray_tracing_interface()->ray_fire(tree, origins, directions, num_rays, hitDistances, surfaceIDs, dist_limit, orientation, exclude_primitives); } +void +XDG::ray_fire_packed(MeshID volume, + const size_t num_rays, + const double dist_limit, + HitOrientation orientation) +{ + TreeID tree = volume_to_surface_tree_map_.at(volume); + return ray_tracing_interface()->ray_fire_packed(tree, num_rays, dist_limit, orientation); +} + std::pair XDG::closest(MeshID volume, const Position& origin) const { @@ -352,4 +362,11 @@ double XDG::measure_volume_area(MeshID volume) const return area; } +void XDG::pack_external_rays(void* origins_device_ptr, + void* directions_device_ptr, + size_t num_rays) + { + return ray_tracing_interface()->pack_external_rays(origins_device_ptr, directions_device_ptr, num_rays); + } + } // namespace xdg \ No newline at end of file From 5e140f9a41b8ec66b7a4cb6d0f0e1bffffaf82ae Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Tue, 25 Nov 2025 16:47:46 +0000 Subject: [PATCH 22/62] Update ray_benchmark tool to make use of ray pre-packing and ray_fire_packed() --- .../{ray-benchmark.cpp => ray_benchmark.cpp} | 85 ++++++++++++++----- tools/ray_benchmark_deviceCode.slang | 45 ++++++++++ tools/ray_benchmark_shared.h | 14 +++ 3 files changed, 122 insertions(+), 22 deletions(-) rename tools/{ray-benchmark.cpp => ray_benchmark.cpp} (64%) create mode 100644 tools/ray_benchmark_deviceCode.slang create mode 100644 tools/ray_benchmark_shared.h diff --git a/tools/ray-benchmark.cpp b/tools/ray_benchmark.cpp similarity index 64% rename from tools/ray-benchmark.cpp rename to tools/ray_benchmark.cpp index 4c116f5f..1dca781c 100644 --- a/tools/ray-benchmark.cpp +++ b/tools/ray_benchmark.cpp @@ -12,28 +12,45 @@ #include "xdg/moab/mesh_manager.h" #include "xdg/vec3da.h" #include "xdg/xdg.h" +#include "xdg/ray_tracers.h" #include "argparse/argparse.hpp" +// GPRT includes +#include "gprt/gprt.h" +#include "ray_benchmark_shared.h" + using namespace xdg; +extern GPRTProgram ray_benchmark_deviceCode; + +inline double rand01(uint32_t &state) +{ + state = state * 1664525u + 1013904223u; + return double(state) * (1.0 / 4294967296.0); +} -inline Direction random_unit_dir(std::mt19937_64 &rng) { - std::uniform_real_distribution U(-1.0, 1.0); +inline Direction random_unit_dir_lcg(uint32_t &state) +{ double x1, x2, s; do { - x1 = U(rng); - x2 = U(rng); - s = x1*x1 + x2*x2; + x1 = rand01(state) * 2.0 - 1.0; + x2 = rand01(state) * 2.0 - 1.0; + s = x1 * x1 + x2 * x2; } while (s <= 0.0 || s >= 1.0); - const double t = 2.0 * std::sqrt(1.0 - s); - return { x1 * t, x2 * t, 1.0 - 2.0 * s }; // already unit length + + double t = 2.0 * std::sqrt(1.0 - s); + return { x1 * t, x2 * t, 1.0 - 2.0 * s }; } -inline void generate_dirs(std::vector &out, uint64_t seed = 12345) { - std::mt19937_64 rng(seed); - for (auto &d : out) d = random_unit_dir(rng); +inline void generate_dirs(std::vector &out, uint32_t seed) +{ + for (uint32_t i = 0; i < out.size(); ++i) { + uint32_t state = seed ^ i; + out[i] = random_unit_dir_lcg(state); + } } + int main(int argc, char** argv) { argparse::ArgumentParser args("XDG Ray Tracing throughput benchmarking tool", "1.0", argparse::default_arguments::help); @@ -46,14 +63,14 @@ int main(int argc, char** argv) { .scan<'i', int>(); args.add_argument("-n", "--num-rays") - .default_value(10'000'000) + .default_value(10'000'000) .help("Number of rays to be cast for the benchmark (default - 10 million)") - .scan<'u', std::size_t>(); + .scan<'u', uint32_t>(); args.add_argument("-s", "--seed") - .default_value(12345) + .default_value(12345) .help("Seed for random number generator (default - 12345)") - .scan<'u', uint64_t>(); + .scan<'u', uint32_t>(); args.add_argument("-o", "-p", "--origin", "--position") .default_value(std::vector{0.0, 0.0, 0.0}) @@ -115,11 +132,10 @@ int main(int argc, char** argv) { const auto& mm = xdg->mesh_manager(); mm->load_file(args.get("filename")); mm->init(); - // mm->parse_metadata(); // Generate a set of random rays - size_t N = args.get("--num-rays"); - uint64_t seed = args.get("--seed"); + std::size_t N = args.get("--num-rays"); + uint32_t seed = args.get("--seed"); Position origin = args.get>("--origin"); std::vector origins(N, origin); std::vector directions(N); @@ -134,8 +150,36 @@ int main(int argc, char** argv) { std::vector hitElements(N, ID_NONE); if (rt_lib == RTLibrary::GPRT) { - // GPRT backend supports batch ray fire (timer placed around raygen launch) - xdg->ray_fire(volume, origins.data(), directions.data(), N, hitDistances.data(), hitElements.data()); + // Create GPRT context for compute shader that generates rays + auto gprt_rti = std::dynamic_pointer_cast(rti); + GPRTContext context = gprt_rti->context(); + GPRTModule module = gprtModuleCreate(context, ray_benchmark_deviceCode); + auto genRandomRays = gprtComputeCreate(context, module, "generate_random_rays"); + + /* + TODO - this exposes rayhitbuffers from the GPRT/vulkan context avaiable within XDG. But in this miniapp we define a new context + We might need to expose the same vulkan/GPRT context if we want to reference pointers to it? + auto rayHitBuffers = xdg->get_device_rayhit_buffers(N); + */ + + // Create device buffers for our origins and directions + GPRTBufferOf originsBuf = gprtDeviceBufferCreate(context, N); + GPRTBufferOf directionsBuf = gprtDeviceBufferCreate(context, N); + + Rays rays = {}; + rays.origins = gprtBufferGetDevicePointer(originsBuf); + rays.directions = gprtBufferGetDevicePointer(directionsBuf); + + GenerateRandomRayParams randomRayParams = {}; + randomRayParams.rays = rays; + randomRayParams.numRays = N; + randomRayParams.origin = {origin.x, origin.y, origin.z}; + randomRayParams.seed = seed; + + gprtComputeLaunch(genRandomRays, {1, 1, 1}, {64, 1, 1}, randomRayParams); + + xdg->pack_external_rays(rays.origins, rays.directions, N); + xdg->ray_fire_packed(volume, N); } else { std::cout << "Volume ID: " << volume << " with: " << mm->num_volume_faces(volume) << " faces" << std::endl; @@ -156,8 +200,5 @@ int main(int argc, char** argv) { std::cout << "---------------------------------------- \n" << std::endl; } - - - return 0; } \ No newline at end of file diff --git a/tools/ray_benchmark_deviceCode.slang b/tools/ray_benchmark_deviceCode.slang new file mode 100644 index 00000000..aa6795b8 --- /dev/null +++ b/tools/ray_benchmark_deviceCode.slang @@ -0,0 +1,45 @@ +#include "ray_benchmark_shared.h" + +/* +For this simple benchmark case we are mocking what a downstream application would do in terms of populating +ray buffers. The idea is that the downstream application generates rays (origins + directions). +*/ +[shader("compute")] +[numthreads(64, 1, 1)] +void generate_random_rays(uint3 DispatchThreadID: SV_DispatchThreadID, uniform GenerateRandomRayParams params) { + uint rayID = DispatchThreadID.x; + uint nRays = params.numRays; + + if (rayID >= nRays) { + return; + } + + uint state = params.seed ^ rayID; // same as cpu: state = seed ^ i + double3 dir = random_unit_dir(state); + + params.rays.directions[rayID] = dir; + params.rays.origins[rayID] = params.origin; +} + +// Helpers + +// Simple LCG random number generator +double rand01(inout uint state) +{ + state = state * 1664525u + 1013904223u; + return double(state) * (1.0 / 4294967296.0); +} + +// return random unit dir +double3 random_unit_dir(inout uint state) +{ + double x1, x2, s; + do { + x1 = rand01(state) * 2.0 - 1.0; + x2 = rand01(state) * 2.0 - 1.0; + s = x1 * x1 + x2 * x2; + } while (s <= 0.0 || s >= 1.0); + + double t = 2.0 * sqrt(1.0 - s); + return double3(x1 * t, x2 * t, 1.0 - 2.0 * s); +} \ No newline at end of file diff --git a/tools/ray_benchmark_shared.h b/tools/ray_benchmark_shared.h new file mode 100644 index 00000000..714851e5 --- /dev/null +++ b/tools/ray_benchmark_shared.h @@ -0,0 +1,14 @@ +#include "gprt.h" + +// A simple struct representing buffer of ray data as generated by our "mock application" +struct Rays { + double3* origins; + double3* directions; +}; + +struct GenerateRandomRayParams { + Rays rays; // pointer to ray data buffer + uint numRays; // number of rays to be generated + double3 origin; // single origin provided for benchmark case + uint seed; // seed for random direction generation +}; \ No newline at end of file From e7bdeaa5b97714b33332861c54acaf6a27ae62ad Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Thu, 27 Nov 2025 16:16:31 +0000 Subject: [PATCH 23/62] Added include guard to header with shared types between slang and C++ --- include/xdg/gprt/shared_structs.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/include/xdg/gprt/shared_structs.h b/include/xdg/gprt/shared_structs.h index 5ae8cc21..6c91340d 100644 --- a/include/xdg/gprt/shared_structs.h +++ b/include/xdg/gprt/shared_structs.h @@ -1,3 +1,6 @@ +#ifndef XDG_GPRT_SHARED_STRUCTS_H +#define XDG_GPRT_SHARED_STRUCTS_H + #include "gprt.h" #include "../shared_enums.h" @@ -63,4 +66,6 @@ struct ExternalRayParams { double3* directions; uint32_t num_rays; uint32_t total_threads; -}; \ No newline at end of file +}; + +#endif \ No newline at end of file From 71e4d4fd016b1eae9b73a05701594636c1093ba6 Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Thu, 27 Nov 2025 19:01:00 +0000 Subject: [PATCH 24/62] Fixed ambiguity with mixing GPRT math types (double3) and linalg --- src/tetrahedron_contain.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/tetrahedron_contain.cpp b/src/tetrahedron_contain.cpp index 743ea1d0..98757768 100644 --- a/src/tetrahedron_contain.cpp +++ b/src/tetrahedron_contain.cpp @@ -13,7 +13,9 @@ bool plucker_tet_containment_test(const Position& point, const Position& v1, const Position& v2, const Position& v3) { - using namespace linalg::aliases; + using linalg::aliases::double3x3; + using linalg::aliases::double3; + using linalg::aliases::double4; // Create matrix T = [v1 - v0, v2 - v0, v3 - v0] Vec3da e0 = v1 - v0; Vec3da e1 = v2 - v0; From d41274647277c197e0f355d0a17c81e46e9f6ed4 Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Thu, 27 Nov 2025 19:05:36 +0000 Subject: [PATCH 25/62] Refactored internal and public facing rayhit buffers to shared POD --- include/xdg/gprt/ray.h | 42 ++++++++++++++++++++++++ include/xdg/gprt/ray_tracer.h | 10 +++--- include/xdg/gprt/shared_structs.h | 25 ++++----------- include/xdg/ray_tracing_interface.h | 10 +++--- include/xdg/xdg.h | 3 +- src/gprt/dbl_deviceCode.slang | 8 ++--- src/gprt/ray_tracer.cpp | 50 +++++++++++------------------ 7 files changed, 81 insertions(+), 67 deletions(-) create mode 100644 include/xdg/gprt/ray.h diff --git a/include/xdg/gprt/ray.h b/include/xdg/gprt/ray.h new file mode 100644 index 00000000..0b345765 --- /dev/null +++ b/include/xdg/gprt/ray.h @@ -0,0 +1,42 @@ +#ifndef _XDG_GPRT_RAY_H +#define _XDG_GPRT_RAY_H + +#include "gprt.h" +#include "../shared_enums.h" + +namespace xdg { + +struct dblRay +{ + double3 origin; + double3 direction; + int32_t* exclude_primitives; // Optional for excluding primitives + int32_t exclude_count; // Number of excluded primitives +}; + +struct dblHit +{ + double distance; + int surf_id; + int primitive_id; + PointInVolume piv; // Point in volume check result (0 for outside, 1 for inside) +}; + +// TODO - Move this to its own header +struct DeviceRayHitBuffers { + dblRay* rayDevPtr; // device pointer to ray buffers + dblHit* hitDevPtr; // device pointer to hit buffers + uint capacity = 0; + + // TODO - Renable once I figure out a way to make this slang safe + // bool valid() + // { + // return (rays != 0) && (hits != 0) && (capacity > 0); + // } +}; + + +} + + +#endif \ No newline at end of file diff --git a/include/xdg/gprt/ray_tracer.h b/include/xdg/gprt/ray_tracer.h index 03924563..fa4269c1 100644 --- a/include/xdg/gprt/ray_tracer.h +++ b/include/xdg/gprt/ray_tracer.h @@ -26,15 +26,15 @@ enum class RayGenType { }; struct gprtRayHit { - size_t capacity = 1; // Max number of rays allocated - size_t size = 0; // Current number of active rays + DeviceRayHitBuffers view; // external facing POD for rayhit buffers + size_t size = 0; // Current number of active rays GPRTBufferOf ray = nullptr; GPRTBufferOf hit = nullptr; - dblRay* devRayAddr = nullptr; - dblHit* devHitAddr = nullptr; - bool is_valid() const { return capacity > 0 && ray && hit && devRayAddr && devHitAddr; } + bool is_valid() const { + return view.capacity > 0 && ray && hit && view.rayDevPtr && view.hitDevPtr; + } }; class GPRTRayTracer : public RayTracer { public: diff --git a/include/xdg/gprt/shared_structs.h b/include/xdg/gprt/shared_structs.h index 6c91340d..172acb3a 100644 --- a/include/xdg/gprt/shared_structs.h +++ b/include/xdg/gprt/shared_structs.h @@ -3,6 +3,7 @@ #include "gprt.h" #include "../shared_enums.h" +#include "ray.h" struct GPRTPrimitiveRef { @@ -10,21 +11,6 @@ struct GPRTPrimitiveRef int sense; }; -struct dblRay -{ - double3 origin; - double3 direction; - int32_t* exclude_primitives; // Optional for excluding primitives - int32_t exclude_count; // Number of excluded primitives -}; - -struct dblHit -{ - double distance; - int surf_id; - int primitive_id; - xdg::PointInVolume piv; // Point in volume check result (0 for outside, 1 for inside) -}; /* variables for double precision triangle mesh geometry */ struct DPTriangleGeomData { @@ -36,7 +22,7 @@ struct DPTriangleGeomData { int2 vols; int forward_vol; int reverse_vol; - dblRay *ray; // double precision rays + xdg::dblRay *ray; // double precision rays xdg::HitOrientation hitOrientation; int forward_tree; // TreeID of the forward volume int reverse_tree; // TreeID of the reverse volume @@ -45,8 +31,8 @@ struct DPTriangleGeomData { }; struct dblRayGenData { - dblRay *ray; - dblHit *hit; + xdg::dblRay *ray; + xdg::dblHit *hit; }; /* A small structure of constants that can change every frame without rebuilding the @@ -60,8 +46,9 @@ struct dblRayFirePushConstants { xdg::HitOrientation hitOrientation; }; +// TODO - Drop this in favour of exposing buffers directly struct ExternalRayParams { - dblRay* xdgRays; + xdg::dblRay* xdgRays; double3* origins; double3* directions; uint32_t num_rays; diff --git a/include/xdg/ray_tracing_interface.h b/include/xdg/ray_tracing_interface.h index 222967dd..7f8e64fd 100644 --- a/include/xdg/ray_tracing_interface.h +++ b/include/xdg/ray_tracing_interface.h @@ -11,10 +11,14 @@ #include "xdg/mesh_manager_interface.h" #include "xdg/primitive_ref.h" #include "xdg/geometry_data.h" +#include "xdg/gprt/ray.h" + namespace xdg { +struct DeviceRayHitBuffers; // forward declaration + class RayTracer { public: // Constructors/Destructors @@ -232,12 +236,6 @@ class RayTracer { { fatal_error("GPU ray tracing not supported with this RayTracer backend"); } - struct DeviceRayHitBuffers { - void* rays; // device pointer to ray buffers - void* hits; // device pointer to hit buffers - size_t capacity = 0; - bool valid() const { return rays && hits && capacity > 0; } - }; /** * @brief Check whether the current ray buffer capacity is sufficient for the number of rays requested diff --git a/include/xdg/xdg.h b/include/xdg/xdg.h index 76922870..66684cb5 100644 --- a/include/xdg/xdg.h +++ b/include/xdg/xdg.h @@ -10,6 +10,7 @@ namespace xdg { +struct DeviceRayHitBuffers; // forward declaration class XDG { public: @@ -189,7 +190,7 @@ Direction surface_normal(MeshID surface, ray_tracing_interface_ = ray_tracing_interface; } - RayTracer::DeviceRayHitBuffers get_device_rayhit_buffers(const size_t requiredCapacity) + DeviceRayHitBuffers get_device_rayhit_buffers(const size_t requiredCapacity) { return ray_tracing_interface()->get_device_rayhit_buffers(requiredCapacity); } diff --git a/src/gprt/dbl_deviceCode.slang b/src/gprt/dbl_deviceCode.slang index 0732f92e..29595a8f 100644 --- a/src/gprt/dbl_deviceCode.slang +++ b/src/gprt/dbl_deviceCode.slang @@ -125,7 +125,7 @@ void populate_aabbs(uint3 DispatchThreadID: SV_DispatchThreadID, uniform DPTrian record.aabbs[2 * primID + 1] = fpaabbmax; } -// TODO - does threads per work group matter at all? +// TODO - consider dropping this in favour of having external application fill buffers directly [shader("compute")] [numthreads(256, 1, 1)] void pack_external_rays(uint3 DispatchThreadID: SV_DispatchThreadID, uniform ExternalRayParams extParams) { @@ -135,13 +135,13 @@ void pack_external_rays(uint3 DispatchThreadID: SV_DispatchThreadID, uniform Ext // return; // Global thread index (we only use the x-dimension) - uint idx = DispatchThreadID.x; + uint globalThreadID = DispatchThreadID.x; uint stride = extParams.total_threads; // Groups * 256 // Grid-stride loop: each thread handles ray idx, idx+stride, idx+2*stride, ... - for (; idx < extParams.num_rays; idx += stride) + for (uint idx = globalThreadID; idx < extParams.num_rays; idx += stride) { - dblRay r; + xdg::dblRay r; r.origin = extParams.origins[idx]; r.direction = extParams.directions[idx]; r.exclude_primitives = nullptr; diff --git a/src/gprt/ray_tracer.cpp b/src/gprt/ray_tracer.cpp index e7093401..788540a3 100644 --- a/src/gprt/ray_tracer.cpp +++ b/src/gprt/ray_tracer.cpp @@ -1,7 +1,5 @@ #include "xdg/gprt/ray_tracer.h" #include "gprt/gprt.h" - -#include namespace xdg { GPRTRayTracer::GPRTRayTracer() @@ -10,11 +8,11 @@ GPRTRayTracer::GPRTRayTracer() context_ = gprtContextCreate(); module_ = gprtModuleCreate(context_, dbl_deviceCode); - rayHitBuffers_.capacity = 1; // Preallocate space for 1 ray - rayHitBuffers_.ray = gprtDeviceBufferCreate(context_, rayHitBuffers_.capacity); - rayHitBuffers_.hit = gprtDeviceBufferCreate(context_, rayHitBuffers_.capacity); - rayHitBuffers_.devRayAddr = gprtBufferGetDevicePointer(rayHitBuffers_.ray); - rayHitBuffers_.devHitAddr = gprtBufferGetDevicePointer(rayHitBuffers_.hit); + rayHitBuffers_.view.capacity = 1; // Preallocate space for 1 ray + rayHitBuffers_.ray = gprtDeviceBufferCreate(context_, rayHitBuffers_.view.capacity); + rayHitBuffers_.hit = gprtDeviceBufferCreate(context_, rayHitBuffers_.view.capacity); + rayHitBuffers_.view.rayDevPtr = gprtBufferGetDevicePointer(rayHitBuffers_.ray); + rayHitBuffers_.view.hitDevPtr = gprtBufferGetDevicePointer(rayHitBuffers_.hit); excludePrimitivesBuffer_ = gprtDeviceBufferCreate(context_); // initialise buffer of size 1 @@ -448,16 +446,9 @@ void GPRTRayTracer::ray_fire(TreeID tree, pushConstants.volume_accel = gprtAccelGetDeviceAddress(volume); pushConstants.volume_tree = tree; - std::cout << "Starting ray fire benchmark with " << num_rays << " rays" << " using " - << "GPRT (FP64)" << ": \n" << std::endl; - auto start = std::chrono::high_resolution_clock::now(); - // Launch the ray generation shader with push constants and buffer bindings gprtRayGenLaunch1D(context_, rayGen, num_rays, pushConstants); gprtGraphicsSynchronize(context_); - auto end = std::chrono::high_resolution_clock::now(); - std::chrono::duration elapsed = end - start; - double rays_per_second = static_cast(num_rays) / elapsed.count(); // Retrieve the output from the ray output buffer gprtBufferMap(rayHitBuffers_.hit); @@ -476,13 +467,6 @@ void GPRTRayTracer::ray_fire(TreeID tree, } } gprtBufferUnmap(rayHitBuffers_.hit); // required to sync buffer back on GPU? Maybe this second unmap isn't actually needed since we dont need to resyncrhonize after retrieving the data from device - - std::cout << "----------------------------------------" << std::endl; - std::cout << "Completed " << num_rays << " rays in " << elapsed.count() << " seconds." << std::endl; - std::cout << "Ray tracing throughput: " << rays_per_second << " rays/second." << std::endl; - std::cout << "---------------------------------------- \n" << std::endl; - - return; } @@ -527,14 +511,18 @@ void GPRTRayTracer::create_global_surface_tree() void GPRTRayTracer::check_rayhit_buffer_capacity(const size_t N) { - if (N <= rayHitBuffers_.capacity) return; // current capacity is sufficient + if (N <= rayHitBuffers_.view.capacity) return; // current capacity is sufficient - // Resize buffers to accommodate N rays - size_t newCapacity = std::max(N, rayHitBuffers_.capacity * 2); // double the capacity or set to N, whichever is larger + // Resize buffers to accommodate N rays - double the capacity or set to N, whichever is larger + size_t newCapacity = std::max(uint(N), rayHitBuffers_.view.capacity * 2); gprtBufferResize(context_, rayHitBuffers_.ray, newCapacity, false); gprtBufferResize(context_, rayHitBuffers_.hit, newCapacity, false); - rayHitBuffers_.capacity = newCapacity; + rayHitBuffers_.view.capacity = newCapacity; + + // Get fresh device pointers after resize + rayHitBuffers_.view.rayDevPtr = gprtBufferGetDevicePointer(rayHitBuffers_.ray); + rayHitBuffers_.view.hitDevPtr = gprtBufferGetDevicePointer(rayHitBuffers_.hit); // Since we have resized the ray buffers, we need to update the geom_data->rayIn pointers in all geometries too for (auto const& [surf, geom] : surface_to_geometry_map_) { @@ -552,14 +540,10 @@ void GPRTRayTracer::check_rayhit_buffer_capacity(const size_t N) gprtBuildShaderBindingTable(context_, static_cast(GPRT_SBT_GEOM | GPRT_SBT_RAYGEN)); } -RayTracer::DeviceRayHitBuffers GPRTRayTracer::get_device_rayhit_buffers(const size_t N) +DeviceRayHitBuffers GPRTRayTracer::get_device_rayhit_buffers(const size_t N) { check_rayhit_buffer_capacity(N); - DeviceRayHitBuffers buffers; - buffers.rays = rayHitBuffers_.devRayAddr; - buffers.hits = rayHitBuffers_.devHitAddr; - buffers.capacity = rayHitBuffers_.capacity; - return buffers; + return rayHitBuffers_.view; } void GPRTRayTracer::pack_external_rays(void* origins_device_ptr, @@ -577,7 +561,7 @@ void GPRTRayTracer::pack_external_rays(void* origins_device_ptr, const int neededGroups = (params.num_rays + threadsPerGroup - 1) / threadsPerGroup; const int groups = std::min(neededGroups, WORKGROUP_LIMIT); - params.xdgRays = rayHitBuffers_.devRayAddr; // dblRay* + params.xdgRays = rayHitBuffers_.view.rayDevPtr; // dblRay* params.origins = static_cast(origins_device_ptr); params.directions = static_cast(directions_device_ptr); params.total_threads = groups * threadsPerGroup; @@ -587,7 +571,9 @@ void GPRTRayTracer::pack_external_rays(void* origins_device_ptr, { threadsPerGroup, 1, 1 }, params); gprtComputeSynchronize(context_); + } + } // namespace xdg From be5ea2302a100abda2a46b880f9572f020cff920 Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Thu, 27 Nov 2025 19:08:13 +0000 Subject: [PATCH 26/62] Refactored ray-benchmark miniapp to write into XDG's rayhit buffers directly via an "external" compute shader --- tools/ray_benchmark.cpp | 49 ++++++++++++++++++---------- tools/ray_benchmark_deviceCode.slang | 24 +++++++------- tools/ray_benchmark_shared.h | 9 ++--- 3 files changed, 47 insertions(+), 35 deletions(-) diff --git a/tools/ray_benchmark.cpp b/tools/ray_benchmark.cpp index 1dca781c..97939c0f 100644 --- a/tools/ray_benchmark.cpp +++ b/tools/ray_benchmark.cpp @@ -149,6 +149,10 @@ int main(int argc, char** argv) { std::vector hitDistances(N, -1.0); std::vector hitElements(N, ID_NONE); + auto start = std::chrono::high_resolution_clock::now(); + auto end = std::chrono::high_resolution_clock::now(); + std::chrono::duration elapsed = end - start; + if (rt_lib == RTLibrary::GPRT) { // Create GPRT context for compute shader that generates rays auto gprt_rti = std::dynamic_pointer_cast(rti); @@ -156,41 +160,52 @@ int main(int argc, char** argv) { GPRTModule module = gprtModuleCreate(context, ray_benchmark_deviceCode); auto genRandomRays = gprtComputeCreate(context, module, "generate_random_rays"); - /* - TODO - this exposes rayhitbuffers from the GPRT/vulkan context avaiable within XDG. But in this miniapp we define a new context - We might need to expose the same vulkan/GPRT context if we want to reference pointers to it? - auto rayHitBuffers = xdg->get_device_rayhit_buffers(N); - */ + // this exposes rayhit buffers from the GPRT/vulkan context avaiable within XDG + auto rayHitBuffers = gprt_rti->get_device_rayhit_buffers(N); - // Create device buffers for our origins and directions - GPRTBufferOf originsBuf = gprtDeviceBufferCreate(context, N); - GPRTBufferOf directionsBuf = gprtDeviceBufferCreate(context, N); - Rays rays = {}; - rays.origins = gprtBufferGetDevicePointer(originsBuf); - rays.directions = gprtBufferGetDevicePointer(directionsBuf); + constexpr int threadsPerGroup = 64; + const int neededGroups = (N + threadsPerGroup - 1) / threadsPerGroup; + const int groups = std::min(neededGroups, WORKGROUP_LIMIT); GenerateRandomRayParams randomRayParams = {}; - randomRayParams.rays = rays; + randomRayParams.rays = rayHitBuffers.rayDevPtr; // assign xdg ray buffer pointer for compute shader randomRayParams.numRays = N; randomRayParams.origin = {origin.x, origin.y, origin.z}; randomRayParams.seed = seed; + randomRayParams.total_threads = groups * threadsPerGroup; + + gprtComputeLaunch(genRandomRays, {groups, 1, 1}, {threadsPerGroup, 1, 1}, randomRayParams); + gprtComputeSynchronize(context); + + std::cout << "Volume ID: " << volume << " with: " << mm->num_volume_faces(volume) + << " faces" << std::endl; + + std::cout << "Starting ray fire benchmark with " << N << " rays" << " using " + << rt_str << ": \n" << std::endl; + start = std::chrono::high_resolution_clock::now(); + + xdg->ray_fire_packed(volume, N); // ray_fire against pre-packed rays on device - gprtComputeLaunch(genRandomRays, {1, 1, 1}, {64, 1, 1}, randomRayParams); + end = std::chrono::high_resolution_clock::now(); + elapsed = end - start; + double rays_per_second = static_cast(N) / elapsed.count(); - xdg->pack_external_rays(rays.origins, rays.directions, N); - xdg->ray_fire_packed(volume, N); + std::cout << "----------------------------------------" << std::endl; + std::cout << "Completed " << N << " rays in " << elapsed.count() << " seconds." << std::endl; + std::cout << "Ray tracing throughput: " << rays_per_second << " rays/second." << std::endl; + std::cout << "---------------------------------------- \n" << std::endl; } else { std::cout << "Volume ID: " << volume << " with: " << mm->num_volume_faces(volume) << " faces" << std::endl; std::cout << "Starting ray fire benchmark with " << N << " rays" << " using " << rt_str << ": \n" << std::endl; - auto start = std::chrono::high_resolution_clock::now(); + start = std::chrono::high_resolution_clock::now(); for (size_t i = 0; i < N; ++i) { auto result = xdg->ray_fire(volume, origin, directions[i]); } - auto end = std::chrono::high_resolution_clock::now(); + end = std::chrono::high_resolution_clock::now(); std::chrono::duration elapsed = end - start; double rays_per_second = static_cast(N) / elapsed.count(); diff --git a/tools/ray_benchmark_deviceCode.slang b/tools/ray_benchmark_deviceCode.slang index aa6795b8..29c6d7c7 100644 --- a/tools/ray_benchmark_deviceCode.slang +++ b/tools/ray_benchmark_deviceCode.slang @@ -7,18 +7,18 @@ ray buffers. The idea is that the downstream application generates rays (origins [shader("compute")] [numthreads(64, 1, 1)] void generate_random_rays(uint3 DispatchThreadID: SV_DispatchThreadID, uniform GenerateRandomRayParams params) { - uint rayID = DispatchThreadID.x; - uint nRays = params.numRays; - - if (rayID >= nRays) { - return; + uint globalThreadID = DispatchThreadID.x; + uint stride = params.total_threads; + uint nRays = params.numRays; + + for (uint idx = globalThreadID; idx < nRays; idx += stride) + { + uint state = params.seed ^ idx; + params.rays[idx].direction = random_unit_dir(state); + params.rays[idx].origin = params.origin; + params.rays[idx].exclude_primitives = nullptr; + params.rays[idx].exclude_count = 0; } - - uint state = params.seed ^ rayID; // same as cpu: state = seed ^ i - double3 dir = random_unit_dir(state); - - params.rays.directions[rayID] = dir; - params.rays.origins[rayID] = params.origin; } // Helpers @@ -27,7 +27,7 @@ void generate_random_rays(uint3 DispatchThreadID: SV_DispatchThreadID, uniform G double rand01(inout uint state) { state = state * 1664525u + 1013904223u; - return double(state) * (1.0 / 4294967296.0); + return double(state) * double(1.0 / 4294967296.0); } // return random unit dir diff --git a/tools/ray_benchmark_shared.h b/tools/ray_benchmark_shared.h index 714851e5..dabf5e04 100644 --- a/tools/ray_benchmark_shared.h +++ b/tools/ray_benchmark_shared.h @@ -1,14 +1,11 @@ #include "gprt.h" -// A simple struct representing buffer of ray data as generated by our "mock application" -struct Rays { - double3* origins; - double3* directions; -}; +#include "../include/xdg/gprt/ray.h" struct GenerateRandomRayParams { - Rays rays; // pointer to ray data buffer + xdg::dblRay* rays; // pointer to ray data buffer uint numRays; // number of rays to be generated double3 origin; // single origin provided for benchmark case uint seed; // seed for random direction generation + uint total_threads; }; \ No newline at end of file From 59c97c2d1c89763c91f1f702d32b776c18ae40e3 Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Fri, 28 Nov 2025 13:53:02 +0000 Subject: [PATCH 27/62] Refactored ray_benchmark miniapp to make use of new xdg::Timer objects --- tools/CMakeLists.txt | 1 - tools/ray_benchmark.cpp | 208 ++++++++++++++++++++++++---------------- 2 files changed, 127 insertions(+), 82 deletions(-) diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 6fde5577..a3fa0017 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -8,7 +8,6 @@ batch_point_in_volume overlap_check walk_elements tally_segments -ray-benchmark ) #=============================================================================== diff --git a/tools/ray_benchmark.cpp b/tools/ray_benchmark.cpp index 97939c0f..6e957eb6 100644 --- a/tools/ray_benchmark.cpp +++ b/tools/ray_benchmark.cpp @@ -4,8 +4,6 @@ #include #include #include -#include - #include "xdg/error.h" #include "xdg/mesh_manager_interface.h" @@ -13,6 +11,7 @@ #include "xdg/vec3da.h" #include "xdg/xdg.h" #include "xdg/ray_tracers.h" +#include "xdg/timer.h" #include "argparse/argparse.hpp" @@ -25,17 +24,17 @@ extern GPRTProgram ray_benchmark_deviceCode; inline double rand01(uint32_t &state) { - state = state * 1664525u + 1013904223u; - return double(state) * (1.0 / 4294967296.0); + state = state * 1664525u + 1013904223u; + return double(state) * (1.0 / 4294967296.0); } inline Direction random_unit_dir_lcg(uint32_t &state) { double x1, x2, s; do { - x1 = rand01(state) * 2.0 - 1.0; - x2 = rand01(state) * 2.0 - 1.0; - s = x1 * x1 + x2 * x2; + x1 = rand01(state) * 2.0 - 1.0; + x2 = rand01(state) * 2.0 - 1.0; + s = x1 * x1 + x2 * x2; } while (s <= 0.0 || s >= 1.0); double t = 2.0 * std::sqrt(1.0 - s); @@ -50,7 +49,6 @@ inline void generate_dirs(std::vector &out, uint32_t seed) } } - int main(int argc, char** argv) { argparse::ArgumentParser args("XDG Ray Tracing throughput benchmarking tool", "1.0", argparse::default_arguments::help); @@ -91,8 +89,8 @@ int main(int argc, char** argv) { .help("List all volumes in the file and exit"); args.add_description( - "This tool supports can be used to benchmark XDG ray tracing throughput on a given mesh against" - "a given volume \n." + "This tool supports can be used to benchmark XDG ray tracing throughput on a given mesh against" + "a given volume \n." "A single origin/seed point is provided and ray directions are randomly generated in 360 degrees from that position" ); @@ -102,118 +100,166 @@ int main(int argc, char** argv) { catch (const std::runtime_error& err) { std::cout << err.what() << std::endl; std::cout << args; - exit(0); + return 1; } std::string mesh_str = args.get("--mesh-library"); - std::string rt_str = args.get("--rt-library"); + std::string rt_str = args.get("--rt-library"); RTLibrary rt_lib; if (rt_str == "EMBREE") - rt_lib = RTLibrary::EMBREE; + rt_lib = RTLibrary::EMBREE; else if (rt_str == "GPRT") - rt_lib = RTLibrary::GPRT; + rt_lib = RTLibrary::GPRT; else - fatal_error("Invalid ray tracing library '{}' specified", rt_str); + fatal_error("Invalid ray tracing library '{}' specified", rt_str); MeshLibrary mesh_lib; - if (mesh_str == "MOAB") - mesh_lib = MeshLibrary::MOAB; - else if (mesh_str == "LIBMESH") { - mesh_lib = MeshLibrary::LIBMESH; - if (rt_lib == RTLibrary::GPRT) + if (mesh_str == "MOAB") { + mesh_lib = MeshLibrary::MOAB; + } else if (mesh_str == "LIBMESH") { + mesh_lib = MeshLibrary::LIBMESH; + if (rt_lib == RTLibrary::GPRT) fatal_error("LibMesh is not currently supported with GPRT"); + } else { + fatal_error("Invalid mesh library '{}' specified", mesh_str); } - else - fatal_error("Invalid mesh library '{}' specified", mesh_str); - // create xdg instance + // Full wall-clock timer (post-argparse) + Timer wall_timer; + wall_timer.start(); + + // Separate timers for setup, generation, and tracing + Timer setup_timer; + Timer gen_timer; + Timer trace_timer; + + // -------------------------- + // XDG setup timing + // -------------------------- + setup_timer.start(); + std::shared_ptr xdg = XDG::create(mesh_lib, rt_lib); const auto& mm = xdg->mesh_manager(); mm->load_file(args.get("filename")); mm->init(); - // Generate a set of random rays - std::size_t N = args.get("--num-rays"); - uint32_t seed = args.get("--seed"); - Position origin = args.get>("--origin"); - std::vector origins(N, origin); - std::vector directions(N); - generate_dirs(directions, seed); - MeshID volume = args.get("volume"); xdg->prepare_raytracer(); xdg->prepare_volume_for_raytracing(volume); auto rti = xdg->ray_tracing_interface(); - std::vector hitDistances(N, -1.0); - std::vector hitElements(N, ID_NONE); + setup_timer.stop(); - auto start = std::chrono::high_resolution_clock::now(); - auto end = std::chrono::high_resolution_clock::now(); - std::chrono::duration elapsed = end - start; + std::size_t N = args.get("--num-rays"); + uint32_t seed = args.get("--seed"); + Position origin = args.get>("--origin"); + + std::cout << "Volume ID: " << volume << " with: " + << mm->num_volume_faces(volume) << " faces" << std::endl; + + std::cout << "Starting ray fire benchmark with " << N << " rays" + << " using " << rt_str << ": \n" << std::endl; + + std::cout << "XDG initalisation Time = " << setup_timer.elapsed() << "s" << std::endl; + + // -------------------------- + // Backend-specific sections + // -------------------------- if (rt_lib == RTLibrary::GPRT) { - // Create GPRT context for compute shader that generates rays + // One-time GPRT compute setup (not timed as generation) auto gprt_rti = std::dynamic_pointer_cast(rti); + if (!gprt_rti) + fatal_error("Failed to cast RayTracer to GPRTRayTracer"); + GPRTContext context = gprt_rti->context(); - GPRTModule module = gprtModuleCreate(context, ray_benchmark_deviceCode); - auto genRandomRays = gprtComputeCreate(context, module, "generate_random_rays"); - - // this exposes rayhit buffers from the GPRT/vulkan context avaiable within XDG - auto rayHitBuffers = gprt_rti->get_device_rayhit_buffers(N); + GPRTModule module = gprtModuleCreate(context, ray_benchmark_deviceCode); + auto genRandomRays = gprtComputeCreate( + context, module, "generate_random_rays"); + // ---- Random ray generation on device ---- + gen_timer.start(); + + auto rayHitBuffers = gprt_rti->get_device_rayhit_buffers(N); constexpr int threadsPerGroup = 64; - const int neededGroups = (N + threadsPerGroup - 1) / threadsPerGroup; - const int groups = std::min(neededGroups, WORKGROUP_LIMIT); + const int neededGroups = (int)((N + threadsPerGroup - 1) / threadsPerGroup); + const int groups = std::min(neededGroups, WORKGROUP_LIMIT); GenerateRandomRayParams randomRayParams = {}; - randomRayParams.rays = rayHitBuffers.rayDevPtr; // assign xdg ray buffer pointer for compute shader - randomRayParams.numRays = N; - randomRayParams.origin = {origin.x, origin.y, origin.z}; - randomRayParams.seed = seed; - randomRayParams.total_threads = groups * threadsPerGroup; - - gprtComputeLaunch(genRandomRays, {groups, 1, 1}, {threadsPerGroup, 1, 1}, randomRayParams); + randomRayParams.rays = rayHitBuffers.rayDevPtr; // xdg::dblRay* on device + randomRayParams.numRays = (uint32_t)N; + randomRayParams.origin = { origin.x, origin.y, origin.z }; + randomRayParams.seed = seed; + randomRayParams.total_threads = (uint32_t)(groups * threadsPerGroup); + + gprtComputeLaunch(genRandomRays, + { (uint32_t)groups, 1, 1 }, + { (uint32_t)threadsPerGroup, 1, 1 }, + randomRayParams); gprtComputeSynchronize(context); - std::cout << "Volume ID: " << volume << " with: " << mm->num_volume_faces(volume) - << " faces" << std::endl; - - std::cout << "Starting ray fire benchmark with " << N << " rays" << " using " - << rt_str << ": \n" << std::endl; - start = std::chrono::high_resolution_clock::now(); + gen_timer.stop(); + std::cout << "Random ray generation (via external compute shader) Time = " + << gen_timer.elapsed() << "s" << std::endl; + // ---- Ray tracing on device ---- + trace_timer.start(); xdg->ray_fire_packed(volume, N); // ray_fire against pre-packed rays on device + trace_timer.stop(); - end = std::chrono::high_resolution_clock::now(); - elapsed = end - start; - double rays_per_second = static_cast(N) / elapsed.count(); - - std::cout << "----------------------------------------" << std::endl; - std::cout << "Completed " << N << " rays in " << elapsed.count() << " seconds." << std::endl; - std::cout << "Ray tracing throughput: " << rays_per_second << " rays/second." << std::endl; - std::cout << "---------------------------------------- \n" << std::endl; } else { - std::cout << "Volume ID: " << volume << " with: " << mm->num_volume_faces(volume) - << " faces" << std::endl; - - std::cout << "Starting ray fire benchmark with " << N << " rays" << " using " - << rt_str << ": \n" << std::endl; - start = std::chrono::high_resolution_clock::now(); - for (size_t i = 0; i < N; ++i) { + // EMBREE / CPU backend + + // ---- Random ray generation on host ---- + gen_timer.start(); + std::vector directions(N); + generate_dirs(directions, seed); + gen_timer.stop(); + std::cout << "Random ray generation Time = " + << gen_timer.elapsed() << "s" << std::endl; + + // ---- Ray tracing on host ---- + trace_timer.start(); + for (std::size_t i = 0; i < N; ++i) { auto result = xdg->ray_fire(volume, origin, directions[i]); + (void)result; // suppress unused warning } - end = std::chrono::high_resolution_clock::now(); - std::chrono::duration elapsed = end - start; - double rays_per_second = static_cast(N) / elapsed.count(); - - std::cout << "----------------------------------------" << std::endl; - std::cout << "Completed " << N << " rays in " << elapsed.count() << " seconds." << std::endl; - std::cout << "Ray tracing throughput: " << rays_per_second << " rays/second." << std::endl; - std::cout << "---------------------------------------- \n" << std::endl; + trace_timer.stop(); } + // -------------------------- + // Final reporting + // -------------------------- + double setup_time = setup_timer.elapsed(); + double gen_time = gen_timer.elapsed(); + double trace_time = trace_timer.elapsed(); + + double trace_only_rps = (trace_time > 0.0) + ? static_cast(N) / trace_time + : 0.0; + + double end_to_end_time = gen_time + trace_time; + double end_to_end_rps = (end_to_end_time > 0.0) + ? static_cast(N) / end_to_end_time + : 0.0; + + wall_timer.stop(); + double wall_time = wall_timer.elapsed(); + + std::cout << "Generation + tracing time = " << end_to_end_time + << "s" << std::endl; + std::cout << "End-to-end throughput = " << end_to_end_rps + << " rays/s" << std::endl; + std::cout << "Full wall-clock time = " << wall_time + << "s (post-argparse)" << std::endl; + + std::cout << "----------------------------------------" << std::endl; + std::cout << "Ray Tracing Time (trace-only) = " << trace_time + << "s for " << N << " rays" << std::endl; + std::cout << "Trace-only throughput = " << trace_only_rps + << " rays/s" << std::endl; + std::cout << "---------------------------------------- \n" << std::endl; return 0; -} \ No newline at end of file +} From 3c841519bf7fb6e64e4fb4a6cdae60934ccb0fab Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Wed, 3 Dec 2025 13:06:57 +0000 Subject: [PATCH 28/62] Switch constant DILATION_FACTOR to be inline const so it compiles with clang --- include/xdg/constants.h | 2 +- vendor/GPRT | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/xdg/constants.h b/include/xdg/constants.h index ffd69c14..5fcdcfa9 100644 --- a/include/xdg/constants.h +++ b/include/xdg/constants.h @@ -24,7 +24,7 @@ constexpr double INFTY {std::numeric_limits::max()}; constexpr double INFTYF {std::numeric_limits::max()}; #endif -constexpr double DILATION_FACTOR {std::pow(10, -std::numeric_limits::digits10)}; +inline const double DILATION_FACTOR {std::pow(10, -std::numeric_limits::digits10)}; // TODO : Consider this as an option for managing missed hits? constexpr double PLUCKER_ZERO_TOL {20 * std::numeric_limits::epsilon()}; diff --git a/vendor/GPRT b/vendor/GPRT index f1e95e41..d95e1cae 160000 --- a/vendor/GPRT +++ b/vendor/GPRT @@ -1 +1 @@ -Subproject commit f1e95e4188cde591547d6b4a33a70bf2afaeec59 +Subproject commit d95e1caecd79233844fafd85332fc2499ab8e041 From bbc9562f95ca592c77f75d0696d69fa12a57c384 Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Wed, 3 Dec 2025 16:01:01 +0000 Subject: [PATCH 29/62] Ensure embree path is making use of all CPU threads available --- tools/ray_benchmark.cpp | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/tools/ray_benchmark.cpp b/tools/ray_benchmark.cpp index 6e957eb6..6fcc3cd8 100644 --- a/tools/ray_benchmark.cpp +++ b/tools/ray_benchmark.cpp @@ -19,6 +19,8 @@ #include "gprt/gprt.h" #include "ray_benchmark_shared.h" +#include + using namespace xdg; extern GPRTProgram ray_benchmark_deviceCode; @@ -41,14 +43,6 @@ inline Direction random_unit_dir_lcg(uint32_t &state) return { x1 * t, x2 * t, 1.0 - 2.0 * s }; } -inline void generate_dirs(std::vector &out, uint32_t seed) -{ - for (uint32_t i = 0; i < out.size(); ++i) { - uint32_t state = seed ^ i; - out[i] = random_unit_dir_lcg(state); - } -} - int main(int argc, char** argv) { argparse::ArgumentParser args("XDG Ray Tracing throughput benchmarking tool", "1.0", argparse::default_arguments::help); @@ -158,6 +152,11 @@ int main(int argc, char** argv) { std::cout << "Volume ID: " << volume << " with: " << mm->num_volume_faces(volume) << " faces" << std::endl; + + if (rt_lib == RTLibrary::EMBREE) { + int num_threads = omp_get_max_threads(); + rt_str += " (" + std::to_string(num_threads) + " CPU threads)"; + } std::cout << "Starting ray fire benchmark with " << N << " rays" << " using " << rt_str << ": \n" << std::endl; @@ -215,16 +214,23 @@ int main(int argc, char** argv) { // ---- Random ray generation on host ---- gen_timer.start(); std::vector directions(N); - generate_dirs(directions, seed); + + #pragma omp parallel for schedule(static) + for (std::size_t i = 0; i < N; ++i) + { + uint32_t state = seed ^ i; + directions[i] = random_unit_dir_lcg(state); + } gen_timer.stop(); + std::cout << "Random ray generation Time = " << gen_timer.elapsed() << "s" << std::endl; // ---- Ray tracing on host ---- trace_timer.start(); + #pragma omp parallel for schedule(static) for (std::size_t i = 0; i < N; ++i) { auto result = xdg->ray_fire(volume, origin, directions[i]); - (void)result; // suppress unused warning } trace_timer.stop(); } From 2364b0405be1bdda380022597a3da50805d223ae Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Wed, 3 Dec 2025 16:41:54 +0000 Subject: [PATCH 30/62] Extended random ray generation to get random origins too --- tools/ray_benchmark.cpp | 47 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/tools/ray_benchmark.cpp b/tools/ray_benchmark.cpp index 6fcc3cd8..a97d4beb 100644 --- a/tools/ray_benchmark.cpp +++ b/tools/ray_benchmark.cpp @@ -73,7 +73,7 @@ int main(int argc, char** argv) { .help("Mesh library to use. One of (MOAB, LIBMESH)") .default_value("MOAB"); - args.add_argument("-r", "--rt-library") + args.add_argument("-rt", "--rt-library") .help("Ray tracing library to use. One of (EMBREE, GPRT)") .default_value("EMBREE"); @@ -82,6 +82,11 @@ int main(int argc, char** argv) { .implicit_value(true) .help("List all volumes in the file and exit"); + args.add_argument("-sr", "--source-radius") + .default_value(0.0) + .help("Radius of a scattered source blob around the origin (0.0 = point source)") + .scan<'g', double>(); + args.add_description( "This tool supports can be used to benchmark XDG ray tracing throughput on a given mesh against" "a given volume \n." @@ -148,6 +153,7 @@ int main(int argc, char** argv) { std::size_t N = args.get("--num-rays"); uint32_t seed = args.get("--seed"); Position origin = args.get>("--origin"); + double source_radius = args.get("--source-radius"); std::cout << "Volume ID: " << volume << " with: " << mm->num_volume_faces(volume) << " faces" << std::endl; @@ -214,12 +220,24 @@ int main(int argc, char** argv) { // ---- Random ray generation on host ---- gen_timer.start(); std::vector directions(N); + std::vector origins(N); #pragma omp parallel for schedule(static) - for (std::size_t i = 0; i < N; ++i) - { + for (std::size_t i = 0; i < N; ++i) { + // Always generate random directions uint32_t state = seed ^ i; directions[i] = random_unit_dir_lcg(state); + if (source_radius > 0.0) { + + // random origins (spherical source) + double r = source_radius * std::cbrt(rand01(state)); // uniform in ball + double dx = directions[i].x * r; + double dy = directions[i].y * r; + double dz = directions[i].z * r; + origins[i] = {origin.x + dx, origin.y + dy, origin.z + dz}; + } else { + origins[i] = origin; + } } gen_timer.stop(); @@ -230,7 +248,7 @@ int main(int argc, char** argv) { trace_timer.start(); #pragma omp parallel for schedule(static) for (std::size_t i = 0; i < N; ++i) { - auto result = xdg->ray_fire(volume, origin, directions[i]); + auto result = xdg->ray_fire(volume, origins[i], directions[i]); } trace_timer.stop(); } @@ -269,3 +287,24 @@ int main(int argc, char** argv) { std::cout << "---------------------------------------- \n" << std::endl; return 0; } + +// Generates a random point cloud with radius (--source-radius) +// std::pair point_source(std::size_t N, std::uint32_t seed) +// { +// // Always generate random directions +// uint32_t state = seed ^ i; +// directions[i] = random_unit_dir_lcg(state); +// if (source_radius > 0.0) { + +// // random origins (spherical source) +// double r = source_radius * std::cbrt(rand01(state)); // uniform in ball +// double dx = directions[i].x * r; +// double dy = directions[i].y * r; +// double dz = directions[i].z * r; +// origins[i] = {origin.x + dx, origin.y + dy, origin.z + dz}; +// } else { +// origins[i] = origin; +// } +// +// return {origin, direction}; +// } \ No newline at end of file From 498a10d51649455535858dedf28d008748b8fb12 Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Wed, 3 Dec 2025 18:21:50 +0000 Subject: [PATCH 31/62] Added in source-radius for GPRT but fails to compile --- tools/ray_benchmark.cpp | 65 ++++++++++------------------ tools/ray_benchmark_deviceCode.slang | 36 ++++++++++----- tools/ray_benchmark_shared.h | 1 + 3 files changed, 49 insertions(+), 53 deletions(-) diff --git a/tools/ray_benchmark.cpp b/tools/ray_benchmark.cpp index a97d4beb..583315d1 100644 --- a/tools/ray_benchmark.cpp +++ b/tools/ray_benchmark.cpp @@ -43,6 +43,20 @@ inline Direction random_unit_dir_lcg(uint32_t &state) return { x1 * t, x2 * t, 1.0 - 2.0 * s }; } +// Generates a random point cloud with radius (--source-radius) +inline std::pair random_spherical_source(const Position& origin, std::uint32_t state, double source_radius) +{ + // Always generate random direction + Direction dir = random_unit_dir_lcg(state); + Position pos = origin; + if (source_radius > 0.0) { + // random origins (spherical source) + double r = source_radius * std::cbrt(rand01(state)); // uniform in ball + pos += dir * r; + } + return {pos, dir}; +} + int main(int argc, char** argv) { argparse::ArgumentParser args("XDG Ray Tracing throughput benchmarking tool", "1.0", argparse::default_arguments::help); @@ -145,7 +159,6 @@ int main(int argc, char** argv) { MeshID volume = args.get("volume"); xdg->prepare_raytracer(); - xdg->prepare_volume_for_raytracing(volume); auto rti = xdg->ray_tracing_interface(); setup_timer.stop(); @@ -193,10 +206,11 @@ int main(int argc, char** argv) { const int groups = std::min(neededGroups, WORKGROUP_LIMIT); GenerateRandomRayParams randomRayParams = {}; - randomRayParams.rays = rayHitBuffers.rayDevPtr; // xdg::dblRay* on device - randomRayParams.numRays = (uint32_t)N; - randomRayParams.origin = { origin.x, origin.y, origin.z }; - randomRayParams.seed = seed; + randomRayParams.rays = rayHitBuffers.rayDevPtr; // xdg::dblRay* on device + randomRayParams.numRays = (uint32_t)N; + randomRayParams.source_radius = source_radius; + randomRayParams.origin = { origin.x, origin.y, origin.z }; + randomRayParams.seed = seed; randomRayParams.total_threads = (uint32_t)(groups * threadsPerGroup); gprtComputeLaunch(genRandomRays, @@ -223,21 +237,11 @@ int main(int argc, char** argv) { std::vector origins(N); #pragma omp parallel for schedule(static) - for (std::size_t i = 0; i < N; ++i) { - // Always generate random directions + for (uint32_t i = 0; i < N; ++i) { uint32_t state = seed ^ i; - directions[i] = random_unit_dir_lcg(state); - if (source_radius > 0.0) { - - // random origins (spherical source) - double r = source_radius * std::cbrt(rand01(state)); // uniform in ball - double dx = directions[i].x * r; - double dy = directions[i].y * r; - double dz = directions[i].z * r; - origins[i] = {origin.x + dx, origin.y + dy, origin.z + dz}; - } else { - origins[i] = origin; - } + auto [pos,dir] = random_spherical_source(origin, state, source_radius); + origins[i] = pos; + directions[i] = dir; } gen_timer.stop(); @@ -286,25 +290,4 @@ int main(int argc, char** argv) { << " rays/s" << std::endl; std::cout << "---------------------------------------- \n" << std::endl; return 0; -} - -// Generates a random point cloud with radius (--source-radius) -// std::pair point_source(std::size_t N, std::uint32_t seed) -// { -// // Always generate random directions -// uint32_t state = seed ^ i; -// directions[i] = random_unit_dir_lcg(state); -// if (source_radius > 0.0) { - -// // random origins (spherical source) -// double r = source_radius * std::cbrt(rand01(state)); // uniform in ball -// double dx = directions[i].x * r; -// double dy = directions[i].y * r; -// double dz = directions[i].z * r; -// origins[i] = {origin.x + dx, origin.y + dy, origin.z + dz}; -// } else { -// origins[i] = origin; -// } -// -// return {origin, direction}; -// } \ No newline at end of file +} \ No newline at end of file diff --git a/tools/ray_benchmark_deviceCode.slang b/tools/ray_benchmark_deviceCode.slang index 29c6d7c7..bafbf21c 100644 --- a/tools/ray_benchmark_deviceCode.slang +++ b/tools/ray_benchmark_deviceCode.slang @@ -1,24 +1,36 @@ #include "ray_benchmark_shared.h" -/* +/* For this simple benchmark case we are mocking what a downstream application would do in terms of populating ray buffers. The idea is that the downstream application generates rays (origins + directions). */ [shader("compute")] [numthreads(64, 1, 1)] -void generate_random_rays(uint3 DispatchThreadID: SV_DispatchThreadID, uniform GenerateRandomRayParams params) { - uint globalThreadID = DispatchThreadID.x; - uint stride = params.total_threads; - uint nRays = params.numRays; +void generate_random_rays(uint3 DispatchThreadID: SV_DispatchThreadID, + uniform GenerateRandomRayParams params) +{ + uint globalThreadID = DispatchThreadID.x; + uint stride = params.total_threads; + uint nRays = params.numRays; - for (uint idx = globalThreadID; idx < nRays; idx += stride) + for (uint idx = globalThreadID; idx < nRays; idx += stride) { - uint state = params.seed ^ idx; - params.rays[idx].direction = random_unit_dir(state); - params.rays[idx].origin = params.origin; - params.rays[idx].exclude_primitives = nullptr; - params.rays[idx].exclude_count = 0; + uint state = params.seed ^ idx; + + double3 dir = random_unit_dir_lcg(state); + + double3 pos = params.origin; + if (params.source_radius > 0.0) { + double u = float(rand01(state)); + float r = float(params.source_radius) * pow(float(u), 1.0f / 3.0f); // cbrt(u) + pos += dir * double(r); } + + params.rays[idx].origin = pos; + params.rays[idx].direction = dir; + params.rays[idx].exclude_primitives = nullptr; + params.rays[idx].exclude_count = 0; + } } // Helpers @@ -31,7 +43,7 @@ double rand01(inout uint state) } // return random unit dir -double3 random_unit_dir(inout uint state) +double3 random_unit_dir_lcg(inout uint state) { double x1, x2, s; do { diff --git a/tools/ray_benchmark_shared.h b/tools/ray_benchmark_shared.h index dabf5e04..e9025486 100644 --- a/tools/ray_benchmark_shared.h +++ b/tools/ray_benchmark_shared.h @@ -8,4 +8,5 @@ struct GenerateRandomRayParams { double3 origin; // single origin provided for benchmark case uint seed; // seed for random direction generation uint total_threads; + double source_radius; // 0.0 = point volume, >0.0 = spherical cloud }; \ No newline at end of file From 67002b1e6eb517f7ec787d51fa334d1cf210cad4 Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Thu, 4 Dec 2025 10:43:38 +0000 Subject: [PATCH 32/62] No idea why but apparently I need the xdg->prepare_volume_for_raytracing(volume) call --- tools/ray_benchmark.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/ray_benchmark.cpp b/tools/ray_benchmark.cpp index 583315d1..2b6672ad 100644 --- a/tools/ray_benchmark.cpp +++ b/tools/ray_benchmark.cpp @@ -159,6 +159,7 @@ int main(int argc, char** argv) { MeshID volume = args.get("volume"); xdg->prepare_raytracer(); + xdg->prepare_volume_for_raytracing(volume); auto rti = xdg->ray_tracing_interface(); setup_timer.stop(); From 5cf01690dac12defcd7a4da4aec3da38e7fe0218 Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Fri, 5 Dec 2025 14:48:36 +0000 Subject: [PATCH 33/62] Moved some header definitions around to make it possible to compile with GPRT disabled --- include/xdg/gprt/ray.h | 12 ------------ include/xdg/gprt/ray_tracer.h | 4 +--- include/xdg/ray_tracing_interface.h | 16 +++++++++++++--- 3 files changed, 14 insertions(+), 18 deletions(-) diff --git a/include/xdg/gprt/ray.h b/include/xdg/gprt/ray.h index 0b345765..3825b3d7 100644 --- a/include/xdg/gprt/ray.h +++ b/include/xdg/gprt/ray.h @@ -23,18 +23,6 @@ struct dblHit }; // TODO - Move this to its own header -struct DeviceRayHitBuffers { - dblRay* rayDevPtr; // device pointer to ray buffers - dblHit* hitDevPtr; // device pointer to hit buffers - uint capacity = 0; - - // TODO - Renable once I figure out a way to make this slang safe - // bool valid() - // { - // return (rays != 0) && (hits != 0) && (capacity > 0); - // } -}; - } diff --git a/include/xdg/gprt/ray_tracer.h b/include/xdg/gprt/ray_tracer.h index fa4269c1..7d3b6d94 100644 --- a/include/xdg/gprt/ray_tracer.h +++ b/include/xdg/gprt/ray_tracer.h @@ -7,11 +7,9 @@ #include "xdg/constants.h" #include "xdg/mesh_manager_interface.h" -#include "xdg/primitive_ref.h" -#include "xdg/geometry_data.h" #include "xdg/ray_tracing_interface.h" -#include "xdg/ray.h" #include "xdg/error.h" + #include "gprt/gprt.h" #include "shared_structs.h" diff --git a/include/xdg/ray_tracing_interface.h b/include/xdg/ray_tracing_interface.h index 7f8e64fd..3bff6739 100644 --- a/include/xdg/ray_tracing_interface.h +++ b/include/xdg/ray_tracing_interface.h @@ -11,13 +11,23 @@ #include "xdg/mesh_manager_interface.h" #include "xdg/primitive_ref.h" #include "xdg/geometry_data.h" -#include "xdg/gprt/ray.h" - namespace xdg { -struct DeviceRayHitBuffers; // forward declaration +struct dblRay; // forward declaration +struct dblHit; // forward declaration +struct DeviceRayHitBuffers { + dblRay* rayDevPtr; // device pointer to ray buffers + dblHit* hitDevPtr; // device pointer to hit buffers + uint capacity = 0; + + // TODO - Renable once I figure out a way to make this slang safe + // bool valid() + // { + // return (rays != 0) && (hits != 0) && (capacity > 0); + // } +}; class RayTracer { public: From dc9b55ae79d60496e82827d17ec1d5825da1dc85 Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Fri, 5 Dec 2025 17:18:13 +0000 Subject: [PATCH 34/62] Added a python script to drive multiple ray-benchmarks --- tools/ray-benchmark-driver.py | 297 ++++++++++++++++++++++++++++++++++ 1 file changed, 297 insertions(+) create mode 100644 tools/ray-benchmark-driver.py diff --git a/tools/ray-benchmark-driver.py b/tools/ray-benchmark-driver.py new file mode 100644 index 00000000..1839418f --- /dev/null +++ b/tools/ray-benchmark-driver.py @@ -0,0 +1,297 @@ +#!/usr/bin/env python3 +import subprocess +import statistics +import sys +import csv +import os + +# --- CONFIG --- + +BENCHMARK = "./tools/ray-benchmark" +MESH_PATH = "../dagmc_xdg_test.h5m" +VOLUME_ID = "2" +NUM_RAYS = "80000000" +ORIGIN = ["-o", "180", "250", "-27"] # x y z as strings + +# --- PARSING HELPERS --- + +def parse_float_before_s(s: str) -> float: + """ + Given a string like 'XDG initalisation Time = 1.25017s', + pull out 1.25017 as float. + """ + try: + after_eq = s.split('=', 1)[1] + number_str = after_eq.split('s', 1)[0].strip() + return float(number_str) + except Exception as e: + raise ValueError(f"Failed to parse float from line: {s!r}") from e + +def parse_throughput_line(s: str) -> float: + """ + Given a string like 'Trace-only throughput = 2.64065e+09 rays/s', + pull out 2.64065e+09 as float. + """ + try: + after_eq = s.split('=', 1)[1] + number_str = after_eq.split('rays', 1)[0].strip() + return float(number_str) + except Exception as e: + raise ValueError(f"Failed to parse throughput from line: {s!r}") from e + +def parse_benchmark_output(output: str): + """ + Parse the benchmark stdout text and return a dict of metrics. + Expected keys: + - xdg_init + - gen + - gen_trace + - end_to_end + - wall_clock + - trace_only + - trace_only_throughput + """ + metrics = {} + + for line in output.splitlines(): + line = line.strip() + + if line.startswith("XDG initalisation Time"): + metrics["xdg_init"] = parse_float_before_s(line) + + elif line.startswith("Random ray generation"): + metrics["gen"] = parse_float_before_s(line) + + elif line.startswith("Generation + tracing time"): + metrics["gen_trace"] = parse_float_before_s(line) + + elif line.startswith("End-to-end throughput"): + metrics["end_to_end"] = parse_throughput_line(line) + + elif line.startswith("Full wall-clock time"): + metrics["wall_clock"] = parse_float_before_s(line) + + elif line.startswith("Ray Tracing Time (trace-only)"): + metrics["trace_only"] = parse_float_before_s(line) + + elif line.startswith("Trace-only throughput"): + metrics["trace_only_throughput"] = parse_throughput_line(line) + + required = [ + "xdg_init", "gen", "gen_trace", "end_to_end", + "wall_clock", "trace_only", "trace_only_throughput" + ] + missing = [k for k in required if k not in metrics] + if missing: + raise RuntimeError(f"Missing metrics in output: {missing}") + + return metrics + +# --- MAIN DRIVER --- + +def main(): + # Ask for backend + backend_in = input("Choose backend (embree/gprt): ").strip().lower() + if backend_in not in ("embree", "gprt"): + print("Invalid backend, please choose 'embree' or 'gprt'.") + sys.exit(1) + + base_backend = backend_in.upper() # what we pass to -r: EMBREE or GPRT + + # If GPRT, ask for which variant + if backend_in == "gprt": + mode_in = input( + "GPRT mode: [1] GPRT (FP64), [2] GPRT (FP32) + RT cores [1]: " + ).strip() + if mode_in == "2": + variant = "fp32_rt" + label = "GPRT (FP32) + RT cores" + else: + variant = "fp64" + label = "GPRT (FP64)" + else: + # Embree is effectively FP64 for your purposes + variant = "fp64" + label = "Embree" + + runs_str = input("How many runs? ").strip() + try: + num_runs = int(runs_str) + if num_runs <= 0: + raise ValueError + except ValueError: + print("Number of runs must be a positive integer.") + sys.exit(1) + + # Ask for CSV filename + csv_filename = input("CSV output file [benchmarks.csv]: ").strip() + if not csv_filename: + csv_filename = "benchmarks.csv" + + mesh_name = os.path.basename(MESH_PATH) + + all_metrics = { + "xdg_init": [], + "gen": [], + "gen_trace": [], + "end_to_end": [], + "wall_clock": [], + "trace_only": [], + "trace_only_throughput": [], + } + + # CSV header: machine-friendly backend/variant, plus pretty label + header = [ + "backend", # EMBREE / GPRT + "variant", # fp64 / fp32_rt + "label", # Embree / GPRT (FP64) / GPRT (FP32) + RT cores + "mesh_name", + "volume_id", + "num_rays", + "run_index", + "xdg_init", + "gen", + "gen_trace", + "end_to_end", + "wall_clock", + "trace_only", + "trace_only_throughput", + ] + + # Decide whether to append or overwrite + file_exists = os.path.exists(csv_filename) + write_header = False + file_mode = "w" + append_mode = False + + if file_exists: + choice = input( + f"File '{csv_filename}' already exists. " + "[o]verwrite, [a]ppend, or e[x]it? [a]: " + ).strip().lower() + + if choice in ("x", "q"): + print("Aborting, no benchmarks run.") + sys.exit(0) + elif choice in ("", "a"): + file_mode = "a" + write_header = False # assume header already there + append_mode = True + elif choice == "o": + file_mode = "w" + write_header = True + append_mode = False + else: + print("Unrecognized choice, aborting.") + sys.exit(1) + else: + # new file: write header + file_mode = "w" + write_header = True + append_mode = False + + csv_file = open(csv_filename, file_mode, newline="") + + # If appending, add a separation comment line so it's obvious this is a new batch + if append_mode: + csv_file.write( + f"\n# --- New benchmark batch: " + f"label={label}, backend={base_backend}, variant={variant}, " + f"mesh={mesh_name}, volume={VOLUME_ID}, " + f"rays={NUM_RAYS}, runs={num_runs} ---\n" + ) + + writer = csv.writer(csv_file) + + if write_header: + writer.writerow(header) + + try: + for i in range(1, num_runs + 1): + print(f"\n=== Run {i}/{num_runs} ({label}) ===") + + cmd = [ + BENCHMARK, + MESH_PATH, + VOLUME_ID, + "-r", base_backend, # EMBREE or GPRT + "-n", NUM_RAYS, + *ORIGIN, + ] + + print("Running:", " ".join(cmd)) + + try: + result = subprocess.run( + cmd, + check=True, + text=True, + capture_output=True, + ) + except subprocess.CalledProcessError as e: + print("Benchmark command failed!") + print("STDOUT:\n", e.stdout) + print("STDERR:\n", e.stderr) + sys.exit(1) + + try: + metrics = parse_benchmark_output(result.stdout) + except Exception as e: + print("Failed to parse benchmark output:", e) + print("Raw output:\n", result.stdout) + sys.exit(1) + + # store for averages + for k in all_metrics.keys(): + all_metrics[k].append(metrics[k]) + + # write CSV row + writer.writerow([ + base_backend, # backend + variant, # variant + label, # label + mesh_name, + VOLUME_ID, + NUM_RAYS, + i, # run_index + metrics["xdg_init"], + metrics["gen"], + metrics["gen_trace"], + metrics["end_to_end"], + metrics["wall_clock"], + metrics["trace_only"], + metrics["trace_only_throughput"], + ]) + + # per-run summary + print(f"XDG init : {metrics['xdg_init']:.6f} s") + print(f"Generation : {metrics['gen']:.6f} s") + print(f"Gen + trace : {metrics['gen_trace']:.6f} s") + print(f"End-to-end : {metrics['end_to_end']:.3e} rays/s") + print(f"Wall-clock : {metrics['wall_clock']:.6f} s") + print(f"Trace-only : {metrics['trace_only']:.6f} s") + print(f"Trace-only thrpt : {metrics['trace_only_throughput']:.3e} rays/s") + + finally: + csv_file.close() + + # Averages + print( + "\n=== Averages over", + num_runs, + f"runs (label: {label}) ===" + ) + + def avg(key): return statistics.mean(all_metrics[key]) + + print(f"Avg XDG init : {avg('xdg_init'):.6f} s") + print(f"Avg Generation : {avg('gen'):.6f} s") + print(f"Avg Gen + trace : {avg('gen_trace'):.6f} s") + print(f"Avg End-to-end : {avg('end_to_end'):.3e} rays/s") + print(f"Avg Wall-clock : {avg('wall_clock'):.6f} s") + print(f"Avg Trace-only : {avg('trace_only'):.6f} s") + print(f"Avg Trace-only thrpt : {avg('trace_only_throughput'):.3e} rays/s") + print(f"\nResults written to: {csv_filename}") + +if __name__ == "__main__": + main() From cb386890377a97f1fd8138414dd2ce0460418765 Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Tue, 13 Jan 2026 12:14:07 +0000 Subject: [PATCH 35/62] Updated GPRT render tool to make use of upstream API changes for GUI sampler --- tests/test_files | 2 +- vendor/GPRT | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_files b/tests/test_files index a3caf0af..ca579198 160000 --- a/tests/test_files +++ b/tests/test_files @@ -1 +1 @@ -Subproject commit a3caf0af3f128944c4d6eac93b481df6e4efd97c +Subproject commit ca57919851224047ef86fab177a0bfe9fa920127 diff --git a/vendor/GPRT b/vendor/GPRT index d95e1cae..405d9ee9 160000 --- a/vendor/GPRT +++ b/vendor/GPRT @@ -1 +1 @@ -Subproject commit d95e1caecd79233844fafd85332fc2499ab8e041 +Subproject commit 405d9ee9f5ee8e1a0455f776f9e2c3adffb64160 From 6494af3e8fcd9ba72b8f2fe27c56ffd990fc8578 Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Tue, 13 Jan 2026 12:33:01 +0000 Subject: [PATCH 36/62] Updated batch query tests to make use of TEMPLATE_TEST_CASE pattern --- tests/test_point_in_volume.cpp | 8 ++++++-- tests/test_ray_fire.cpp | 7 +++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/tests/test_point_in_volume.cpp b/tests/test_point_in_volume.cpp index 2ee445f5..a09d6a68 100644 --- a/tests/test_point_in_volume.cpp +++ b/tests/test_point_in_volume.cpp @@ -10,6 +10,7 @@ #include "mesh_mock.h" using namespace xdg; +using namespace xdg::test; static void make_points(size_t N, std::vector& points, @@ -93,8 +94,11 @@ TEMPLATE_TEST_CASE("Point-in-volume on MeshMock", "[piv][mock]", } } -TEST_CASE("Batch API Point-in-volume on MeshMock", "[piv][mock][batch]") { - auto rt_backend = GENERATE(RTLibrary::EMBREE, RTLibrary::GPRT); +TEMPLATE_TEST_CASE("Batch API Point-in-volume on MeshMock", "[piv][mock][batch]", + Embree_Raytracer, + GPRT_Raytracer) +{ + constexpr auto rt_backend = TestType::value; DYNAMIC_SECTION(fmt::format("Backend = {}", rt_backend)) { check_ray_tracer_supported(rt_backend); diff --git a/tests/test_ray_fire.cpp b/tests/test_ray_fire.cpp index 08814084..0b7e0522 100644 --- a/tests/test_ray_fire.cpp +++ b/tests/test_ray_fire.cpp @@ -112,8 +112,11 @@ TEMPLATE_TEST_CASE("Ray Fire on MeshMock (per-backend sections)", "[rayfire][moc } } -TEST_CASE("Batch API Ray Fire on MeshMock", "[rayfire][mock][batch]") { - auto rt_backend = GENERATE(RTLibrary::EMBREE, RTLibrary::GPRT); +TEMPLATE_TEST_CASE("Batch API Ray Fire on MeshMock", "[rayfire][mock][batch]", + Embree_Raytracer, + GPRT_Raytracer) +{ + constexpr auto rt_backend = TestType::value; DYNAMIC_SECTION(fmt::format("Backend = {}", rt_backend)) { check_ray_tracer_supported(rt_backend); From 69742fa99ef1debecb8d3a74d3cf6aa92e12679f Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Wed, 14 Jan 2026 15:27:00 +0000 Subject: [PATCH 37/62] Set default RayHit buffer size to be 1m rays --- src/gprt/ray_tracer.cpp | 2 +- tools/batch_ray_fire.cpp | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/gprt/ray_tracer.cpp b/src/gprt/ray_tracer.cpp index 788540a3..783d39fe 100644 --- a/src/gprt/ray_tracer.cpp +++ b/src/gprt/ray_tracer.cpp @@ -8,7 +8,7 @@ GPRTRayTracer::GPRTRayTracer() context_ = gprtContextCreate(); module_ = gprtModuleCreate(context_, dbl_deviceCode); - rayHitBuffers_.view.capacity = 1; // Preallocate space for 1 ray + rayHitBuffers_.view.capacity = 1e6; // Preallocate space for 1m rays rayHitBuffers_.ray = gprtDeviceBufferCreate(context_, rayHitBuffers_.view.capacity); rayHitBuffers_.hit = gprtDeviceBufferCreate(context_, rayHitBuffers_.view.capacity); rayHitBuffers_.view.rayDevPtr = gprtBufferGetDevicePointer(rayHitBuffers_.ray); diff --git a/tools/batch_ray_fire.cpp b/tools/batch_ray_fire.cpp index 27c37dfc..a208ed74 100644 --- a/tools/batch_ray_fire.cpp +++ b/tools/batch_ray_fire.cpp @@ -6,7 +6,6 @@ #include "xdg/error.h" #include "xdg/mesh_manager_interface.h" -#include "xdg/moab/mesh_manager.h" #include "xdg/vec3da.h" #include "xdg/xdg.h" From 553e0b9317e440096ceadc45f3d7eb836f7188b9 Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Mon, 19 Jan 2026 15:49:42 +0000 Subject: [PATCH 38/62] Attempting to implement a callback based method for populating internal XDG buffers --- include/xdg/constants.h | 2 +- include/xdg/gprt/ray.h | 5 +-- include/xdg/gprt/ray_tracer.h | 9 ++++ include/xdg/ray_tracing_interface.h | 42 +++++++++++++++--- include/xdg/xdg.h | 6 +++ src/gprt/dbl_deviceCode.slang | 27 +++++++----- src/gprt/ray_tracer.cpp | 16 +++++++ tools/ray_benchmark.cpp | 67 ++++++++++++++++------------- 8 files changed, 123 insertions(+), 51 deletions(-) diff --git a/include/xdg/constants.h b/include/xdg/constants.h index 5fcdcfa9..ffd69c14 100644 --- a/include/xdg/constants.h +++ b/include/xdg/constants.h @@ -24,7 +24,7 @@ constexpr double INFTY {std::numeric_limits::max()}; constexpr double INFTYF {std::numeric_limits::max()}; #endif -inline const double DILATION_FACTOR {std::pow(10, -std::numeric_limits::digits10)}; +constexpr double DILATION_FACTOR {std::pow(10, -std::numeric_limits::digits10)}; // TODO : Consider this as an option for managing missed hits? constexpr double PLUCKER_ZERO_TOL {20 * std::numeric_limits::epsilon()}; diff --git a/include/xdg/gprt/ray.h b/include/xdg/gprt/ray.h index 3825b3d7..5803a012 100644 --- a/include/xdg/gprt/ray.h +++ b/include/xdg/gprt/ray.h @@ -10,8 +10,9 @@ struct dblRay { double3 origin; double3 direction; + uint enabled; // Flag to indicate if the ray is active int32_t* exclude_primitives; // Optional for excluding primitives - int32_t exclude_count; // Number of excluded primitives + int32_t exclude_count; // Number of excluded primitives }; struct dblHit @@ -22,8 +23,6 @@ struct dblHit PointInVolume piv; // Point in volume check result (0 for outside, 1 for inside) }; -// TODO - Move this to its own header - } diff --git a/include/xdg/gprt/ray_tracer.h b/include/xdg/gprt/ray_tracer.h index 7d3b6d94..ff019d67 100644 --- a/include/xdg/gprt/ray_tracer.h +++ b/include/xdg/gprt/ray_tracer.h @@ -129,6 +129,15 @@ class GPRTRayTracer : public RayTracer { void* directions_device_ptr, size_t num_rays) override; + /** + * @brief Allocate device buffers and invoke a callback to populate them + * + * This method enables downstream applications to populate ray buffers using + * any compute API (GPRT, CUDA, HIP, etc.) without XDG needing to know the details. + */ + void populate_rays_external(size_t numRays, + const RayPopulationCallback& callback) override; + GPRTContext context() { return context_; diff --git a/include/xdg/ray_tracing_interface.h b/include/xdg/ray_tracing_interface.h index 3bff6739..7069ab0b 100644 --- a/include/xdg/ray_tracing_interface.h +++ b/include/xdg/ray_tracing_interface.h @@ -4,6 +4,7 @@ #include #include #include +#include #include "xdg/error.h" #include "xdg/constants.h" @@ -21,14 +22,19 @@ struct DeviceRayHitBuffers { dblRay* rayDevPtr; // device pointer to ray buffers dblHit* hitDevPtr; // device pointer to hit buffers uint capacity = 0; - - // TODO - Renable once I figure out a way to make this slang safe - // bool valid() - // { - // return (rays != 0) && (hits != 0) && (capacity > 0); - // } }; +/** + * @brief Callback signature for external ray population + * + * Allows downstream applications to populate ray buffers using their own compute backend + * (GPRT, CUDA, HIP, OpenCL, etc.) without XDG needing to know which API is used. + * + * @param buffer Device ray buffer to be populated + * @param numRays Number of rays to generate/populate + */ +using RayPopulationCallback = std::function; + class RayTracer { public: // Constructors/Destructors @@ -271,6 +277,30 @@ class RayTracer { return; } + /** + * @brief Allocate device ray buffers and populate them via a user-provided callback + * + * This method allows downstream applications to populate ray buffers using any compute + * backend (GPRT, CUDA, HIP, OpenCL, etc.) without coupling them to XDG's internals. + * + * The workflow: + * 1. XDG allocates device memory for rays (if not already large enough) + * 2. XDG passes device pointers to the callback + * 3. User's callback populates the buffers using their preferred compute API + * 4. User's callback returns (XDG assumes buffers are now populated) + * 5. XDG proceeds with ray tracing against the populated data + * + * This enables true zero-copy scenarios where downstream GPU code writes directly + * to XDG's device buffers without any host-side transfers. + * + * @param numRays Number of rays to allocate space for + * @param callback Function that will populate the ray buffer. Receives the allocated buffer and ray count. + */ + virtual void populate_rays_external(size_t numRays, + const RayPopulationCallback& callback) { + fatal_error("GPU ray tracing not supported with this RayTracer backend"); + } + protected: // Common functions across RayTracers const double bounding_box_bump(const std::shared_ptr mesh_manager, MeshID volume_id); // return a bump value based on the size of a bounding box (minimum 1e-3). Should this be a part of mesh_manager? diff --git a/include/xdg/xdg.h b/include/xdg/xdg.h index 66684cb5..c322158c 100644 --- a/include/xdg/xdg.h +++ b/include/xdg/xdg.h @@ -199,6 +199,12 @@ Direction surface_normal(MeshID surface, void* directions_device_ptr, size_t num_rays); + void populate_rays_external(size_t numRays, + const RayPopulationCallback& callback) + { + return ray_tracing_interface()->populate_rays_external(numRays, callback); + } + // Accessors const std::shared_ptr& ray_tracing_interface() const { return ray_tracing_interface_; diff --git a/src/gprt/dbl_deviceCode.slang b/src/gprt/dbl_deviceCode.slang index 29595a8f..544a8fdf 100644 --- a/src/gprt/dbl_deviceCode.slang +++ b/src/gprt/dbl_deviceCode.slang @@ -53,11 +53,12 @@ void ray_fire_miss(inout RayFirePayload payload) { void ray_fire(uniform dblRayGenData record, uniform DPTriangleGeomData mesh) { RayFirePayload payload; uint rayID = DispatchRaysIndex().x; + xdg::dblRay ray = record.ray[rayID]; // recover ray from buffer // Trace the ray into the scene RayDesc rayDesc; - rayDesc.Origin = float3(record.ray[rayID].origin); - rayDesc.Direction = normalize(float3(record.ray[rayID].direction)); + rayDesc.Origin = float3(ray.origin); + rayDesc.Direction = normalize(float3(ray.direction)); rayDesc.TMin = float(PC.tMin); rayDesc.TMax = float(PC.tMax); @@ -68,7 +69,9 @@ void ray_fire(uniform dblRayGenData record, uniform DPTriangleGeomData mesh) { payload.surf_id = -1; payload.tlas = world; - TraceRay(world, RAY_FLAG_NONE, 0xff, 0, 1, rayDesc, payload); + if (ray.enabled == 1u) { // skip RT pipeline for rays that are marked as disabled + TraceRay(world, RAY_FLAG_NONE, 0xff, 0, 1, rayDesc, payload); + } // Store the distance to the hit point and the surface ID in buffers for CPU record.hit[rayID].distance = payload.distance; @@ -80,11 +83,12 @@ void ray_fire(uniform dblRayGenData record, uniform DPTriangleGeomData mesh) { void point_in_volume(uniform dblRayGenData record, uniform DPTriangleGeomData mesh) { RayFirePayload payload; uint rayID = DispatchRaysIndex().x; + xdg::dblRay ray = record.ray[rayID]; // recover ray from buffer // Trace the ray into the scene RayDesc rayDesc; - rayDesc.Origin = float3(record.ray[rayID].origin); - rayDesc.Direction = float3(normalize(record.ray[rayID].direction)); + rayDesc.Origin = float3(ray.origin); + rayDesc.Direction = float3(normalize(ray.direction)); rayDesc.TMin = float(PC.tMin); rayDesc.TMax = float(PC.tMax); @@ -95,7 +99,9 @@ void point_in_volume(uniform dblRayGenData record, uniform DPTriangleGeomData me payload.tlas = world; payload.piv = xdg::PointInVolume::OUTSIDE; // Initialize point in volume check result to outside (0) - TraceRay(world, RAY_FLAG_NONE, 0xff, 0, 1, rayDesc, payload); + if (ray.enabled == 1u) { // skip RT pipeline for rays that are marked as disabled + TraceRay(world, RAY_FLAG_NONE, 0xff, 0, 1, rayDesc, payload); + } record.hit[rayID].surf_id = payload.surf_id; record.hit[rayID].piv = payload.piv; // Point in volume check result @@ -162,6 +168,7 @@ void DPTrianglePluckerIntersection(uniform DPTriangleGeomData record) uint rayID = DispatchRaysIndex().x; uint nRays = DispatchRaysDimensions().x; uint flags = RayFlags(); + xdg::dblRay ray = record.ray[rayID]; // recover ray from buffer if (rayID >= nRays) { return; @@ -183,8 +190,8 @@ void DPTrianglePluckerIntersection(uniform DPTriangleGeomData record) double3 v1 = record.vertex[indices[1]]; double3 v2 = record.vertex[indices[2]]; - double3 origin = record.ray[rayID].origin; - double3 direction = record.ray[rayID].direction; + double3 origin = ray.origin; + double3 direction = ray.direction; double tMin = PC.tMin; double tMax = PC.tMax; @@ -273,9 +280,9 @@ void DPTrianglePluckerIntersection(uniform DPTriangleGeomData record) return; } - for (int i = 0; i < record.ray[rayID].exclude_count; ++i) + for (int i = 0; i < ray.exclude_count; ++i) { - if (record.ray[rayID].exclude_primitives[i] == global_prim_id) { + if (ray.exclude_primitives[i] == global_prim_id) { return; } } diff --git a/src/gprt/ray_tracer.cpp b/src/gprt/ray_tracer.cpp index 783d39fe..06807a85 100644 --- a/src/gprt/ray_tracer.cpp +++ b/src/gprt/ray_tracer.cpp @@ -574,6 +574,22 @@ void GPRTRayTracer::pack_external_rays(void* origins_device_ptr, } +void GPRTRayTracer::populate_rays_external(size_t numRays, + const RayPopulationCallback& callback) +{ + if (numRays == 0) return; + + // Ensure device buffers are large enough + check_rayhit_buffer_capacity(numRays); + + // Pass control to the user's callback with device pointers + // The callback will use whatever compute API it prefers to populate the buffers + callback(rayHitBuffers_.view, numRays); + + // After callback returns, we assume the ray buffer is populated and ready to trace + // Note: The callback is responsible for synchronization if using an async API +} } // namespace xdg + diff --git a/tools/ray_benchmark.cpp b/tools/ray_benchmark.cpp index 2b6672ad..2ee70ec8 100644 --- a/tools/ray_benchmark.cpp +++ b/tools/ray_benchmark.cpp @@ -15,7 +15,7 @@ #include "argparse/argparse.hpp" -// GPRT includes +// GPRT includes - only for use in ray generation callback #include "gprt/gprt.h" #include "ray_benchmark_shared.h" @@ -187,38 +187,43 @@ int main(int argc, char** argv) { // -------------------------- if (rt_lib == RTLibrary::GPRT) { - // One-time GPRT compute setup (not timed as generation) - auto gprt_rti = std::dynamic_pointer_cast(rti); - if (!gprt_rti) - fatal_error("Failed to cast RayTracer to GPRTRayTracer"); - - GPRTContext context = gprt_rti->context(); - GPRTModule module = gprtModuleCreate(context, ray_benchmark_deviceCode); - auto genRandomRays = gprtComputeCreate( - context, module, "generate_random_rays"); - - // ---- Random ray generation on device ---- + // ---- Random ray generation on device via callback ---- gen_timer.start(); - auto rayHitBuffers = gprt_rti->get_device_rayhit_buffers(N); - - constexpr int threadsPerGroup = 64; - const int neededGroups = (int)((N + threadsPerGroup - 1) / threadsPerGroup); - const int groups = std::min(neededGroups, WORKGROUP_LIMIT); - - GenerateRandomRayParams randomRayParams = {}; - randomRayParams.rays = rayHitBuffers.rayDevPtr; // xdg::dblRay* on device - randomRayParams.numRays = (uint32_t)N; - randomRayParams.source_radius = source_radius; - randomRayParams.origin = { origin.x, origin.y, origin.z }; - randomRayParams.seed = seed; - randomRayParams.total_threads = (uint32_t)(groups * threadsPerGroup); - - gprtComputeLaunch(genRandomRays, - { (uint32_t)groups, 1, 1 }, - { (uint32_t)threadsPerGroup, 1, 1 }, - randomRayParams); - gprtComputeSynchronize(context); + // Define the ray generation callback that uses GPRT + // This callback runs inside populate_rays_external and receives XDG's device buffers + auto generateRaysCallback = [&](const DeviceRayHitBuffers& buffer, size_t numRays) { + // User creates their own GPRT context and kernel here + // This is completely decoupled from XDG's GPRT context + GPRTContext context = gprtContextCreate(); + GPRTModule module = gprtModuleCreate(context, ray_benchmark_deviceCode); + auto genRandomRays = gprtComputeCreate( + context, module, "generate_random_rays"); + + constexpr int threadsPerGroup = 64; + const int neededGroups = (int)((numRays + threadsPerGroup - 1) / threadsPerGroup); + const int groups = std::min(neededGroups, WORKGROUP_LIMIT); + + GenerateRandomRayParams randomRayParams = {}; + randomRayParams.rays = buffer.rayDevPtr; // XDG's device ray buffer + randomRayParams.numRays = (uint32_t)numRays; + randomRayParams.source_radius = source_radius; + randomRayParams.origin = { origin.x, origin.y, origin.z }; + randomRayParams.seed = seed; + randomRayParams.total_threads = (uint32_t)(groups * threadsPerGroup); + + gprtComputeLaunch(genRandomRays, + { (uint32_t)groups, 1, 1 }, + { (uint32_t)threadsPerGroup, 1, 1 }, + randomRayParams); + gprtComputeSynchronize(context); + + // Cleanup the user's context (not XDG's context) + gprtContextDestroy(context); + }; + + // Let XDG allocate buffers and invoke the callback to populate them + xdg->populate_rays_external(N, generateRaysCallback); gen_timer.stop(); std::cout << "Random ray generation (via external compute shader) Time = " From f0f7389547c3cbf78218115e44a19de7356a14b8 Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Mon, 19 Jan 2026 15:56:58 +0000 Subject: [PATCH 39/62] Removed the now redundant pack_external_rays() code path --- include/xdg/gprt/ray_tracer.h | 5 ----- include/xdg/ray_tracing_interface.h | 7 ------- include/xdg/xdg.h | 4 ---- src/gprt/ray_tracer.cpp | 29 ----------------------------- src/xdg.cpp | 7 ------- 5 files changed, 52 deletions(-) diff --git a/include/xdg/gprt/ray_tracer.h b/include/xdg/gprt/ray_tracer.h index ff019d67..85a35eef 100644 --- a/include/xdg/gprt/ray_tracer.h +++ b/include/xdg/gprt/ray_tracer.h @@ -125,10 +125,6 @@ class GPRTRayTracer : public RayTracer { // Method to expose device ray and hit buffers for external population DeviceRayHitBuffers get_device_rayhit_buffers(const size_t N) override; - void pack_external_rays(void* origins_device_ptr, - void* directions_device_ptr, - size_t num_rays) override; - /** * @brief Allocate device buffers and invoke a callback to populate them * @@ -157,7 +153,6 @@ class GPRTRayTracer : public RayTracer { GPRTMissOf missProgram_; GPRTComputeOf aabbPopulationProgram_; // packRaysProgam_; //get_device_rayhit_buffers(requiredCapacity); } - void pack_external_rays(void* origins_device_ptr, - void* directions_device_ptr, - size_t num_rays); - void populate_rays_external(size_t numRays, const RayPopulationCallback& callback) { diff --git a/src/gprt/ray_tracer.cpp b/src/gprt/ray_tracer.cpp index 06807a85..5fc5c5f0 100644 --- a/src/gprt/ray_tracer.cpp +++ b/src/gprt/ray_tracer.cpp @@ -75,7 +75,6 @@ void GPRTRayTracer::setup_shaders() missProgram_ = gprtMissCreate(context_, module_, "ray_fire_miss"); aabbPopulationProgram_ = gprtComputeCreate(context_, module_, "populate_aabbs"); - packRaysProgam_ = gprtComputeCreate(context_, module_, "pack_external_rays"); // Create a "triangle" geometry type and set its closest-hit program trianglesGeomType_ = gprtGeomTypeCreate(context_, GPRT_AABBS); @@ -546,34 +545,6 @@ DeviceRayHitBuffers GPRTRayTracer::get_device_rayhit_buffers(const size_t N) return rayHitBuffers_.view; } -void GPRTRayTracer::pack_external_rays(void* origins_device_ptr, - void* directions_device_ptr, - size_t num_rays) -{ - if (num_rays == 0) return; - - check_rayhit_buffer_capacity(num_rays); - ExternalRayParams params = {}; - params.num_rays = num_rays; - - // Workgroup setup - constexpr int threadsPerGroup = 256; - const int neededGroups = (params.num_rays + threadsPerGroup - 1) / threadsPerGroup; - const int groups = std::min(neededGroups, WORKGROUP_LIMIT); - - params.xdgRays = rayHitBuffers_.view.rayDevPtr; // dblRay* - params.origins = static_cast(origins_device_ptr); - params.directions = static_cast(directions_device_ptr); - params.total_threads = groups * threadsPerGroup; - - gprtComputeLaunch(packRaysProgam_, - { groups, 1, 1 }, - { threadsPerGroup, 1, 1 }, - params); - gprtComputeSynchronize(context_); - -} - void GPRTRayTracer::populate_rays_external(size_t numRays, const RayPopulationCallback& callback) { diff --git a/src/xdg.cpp b/src/xdg.cpp index b6e816b4..e6f6d915 100644 --- a/src/xdg.cpp +++ b/src/xdg.cpp @@ -362,11 +362,4 @@ double XDG::measure_volume_area(MeshID volume) const return area; } -void XDG::pack_external_rays(void* origins_device_ptr, - void* directions_device_ptr, - size_t num_rays) - { - return ray_tracing_interface()->pack_external_rays(origins_device_ptr, directions_device_ptr, num_rays); - } - } // namespace xdg \ No newline at end of file From ce80cdd480474359caf5ae12a8e7ce9a3abc7168 Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Mon, 19 Jan 2026 16:25:10 +0000 Subject: [PATCH 40/62] Made DeviceRayHitBuffers more opaque to abstract away from GPRT specific logic --- include/xdg/ray_tracing_interface.h | 37 ++++++++++++++++++++++------- src/gprt/ray_tracer.cpp | 9 ++++--- tools/ray_benchmark.cpp | 35 +++++++++++++-------------- 3 files changed, 52 insertions(+), 29 deletions(-) diff --git a/include/xdg/ray_tracing_interface.h b/include/xdg/ray_tracing_interface.h index f803b0e5..16071305 100644 --- a/include/xdg/ray_tracing_interface.h +++ b/include/xdg/ray_tracing_interface.h @@ -16,21 +16,42 @@ namespace xdg { -struct dblRay; // forward declaration -struct dblHit; // forward declaration +/** + * @brief Device ray/hit buffer descriptor + * + * This structure provides access to device-allocated ray and hit buffers + * in a backend-agnostic way. The buffers contain XDG's standard ray and hit + * data structures (dblRay and dblHit), regardless of which compute backend + * is being used. + * + * Key design principle: + * - Device pointers are opaque (void*) + * - The data layout is always the XDG types dblRay and dblHit + * - Downstream code can write to these buffers (hopefully) using any compute API + * + * For type-safe access in downstream code: + * - Cast rayDevPtr to (dblRay*) when using C++ or kernels + * - Cast hitDevPtr to (dblHit*) when reading hit results + */ struct DeviceRayHitBuffers { - dblRay* rayDevPtr; // device pointer to ray buffers - dblHit* hitDevPtr; // device pointer to hit buffers - uint capacity = 0; + void* rayDevPtr; + void* hitDevPtr; + size_t capacity; // Number of rays the buffer can hold + size_t rayStride; // Bytes between ray elements - sizeof(dblRay) + size_t hitStride; // Bytes between hit elements - sizeof(dblHit) }; /** - * @brief Callback signature for external ray population + * @brief Callback alias for external ray population * * Allows downstream applications to populate ray buffers using their own compute backend - * (GPRT, CUDA, HIP, OpenCL, etc.) without XDG needing to know which API is used. + * (GPRT, CUDA, OpenMP) without XDG needing to know the specifics. + * + * The callback receives opaque device pointers and should interpret them according to + * the buffer metadata (stride information). Alternatively, users can rely on the standard + * dblRay/dblHit layouts if they don't need custom padding/alignment. * - * @param buffer Device ray buffer to be populated + * @param buffer Device ray buffer descriptor with opaque pointers and metadata * @param numRays Number of rays to generate/populate */ using RayPopulationCallback = std::function; diff --git a/src/gprt/ray_tracer.cpp b/src/gprt/ray_tracer.cpp index 5fc5c5f0..47f484db 100644 --- a/src/gprt/ray_tracer.cpp +++ b/src/gprt/ray_tracer.cpp @@ -13,6 +13,8 @@ GPRTRayTracer::GPRTRayTracer() rayHitBuffers_.hit = gprtDeviceBufferCreate(context_, rayHitBuffers_.view.capacity); rayHitBuffers_.view.rayDevPtr = gprtBufferGetDevicePointer(rayHitBuffers_.ray); rayHitBuffers_.view.hitDevPtr = gprtBufferGetDevicePointer(rayHitBuffers_.hit); + rayHitBuffers_.view.rayStride = sizeof(dblRay); + rayHitBuffers_.view.hitStride = sizeof(dblHit); excludePrimitivesBuffer_ = gprtDeviceBufferCreate(context_); // initialise buffer of size 1 @@ -513,7 +515,7 @@ void GPRTRayTracer::check_rayhit_buffer_capacity(const size_t N) if (N <= rayHitBuffers_.view.capacity) return; // current capacity is sufficient // Resize buffers to accommodate N rays - double the capacity or set to N, whichever is larger - size_t newCapacity = std::max(uint(N), rayHitBuffers_.view.capacity * 2); + size_t newCapacity = std::max(N, rayHitBuffers_.view.capacity * 2); gprtBufferResize(context_, rayHitBuffers_.ray, newCapacity, false); gprtBufferResize(context_, rayHitBuffers_.hit, newCapacity, false); @@ -522,6 +524,8 @@ void GPRTRayTracer::check_rayhit_buffer_capacity(const size_t N) // Get fresh device pointers after resize rayHitBuffers_.view.rayDevPtr = gprtBufferGetDevicePointer(rayHitBuffers_.ray); rayHitBuffers_.view.hitDevPtr = gprtBufferGetDevicePointer(rayHitBuffers_.hit); + rayHitBuffers_.view.rayStride = sizeof(dblRay); + rayHitBuffers_.view.hitStride = sizeof(dblHit); // Since we have resized the ray buffers, we need to update the geom_data->rayIn pointers in all geometries too for (auto const& [surf, geom] : surface_to_geometry_map_) { @@ -553,8 +557,7 @@ void GPRTRayTracer::populate_rays_external(size_t numRays, // Ensure device buffers are large enough check_rayhit_buffer_capacity(numRays); - // Pass control to the user's callback with device pointers - // The callback will use whatever compute API it prefers to populate the buffers + // Use the user callback to populate the rays directly on the device callback(rayHitBuffers_.view, numRays); // After callback returns, we assume the ray buffer is populated and ready to trace diff --git a/tools/ray_benchmark.cpp b/tools/ray_benchmark.cpp index 2ee70ec8..9120d693 100644 --- a/tools/ray_benchmark.cpp +++ b/tools/ray_benchmark.cpp @@ -182,30 +182,29 @@ int main(int argc, char** argv) { std::cout << "XDG initalisation Time = " << setup_timer.elapsed() << "s" << std::endl; - // -------------------------- - // Backend-specific sections - // -------------------------- - if (rt_lib == RTLibrary::GPRT) { - // ---- Random ray generation on device via callback ---- + // ---- Random ray generation on device via callback method ---- gen_timer.start(); - // Define the ray generation callback that uses GPRT - // This callback runs inside populate_rays_external and receives XDG's device buffers - auto generateRaysCallback = [&](const DeviceRayHitBuffers& buffer, size_t numRays) { - // User creates their own GPRT context and kernel here - // This is completely decoupled from XDG's GPRT context - GPRTContext context = gprtContextCreate(); - GPRTModule module = gprtModuleCreate(context, ray_benchmark_deviceCode); - auto genRandomRays = gprtComputeCreate( - context, module, "generate_random_rays"); + /* + - User creates their own GPU compute API method to populate rays and passes that to XDG + - In this miniapp we are using GPRT as a demonstration + - This callback runs inside populate_rays_external and receives XDG's device buffers + */ + auto generateRaysCallback = [&](const DeviceRayHitBuffers& buffer, size_t numRays) { + + + GPRTContext context = gprtContextCreate(); // Note this is the user's GPRT context, not XDG's internal one stored in GPRTRayTracer + GPRTModule module = gprtModuleCreate(context, ray_benchmark_deviceCode); + auto genRandomRays = gprtComputeCreate( + context, module, "generate_random_rays"); constexpr int threadsPerGroup = 64; const int neededGroups = (int)((numRays + threadsPerGroup - 1) / threadsPerGroup); const int groups = std::min(neededGroups, WORKGROUP_LIMIT); GenerateRandomRayParams randomRayParams = {}; - randomRayParams.rays = buffer.rayDevPtr; // XDG's device ray buffer + randomRayParams.rays = static_cast(buffer.rayDevPtr); // Cast opaque pointer to typed dblRay* randomRayParams.numRays = (uint32_t)numRays; randomRayParams.source_radius = source_radius; randomRayParams.origin = { origin.x, origin.y, origin.z }; @@ -222,7 +221,7 @@ int main(int argc, char** argv) { gprtContextDestroy(context); }; - // Let XDG allocate buffers and invoke the callback to populate them + // Let XDG internally allocate buffers and invoke the callback to populate them xdg->populate_rays_external(N, generateRaysCallback); gen_timer.stop(); @@ -234,8 +233,8 @@ int main(int argc, char** argv) { xdg->ray_fire_packed(volume, N); // ray_fire against pre-packed rays on device trace_timer.stop(); - } else { - // EMBREE / CPU backend + } + else { // EMBREE / CPU backend // ---- Random ray generation on host ---- gen_timer.start(); From 201d06934c1cdb344bfd5b93696081ac87d628fc Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Mon, 19 Jan 2026 16:42:36 +0000 Subject: [PATCH 41/62] Updated some comments --- include/xdg/ray_tracing_interface.h | 6 +++--- tools/ray_benchmark.cpp | 9 ++++----- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/include/xdg/ray_tracing_interface.h b/include/xdg/ray_tracing_interface.h index 16071305..2c68ea37 100644 --- a/include/xdg/ray_tracing_interface.h +++ b/include/xdg/ray_tracing_interface.h @@ -300,11 +300,11 @@ class RayTracer { * The workflow: * 1. XDG allocates device memory for rays (if not already large enough) * 2. XDG passes device pointers to the callback - * 3. User's callback populates the buffers using their preferred compute API + * 3. User's callback populates the buffers using their preferred compute kernel/shader * 4. User's callback returns (XDG assumes buffers are now populated) - * 5. XDG proceeds with ray tracing against the populated data + * 5. Call xdg::ray_fire_packed() to trace the populated rays * - * This enables true zero-copy scenarios where downstream GPU code writes directly + * This avoids unnecessary host-device transfers by allowing users to write directly * to XDG's device buffers without any host-side transfers. * * @param numRays Number of rays to allocate space for diff --git a/tools/ray_benchmark.cpp b/tools/ray_benchmark.cpp index 9120d693..d4141acd 100644 --- a/tools/ray_benchmark.cpp +++ b/tools/ray_benchmark.cpp @@ -186,11 +186,10 @@ int main(int argc, char** argv) { // ---- Random ray generation on device via callback method ---- gen_timer.start(); - /* - - User creates their own GPU compute API method to populate rays and passes that to XDG - - In this miniapp we are using GPRT as a demonstration - - This callback runs inside populate_rays_external and receives XDG's device buffers - */ + + // - User creates their own GPU compute API method to populate rays and passes that to XDG + // - In this miniapp we are using GPRT as a demonstration + // - This callback runs inside populate_rays_external and receives XDG's device buffers auto generateRaysCallback = [&](const DeviceRayHitBuffers& buffer, size_t numRays) { From 7c25fda1673aa90fe78bc29196a180949b320527 Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Mon, 19 Jan 2026 17:02:44 +0000 Subject: [PATCH 42/62] Renamed ray_fire_packed() to ray_fire_prepared() --- include/xdg/gprt/ray_tracer.h | 8 ++++---- include/xdg/ray_tracing_interface.h | 4 ++-- include/xdg/xdg.h | 8 ++++---- src/gprt/ray_tracer.cpp | 8 ++++---- src/xdg.cpp | 10 +++++----- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/include/xdg/gprt/ray_tracer.h b/include/xdg/gprt/ray_tracer.h index 85a35eef..42e85294 100644 --- a/include/xdg/gprt/ray_tracer.h +++ b/include/xdg/gprt/ray_tracer.h @@ -103,10 +103,10 @@ class GPRTRayTracer : public RayTracer { HitOrientation orientation = HitOrientation::EXITING, std::vector* const exclude_primitives = nullptr) override; - void ray_fire_packed(TreeID tree, - const size_t num_rays, - const double dist_limit = INFTY, - HitOrientation orientation = HitOrientation::EXITING) override; + void ray_fire_prepared(TreeID tree, + const size_t num_rays, + const double dist_limit = INFTY, + HitOrientation orientation = HitOrientation::EXITING) override; std::pair closest(TreeID scene, const Position& origin) override {}; diff --git a/include/xdg/ray_tracing_interface.h b/include/xdg/ray_tracing_interface.h index 2c68ea37..341a7cbd 100644 --- a/include/xdg/ray_tracing_interface.h +++ b/include/xdg/ray_tracing_interface.h @@ -266,7 +266,7 @@ class RayTracer { * @param[in] orientation (optional) flag to consider whether Entering/Exiting hits should be rejected. Defaults to EXITING * @return Void. Outputs stored in dblHit buffer on device */ - virtual void ray_fire_packed(TreeID tree, + virtual void ray_fire_prepared(TreeID tree, const size_t num_rays, const double dist_limit = INFTY, HitOrientation orientation = HitOrientation::EXITING) @@ -302,7 +302,7 @@ class RayTracer { * 2. XDG passes device pointers to the callback * 3. User's callback populates the buffers using their preferred compute kernel/shader * 4. User's callback returns (XDG assumes buffers are now populated) - * 5. Call xdg::ray_fire_packed() to trace the populated rays + * 5. Call xdg::ray_fire_prepared() to trace the populated rays * * This avoids unnecessary host-device transfers by allowing users to write directly * to XDG's device buffers without any host-side transfers. diff --git a/include/xdg/xdg.h b/include/xdg/xdg.h index 78c68167..9351a8d7 100644 --- a/include/xdg/xdg.h +++ b/include/xdg/xdg.h @@ -155,10 +155,10 @@ void ray_fire(MeshID volume, HitOrientation orientation = HitOrientation::EXITING, std::vector* const exclude_primitives = nullptr); -void ray_fire_packed(MeshID volume, - const size_t num_rays, - const double dist_limit = INFTY, - HitOrientation orientation = HitOrientation::EXITING); +void ray_fire_prepared(MeshID volume, + const size_t num_rays, + const double dist_limit = INFTY, + HitOrientation orientation = HitOrientation::EXITING); std::pair closest(MeshID volume, const Position& origin) const; diff --git a/src/gprt/ray_tracer.cpp b/src/gprt/ray_tracer.cpp index 47f484db..b869fee2 100644 --- a/src/gprt/ray_tracer.cpp +++ b/src/gprt/ray_tracer.cpp @@ -472,10 +472,10 @@ void GPRTRayTracer::ray_fire(TreeID tree, } void -GPRTRayTracer::ray_fire_packed(TreeID tree, - const size_t num_rays, - const double dist_limit, - HitOrientation orientation) +GPRTRayTracer::ray_fire_prepared(TreeID tree, + const size_t num_rays, + const double dist_limit, + HitOrientation orientation) { if (num_rays == 0) return; // no work to do. Early exit diff --git a/src/xdg.cpp b/src/xdg.cpp index e6f6d915..a2a0db0c 100644 --- a/src/xdg.cpp +++ b/src/xdg.cpp @@ -267,13 +267,13 @@ XDG::ray_fire(MeshID volume, } void -XDG::ray_fire_packed(MeshID volume, - const size_t num_rays, - const double dist_limit, - HitOrientation orientation) +XDG::ray_fire_prepared(MeshID volume, + const size_t num_rays, + const double dist_limit, + HitOrientation orientation) { TreeID tree = volume_to_surface_tree_map_.at(volume); - return ray_tracing_interface()->ray_fire_packed(tree, num_rays, dist_limit, orientation); + return ray_tracing_interface()->ray_fire_prepared(tree, num_rays, dist_limit, orientation); } std::pair XDG::closest(MeshID volume, From 2d78b02fe995b9ee6827387e48a0d92deb46e8db Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Mon, 19 Jan 2026 17:03:09 +0000 Subject: [PATCH 43/62] Added the required CMake linking to GPRT for ray_benchmark miniapp --- tools/CMakeLists.txt | 26 +++++++++++++++++++++++++- tools/ray_benchmark.cpp | 2 +- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index a3fa0017..53cf2d1b 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -32,4 +32,28 @@ foreach(tool ${TOOL_NAMES}) target_compile_definitions(${tool_exec} PUBLIC XDG_OPENMP) endif() install(TARGETS ${tool_exec} DESTINATION ${CMAKE_INSTALL_BINDIR}/tools) -endforeach() \ No newline at end of file +endforeach() + +#=============================================================================== +# ray-benchmark (special case - requires linking directly to GPRT) +#=============================================================================== +if (XDG_ENABLE_GPRT) + # Embed and compile the device code + embed_devicecode( + OUTPUT_TARGET + ray_benchmark_deviceCode + HEADERS + ${CMAKE_CURRENT_SOURCE_DIR}/ray_benchmark_shared.h + SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/ray_benchmark_deviceCode.slang + ) + + # Create the ray-benchmark executable + add_executable(ray-benchmark ray_benchmark.cpp) + target_link_libraries(ray-benchmark xdg argparse ray_benchmark_deviceCode) + if (OpenMP_CXX_FOUND) + target_link_libraries(ray-benchmark OpenMP::OpenMP_CXX) + target_compile_definitions(ray-benchmark PUBLIC XDG_OPENMP) + endif() + install(TARGETS ray-benchmark DESTINATION ${CMAKE_INSTALL_BINDIR}/tools) +endif() \ No newline at end of file diff --git a/tools/ray_benchmark.cpp b/tools/ray_benchmark.cpp index d4141acd..76d12ede 100644 --- a/tools/ray_benchmark.cpp +++ b/tools/ray_benchmark.cpp @@ -229,7 +229,7 @@ int main(int argc, char** argv) { // ---- Ray tracing on device ---- trace_timer.start(); - xdg->ray_fire_packed(volume, N); // ray_fire against pre-packed rays on device + xdg->ray_fire_prepared(volume, N); // ray_fire against pre-populated rays on device trace_timer.stop(); } From 5704d88768442a91ac32927c90a800b6e5115b1a Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Wed, 21 Jan 2026 18:12:08 +0000 Subject: [PATCH 44/62] Adding the ability to trace against multiple volumes for prepared rays --- include/xdg/gprt/ray.h | 1 + include/xdg/gprt/ray_tracer.h | 15 ++++++------ include/xdg/gprt/shared_structs.h | 1 + include/xdg/ray_tracing_interface.h | 7 +++--- include/xdg/xdg.h | 7 +++--- src/gprt/ray_tracer.cpp | 38 ++++++++++++++++++----------- src/xdg.cpp | 10 +++----- tools/ray_benchmark.cpp | 2 +- 8 files changed, 45 insertions(+), 36 deletions(-) diff --git a/include/xdg/gprt/ray.h b/include/xdg/gprt/ray.h index 3825b3d7..fd60a18c 100644 --- a/include/xdg/gprt/ray.h +++ b/include/xdg/gprt/ray.h @@ -10,6 +10,7 @@ struct dblRay { double3 origin; double3 direction; + SurfaceAccelerationStructure surface_accel; // TLAS we are tracing against int32_t* exclude_primitives; // Optional for excluding primitives int32_t exclude_count; // Number of excluded primitives }; diff --git a/include/xdg/gprt/ray_tracer.h b/include/xdg/gprt/ray_tracer.h index 7d3b6d94..5c1eb5b2 100644 --- a/include/xdg/gprt/ray_tracer.h +++ b/include/xdg/gprt/ray_tracer.h @@ -103,10 +103,9 @@ class GPRTRayTracer : public RayTracer { HitOrientation orientation = HitOrientation::EXITING, std::vector* const exclude_primitives = nullptr) override; - void ray_fire_packed(TreeID tree, - const size_t num_rays, - const double dist_limit = INFTY, - HitOrientation orientation = HitOrientation::EXITING) override; + void ray_fire_prepared(const size_t num_rays, + const double dist_limit = INFTY, + HitOrientation orientation = HitOrientation::EXITING) override; std::pair closest(TreeID scene, const Position& origin) override {}; @@ -166,8 +165,10 @@ class GPRTRayTracer : public RayTracer { // Internal GPRT Mappings std::unordered_map surface_volume_tree_to_accel_map; // Map from XDG::TreeID to GPRTAccel for volume TLAS - std::vector blas_handles_; // Store BLAS handles so that they can be explicitly referenced in destructor - + + std::vector tlas_handles_; // Store TLAS handles so that they can be explicitly referenced in destructor + GPRTBufferOf tlas_handle_buffer_; // Device buffer storing TLAS handles for device side MeshID->Accel map + // Global Tree IDs GPRTAccel global_surface_accel_ {nullptr}; GPRTAccel global_element_accel_ {nullptr}; @@ -176,4 +177,4 @@ class GPRTRayTracer : public RayTracer { } // namespace xdg -#endif // include guard \ No newline at end of file +#endif // include guard diff --git a/include/xdg/gprt/shared_structs.h b/include/xdg/gprt/shared_structs.h index 172acb3a..1176ed6e 100644 --- a/include/xdg/gprt/shared_structs.h +++ b/include/xdg/gprt/shared_structs.h @@ -33,6 +33,7 @@ struct DPTriangleGeomData { struct dblRayGenData { xdg::dblRay *ray; xdg::dblHit *hit; + SurfaceAccelerationStructure* meshid_to_accel_address; // MeshID->TLAS address table }; /* A small structure of constants that can change every frame without rebuilding the diff --git a/include/xdg/ray_tracing_interface.h b/include/xdg/ray_tracing_interface.h index 3bff6739..7d9adb63 100644 --- a/include/xdg/ray_tracing_interface.h +++ b/include/xdg/ray_tracing_interface.h @@ -239,10 +239,9 @@ class RayTracer { * @param[in] orientation (optional) flag to consider whether Entering/Exiting hits should be rejected. Defaults to EXITING * @return Void. Outputs stored in dblHit buffer on device */ - virtual void ray_fire_packed(TreeID tree, - const size_t num_rays, - const double dist_limit = INFTY, - HitOrientation orientation = HitOrientation::EXITING) + virtual void ray_fire_prepared(const size_t num_rays, + const double dist_limit = INFTY, + HitOrientation orientation = HitOrientation::EXITING) { fatal_error("GPU ray tracing not supported with this RayTracer backend"); } diff --git a/include/xdg/xdg.h b/include/xdg/xdg.h index 66684cb5..94f1b0de 100644 --- a/include/xdg/xdg.h +++ b/include/xdg/xdg.h @@ -155,10 +155,9 @@ void ray_fire(MeshID volume, HitOrientation orientation = HitOrientation::EXITING, std::vector* const exclude_primitives = nullptr); -void ray_fire_packed(MeshID volume, - const size_t num_rays, - const double dist_limit = INFTY, - HitOrientation orientation = HitOrientation::EXITING); +void ray_fire_prepared(const size_t num_rays, + const double dist_limit = INFTY, + HitOrientation orientation = HitOrientation::EXITING); std::pair closest(MeshID volume, const Position& origin) const; diff --git a/src/gprt/ray_tracer.cpp b/src/gprt/ray_tracer.cpp index 783d39fe..52c4ddcd 100644 --- a/src/gprt/ray_tracer.cpp +++ b/src/gprt/ray_tracer.cpp @@ -4,10 +4,12 @@ namespace xdg { GPRTRayTracer::GPRTRayTracer() { + gprtRequestRayTypeCount(numRayTypes_); // Set the number of shaders which can be set to the same geometry context_ = gprtContextCreate(); module_ = gprtModuleCreate(context_, dbl_deviceCode); + // Buffer setup rayHitBuffers_.view.capacity = 1e6; // Preallocate space for 1m rays rayHitBuffers_.ray = gprtDeviceBufferCreate(context_, rayHitBuffers_.view.capacity); rayHitBuffers_.hit = gprtDeviceBufferCreate(context_, rayHitBuffers_.view.capacity); @@ -16,6 +18,8 @@ GPRTRayTracer::GPRTRayTracer() excludePrimitivesBuffer_ = gprtDeviceBufferCreate(context_); // initialise buffer of size 1 + tlas_handle_buffer_ = gprtDeviceBufferCreate(context_); // initialise buffer of size 1 + setup_shaders(); @@ -31,6 +35,8 @@ GPRTRayTracer::GPRTRayTracer() // Set up build parameters for acceleration structures buildParams_.buildMode = GPRT_BUILD_MODE_FAST_BUILD_NO_UPDATE; + + } GPRTRayTracer::~GPRTRayTracer() @@ -45,11 +51,6 @@ GPRTRayTracer::~GPRTRayTracer() gprtAccelDestroy(accel); } - // Destroy BLAS structures - for (const auto& blas : blas_handles_) { - gprtAccelDestroy(blas); - } - // Destroy Geoms and Types for (const auto& [surf, geom] : surface_to_geometry_map_) { gprtGeomDestroy(geom); @@ -60,6 +61,7 @@ GPRTRayTracer::~GPRTRayTracer() gprtBufferDestroy(rayHitBuffers_.ray); gprtBufferDestroy(rayHitBuffers_.hit); gprtBufferDestroy(excludePrimitivesBuffer_); + gprtBufferDestroy(tlas_handle_buffer_); // Destroy module and context gprtModuleDestroy(module_); @@ -88,6 +90,18 @@ void GPRTRayTracer::init() // Build the shader binding table (SBT) after all shader programs and acceleration structures are set up gprtBuildShaderBindingTable(context_, GPRT_SBT_ALL); // Note that should we need to update any shaders or acceleration structures, we must rebuild the SBT + + // Build Device-side TLAS map + gprtBufferResize(context_, tlas_handle_buffer_, tlas_handles_.size(), false); + gprtBufferMap(tlas_handle_buffer_); + std::copy(tlas_handles_.begin(), tlas_handles_.end(), gprtBufferGetHostPointer(tlas_handle_buffer_)); + gprtBufferUnmap(tlas_handle_buffer_); + + // Bind to raygen data + for (auto type : {RayGenType::RAY_FIRE, RayGenType::POINT_IN_VOLUME}) { + auto* raygendata = gprtRayGenGetParameters(rayGenPrograms_.at(type)); + raygendata->meshid_to_accel_address = gprtBufferGetDevicePointer(tlas_handle_buffer_); + } } std::pair @@ -205,8 +219,8 @@ GPRTRayTracer::create_surface_tree(const std::shared_ptr& mesh_mana GPRTAccel volume_tlas = gprtInstanceAccelCreate(context_, surfaceBlasInstances.size(), instanceBuffer); gprtAccelBuild(context_, volume_tlas, buildParams_); surface_volume_tree_to_accel_map[tree] = volume_tlas; - - return tree; + tlas_handles_.push_back(gprtAccelGetDeviceAddress(volume_tlas)); // Store TLAS handle for population of device side MeshID->Accel map + return tree; } ElementTreeID @@ -471,24 +485,20 @@ void GPRTRayTracer::ray_fire(TreeID tree, } void -GPRTRayTracer::ray_fire_packed(TreeID tree, - const size_t num_rays, - const double dist_limit, - HitOrientation orientation) +GPRTRayTracer::ray_fire_prepared(const size_t num_rays, + const double dist_limit, + HitOrientation orientation) { if (num_rays == 0) return; // no work to do. Early exit check_rayhit_buffer_capacity(num_rays); - GPRTAccel volume = surface_volume_tree_to_accel_map.at(tree); auto rayGen = rayGenPrograms_.at(RayGenType::RAY_FIRE); dblRayFirePushConstants pushConstants; - pushConstants.volume_accel = gprtAccelGetDeviceAddress(volume); pushConstants.tMax = dist_limit; pushConstants.tMin = 0.0; pushConstants.hitOrientation = orientation; // Set orientation for the ray - pushConstants.volume_tree = tree; // Set the TreeID of the volume being queried gprtRayGenLaunch1D(context_, rayGen, num_rays, pushConstants); gprtGraphicsSynchronize(context_); diff --git a/src/xdg.cpp b/src/xdg.cpp index b6e816b4..da7ae743 100644 --- a/src/xdg.cpp +++ b/src/xdg.cpp @@ -267,13 +267,11 @@ XDG::ray_fire(MeshID volume, } void -XDG::ray_fire_packed(MeshID volume, - const size_t num_rays, - const double dist_limit, - HitOrientation orientation) +XDG::ray_fire_prepared(const size_t num_rays, + const double dist_limit, + HitOrientation orientation) { - TreeID tree = volume_to_surface_tree_map_.at(volume); - return ray_tracing_interface()->ray_fire_packed(tree, num_rays, dist_limit, orientation); + return ray_tracing_interface()->ray_fire_prepared(num_rays, dist_limit, orientation); } std::pair XDG::closest(MeshID volume, diff --git a/tools/ray_benchmark.cpp b/tools/ray_benchmark.cpp index 2b6672ad..7c01936b 100644 --- a/tools/ray_benchmark.cpp +++ b/tools/ray_benchmark.cpp @@ -226,7 +226,7 @@ int main(int argc, char** argv) { // ---- Ray tracing on device ---- trace_timer.start(); - xdg->ray_fire_packed(volume, N); // ray_fire against pre-packed rays on device + xdg->ray_fire_prepared(N); // ray_fire against pre-packed rays on device trace_timer.stop(); } else { From b920f9cdd86afd0567c535a410b3ac9abdde9a7a Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Fri, 23 Jan 2026 15:56:13 +0000 Subject: [PATCH 45/62] Added the ability to trace against multiple volumes within same raygen call --- include/xdg/gprt/ray.h | 4 ++- include/xdg/gprt/ray_tracer.h | 19 ++++++++-- include/xdg/gprt/shared_structs.h | 3 +- include/xdg/ray_tracing_interface.h | 2 +- src/gprt/dbl_deviceCode.slang | 12 ++++--- src/gprt/ray_tracer.cpp | 52 ++++++++++++++++++++++++---- tools/ray_benchmark.cpp | 19 ++++++---- tools/ray_benchmark_deviceCode.slang | 13 ++++++- tools/ray_benchmark_shared.h | 20 +++++++---- 9 files changed, 114 insertions(+), 30 deletions(-) diff --git a/include/xdg/gprt/ray.h b/include/xdg/gprt/ray.h index 5803a012..b87734cd 100644 --- a/include/xdg/gprt/ray.h +++ b/include/xdg/gprt/ray.h @@ -10,11 +10,13 @@ struct dblRay { double3 origin; double3 direction; + int volume_mesh_id; // MeshID of the volume this ray will be traced against uint enabled; // Flag to indicate if the ray is active int32_t* exclude_primitives; // Optional for excluding primitives int32_t exclude_count; // Number of excluded primitives }; + struct dblHit { double distance; @@ -26,4 +28,4 @@ struct dblHit } -#endif \ No newline at end of file +#endif diff --git a/include/xdg/gprt/ray_tracer.h b/include/xdg/gprt/ray_tracer.h index 42e85294..0f99f633 100644 --- a/include/xdg/gprt/ray_tracer.h +++ b/include/xdg/gprt/ray_tracer.h @@ -139,6 +139,16 @@ class GPRTRayTracer : public RayTracer { return context_; } + SurfaceAccelerationStructure* tlas_handle_device_ptr() const + { + return gprtBufferGetDevicePointer(tlas_handle_buffer_); + } + + size_t tlas_handle_count() const + { + return tlas_handles_.size(); + } + private: // GPRT objects @@ -170,7 +180,12 @@ class GPRTRayTracer : public RayTracer { // Internal GPRT Mappings std::unordered_map surface_volume_tree_to_accel_map; // Map from XDG::TreeID to GPRTAccel for volume TLAS - std::vector blas_handles_; // Store BLAS handles so that they can be explicitly referenced in destructor + std::unordered_map surface_tree_to_volume_map_; + std::vector tlas_handles_; // Host side storage of TLAS device addresses + GPRTBufferOf tlas_handle_buffer_; // Device buffer for TLAS addresses + bool initialized_ {false}; // flag to indicate if init() has been called + + void update_tlas_table_(); // Global Tree IDs GPRTAccel global_surface_accel_ {nullptr}; @@ -180,4 +195,4 @@ class GPRTRayTracer : public RayTracer { } // namespace xdg -#endif // include guard \ No newline at end of file +#endif // include guard diff --git a/include/xdg/gprt/shared_structs.h b/include/xdg/gprt/shared_structs.h index 172acb3a..96df1866 100644 --- a/include/xdg/gprt/shared_structs.h +++ b/include/xdg/gprt/shared_structs.h @@ -33,6 +33,7 @@ struct DPTriangleGeomData { struct dblRayGenData { xdg::dblRay *ray; xdg::dblHit *hit; + SurfaceAccelerationStructure* meshid_to_accel_address; // MeshID->TLAS address table to recover volume to trace against }; /* A small structure of constants that can change every frame without rebuilding the @@ -55,4 +56,4 @@ struct ExternalRayParams { uint32_t total_threads; }; -#endif \ No newline at end of file +#endif diff --git a/include/xdg/ray_tracing_interface.h b/include/xdg/ray_tracing_interface.h index 341a7cbd..68105759 100644 --- a/include/xdg/ray_tracing_interface.h +++ b/include/xdg/ray_tracing_interface.h @@ -342,4 +342,4 @@ class RayTracer { } // namespace xdg -#endif // include guard \ No newline at end of file +#endif // include guard diff --git a/src/gprt/dbl_deviceCode.slang b/src/gprt/dbl_deviceCode.slang index 544a8fdf..d19d5e4a 100644 --- a/src/gprt/dbl_deviceCode.slang +++ b/src/gprt/dbl_deviceCode.slang @@ -62,15 +62,17 @@ void ray_fire(uniform dblRayGenData record, uniform DPTriangleGeomData mesh) { rayDesc.TMin = float(PC.tMin); rayDesc.TMax = float(PC.tMax); - SurfaceAccelerationStructure world = PC.volume_accel; + int mesh_id = ray.volume_mesh_id; + // Recover the TLAS we are tracing against for this ray + SurfaceAccelerationStructure world = record.meshid_to_accel_address[mesh_id]; - // Pass the ray's origin and direction to the payload + // Set payload default values payload.distance = -1.0f; payload.surf_id = -1; payload.tlas = world; - if (ray.enabled == 1u) { // skip RT pipeline for rays that are marked as disabled - TraceRay(world, RAY_FLAG_NONE, 0xff, 0, 1, rayDesc, payload); + if (ray.enabled == 1u) { + TraceRay(PC.volume_accel, RAY_FLAG_NONE, 0xff, 0, 1, rayDesc, payload); } // Store the distance to the hit point and the surface ID in buffers for CPU @@ -347,4 +349,4 @@ float next_after(float a) { a_++; } return asfloat(a_); -} \ No newline at end of file +} diff --git a/src/gprt/ray_tracer.cpp b/src/gprt/ray_tracer.cpp index b869fee2..f4980b6c 100644 --- a/src/gprt/ray_tracer.cpp +++ b/src/gprt/ray_tracer.cpp @@ -18,6 +18,8 @@ GPRTRayTracer::GPRTRayTracer() excludePrimitivesBuffer_ = gprtDeviceBufferCreate(context_); // initialise buffer of size 1 + tlas_handle_buffer_ = gprtDeviceBufferCreate(context_); + setup_shaders(); @@ -48,9 +50,9 @@ GPRTRayTracer::~GPRTRayTracer() } // Destroy BLAS structures - for (const auto& blas : blas_handles_) { - gprtAccelDestroy(blas); - } + // for (const auto& blas : blas_handles_) { + // gprtAccelDestroy(blas); + // } // Destroy Geoms and Types for (const auto& [surf, geom] : surface_to_geometry_map_) { @@ -62,6 +64,7 @@ GPRTRayTracer::~GPRTRayTracer() gprtBufferDestroy(rayHitBuffers_.ray); gprtBufferDestroy(rayHitBuffers_.hit); gprtBufferDestroy(excludePrimitivesBuffer_); + gprtBufferDestroy(tlas_handle_buffer_); // Destroy module and context gprtModuleDestroy(module_); @@ -86,9 +89,13 @@ void GPRTRayTracer::setup_shaders() void GPRTRayTracer::init() { + update_tlas_table_(); + // Build the shader binding table (SBT) after all shader programs and acceleration structures are set up gprtBuildShaderBindingTable(context_, GPRT_SBT_ALL); // Note that should we need to update any shaders or acceleration structures, we must rebuild the SBT + + initialized_ = true; } std::pair @@ -206,7 +213,17 @@ GPRTRayTracer::create_surface_tree(const std::shared_ptr& mesh_mana GPRTAccel volume_tlas = gprtInstanceAccelCreate(context_, surfaceBlasInstances.size(), instanceBuffer); gprtAccelBuild(context_, volume_tlas, buildParams_); surface_volume_tree_to_accel_map[tree] = volume_tlas; - + surface_tree_to_volume_map_[tree] = volume_id; + if (volume_id >= tlas_handles_.size()) { + tlas_handles_.resize(volume_id + 1, SurfaceAccelerationStructure{}); + } + tlas_handles_[volume_id] = gprtAccelGetDeviceAddress(volume_tlas); + + if (initialized_) { + update_tlas_table_(); + gprtBuildShaderBindingTable(context_, GPRT_SBT_ALL); + } + return tree; } @@ -242,6 +259,8 @@ bool GPRTRayTracer::point_in_volume(SurfaceTreeID tree, dblRay* ray = gprtBufferGetHostPointer(rayHitBuffers_.ray); ray[0].origin = {point.x, point.y, point.z}; ray[0].direction = {directionUsed.x, directionUsed.y, directionUsed.z}; + ray[0].volume_mesh_id = surface_tree_to_volume_map_.at(tree); + ray[0].enabled = 1; // Ensure the ray is enabled if (exclude_primitives) { if (!exclude_primitives->empty()) gprtBufferResize(context_, excludePrimitivesBuffer_, exclude_primitives->size(), false); @@ -296,11 +315,13 @@ std::pair GPRTRayTracer::ray_fire(SurfaceTreeID tree, GPRTAccel volume = surface_volume_tree_to_accel_map.at(tree); auto rayGen = rayGenPrograms_.at(RayGenType::RAY_FIRE); dblRayGenData* rayGenData = gprtRayGenGetParameters(rayGen); - + gprtBufferMap(rayHitBuffers_.ray); // Update the ray input buffer dblRay* ray = gprtBufferGetHostPointer(rayHitBuffers_.ray); ray[0].origin = {origin.x, origin.y, origin.z}; ray[0].direction = {direction.x, direction.y, direction.z}; + ray[0].volume_mesh_id = surface_tree_to_volume_map_.at(tree); + ray[0].enabled = 1; // Ensure the ray is enabled if (exclude_primitives) { if (!exclude_primitives->empty()) gprtBufferResize(context_, excludePrimitivesBuffer_, exclude_primitives->size(), false); @@ -371,6 +392,8 @@ void GPRTRayTracer::point_in_volume(TreeID tree, for (size_t i = 0; i < num_points; ++i) { ray[i].origin = {points[i].x, points[i].y, points[i].z}; ray[i].exclude_primitives = nullptr; + ray[i].volume_mesh_id = surface_tree_to_volume_map_.at(tree); + ray[i].enabled = 1; // Ensure the ray is enabled } // Directions @@ -435,6 +458,8 @@ void GPRTRayTracer::ray_fire(TreeID tree, ray[i].origin = {origin.x, origin.y, origin.z}; ray[i].direction = {direction.x, direction.y, direction.z}; ray[i].exclude_primitives = nullptr; // Not currently supported in batch version + ray[i].volume_mesh_id = surface_tree_to_volume_map_.at(tree); + ray[i].enabled = 1; // Ensure the ray is enabled } gprtBufferUnmap(rayHitBuffers_.ray); // required to sync buffer back on GPU? @@ -538,11 +563,26 @@ void GPRTRayTracer::check_rayhit_buffer_capacity(const size_t N) dblRayGenData* rayGenData = gprtRayGenGetParameters(rayGen); rayGenData->ray = gprtBufferGetDevicePointer(rayHitBuffers_.ray); rayGenData->hit = gprtBufferGetDevicePointer(rayHitBuffers_.hit); + rayGenData->meshid_to_accel_address = gprtBufferGetDevicePointer(tlas_handle_buffer_); } gprtBuildShaderBindingTable(context_, static_cast(GPRT_SBT_GEOM | GPRT_SBT_RAYGEN)); } +// Update the TLAS table (MeshID -> SurfaceAccelerationStructure) buffer on the device +void GPRTRayTracer::update_tlas_table_() +{ + gprtBufferResize(context_, tlas_handle_buffer_, tlas_handles_.size(), false); + gprtBufferMap(tlas_handle_buffer_); + std::copy(tlas_handles_.begin(), tlas_handles_.end(), gprtBufferGetHostPointer(tlas_handle_buffer_)); + gprtBufferUnmap(tlas_handle_buffer_); + + for (auto type : {RayGenType::RAY_FIRE, RayGenType::POINT_IN_VOLUME}) { + auto* raygendata = gprtRayGenGetParameters(rayGenPrograms_.at(type)); + raygendata->meshid_to_accel_address = gprtBufferGetDevicePointer(tlas_handle_buffer_); + } +} + DeviceRayHitBuffers GPRTRayTracer::get_device_rayhit_buffers(const size_t N) { check_rayhit_buffer_capacity(N); @@ -565,5 +605,3 @@ void GPRTRayTracer::populate_rays_external(size_t numRays, } } // namespace xdg - - diff --git a/tools/ray_benchmark.cpp b/tools/ray_benchmark.cpp index 76d12ede..01e9195d 100644 --- a/tools/ray_benchmark.cpp +++ b/tools/ray_benchmark.cpp @@ -182,6 +182,7 @@ int main(int argc, char** argv) { std::cout << "XDG initalisation Time = " << setup_timer.elapsed() << "s" << std::endl; + std::shared_ptr gprt_rt; if (rt_lib == RTLibrary::GPRT) { // ---- Random ray generation on device via callback method ---- gen_timer.start(); @@ -190,10 +191,13 @@ int main(int argc, char** argv) { // - User creates their own GPU compute API method to populate rays and passes that to XDG // - In this miniapp we are using GPRT as a demonstration // - This callback runs inside populate_rays_external and receives XDG's device buffers - auto generateRaysCallback = [&](const DeviceRayHitBuffers& buffer, size_t numRays) { - + gprt_rt = std::dynamic_pointer_cast(xdg->ray_tracing_interface()); + if (!gprt_rt) { + fatal_error("GPRT backend requested but ray tracing interface is not GPRTRayTracer"); + } - GPRTContext context = gprtContextCreate(); // Note this is the user's GPRT context, not XDG's internal one stored in GPRTRayTracer + auto generateRaysCallback = [&](const DeviceRayHitBuffers& buffer, size_t numRays) { + GPRTContext context = gprt_rt->context(); GPRTModule module = gprtModuleCreate(context, ray_benchmark_deviceCode); auto genRandomRays = gprtComputeCreate( context, module, "generate_random_rays"); @@ -209,6 +213,8 @@ int main(int argc, char** argv) { randomRayParams.origin = { origin.x, origin.y, origin.z }; randomRayParams.seed = seed; randomRayParams.total_threads = (uint32_t)(groups * threadsPerGroup); + randomRayParams.volume_mesh_id = volume; + randomRayParams.enabled = 1u; gprtComputeLaunch(genRandomRays, { (uint32_t)groups, 1, 1 }, @@ -216,8 +222,8 @@ int main(int argc, char** argv) { randomRayParams); gprtComputeSynchronize(context); - // Cleanup the user's context (not XDG's context) - gprtContextDestroy(context); + gprtComputeDestroy(genRandomRays); + gprtModuleDestroy(module); }; // Let XDG internally allocate buffers and invoke the callback to populate them @@ -229,6 +235,7 @@ int main(int argc, char** argv) { // ---- Ray tracing on device ---- trace_timer.start(); + printf("Tracing volume MeshID: %d\n", volume); xdg->ray_fire_prepared(volume, N); // ray_fire against pre-populated rays on device trace_timer.stop(); @@ -294,4 +301,4 @@ int main(int argc, char** argv) { << " rays/s" << std::endl; std::cout << "---------------------------------------- \n" << std::endl; return 0; -} \ No newline at end of file +} diff --git a/tools/ray_benchmark_deviceCode.slang b/tools/ray_benchmark_deviceCode.slang index bafbf21c..e076cb8a 100644 --- a/tools/ray_benchmark_deviceCode.slang +++ b/tools/ray_benchmark_deviceCode.slang @@ -30,9 +30,20 @@ void generate_random_rays(uint3 DispatchThreadID: SV_DispatchThreadID, params.rays[idx].direction = dir; params.rays[idx].exclude_primitives = nullptr; params.rays[idx].exclude_count = 0; + params.rays[idx].enabled = params.enabled; + params.rays[idx].volume_mesh_id = params.volume_mesh_id; } } +[shader("compute")] +[numthreads(1, 1, 1)] +void debug_read_tlas(uint3 DispatchThreadID: SV_DispatchThreadID, + uniform DebugReadTLASParams params) +{ + if (DispatchThreadID.x > 0) return; + params.out[0] = params.table[params.index]; +} + // Helpers // Simple LCG random number generator @@ -54,4 +65,4 @@ double3 random_unit_dir_lcg(inout uint state) double t = 2.0 * sqrt(1.0 - s); return double3(x1 * t, x2 * t, 1.0 - 2.0 * s); -} \ No newline at end of file +} diff --git a/tools/ray_benchmark_shared.h b/tools/ray_benchmark_shared.h index e9025486..24d93168 100644 --- a/tools/ray_benchmark_shared.h +++ b/tools/ray_benchmark_shared.h @@ -3,10 +3,18 @@ #include "../include/xdg/gprt/ray.h" struct GenerateRandomRayParams { - xdg::dblRay* rays; // pointer to ray data buffer - uint numRays; // number of rays to be generated - double3 origin; // single origin provided for benchmark case - uint seed; // seed for random direction generation + xdg::dblRay* rays; + uint numRays; + double3 origin; + uint seed; uint total_threads; - double source_radius; // 0.0 = point volume, >0.0 = spherical cloud -}; \ No newline at end of file + double source_radius; + int volume_mesh_id; + uint enabled; +}; + +struct DebugReadTLASParams { + SurfaceAccelerationStructure* table; + uint index; + SurfaceAccelerationStructure* out; +}; From 055eca859dcb7248c3037819a6cea16a5c1c7b24 Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Mon, 26 Jan 2026 17:26:36 +0000 Subject: [PATCH 46/62] Abstracted some methods from ray_benchmark into functions for use in tests --- tests/test_point_in_volume.cpp | 4 +- tools/CMakeLists.txt | 24 +----- tools/ray_benchmark/CMakeLists.txt | 33 ++++++++ tools/{ => ray_benchmark}/ray_benchmark.cpp | 80 +------------------ .../ray_benchmark_deviceCode.slang | 13 +-- .../ray_benchmark_driver.py} | 0 tools/ray_benchmark/ray_benchmark_shared.h | 14 ++++ tools/ray_benchmark_shared.h | 20 ----- 8 files changed, 55 insertions(+), 133 deletions(-) create mode 100644 tools/ray_benchmark/CMakeLists.txt rename tools/{ => ray_benchmark}/ray_benchmark.cpp (70%) rename tools/{ => ray_benchmark}/ray_benchmark_deviceCode.slang (86%) rename tools/{ray-benchmark-driver.py => ray_benchmark/ray_benchmark_driver.py} (100%) create mode 100644 tools/ray_benchmark/ray_benchmark_shared.h delete mode 100644 tools/ray_benchmark_shared.h diff --git a/tests/test_point_in_volume.cpp b/tests/test_point_in_volume.cpp index a09d6a68..7e771499 100644 --- a/tests/test_point_in_volume.cpp +++ b/tests/test_point_in_volume.cpp @@ -39,7 +39,6 @@ TEMPLATE_TEST_CASE("Point-in-volume on MeshMock", "[piv][mock]", check_ray_tracer_supported(rt_backend); // skip if backend not enabled at configuration time auto rti = create_raytracer(rt_backend); REQUIRE(rti); - rti->init(); // Keep MeshMock usage consistent across backends auto mm = std::make_shared(false); @@ -160,4 +159,5 @@ TEMPLATE_TEST_CASE("Batch API Point-in-volume on MeshMock", "[piv][mock][batch]" } } } -} \ No newline at end of file +} + diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 53cf2d1b..00d7ecd3 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -34,26 +34,4 @@ foreach(tool ${TOOL_NAMES}) install(TARGETS ${tool_exec} DESTINATION ${CMAKE_INSTALL_BINDIR}/tools) endforeach() -#=============================================================================== -# ray-benchmark (special case - requires linking directly to GPRT) -#=============================================================================== -if (XDG_ENABLE_GPRT) - # Embed and compile the device code - embed_devicecode( - OUTPUT_TARGET - ray_benchmark_deviceCode - HEADERS - ${CMAKE_CURRENT_SOURCE_DIR}/ray_benchmark_shared.h - SOURCES - ${CMAKE_CURRENT_SOURCE_DIR}/ray_benchmark_deviceCode.slang - ) - - # Create the ray-benchmark executable - add_executable(ray-benchmark ray_benchmark.cpp) - target_link_libraries(ray-benchmark xdg argparse ray_benchmark_deviceCode) - if (OpenMP_CXX_FOUND) - target_link_libraries(ray-benchmark OpenMP::OpenMP_CXX) - target_compile_definitions(ray-benchmark PUBLIC XDG_OPENMP) - endif() - install(TARGETS ray-benchmark DESTINATION ${CMAKE_INSTALL_BINDIR}/tools) -endif() \ No newline at end of file +add_subdirectory(ray_benchmark) diff --git a/tools/ray_benchmark/CMakeLists.txt b/tools/ray_benchmark/CMakeLists.txt new file mode 100644 index 00000000..9566a620 --- /dev/null +++ b/tools/ray_benchmark/CMakeLists.txt @@ -0,0 +1,33 @@ +#=============================================================================== +# ray-benchmark (special case - requires linking directly to GPRT) +#=============================================================================== +if (XDG_ENABLE_GPRT) + # Embed and compile the device code + embed_devicecode( + OUTPUT_TARGET + ray_benchmark_deviceCode + HEADERS + ${CMAKE_CURRENT_SOURCE_DIR}/ray_benchmark_shared.h + SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/ray_benchmark_deviceCode.slang + ) + + # Create the ray-benchmark executable + add_executable(ray-benchmark ray_benchmark.cpp) + target_link_libraries(ray-benchmark xdg argparse ray_benchmark_deviceCode) + # Keep the runtime output alongside other tools for single- and multi-config generators. + get_filename_component(TOOLS_BIN_DIR "${CMAKE_CURRENT_BINARY_DIR}" DIRECTORY) + set_target_properties(ray-benchmark PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${TOOLS_BIN_DIR}" + ) + foreach(config DEBUG RELEASE RELWITHDEBINFO MINSIZEREL) + set_target_properties(ray-benchmark PROPERTIES + RUNTIME_OUTPUT_DIRECTORY_${config} "${TOOLS_BIN_DIR}" + ) + endforeach() + if (OpenMP_CXX_FOUND) + target_link_libraries(ray-benchmark OpenMP::OpenMP_CXX) + target_compile_definitions(ray-benchmark PUBLIC XDG_OPENMP) + endif() + install(TARGETS ray-benchmark DESTINATION ${CMAKE_INSTALL_BINDIR}/tools) +endif() diff --git a/tools/ray_benchmark.cpp b/tools/ray_benchmark/ray_benchmark.cpp similarity index 70% rename from tools/ray_benchmark.cpp rename to tools/ray_benchmark/ray_benchmark.cpp index 01e9195d..372eafd3 100644 --- a/tools/ray_benchmark.cpp +++ b/tools/ray_benchmark/ray_benchmark.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include "xdg/error.h" #include "xdg/mesh_manager_interface.h" @@ -15,47 +14,11 @@ #include "argparse/argparse.hpp" -// GPRT includes - only for use in ray generation callback -#include "gprt/gprt.h" -#include "ray_benchmark_shared.h" +#include "ray_benchmark.h" #include using namespace xdg; -extern GPRTProgram ray_benchmark_deviceCode; - -inline double rand01(uint32_t &state) -{ - state = state * 1664525u + 1013904223u; - return double(state) * (1.0 / 4294967296.0); -} - -inline Direction random_unit_dir_lcg(uint32_t &state) -{ - double x1, x2, s; - do { - x1 = rand01(state) * 2.0 - 1.0; - x2 = rand01(state) * 2.0 - 1.0; - s = x1 * x1 + x2 * x2; - } while (s <= 0.0 || s >= 1.0); - - double t = 2.0 * std::sqrt(1.0 - s); - return { x1 * t, x2 * t, 1.0 - 2.0 * s }; -} - -// Generates a random point cloud with radius (--source-radius) -inline std::pair random_spherical_source(const Position& origin, std::uint32_t state, double source_radius) -{ - // Always generate random direction - Direction dir = random_unit_dir_lcg(state); - Position pos = origin; - if (source_radius > 0.0) { - // random origins (spherical source) - double r = source_radius * std::cbrt(rand01(state)); // uniform in ball - pos += dir * r; - } - return {pos, dir}; -} int main(int argc, char** argv) { @@ -187,44 +150,9 @@ int main(int argc, char** argv) { // ---- Random ray generation on device via callback method ---- gen_timer.start(); - - // - User creates their own GPU compute API method to populate rays and passes that to XDG - // - In this miniapp we are using GPRT as a demonstration - // - This callback runs inside populate_rays_external and receives XDG's device buffers gprt_rt = std::dynamic_pointer_cast(xdg->ray_tracing_interface()); - if (!gprt_rt) { - fatal_error("GPRT backend requested but ray tracing interface is not GPRTRayTracer"); - } - - auto generateRaysCallback = [&](const DeviceRayHitBuffers& buffer, size_t numRays) { - GPRTContext context = gprt_rt->context(); - GPRTModule module = gprtModuleCreate(context, ray_benchmark_deviceCode); - auto genRandomRays = gprtComputeCreate( - context, module, "generate_random_rays"); - - constexpr int threadsPerGroup = 64; - const int neededGroups = (int)((numRays + threadsPerGroup - 1) / threadsPerGroup); - const int groups = std::min(neededGroups, WORKGROUP_LIMIT); - - GenerateRandomRayParams randomRayParams = {}; - randomRayParams.rays = static_cast(buffer.rayDevPtr); // Cast opaque pointer to typed dblRay* - randomRayParams.numRays = (uint32_t)numRays; - randomRayParams.source_radius = source_radius; - randomRayParams.origin = { origin.x, origin.y, origin.z }; - randomRayParams.seed = seed; - randomRayParams.total_threads = (uint32_t)(groups * threadsPerGroup); - randomRayParams.volume_mesh_id = volume; - randomRayParams.enabled = 1u; - - gprtComputeLaunch(genRandomRays, - { (uint32_t)groups, 1, 1 }, - { (uint32_t)threadsPerGroup, 1, 1 }, - randomRayParams); - gprtComputeSynchronize(context); - - gprtComputeDestroy(genRandomRays); - gprtModuleDestroy(module); - }; + auto generateRaysCallback = + tools::benchmark::make_generate_rays_callback(gprt_rt->context(), origin, source_radius, seed, volume); // Let XDG internally allocate buffers and invoke the callback to populate them xdg->populate_rays_external(N, generateRaysCallback); @@ -250,7 +178,7 @@ int main(int argc, char** argv) { #pragma omp parallel for schedule(static) for (uint32_t i = 0; i < N; ++i) { uint32_t state = seed ^ i; - auto [pos,dir] = random_spherical_source(origin, state, source_radius); + auto [pos,dir] = tools::benchmark::random_spherical_source(origin, state, source_radius); origins[i] = pos; directions[i] = dir; } diff --git a/tools/ray_benchmark_deviceCode.slang b/tools/ray_benchmark/ray_benchmark_deviceCode.slang similarity index 86% rename from tools/ray_benchmark_deviceCode.slang rename to tools/ray_benchmark/ray_benchmark_deviceCode.slang index e076cb8a..e9a6aefb 100644 --- a/tools/ray_benchmark_deviceCode.slang +++ b/tools/ray_benchmark/ray_benchmark_deviceCode.slang @@ -35,17 +35,6 @@ void generate_random_rays(uint3 DispatchThreadID: SV_DispatchThreadID, } } -[shader("compute")] -[numthreads(1, 1, 1)] -void debug_read_tlas(uint3 DispatchThreadID: SV_DispatchThreadID, - uniform DebugReadTLASParams params) -{ - if (DispatchThreadID.x > 0) return; - params.out[0] = params.table[params.index]; -} - -// Helpers - // Simple LCG random number generator double rand01(inout uint state) { @@ -65,4 +54,4 @@ double3 random_unit_dir_lcg(inout uint state) double t = 2.0 * sqrt(1.0 - s); return double3(x1 * t, x2 * t, 1.0 - 2.0 * s); -} +} \ No newline at end of file diff --git a/tools/ray-benchmark-driver.py b/tools/ray_benchmark/ray_benchmark_driver.py similarity index 100% rename from tools/ray-benchmark-driver.py rename to tools/ray_benchmark/ray_benchmark_driver.py diff --git a/tools/ray_benchmark/ray_benchmark_shared.h b/tools/ray_benchmark/ray_benchmark_shared.h new file mode 100644 index 00000000..a3fffcf0 --- /dev/null +++ b/tools/ray_benchmark/ray_benchmark_shared.h @@ -0,0 +1,14 @@ +#include "gprt.h" + +#include "../../include/xdg/gprt/ray.h" + +struct GenerateRandomRayParams { + xdg::dblRay* rays; + uint numRays; + double3 origin; + uint seed; + uint total_threads; + double source_radius; + int volume_mesh_id; + uint enabled; +}; diff --git a/tools/ray_benchmark_shared.h b/tools/ray_benchmark_shared.h deleted file mode 100644 index 24d93168..00000000 --- a/tools/ray_benchmark_shared.h +++ /dev/null @@ -1,20 +0,0 @@ -#include "gprt.h" - -#include "../include/xdg/gprt/ray.h" - -struct GenerateRandomRayParams { - xdg::dblRay* rays; - uint numRays; - double3 origin; - uint seed; - uint total_threads; - double source_radius; - int volume_mesh_id; - uint enabled; -}; - -struct DebugReadTLASParams { - SurfaceAccelerationStructure* table; - uint index; - SurfaceAccelerationStructure* out; -}; From c6c71fa6624598d8a5a4eae9a26c367fce617659 Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Wed, 28 Jan 2026 16:51:02 +0000 Subject: [PATCH 47/62] Added test for filling ray buffers directly + ray_fire_prepared code path --- include/xdg/gprt/ray.h | 9 ++ include/xdg/gprt/ray_tracer.h | 3 + include/xdg/gprt/shared_structs.h | 9 -- include/xdg/xdg.h | 7 + src/gprt/dbl_deviceCode.slang | 25 ---- src/gprt/ray_tracer.cpp | 18 +++ src/xdg.cpp | 17 ++- tests/CMakeLists.txt | 13 ++ tests/test_direct_ray_buffer_access.cpp | 124 ++++++++++++++++++ ..._direct_ray_buffer_access_deviceCode.slang | 24 ++++ tests/test_direct_ray_buffer_access_shared.h | 14 ++ tests/test_ray_fire.cpp | 16 --- tests/util.h | 17 +++ tools/ray_benchmark/ray_benchmark.h | 97 ++++++++++++++ 14 files changed, 342 insertions(+), 51 deletions(-) create mode 100644 tests/test_direct_ray_buffer_access.cpp create mode 100644 tests/test_direct_ray_buffer_access_deviceCode.slang create mode 100644 tests/test_direct_ray_buffer_access_shared.h create mode 100644 tools/ray_benchmark/ray_benchmark.h diff --git a/include/xdg/gprt/ray.h b/include/xdg/gprt/ray.h index b87734cd..d82eb8d5 100644 --- a/include/xdg/gprt/ray.h +++ b/include/xdg/gprt/ray.h @@ -4,6 +4,15 @@ #include "gprt.h" #include "../shared_enums.h" +/* + * Double-precision ray and hit structures used by the GPRT backend. + * + * These types are not inherently GPRT-specific, but we keep them here for now + * since GPRT is the only GPU backend. If another GPU backend is added, these + * can be reused. Unifying them with the CPU/Embree types is possible, but may + * not be worth the added complexity at this stage. + */ + namespace xdg { struct dblRay diff --git a/include/xdg/gprt/ray_tracer.h b/include/xdg/gprt/ray_tracer.h index 0f99f633..5d80c8d8 100644 --- a/include/xdg/gprt/ray_tracer.h +++ b/include/xdg/gprt/ray_tracer.h @@ -134,6 +134,9 @@ class GPRTRayTracer : public RayTracer { void populate_rays_external(size_t numRays, const RayPopulationCallback& callback) override; + void download_hits(const size_t num_rays, + std::vector& hits); + GPRTContext context() { return context_; diff --git a/include/xdg/gprt/shared_structs.h b/include/xdg/gprt/shared_structs.h index 96df1866..755b48b3 100644 --- a/include/xdg/gprt/shared_structs.h +++ b/include/xdg/gprt/shared_structs.h @@ -47,13 +47,4 @@ struct dblRayFirePushConstants { xdg::HitOrientation hitOrientation; }; -// TODO - Drop this in favour of exposing buffers directly -struct ExternalRayParams { - xdg::dblRay* xdgRays; - double3* origins; - double3* directions; - uint32_t num_rays; - uint32_t total_threads; -}; - #endif diff --git a/include/xdg/xdg.h b/include/xdg/xdg.h index 9351a8d7..477907ad 100644 --- a/include/xdg/xdg.h +++ b/include/xdg/xdg.h @@ -11,6 +11,7 @@ namespace xdg { struct DeviceRayHitBuffers; // forward declaration +struct dblHit; // forward declaration class XDG { public: @@ -201,6 +202,12 @@ Direction surface_normal(MeshID surface, return ray_tracing_interface()->populate_rays_external(numRays, callback); } +// Device to host transfer of hit buffers (GPRT only for now) +#ifdef XDG_ENABLE_GPRT + void transfer_hits_buffer_to_host(const size_t num_rays, + std::vector& hits); +#endif + // Accessors const std::shared_ptr& ray_tracing_interface() const { return ray_tracing_interface_; diff --git a/src/gprt/dbl_deviceCode.slang b/src/gprt/dbl_deviceCode.slang index d19d5e4a..b5332dce 100644 --- a/src/gprt/dbl_deviceCode.slang +++ b/src/gprt/dbl_deviceCode.slang @@ -133,31 +133,6 @@ void populate_aabbs(uint3 DispatchThreadID: SV_DispatchThreadID, uniform DPTrian record.aabbs[2 * primID + 1] = fpaabbmax; } -// TODO - consider dropping this in favour of having external application fill buffers directly -[shader("compute")] -[numthreads(256, 1, 1)] -void pack_external_rays(uint3 DispatchThreadID: SV_DispatchThreadID, uniform ExternalRayParams extParams) { - - // guard against more threads than primitives - Will this ever happen when performing operations on every ray? Probably not - // if (rayID >= extParams.num_rays) - // return; - - // Global thread index (we only use the x-dimension) - uint globalThreadID = DispatchThreadID.x; - uint stride = extParams.total_threads; // Groups * 256 - - // Grid-stride loop: each thread handles ray idx, idx+stride, idx+2*stride, ... - for (uint idx = globalThreadID; idx < extParams.num_rays; idx += stride) - { - xdg::dblRay r; - r.origin = extParams.origins[idx]; - r.direction = extParams.directions[idx]; - r.exclude_primitives = nullptr; - r.exclude_count = 0; - - extParams.xdgRays[idx] = r; - } -} // ------------------------------------------------ CUSTOM INTERSECTION SHADERS ------------------------------------------------ /* 1D ray generation intersection with a double precision triangle using the Plucker intersection algorithm*/ diff --git a/src/gprt/ray_tracer.cpp b/src/gprt/ray_tracer.cpp index f4980b6c..533833e2 100644 --- a/src/gprt/ray_tracer.cpp +++ b/src/gprt/ray_tracer.cpp @@ -604,4 +604,22 @@ void GPRTRayTracer::populate_rays_external(size_t numRays, // Note: The callback is responsible for synchronization if using an async API } +void GPRTRayTracer::download_hits(const size_t num_rays, + std::vector& hits) +{ + if (num_rays == 0) { + hits.clear(); + return; + } + if (num_rays > rayHitBuffers_.view.capacity) { + fatal_error("Requested {} hits, but hit buffer capacity is {}", num_rays, rayHitBuffers_.view.capacity); + } + + hits.resize(num_rays); + gprtBufferMap(rayHitBuffers_.hit); + dblHit* hit = gprtBufferGetHostPointer(rayHitBuffers_.hit); + std::copy(hit, hit + num_rays, hits.begin()); + gprtBufferUnmap(rayHitBuffers_.hit); +} + } // namespace xdg diff --git a/src/xdg.cpp b/src/xdg.cpp index a2a0db0c..c767e5a8 100644 --- a/src/xdg.cpp +++ b/src/xdg.cpp @@ -9,6 +9,9 @@ #include "xdg/mesh_managers.h" #include "xdg/ray_tracers.h" +#ifdef XDG_ENABLE_GPRT +#include "xdg/gprt/ray.h" +#endif namespace xdg { @@ -52,6 +55,18 @@ void XDG::prepare_volume_for_raytracing(MeshID volume) { volume_to_point_location_tree_map_[volume] = volume_tree; } +#ifdef XDG_ENABLE_GPRT +void XDG::transfer_hits_buffer_to_host(const size_t num_rays, + std::vector& hits) +{ + auto gprt_rt = std::dynamic_pointer_cast(ray_tracing_interface()); + if (!gprt_rt) { + fatal_error("transfer_hits_buffer_to_host is only supported with the GPRT ray tracer"); + } + gprt_rt->download_hits(num_rays, hits); +} +#endif + std::shared_ptr XDG::create(MeshLibrary mesh_lib, RTLibrary ray_tracing_lib) { std::shared_ptr xdg = std::make_shared(); @@ -362,4 +377,4 @@ double XDG::measure_volume_area(MeshID volume) const return area; } -} // namespace xdg \ No newline at end of file +} // namespace xdg diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d7286555..84805279 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -18,6 +18,7 @@ test_tet_containment test_tracks test_tet_intersection test_tally_segments +test_direct_ray_buffer_access ) if (XDG_ENABLE_MOAB) @@ -48,6 +49,18 @@ foreach(test ${TEST_NAMES}) TEST_PREFIX "${test}::") endforeach() +if (XDG_ENABLE_GPRT) + embed_devicecode( + OUTPUT_TARGET + test_direct_ray_buffer_access_deviceCode + HEADERS + ${CMAKE_CURRENT_SOURCE_DIR}/test_direct_ray_buffer_access_shared.h + SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/test_direct_ray_buffer_access_deviceCode.slang + ) + target_link_libraries(test_direct_ray_buffer_access test_direct_ray_buffer_access_deviceCode) +endif() + set( TEST_FILES diff --git a/tests/test_direct_ray_buffer_access.cpp b/tests/test_direct_ray_buffer_access.cpp new file mode 100644 index 00000000..309ce9e9 --- /dev/null +++ b/tests/test_direct_ray_buffer_access.cpp @@ -0,0 +1,124 @@ +// for testing +#include +#include +#include +#include + +// xdg includes +#include "xdg/constants.h" +#include "xdg/mesh_manager_interface.h" +#include "xdg/gprt/ray_tracer.h" +#include "xdg/xdg.h" +#include "mesh_mock.h" +#include "test_direct_ray_buffer_access_shared.h" +#include "util.h" +#include "gprt.h" + +#include + +using namespace xdg; +using namespace xdg::test; + +extern GPRTProgram test_direct_ray_buffer_access_deviceCode; + +// This is a GPU only test - skip if no GPU ray tracing backends are enabled +TEMPLATE_TEST_CASE("Ray Fire with external populated rays", "[rayfire][mock]", + GPRT_Raytracer) +{ + constexpr auto rt_backend = TestType::value; + + DYNAMIC_SECTION(fmt::format("Backend = {}", rt_backend)) { + check_ray_tracer_supported(rt_backend); + + auto rti = create_raytracer(rt_backend); + REQUIRE(rti); + + auto mm = std::make_shared(false); + mm->init(); + REQUIRE(mm->mesh_library() == MeshLibrary::MOCK); + + auto xdg = std::make_shared(); + xdg->set_mesh_manager_interface(mm); + xdg->set_ray_tracing_interface(rti); + xdg->prepare_raytracer(); + + std::vector origins; + std::vector directions; + size_t N = 64; + make_rays(N, origins, directions); + + auto gprt_rt = std::dynamic_pointer_cast(rti); + REQUIRE(gprt_rt); + + const MeshID volume_id = mm->volumes()[0]; + GPRTContext context = gprt_rt->context(); + GPRTModule module = gprtModuleCreate(context, test_direct_ray_buffer_access_deviceCode); + auto packRays = gprtComputeCreate(context, module, "pack_external_rays"); + + std::vector expected_distances(N, INFTY); + std::vector expected_surfaces(N, ID_NONE); + xdg->ray_fire(volume_id, + origins.data(), + directions.data(), + N, + expected_distances.data(), + expected_surfaces.data()); + + // Create callback to populate rays on device + RayPopulationCallback populate_callback = [&volume_id, &origins, &directions, &context, &packRays] + (const DeviceRayHitBuffers& buffer, size_t numRays) { + REQUIRE(origins.size() >= numRays); + REQUIRE(directions.size() >= numRays); + + // Convert to double3 for use on GPU + std::vector origins_device(numRays); + std::vector directions_device(numRays); + for (size_t i = 0; i < numRays; ++i) { + origins_device[i] = {origins[i].x, origins[i].y, origins[i].z}; + directions_device[i] = {directions[i].x, directions[i].y, directions[i].z}; + } + + auto origins_buffer = gprtDeviceBufferCreate(context, numRays, origins_device.data()); + auto directions_buffer = gprtDeviceBufferCreate(context, numRays, directions_device.data()); + + constexpr uint32_t threads_per_group = 256; + const uint32_t groups = static_cast((numRays + threads_per_group - 1) / threads_per_group); + + ExternalRayParams params = {}; + params.xdgRays = static_cast(buffer.rayDevPtr); + params.origins = gprtBufferGetDevicePointer(origins_buffer); + params.directions = gprtBufferGetDevicePointer(directions_buffer); + params.num_rays = static_cast(numRays); + params.total_threads = groups * threads_per_group; + params.volume_mesh_id = volume_id; + params.enabled = 1u; + + gprtComputeLaunch(packRays, + { groups, 1, 1 }, + { threads_per_group, 1, 1 }, + params); + gprtComputeSynchronize(context); + + gprtBufferDestroy(origins_buffer); + gprtBufferDestroy(directions_buffer); + }; + + // Populate rays via external API + xdg->populate_rays_external(N, populate_callback); + + xdg->ray_fire_prepared(volume_id, N); + std::vector hits; + xdg->transfer_hits_buffer_to_host(N, hits); + + REQUIRE(hits.size() == N); + for (size_t i = 0; i < N; ++i) { + REQUIRE(hits[i].surf_id == expected_surfaces[i]); + if (expected_surfaces[i] != ID_NONE) { + REQUIRE_THAT(hits[i].distance, Catch::Matchers::WithinAbs(expected_distances[i], 1e-6)); + } + } + + gprtComputeDestroy(packRays); + gprtModuleDestroy(module); + } +} diff --git a/tests/test_direct_ray_buffer_access_deviceCode.slang b/tests/test_direct_ray_buffer_access_deviceCode.slang new file mode 100644 index 00000000..a3e8729a --- /dev/null +++ b/tests/test_direct_ray_buffer_access_deviceCode.slang @@ -0,0 +1,24 @@ +#include "test_direct_ray_buffer_access_shared.h" + +[shader("compute")] +[numthreads(256, 1, 1)] +void pack_external_rays(uint3 DispatchThreadID: SV_DispatchThreadID, + uniform ExternalRayParams extParams) +{ + uint globalThreadID = DispatchThreadID.x; + uint stride = extParams.total_threads; // Groups * 256 + + // Grid-stride loop: each thread handles ray idx, idx+stride, idx+2*stride, ... + for (uint idx = globalThreadID; idx < extParams.num_rays; idx += stride) + { + xdg::dblRay r; + r.origin = extParams.origins[idx]; + r.direction = extParams.directions[idx]; + r.exclude_primitives = nullptr; + r.exclude_count = 0; + r.volume_mesh_id = extParams.volume_mesh_id; + r.enabled = extParams.enabled; + + extParams.xdgRays[idx] = r; + } +} diff --git a/tests/test_direct_ray_buffer_access_shared.h b/tests/test_direct_ray_buffer_access_shared.h new file mode 100644 index 00000000..a8e5d553 --- /dev/null +++ b/tests/test_direct_ray_buffer_access_shared.h @@ -0,0 +1,14 @@ +#include "gprt.h" + +#include "../include/xdg/gprt/ray.h" + +struct ExternalRayParams { + xdg::dblRay* xdgRays; + double3* origins; + double3* directions; + uint num_rays; + uint total_threads; + int volume_mesh_id; + uint enabled; +}; + diff --git a/tests/test_ray_fire.cpp b/tests/test_ray_fire.cpp index 0b7e0522..552a4ed6 100644 --- a/tests/test_ray_fire.cpp +++ b/tests/test_ray_fire.cpp @@ -137,22 +137,6 @@ TEMPLATE_TEST_CASE("Batch API Ray Fire on MeshMock", "[rayfire][mock][batch]", rti->init(); - // Helper to synthesize origins/directions for N rays - auto make_rays = [](size_t N, std::vector& origins, std::vector& directions) { - origins.clear(); - directions.clear(); - origins.reserve(N); - directions.reserve(N); - for (size_t i = 0; i < N; ++i) { - int axis = int(i % 3); - double s = (i % 2) ? 1.0 : -1.0; - origins.push_back({0.0, 0.0, 0.0}); - if (axis == 0) directions.push_back({s, 0.0, 0.0}); - else if (axis == 1) directions.push_back({0.0, s, 0.0}); - else directions.push_back({0.0, 0.0, s}); - } - }; - std::vector origins; std::vector directions; size_t N; diff --git a/tests/util.h b/tests/util.h index 579ff652..80964602 100644 --- a/tests/util.h +++ b/tests/util.h @@ -96,3 +96,20 @@ create_raytracer(xdg::RTLibrary rt) { return nullptr; } + +inline void make_rays(size_t N, std::vector& origins, std::vector& directions) +{ + origins.clear(); + directions.clear(); + origins.reserve(N); + directions.reserve(N); + for (size_t i = 0; i < N; ++i) { + int axis = static_cast(i % 3); + double s = (i % 2) ? 1.0 : -1.0; + origins.push_back({0.0, 0.0, 0.0}); + if (axis == 0) directions.push_back({s, 0.0, 0.0}); + else if (axis == 1) directions.push_back({0.0, s, 0.0}); + else directions.push_back({0.0, 0.0, s}); + } +} + diff --git a/tools/ray_benchmark/ray_benchmark.h b/tools/ray_benchmark/ray_benchmark.h new file mode 100644 index 00000000..52b29d62 --- /dev/null +++ b/tools/ray_benchmark/ray_benchmark.h @@ -0,0 +1,97 @@ +#ifndef _XDG_RAY_BENCHMARK_H +#define _XDG_RAY_BENCHMARK_H + +#include +#include +#include + +#include "gprt/gprt.h" +#include "xdg/gprt/ray.h" +#include "xdg/gprt/ray_tracer.h" +#include "xdg/vec3da.h" +#include "xdg/xdg.h" + +#include "ray_benchmark_shared.h" + +extern GPRTProgram ray_benchmark_deviceCode; + +namespace xdg::tools::benchmark { + +inline double rand01(uint32_t &state) +{ + state = state * 1664525u + 1013904223u; + return double(state) * (1.0 / 4294967296.0); +} + +inline Direction random_unit_dir_lcg(uint32_t &state) +{ + double x1, x2, s; + do { + x1 = rand01(state) * 2.0 - 1.0; + x2 = rand01(state) * 2.0 - 1.0; + s = x1 * x1 + x2 * x2; + } while (s <= 0.0 || s >= 1.0); + + double t = 2.0 * std::sqrt(1.0 - s); + return { x1 * t, x2 * t, 1.0 - 2.0 * s }; +} + +// Generates a random point cloud with radius (--source-radius) +inline std::pair random_spherical_source(const Position& origin, + std::uint32_t state, + double source_radius) +{ + // Always generate random direction + Direction dir = random_unit_dir_lcg(state); + Position pos = origin; + if (source_radius > 0.0) { + // random origins (spherical source) + double r = source_radius * std::cbrt(rand01(state)); // uniform in ball + pos += dir * r; + } + return {pos, dir}; +} + +// - User creates their own GPU compute API method to populate rays and passes that to XDG +// - In this miniapp we are using GPRT as a demonstration +// - This callback runs inside populate_rays_external and receives XDG's device buffers +inline RayPopulationCallback make_generate_rays_callback(GPRTContext gprt_context, + Position origin, + double source_radius, + uint32_t seed, + MeshID volume) +{ + return [gprt_context, origin, source_radius, seed, volume](const DeviceRayHitBuffers& buffer, size_t numRays) { + GPRTContext context = gprt_context; + GPRTModule module = gprtModuleCreate(context, ray_benchmark_deviceCode); + auto genRandomRays = gprtComputeCreate( + context, module, "generate_random_rays"); + + constexpr int threadsPerGroup = 64; + const int neededGroups = static_cast((numRays + threadsPerGroup - 1) / threadsPerGroup); + const int groups = std::min(neededGroups, WORKGROUP_LIMIT); + + GenerateRandomRayParams randomRayParams = {}; + randomRayParams.rays = static_cast(buffer.rayDevPtr); // Cast opaque pointer to typed dblRay* + randomRayParams.numRays = static_cast(numRays); + randomRayParams.source_radius = source_radius; + randomRayParams.origin = { origin.x, origin.y, origin.z }; + randomRayParams.seed = seed; + randomRayParams.total_threads = static_cast(groups * threadsPerGroup); + randomRayParams.volume_mesh_id = volume; + randomRayParams.enabled = 1u; + + gprtComputeLaunch(genRandomRays, + { static_cast(groups), 1, 1 }, + { static_cast(threadsPerGroup), 1, 1 }, + randomRayParams); + gprtComputeSynchronize(context); + + gprtComputeDestroy(genRandomRays); + gprtModuleDestroy(module); + }; +} + +} // namespace xdg::tools::benchmark + +#endif // _XDG_RAY_BENCHMARK_H From 6aabf14f5fa3228c6f1f9f65fb9926ab9367bd70 Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Thu, 29 Jan 2026 17:39:14 +0000 Subject: [PATCH 48/62] Removed header causing compilation error + gated test for direct buffer access behind XDG_ENABLE_GPRT --- include/xdg/gprt/ray_tracer.h | 1 - tests/CMakeLists.txt | 6 +++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/include/xdg/gprt/ray_tracer.h b/include/xdg/gprt/ray_tracer.h index d081b899..3001da33 100644 --- a/include/xdg/gprt/ray_tracer.h +++ b/include/xdg/gprt/ray_tracer.h @@ -10,7 +10,6 @@ #include "xdg/ray_tracing_interface.h" #include "xdg/error.h" -#include "gprt/gprt.h" #include "shared_structs.h" extern GPRTProgram dbl_deviceCode; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 84805279..8166eee9 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -18,7 +18,6 @@ test_tet_containment test_tracks test_tet_intersection test_tally_segments -test_direct_ray_buffer_access ) if (XDG_ENABLE_MOAB) @@ -38,6 +37,11 @@ if (XDG_ENABLE_MOAB) list(APPEND TEST_NAMES test_overlap_check) endif() +# This test really should be appended when any GPU library is enabled +if (XDG_ENABLE_GPRT) + list(APPEND TEST_NAMES test_direct_ray_buffer_access) +endif() + foreach(test ${TEST_NAMES}) add_executable(${test} ${test}.cpp) target_link_libraries(${test} xdg Catch2::Catch2WithMain) From 9192c19c3fab409571dad4e9f0e3d74204e427c9 Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Fri, 30 Jan 2026 10:51:48 +0000 Subject: [PATCH 49/62] Fixed redundant include causing libmesh only build to fail --- tools/batch_point_in_volume.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/batch_point_in_volume.cpp b/tools/batch_point_in_volume.cpp index 69dadd57..56ab46c4 100644 --- a/tools/batch_point_in_volume.cpp +++ b/tools/batch_point_in_volume.cpp @@ -7,7 +7,6 @@ #include "xdg/error.h" #include "xdg/mesh_manager_interface.h" -#include "xdg/moab/mesh_manager.h" #include "xdg/vec3da.h" #include "xdg/xdg.h" From 5f930ad96bcedf46e8d09a7343c440cd1250e4e2 Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Fri, 30 Jan 2026 11:46:20 +0000 Subject: [PATCH 50/62] Core API changes to allow batch queries with XDG --- include/xdg/gprt/ray.h | 40 ++++ include/xdg/gprt/ray_tracer.h | 81 +++++-- include/xdg/gprt/shared_structs.h | 36 ++-- include/xdg/ray_tracing_interface.h | 197 ++++++++++++++++- include/xdg/xdg.h | 103 +++++++++ src/gprt/dbl_deviceCode.slang | 64 +++--- src/gprt/ray_tracer.cpp | 317 +++++++++++++++++++++++++--- src/tetrahedron_contain.cpp | 4 +- src/xdg.cpp | 56 ++++- tests/test_files | 2 +- tests/test_point_in_volume.cpp | 1 - vendor/GPRT | 2 +- 12 files changed, 796 insertions(+), 107 deletions(-) create mode 100644 include/xdg/gprt/ray.h diff --git a/include/xdg/gprt/ray.h b/include/xdg/gprt/ray.h new file mode 100644 index 00000000..d82eb8d5 --- /dev/null +++ b/include/xdg/gprt/ray.h @@ -0,0 +1,40 @@ +#ifndef _XDG_GPRT_RAY_H +#define _XDG_GPRT_RAY_H + +#include "gprt.h" +#include "../shared_enums.h" + +/* + * Double-precision ray and hit structures used by the GPRT backend. + * + * These types are not inherently GPRT-specific, but we keep them here for now + * since GPRT is the only GPU backend. If another GPU backend is added, these + * can be reused. Unifying them with the CPU/Embree types is possible, but may + * not be worth the added complexity at this stage. + */ + +namespace xdg { + +struct dblRay +{ + double3 origin; + double3 direction; + int volume_mesh_id; // MeshID of the volume this ray will be traced against + uint enabled; // Flag to indicate if the ray is active + int32_t* exclude_primitives; // Optional for excluding primitives + int32_t exclude_count; // Number of excluded primitives +}; + + +struct dblHit +{ + double distance; + int surf_id; + int primitive_id; + PointInVolume piv; // Point in volume check result (0 for outside, 1 for inside) +}; + +} + + +#endif diff --git a/include/xdg/gprt/ray_tracer.h b/include/xdg/gprt/ray_tracer.h index 8d24d107..3001da33 100644 --- a/include/xdg/gprt/ray_tracer.h +++ b/include/xdg/gprt/ray_tracer.h @@ -7,12 +7,9 @@ #include "xdg/constants.h" #include "xdg/mesh_manager_interface.h" -#include "xdg/primitive_ref.h" -#include "xdg/geometry_data.h" #include "xdg/ray_tracing_interface.h" -#include "xdg/ray.h" #include "xdg/error.h" -#include "gprt/gprt.h" + #include "shared_structs.h" extern GPRTProgram dbl_deviceCode; @@ -26,17 +23,16 @@ enum class RayGenType { }; struct gprtRayHit { - size_t capacity = 1; // Max number of rays allocated - size_t size = 0; // Current number of active rays + DeviceRayHitBuffers view; // external facing POD for rayhit buffers + size_t size = 0; // Current number of active rays GPRTBufferOf ray = nullptr; GPRTBufferOf hit = nullptr; - dblRay* devRayAddr = nullptr; - dblHit* devHitAddr = nullptr; - bool is_valid() const { return capacity > 0 && ray && hit && devRayAddr && devHitAddr; } + bool is_valid() const { + return view.capacity > 0 && ray && hit && view.rayDevPtr && view.hitDevPtr; + } }; - class GPRTRayTracer : public RayTracer { public: GPRTRayTracer(); @@ -83,12 +79,32 @@ class GPRTRayTracer : public RayTracer { const Direction* direction = nullptr, const std::vector* exclude_primitives = nullptr) const override; + void point_in_volume(TreeID tree, + const Position* points, + const size_t num_points, + uint8_t* results, + const Direction* directions = nullptr, + std::vector* exclude_primitives = nullptr) override; + std::pair ray_fire(TreeID scene, const Position& origin, const Direction& direction, const double dist_limit = INFTY, HitOrientation orientation = HitOrientation::EXITING, std::vector* const exclude_primitives = nullptr) override; + void ray_fire(TreeID tree, + const Position* origins, + const Direction* directions, + const size_t num_rays, + double* hitDistances, + MeshID* surfaceIDs, + const double dist_limit = INFTY, + HitOrientation orientation = HitOrientation::EXITING, + std::vector* const exclude_primitives = nullptr) override; + + void ray_fire_prepared(const size_t num_rays, + const double dist_limit = INFTY, + HitOrientation orientation = HitOrientation::EXITING) override; std::pair closest(TreeID scene, const Position& origin) override {}; @@ -100,9 +116,41 @@ class GPRTRayTracer : public RayTracer { fatal_error("Occlusion queries are not currently supported with GPRT ray tracer"); return false; } - + + // Check to see if buffers large enough and resize if not + void check_rayhit_buffer_capacity(const size_t N) override; + + // Method to expose device ray and hit buffers for external population + DeviceRayHitBuffers get_device_rayhit_buffers(const size_t N) override; + + /** + * @brief Allocate device buffers and invoke a callback to populate them + * + * This method enables downstream applications to populate ray buffers using + * any compute API (GPRT, CUDA, HIP, etc.) without XDG needing to know the details. + */ + void populate_rays_external(size_t numRays, + const RayPopulationCallback& callback) override; + + void download_hits(const size_t num_rays, + std::vector& hits); + + GPRTContext context() + { + return context_; + } + + SurfaceAccelerationStructure* tlas_handle_device_ptr() const + { + return gprtBufferGetDevicePointer(tlas_handle_buffer_); + } + + size_t tlas_handle_count() const + { + return tlas_handles_.size(); + } + private: - void check_ray_buffer_capacity(size_t N); // GPRT objects GPRTContext context_; @@ -133,7 +181,12 @@ class GPRTRayTracer : public RayTracer { // Internal GPRT Mappings std::unordered_map surface_volume_tree_to_accel_map; // Map from XDG::TreeID to GPRTAccel for volume TLAS - std::vector blas_handles_; // Store BLAS handles so that they can be explicitly referenced in destructor + std::unordered_map surface_tree_to_volume_map_; + std::vector tlas_handles_; // Host side storage of TLAS device addresses + GPRTBufferOf tlas_handle_buffer_; // Device buffer for TLAS addresses + bool initialized_ {false}; // flag to indicate if init() has been called + + void update_tlas_table_(); // Global Tree IDs GPRTAccel global_surface_accel_ {nullptr}; @@ -143,4 +196,4 @@ class GPRTRayTracer : public RayTracer { } // namespace xdg -#endif // include guard \ No newline at end of file +#endif // include guard diff --git a/include/xdg/gprt/shared_structs.h b/include/xdg/gprt/shared_structs.h index 3855775a..755b48b3 100644 --- a/include/xdg/gprt/shared_structs.h +++ b/include/xdg/gprt/shared_structs.h @@ -1,5 +1,9 @@ +#ifndef XDG_GPRT_SHARED_STRUCTS_H +#define XDG_GPRT_SHARED_STRUCTS_H + #include "gprt.h" #include "../shared_enums.h" +#include "ray.h" struct GPRTPrimitiveRef { @@ -7,26 +11,6 @@ struct GPRTPrimitiveRef int sense; }; -struct dblRay -{ - double3 origin; - double3 direction; - double tMin; // Minimum distance for ray intersection - double tMax; // Maximum distance for ray intersection - int32_t* exclude_primitives; // Optional for excluding primitives - int32_t exclude_count; // Number of excluded primitives - xdg::HitOrientation hitOrientation; - int volume_tree; // TreeID of the volume being queried - SurfaceAccelerationStructure volume_accel; // The volume accel -}; - -struct dblHit -{ - double distance; - int surf_id; - int primitive_id; - xdg::PointInVolume piv; // Point in volume check result (0 for outside, 1 for inside) -}; /* variables for double precision triangle mesh geometry */ struct DPTriangleGeomData { @@ -38,7 +22,7 @@ struct DPTriangleGeomData { int2 vols; int forward_vol; int reverse_vol; - dblRay *ray; // double precision rays + xdg::dblRay *ray; // double precision rays xdg::HitOrientation hitOrientation; int forward_tree; // TreeID of the forward volume int reverse_tree; // TreeID of the reverse volume @@ -47,8 +31,9 @@ struct DPTriangleGeomData { }; struct dblRayGenData { - dblRay *ray; - dblHit *hit; + xdg::dblRay *ray; + xdg::dblHit *hit; + SurfaceAccelerationStructure* meshid_to_accel_address; // MeshID->TLAS address table to recover volume to trace against }; /* A small structure of constants that can change every frame without rebuilding the @@ -57,4 +42,9 @@ struct dblRayGenData { struct dblRayFirePushConstants { double tMax; double tMin; + SurfaceAccelerationStructure volume_accel; + int volume_tree; + xdg::HitOrientation hitOrientation; }; + +#endif diff --git a/include/xdg/ray_tracing_interface.h b/include/xdg/ray_tracing_interface.h index 9d938978..6ab2334e 100644 --- a/include/xdg/ray_tracing_interface.h +++ b/include/xdg/ray_tracing_interface.h @@ -4,7 +4,9 @@ #include #include #include +#include +#include "xdg/error.h" #include "xdg/constants.h" #include "xdg/embree_interface.h" #include "xdg/mesh_manager_interface.h" @@ -14,6 +16,46 @@ namespace xdg { +/** + * @brief Device ray/hit buffer descriptor + * + * This structure provides access to device-allocated ray and hit buffers + * in a backend-agnostic way. The buffers contain XDG's standard ray and hit + * data structures (dblRay and dblHit), regardless of which compute backend + * is being used. + * + * Key design principle: + * - Device pointers are opaque (void*) + * - The data layout is always the XDG types dblRay and dblHit + * - Downstream code can write to these buffers (hopefully) using any compute API + * + * For type-safe access in downstream code: + * - Cast rayDevPtr to (dblRay*) when using C++ or kernels + * - Cast hitDevPtr to (dblHit*) when reading hit results + */ +struct DeviceRayHitBuffers { + void* rayDevPtr; + void* hitDevPtr; + size_t capacity; // Number of rays the buffer can hold + size_t rayStride; // Bytes between ray elements - sizeof(dblRay) + size_t hitStride; // Bytes between hit elements - sizeof(dblHit) +}; + +/** + * @brief Callback alias for external ray population + * + * Allows downstream applications to populate ray buffers using their own compute backend + * (GPRT, CUDA, OpenMP) without XDG needing to know the specifics. + * + * The callback receives opaque device pointers and should interpret them according to + * the buffer metadata (stride information). Alternatively, users can rely on the standard + * dblRay/dblHit layouts if they don't need custom padding/alignment. + * + * @param buffer Device ray buffer descriptor with opaque pointers and metadata + * @param numRays Number of rays to generate/populate + */ +using RayPopulationCallback = std::function; + class RayTracer { public: // Constructors/Destructors @@ -73,12 +115,41 @@ class RayTracer { */ virtual void create_global_element_tree() = 0; - // Query Methods + /** + * @brief Check whether a point lies in a specified volume + * + * This method performs a check to see whether a given point is inside a volume provided. + * It computes this by firing a ray from the point and checking whether or not the ray is Entering or Exiting + * the volume boundary. If no direction is provided, a default direction will be used. + * Note - zero length direction vectors are not explicitly checked for internally and should be avoided to avoid causing undefined behavior. + * + * @param[in] tree The TreeID of the volume we are querying against + * @param[in] point The point to be queried + * @param[in] direction (optional) direction to launch a ray in a specified direction - must be non-zero length + * @param[in] exclude_primitives (optional) vector of surface element MeshIDs to exclude from intersection tests + * @return Boolean result of point in volume check + */ virtual bool point_in_volume(TreeID tree, const Position& point, const Direction* direction = nullptr, const std::vector* exclude_primitives = nullptr) const = 0; - + + /** + * @brief Fire a ray against a given volume and return the first hit + * + * This method fires a ray from a given origin in a specified direction against the surfaces of a volume. + * It returns the distance to the closest hit and the MeshID of the surface hit. The user can specify + * a distance limit and whether Entering/Exiting hits should be rejected. + * Note - zero length direction vectors are not explicitly checked for internally and should be avoided to avoid causing undefined behavior. + * + * @param[in] tree The TreeID of the volume we are querying against + * @param[in] origin An array of Position objects representing the starting points of the rays + * @param[in] direction (optional) Direction object to launch a ray in a specified direction + * @param[in] dist_limit (optional) maximum distance to consider for intersections + * @param[in] orientation (optional) flag to consider whether Entering/Exiting hits should be rejected. Defaults to EXITING + * @param[in] exclude_primitives (optional) vector of surface element MeshIDs to exclude from intersection tests + * @return A pair containing the distance to the closest hit and the MeshID of the surface hit + */ virtual std::pair ray_fire(TreeID tree, const Position& origin, const Direction& direction, @@ -123,6 +194,126 @@ class RayTracer { int num_registered_surface_trees() const { return surface_trees_.size(); }; int num_registered_element_trees() const { return element_trees_.size(); }; + + // GPU Ray Tracing Support + + + /** + * @brief Array based version of point_in_volume query + * + * This method performs a set of point_in_volume queries on a batch of rays defined by their origins and directions. + * It computes whether or not a point lies in a given volume for each point in the batch. With GPRT ray tracing + * this launches the RT pipeline with the number of rays provided. + * + * @param[in] tree The TreeID of the volume we are querying against + * @param[in] points An array of points to query + * @param[in] num_points The number of points to be processed in the batch + * @param[out] results An output array to store the computed results for each point (1 if inside volume, 0 if outside) + * @param[in] directions (optional) array of directions to launch rays in explicit directions per point - these must be non-zero length + * @param[in] exclude_primitives (optional) vector of surface element MeshIDs to exclude from intersection tests + * @return Void. Outputs stored in results array + */ + virtual void point_in_volume(TreeID tree, + const Position* points, + const size_t num_points, + uint8_t* results, + const Direction* directions = nullptr, + std::vector* exclude_primitives = nullptr) + { + fatal_error("GPU ray tracing not supported with this RayTracer backend"); + } + /** + * @brief Array based version of ray_fire query + * + * This method performs a set of ray fire queries on a batch of rays defined by their origins and directions. + * It computes the intersection distances and surface IDs for each ray in the batch. With GPRT ray tracing + * this launches the RT pipeline with the number of rays provided. + * + * @param[in] tree The TreeID of the volume we are querying against + * @param[in] origins An array of Position objects representing the starting points of the rays + * @param[in] directions An array of Direction objects representing the directions of the rays + * @param[in] num_rays The number of rays to be processed in the batch + * @param[out] hitDistances An output array to store the computed intersection distances for each ray + * @param[out] surfaceIDs An output array to store the MeshIDs of the surfaces hit by each ray + * @param[in] dist_limit (optional) maximum distance to consider for intersections + * @param[in] orientation (optional) flag to consider whether Entering/Exiting hits should be rejected. Defaults to EXITING + * @param[in] exclude_primitives (optional) vector of surface element MeshIDs to exclude from intersection tests + * @return Void. Outputs stored in hitDistances and surfaceIDs arrays + */ + virtual void ray_fire(TreeID tree, + const Position* origins, + const Direction* directions, + const size_t num_rays, + double* hitDistances, + MeshID* surfaceIDs, + const double dist_limit = INFTY, + HitOrientation orientation = HitOrientation::EXITING, + std::vector* const exclude_primitives = nullptr) + { + fatal_error("GPU ray tracing not supported with this RayTracer backend"); + } + /** + * @brief Array based version of ray_fire query which assumes ray buffers are already populated on device + * + * This method assumes that ray buffers have been externally populated and simply calls the ray tracing pipeline + * to perform a set of ray fire queries on a batch of rays defined by their origins and directions. + * It computes the intersection distances and surface IDs for each ray in the batch. With GPRT ray tracing + * this launches the RT pipeline with the number of rays provided. The results are stored in the output arrays on device. + * + * @param[in] tree The TreeID of the volume we are querying against + * @param[in] num_rays The number of rays to be processed in the batch + * @param[in] dist_limit (optional) maximum distance to consider for intersections + * @param[in] orientation (optional) flag to consider whether Entering/Exiting hits should be rejected. Defaults to EXITING + * @return Void. Outputs stored in dblHit buffer on device + */ + virtual void ray_fire_prepared(const size_t num_rays, + const double dist_limit = INFTY, + HitOrientation orientation = HitOrientation::EXITING) + { + fatal_error("GPU ray tracing not supported with this RayTracer backend"); + } + + /** + * @brief Check whether the current ray buffer capacity is sufficient for the number of rays requested + * @param[in] num_rays The number of rays to be processed + */ + virtual void check_rayhit_buffer_capacity(const size_t num_rays) { + fatal_error("GPU ray tracing not supported with this RayTracer backend"); + } + + /** + * @brief return device pointers to ray and hit buffers for GPU ray tracing + * @return DeviceRayHitBuffers struct containing device pointers to ray and hit buffers + */ + virtual DeviceRayHitBuffers get_device_rayhit_buffers(const size_t num_rays) { + fatal_error("GPU ray tracing not supported with this RayTracer backend"); + return {}; + } + + /** + * @brief Allocate device ray buffers and populate them via a user-provided callback + * + * This method allows downstream applications to populate ray buffers using any compute + * backend (GPRT, CUDA, HIP, OpenCL, etc.) without coupling them to XDG's internals. + * + * The workflow: + * 1. XDG allocates device memory for rays (if not already large enough) + * 2. XDG passes device pointers to the callback + * 3. User's callback populates the buffers using their preferred compute kernel/shader + * 4. User's callback returns (XDG assumes buffers are now populated) + * 5. Call xdg::ray_fire_prepared() to trace the populated rays + * + * This avoids unnecessary host-device transfers by allowing users to write directly + * to XDG's device buffers without any host-side transfers. + * + * @param numRays Number of rays to allocate space for + * @param callback Function that will populate the ray buffer. Receives the allocated buffer and ray count. + */ + virtual void populate_rays_external(size_t numRays, + const RayPopulationCallback& callback) { + fatal_error("GPU ray tracing not supported with this RayTracer backend"); + } + protected: // Common functions across RayTracers const double bounding_box_bump(const std::shared_ptr mesh_manager, MeshID volume_id); // return a bump value based on the size of a bounding box (minimum 1e-3). Should this be a part of mesh_manager? @@ -150,4 +341,4 @@ class RayTracer { } // namespace xdg -#endif // include guard \ No newline at end of file +#endif // include guard diff --git a/include/xdg/xdg.h b/include/xdg/xdg.h index 82c1f8f2..4190c2ea 100644 --- a/include/xdg/xdg.h +++ b/include/xdg/xdg.h @@ -10,6 +10,8 @@ namespace xdg { +struct DeviceRayHitBuffers; // forward declaration +struct dblHit; // forward declaration class XDG { public: @@ -63,11 +65,40 @@ next_element(MeshID current_element, const Position& r, const Direction& u) const; +/** + * @brief Check whether a point lies in a specified volume + * + * This method performs a check to see whether a given point is inside a volume provided. + * It computes this by firing a ray from the point and checking whether or not the ray is Entering or Exiting + * the volume boundary. If no direction is provided, a default direction will be used. + * Note - zero length direction vectors are not explicitly checked for internally and should be avoided to avoid causing undefined behavior. + * + * @param[in] tree The TreeID of the volume we are querying against + * @param[in] point The point to be queried + * @param[in] direction (optional) direction to launch a ray in a specified direction - must be non-zero length + * @param[in] exclude_primitives (optional) vector of surface element MeshIDs to exclude from intersection tests + * @return Boolean result of point in volume check + */ bool point_in_volume(MeshID volume, const Position point, const Direction* direction = nullptr, const std::vector* exclude_primitives = nullptr) const; +/** + * @brief Fire a ray against a given volume and return the first hit + * + * This method fires a ray from a given origin in a specified direction against the surfaces of a volume. + * It returns the distance to the closest hit and the MeshID of the surface hit. The user can specify + * a distance limit and whether Entering/Exiting hits should be rejected. + * + * @param[in] volume The MeshID of the volume we are querying against + * @param[in] origin An array of Position objects representing the starting points of the rays + * @param[in] direction (optional) Direction object to launch a ray in a specified direction + * @param[in] dist_limit (optional) maximum distance to consider for intersections + * @param[in] orientation (optional) flag to consider whether Entering/Exiting hits should be rejected. Defaults to EXITING + * @param[in] exclude_primitives (optional) vector of surface element MeshIDs to exclude from intersection tests + * @return A pair containing the distance to the closest hit and the MeshID of the surface hit + */ std::pair ray_fire(MeshID volume, const Position& origin, const Direction& direction, @@ -75,6 +106,60 @@ std::pair ray_fire(MeshID volume, HitOrientation orientation = HitOrientation::EXITING, std::vector* const exclude_primitives = nullptr) const; +/** + * @brief Array based version of point_in_volume query + * + * This method performs a set of point_in_volume queries on a batch of rays defined by their origins and directions. + * It computes whether or not a point lies in a given volume for each point in the batch. With GPRT ray tracing + * this launches the RT pipeline with the number of rays provided. + * + * @param[in] tree The TreeID of the volume we are querying against + * @param[in] points An array of points to query + * @param[in] num_points The number of points to be processed in the batch + * @param[out] results An output array to store the computed results for each point (1 if inside volume, 0 if outside) + * @param[in] directions (optional) array of directions to launch rays in explicit directions per point - these must be non-zero length + * @param[in] exclude_primitives (optional) vector of surface element MeshIDs to exclude from intersection tests + * @return Void. Outputs stored in results array + */ +void point_in_volume(MeshID volume, + const Position* points, + const size_t num_points, + uint8_t* results, + const Direction* directions = nullptr, + std::vector* exclude_primitives = nullptr) const; + +/** + * @brief Array based version of ray_fire query + * + * This method performs a set of ray fire queries on a batch of rays defined by their origins and directions. + * It computes the intersection distances and surface IDs for each ray in the batch. With GPRT ray tracing + * this launches the RT pipeline with the number of rays provided. + * + * @param[in] tree The TreeID of the volume we are querying against + * @param[in] origins An array of Position objects representing the starting points of the rays + * @param[in] directions An array of Direction objects representing the directions of the rays + * @param[in] num_rays The number of rays to be processed in the batch + * @param[out] hitDistances An output array to store the computed intersection distances for each ray + * @param[out] surfaceIDs An output array to store the MeshIDs of the surfaces hit by each ray + * @param[in] dist_limit (optional) maximum distance to consider for intersections + * @param[in] orientation (optional) flag to consider whether Entering/Exiting hits should be rejected. Defaults to EXITING + * @param[in] exclude_primitives (optional) vector of surface element MeshIDs to exclude from intersection tests + * @return Void. Outputs stored in hitDistances and surfaceIDs arrays + */ +void ray_fire(MeshID volume, + const Position* origins, + const Direction* directions, + const size_t num_rays, + double* hitDistances, + MeshID* surfaceIDs, + const double dist_limit = INFTY, + HitOrientation orientation = HitOrientation::EXITING, + std::vector* const exclude_primitives = nullptr); + +void ray_fire_prepared(const size_t num_rays, + const double dist_limit = INFTY, + HitOrientation orientation = HitOrientation::EXITING); + std::pair closest(MeshID volume, const Position& origin) const; @@ -105,6 +190,23 @@ Direction surface_normal(MeshID surface, ray_tracing_interface_ = ray_tracing_interface; } + DeviceRayHitBuffers get_device_rayhit_buffers(const size_t requiredCapacity) + { + return ray_tracing_interface()->get_device_rayhit_buffers(requiredCapacity); + } + + void populate_rays_external(size_t numRays, + const RayPopulationCallback& callback) + { + return ray_tracing_interface()->populate_rays_external(numRays, callback); + } + +// Device to host transfer of hit buffers (GPRT only for now) +#ifdef XDG_ENABLE_GPRT + void transfer_hits_buffer_to_host(const size_t num_rays, + std::vector& hits); +#endif + // Accessors const std::shared_ptr& ray_tracing_interface() const { return ray_tracing_interface_; @@ -113,6 +215,7 @@ Direction surface_normal(MeshID surface, const std::shared_ptr& mesh_manager() const { return mesh_manager_; } + // Private methods private: double _triangle_volume_contribution(const PrimitiveRef& triangle) const; diff --git a/src/gprt/dbl_deviceCode.slang b/src/gprt/dbl_deviceCode.slang index ff082a45..b5332dce 100644 --- a/src/gprt/dbl_deviceCode.slang +++ b/src/gprt/dbl_deviceCode.slang @@ -53,22 +53,27 @@ void ray_fire_miss(inout RayFirePayload payload) { void ray_fire(uniform dblRayGenData record, uniform DPTriangleGeomData mesh) { RayFirePayload payload; uint rayID = DispatchRaysIndex().x; + xdg::dblRay ray = record.ray[rayID]; // recover ray from buffer // Trace the ray into the scene RayDesc rayDesc; - rayDesc.Origin = float3(record.ray[rayID].origin); - rayDesc.Direction = normalize(float3(record.ray[rayID].direction)); - rayDesc.TMin = float(record.ray[rayID].tMin); - rayDesc.TMax = float(record.ray[rayID].tMax); + rayDesc.Origin = float3(ray.origin); + rayDesc.Direction = normalize(float3(ray.direction)); + rayDesc.TMin = float(PC.tMin); + rayDesc.TMax = float(PC.tMax); - SurfaceAccelerationStructure world = record.ray[rayID].volume_accel; + int mesh_id = ray.volume_mesh_id; + // Recover the TLAS we are tracing against for this ray + SurfaceAccelerationStructure world = record.meshid_to_accel_address[mesh_id]; - // Pass the ray's origin and direction to the payload + // Set payload default values payload.distance = -1.0f; payload.surf_id = -1; payload.tlas = world; - TraceRay(world, RAY_FLAG_NONE, 0xff, 0, 1, rayDesc, payload); + if (ray.enabled == 1u) { + TraceRay(PC.volume_accel, RAY_FLAG_NONE, 0xff, 0, 1, rayDesc, payload); + } // Store the distance to the hit point and the surface ID in buffers for CPU record.hit[rayID].distance = payload.distance; @@ -80,22 +85,25 @@ void ray_fire(uniform dblRayGenData record, uniform DPTriangleGeomData mesh) { void point_in_volume(uniform dblRayGenData record, uniform DPTriangleGeomData mesh) { RayFirePayload payload; uint rayID = DispatchRaysIndex().x; + xdg::dblRay ray = record.ray[rayID]; // recover ray from buffer // Trace the ray into the scene RayDesc rayDesc; - rayDesc.Origin = float3(record.ray[rayID].origin); - rayDesc.Direction = float3(normalize(record.ray[rayID].direction)); - rayDesc.TMin = float(record.ray[rayID].tMin); - rayDesc.TMax = float(record.ray[rayID].tMax); + rayDesc.Origin = float3(ray.origin); + rayDesc.Direction = float3(normalize(ray.direction)); + rayDesc.TMin = float(PC.tMin); + rayDesc.TMax = float(PC.tMax); - SurfaceAccelerationStructure world = record.ray[rayID].volume_accel; + SurfaceAccelerationStructure world = PC.volume_accel; // Pass the ray's origin and direction to the payload payload.surf_id = -1; payload.tlas = world; payload.piv = xdg::PointInVolume::OUTSIDE; // Initialize point in volume check result to outside (0) - TraceRay(world, RAY_FLAG_NONE, 0xff, 0, 1, rayDesc, payload); + if (ray.enabled == 1u) { // skip RT pipeline for rays that are marked as disabled + TraceRay(world, RAY_FLAG_NONE, 0xff, 0, 1, rayDesc, payload); + } record.hit[rayID].surf_id = payload.surf_id; record.hit[rayID].piv = payload.piv; // Point in volume check result @@ -104,10 +112,14 @@ void point_in_volume(uniform dblRayGenData record, uniform DPTriangleGeomData me // ------------------------------------------------- Compute Shaders ------------------------------------------------- /* A shader to compute and store AABB min/maxes in single precision using double precision coords*/ [shader("compute")] -[numthreads(1, 1, 1)] -void -populate_aabbs(uint3 DispatchThreadID: SV_DispatchThreadID, uniform DPTriangleGeomData record) { +[numthreads(64, 1, 1)] +void populate_aabbs(uint3 DispatchThreadID: SV_DispatchThreadID, uniform DPTriangleGeomData record) { int primID = DispatchThreadID.x; + + // guard against more threads than primitives + if (primID >= record.num_faces) + return; + int3 indices = record.index[primID]; double3 A = record.vertex[indices[0]]; double3 B = record.vertex[indices[1]]; @@ -123,7 +135,6 @@ populate_aabbs(uint3 DispatchThreadID: SV_DispatchThreadID, uniform DPTriangleGe // ------------------------------------------------ CUSTOM INTERSECTION SHADERS ------------------------------------------------ - /* 1D ray generation intersection with a double precision triangle using the Plucker intersection algorithm*/ [shader("intersection")] void DPTrianglePluckerIntersection(uniform DPTriangleGeomData record) @@ -134,6 +145,7 @@ void DPTrianglePluckerIntersection(uniform DPTriangleGeomData record) uint rayID = DispatchRaysIndex().x; uint nRays = DispatchRaysDimensions().x; uint flags = RayFlags(); + xdg::dblRay ray = record.ray[rayID]; // recover ray from buffer if (rayID >= nRays) { return; @@ -155,11 +167,11 @@ void DPTrianglePluckerIntersection(uniform DPTriangleGeomData record) double3 v1 = record.vertex[indices[1]]; double3 v2 = record.vertex[indices[2]]; - double3 origin = record.ray[rayID].origin; - double3 direction = record.ray[rayID].direction; + double3 origin = ray.origin; + double3 direction = ray.direction; - double tMin = record.ray[rayID].tMin; - double tMax = record.ray[rayID].tMax; + double tMin = PC.tMin; + double tMax = PC.tMax; const double3 raya = direction; const double3 rayb = cross(direction, origin); @@ -229,7 +241,7 @@ void DPTrianglePluckerIntersection(uniform DPTriangleGeomData record) double3 norm = record.normals[primID]; // recover double precision normal. TODO - Should we calculate from vertices instead? // sense adjustment of normal - if (record.ray[rayID].volume_tree == record.reverse_tree) + if (PC.volume_tree == record.reverse_tree) { norm = -norm; } @@ -238,16 +250,16 @@ void DPTrianglePluckerIntersection(uniform DPTriangleGeomData record) uint hit_kind = norm_dot_dir < 0 ? HIT_KIND_TRIANGLE_FRONT_FACE : HIT_KIND_TRIANGLE_BACK_FACE; - xdg::HitOrientation hitOrientation = record.ray[rayID].hitOrientation; + xdg::HitOrientation hitOrientation = PC.hitOrientation; if (orientation_cull(direction, norm, hitOrientation)) { return; } - for (int i = 0; i < record.ray[rayID].exclude_count; ++i) + for (int i = 0; i < ray.exclude_count; ++i) { - if (record.ray[rayID].exclude_primitives[i] == global_prim_id) { + if (ray.exclude_primitives[i] == global_prim_id) { return; } } @@ -312,4 +324,4 @@ float next_after(float a) { a_++; } return asfloat(a_); -} \ No newline at end of file +} diff --git a/src/gprt/ray_tracer.cpp b/src/gprt/ray_tracer.cpp index bb3ece22..9c80b586 100644 --- a/src/gprt/ray_tracer.cpp +++ b/src/gprt/ray_tracer.cpp @@ -1,22 +1,27 @@ #include "xdg/gprt/ray_tracer.h" #include "gprt/gprt.h" - namespace xdg { GPRTRayTracer::GPRTRayTracer() { + gprtRequestRayTypeCount(numRayTypes_); // Set the number of shaders which can be set to the same geometry context_ = gprtContextCreate(); module_ = gprtModuleCreate(context_, dbl_deviceCode); - rayHitBuffers_.capacity = 1; // Preallocate space for 1 ray - rayHitBuffers_.ray = gprtDeviceBufferCreate(context_, rayHitBuffers_.capacity); - rayHitBuffers_.hit = gprtDeviceBufferCreate(context_, rayHitBuffers_.capacity); - rayHitBuffers_.devRayAddr = gprtBufferGetDevicePointer(rayHitBuffers_.ray); - rayHitBuffers_.devHitAddr = gprtBufferGetDevicePointer(rayHitBuffers_.hit); + // Buffer setup + rayHitBuffers_.view.capacity = 1e6; // Preallocate space for 1m rays + rayHitBuffers_.ray = gprtDeviceBufferCreate(context_, rayHitBuffers_.view.capacity); + rayHitBuffers_.hit = gprtDeviceBufferCreate(context_, rayHitBuffers_.view.capacity); + rayHitBuffers_.view.rayDevPtr = gprtBufferGetDevicePointer(rayHitBuffers_.ray); + rayHitBuffers_.view.hitDevPtr = gprtBufferGetDevicePointer(rayHitBuffers_.hit); + rayHitBuffers_.view.rayStride = sizeof(dblRay); + rayHitBuffers_.view.hitStride = sizeof(dblHit); excludePrimitivesBuffer_ = gprtDeviceBufferCreate(context_); // initialise buffer of size 1 + tlas_handle_buffer_ = gprtDeviceBufferCreate(context_); // initialise buffer of size 1 + setup_shaders(); @@ -32,6 +37,8 @@ GPRTRayTracer::GPRTRayTracer() // Set up build parameters for acceleration structures buildParams_.buildMode = GPRT_BUILD_MODE_FAST_BUILD_NO_UPDATE; + + } GPRTRayTracer::~GPRTRayTracer() @@ -46,11 +53,6 @@ GPRTRayTracer::~GPRTRayTracer() gprtAccelDestroy(accel); } - // Destroy BLAS structures - for (const auto& blas : blas_handles_) { - gprtAccelDestroy(blas); - } - // Destroy Geoms and Types for (const auto& [surf, geom] : surface_to_geometry_map_) { gprtGeomDestroy(geom); @@ -61,6 +63,7 @@ GPRTRayTracer::~GPRTRayTracer() gprtBufferDestroy(rayHitBuffers_.ray); gprtBufferDestroy(rayHitBuffers_.hit); gprtBufferDestroy(excludePrimitivesBuffer_); + gprtBufferDestroy(tlas_handle_buffer_); // Destroy module and context gprtModuleDestroy(module_); @@ -85,9 +88,13 @@ void GPRTRayTracer::setup_shaders() void GPRTRayTracer::init() { + update_tlas_table_(); + // Build the shader binding table (SBT) after all shader programs and acceleration structures are set up gprtBuildShaderBindingTable(context_, GPRT_SBT_ALL); // Note that should we need to update any shaders or acceleration structures, we must rebuild the SBT + + initialized_ = true; } std::pair @@ -164,8 +171,11 @@ GPRTRayTracer::create_surface_tree(const std::shared_ptr& mesh_mana geom_data->normals = gprtBufferGetDevicePointer(normal_buffer); geom_data->primitive_refs = gprtBufferGetDevicePointer(primitive_refs_buffer); geom_data->num_faces = num_faces; - - gprtComputeLaunch(aabbPopulationProgram_, {num_faces, 1, 1}, {1, 1, 1}, *geom_data); + + constexpr uint32_t threadsPerGroup = 64; // must match [numthreads(64,1,1)] + uint32_t numGroupsX = (num_faces + threadsPerGroup - 1) / threadsPerGroup; + + gprtComputeLaunch(aabbPopulationProgram_, {numGroupsX, 1, 1}, {threadsPerGroup, 1, 1}, *geom_data); GPRTAccel blas = gprtAABBAccelCreate(context_, triangleGeom, buildParams_.buildMode); @@ -202,7 +212,17 @@ GPRTRayTracer::create_surface_tree(const std::shared_ptr& mesh_mana GPRTAccel volume_tlas = gprtInstanceAccelCreate(context_, surfaceBlasInstances.size(), instanceBuffer); gprtAccelBuild(context_, volume_tlas, buildParams_); surface_volume_tree_to_accel_map[tree] = volume_tlas; - + surface_tree_to_volume_map_[tree] = volume_id; + if (volume_id >= tlas_handles_.size()) { + tlas_handles_.resize(volume_id + 1, SurfaceAccelerationStructure{}); + } + tlas_handles_[volume_id] = gprtAccelGetDeviceAddress(volume_tlas); + + if (initialized_) { + update_tlas_table_(); + gprtBuildShaderBindingTable(context_, GPRT_SBT_ALL); + } + return tree; } @@ -222,19 +242,24 @@ bool GPRTRayTracer::point_in_volume(SurfaceTreeID tree, auto rayGen = rayGenPrograms_.at(RayGenType::POINT_IN_VOLUME); dblRayGenData* rayGenPIVData = gprtRayGenGetParameters(rayGen); + const Direction defaultDir = Direction{1. / std::sqrt(2.0), 1. / std::sqrt(2.0), 0.0}; + // Use provided direction or if Direction == nulptr use default direction Direction directionUsed = (direction != nullptr) ? Direction{direction->x, direction->y, direction->z} - : Direction{1. / std::sqrt(2.0), 1. / std::sqrt(2.0), 0.0}; + : defaultDir; + + // Catch directions with zero length + const double l2 = directionUsed.x*directionUsed.x + + directionUsed.y*directionUsed.y + + directionUsed.z*directionUsed.z; + if (l2 == 0.0) directionUsed = defaultDir; gprtBufferMap(rayHitBuffers_.ray); // Update the ray input buffer dblRay* ray = gprtBufferGetHostPointer(rayHitBuffers_.ray); - ray[0].volume_accel = gprtAccelGetDeviceAddress(volume); ray[0].origin = {point.x, point.y, point.z}; ray[0].direction = {directionUsed.x, directionUsed.y, directionUsed.z}; - ray[0].tMax = INFTY; // Set a large distance limit - ray[0].tMin = 0.0; - ray[0].volume_tree = tree; // Set the TreeID of the volume being queried - ray[0].hitOrientation = HitOrientation::ANY; // No orientation culling for point-in-volume check + ray[0].volume_mesh_id = surface_tree_to_volume_map_.at(tree); + ray[0].enabled = 1; // Ensure the ray is enabled if (exclude_primitives) { if (!exclude_primitives->empty()) gprtBufferResize(context_, excludePrimitivesBuffer_, exclude_primitives->size(), false); @@ -252,7 +277,14 @@ bool GPRTRayTracer::point_in_volume(SurfaceTreeID tree, } gprtBufferUnmap(rayHitBuffers_.ray); // required to sync buffer back on GPU? - gprtRayGenLaunch1D(context_, rayGen, 1); // Launch raygen shader (entry point to RT pipeline) + dblRayFirePushConstants pushConstants; + pushConstants.hitOrientation = HitOrientation::ANY; + pushConstants.tMax = INFTY; + pushConstants.tMin = 0.0; + pushConstants.volume_accel = gprtAccelGetDeviceAddress(volume); + pushConstants.volume_tree = tree; + + gprtRayGenLaunch1D(context_, rayGen, 1, pushConstants); // Launch raygen shader (entry point to RT pipeline) gprtGraphicsSynchronize(context_); // Ensure all GPU operations are complete before returning control flow to CPU // Retrieve the hit from the dblHit buffer @@ -282,16 +314,13 @@ std::pair GPRTRayTracer::ray_fire(SurfaceTreeID tree, GPRTAccel volume = surface_volume_tree_to_accel_map.at(tree); auto rayGen = rayGenPrograms_.at(RayGenType::RAY_FIRE); dblRayGenData* rayGenData = gprtRayGenGetParameters(rayGen); - + gprtBufferMap(rayHitBuffers_.ray); // Update the ray input buffer dblRay* ray = gprtBufferGetHostPointer(rayHitBuffers_.ray); - ray[0].volume_accel = gprtAccelGetDeviceAddress(volume); ray[0].origin = {origin.x, origin.y, origin.z}; ray[0].direction = {direction.x, direction.y, direction.z}; - ray[0].tMax = dist_limit; - ray[0].tMin = 0.0; - ray[0].hitOrientation = orientation; // Set orientation for the ray - ray[0].volume_tree = tree; // Set the TreeID of the volume being queried + ray[0].volume_mesh_id = surface_tree_to_volume_map_.at(tree); + ray[0].enabled = 1; // Ensure the ray is enabled if (exclude_primitives) { if (!exclude_primitives->empty()) gprtBufferResize(context_, excludePrimitivesBuffer_, exclude_primitives->size(), false); @@ -309,7 +338,15 @@ std::pair GPRTRayTracer::ray_fire(SurfaceTreeID tree, } gprtBufferUnmap(rayHitBuffers_.ray); // required to sync buffer back on GPU? - gprtRayGenLaunch1D(context_, rayGen, 1); // Launch raygen shader (entry point to RT pipeline) + // Set push constants (same for every ray) + dblRayFirePushConstants pushConstants; + pushConstants.hitOrientation = orientation; + pushConstants.tMax = dist_limit; + pushConstants.tMin = 0.0; + pushConstants.volume_accel = gprtAccelGetDeviceAddress(volume); + pushConstants.volume_tree = tree; + + gprtRayGenLaunch1D(context_, rayGen, 1, pushConstants); // Launch raygen shader (entry point to RT pipeline) gprtGraphicsSynchronize(context_); // Ensure all GPU operations are complete before returning control flow to CPU // Retrieve the hit from the dblHit buffer @@ -326,7 +363,159 @@ std::pair GPRTRayTracer::ray_fire(SurfaceTreeID tree, if (exclude_primitives) exclude_primitives->push_back(primitive_id); return {distance, surface}; } - + +void GPRTRayTracer::point_in_volume(TreeID tree, + const Position* points, + const size_t num_points, + uint8_t* results, + const Direction* directions, + std::vector* exclude_primitives) +{ + if (num_points == 0) return; // no work to do. Early exit + + GPRTAccel volume = surface_volume_tree_to_accel_map.at(tree); + auto rayGen = rayGenPrograms_.at(RayGenType::POINT_IN_VOLUME); + dblRayGenData* rayGenData = gprtRayGenGetParameters(rayGen); + check_rayhit_buffer_capacity(num_points); + + // TODO - handle exclude_primitives for batch version + + // Set a default direction to be used if no direction is provided + const Direction defaultDir = Direction{1. / std::sqrt(2.0), 1. / std::sqrt(2.0), 0.0}; + + // Map the region start + gprtBufferMap(rayHitBuffers_.ray); + dblRay* ray = gprtBufferGetHostPointer(rayHitBuffers_.ray); + + // Common ray params + for (size_t i = 0; i < num_points; ++i) { + ray[i].origin = {points[i].x, points[i].y, points[i].z}; + ray[i].exclude_primitives = nullptr; + ray[i].volume_mesh_id = surface_tree_to_volume_map_.at(tree); + ray[i].enabled = 1; // Ensure the ray is enabled + } + + // Directions + if (!directions) { + for (size_t i = 0; i < num_points; ++i) + ray[i].direction = double3{ defaultDir.x, defaultDir.y, defaultDir.z }; + } else { + for (size_t i = 0; i < num_points; ++i) + ray[i].direction = double3{ directions[i].x, directions[i].y, directions[i].z }; + } + + gprtBufferUnmap(rayHitBuffers_.ray); // required to sync buffer back on GPU? + + // Set push constants (same for every ray) + dblRayFirePushConstants pushConstants; + pushConstants.hitOrientation = HitOrientation::ANY; + pushConstants.tMax = INFTY; + pushConstants.tMin = 0.0; + pushConstants.volume_accel = gprtAccelGetDeviceAddress(volume); + pushConstants.volume_tree = tree; + + gprtRayGenLaunch1D(context_, rayGen, num_points, pushConstants); + gprtGraphicsSynchronize(context_); + + // Retrieve the output from the ray output buffer + gprtBufferMap(rayHitBuffers_.hit); + dblHit* hit = gprtBufferGetHostPointer(rayHitBuffers_.hit); + for (size_t i = 0; i < num_points; ++i) { + auto piv = hit[i].piv; // Point in volume check result + results[i] = static_cast(piv); + } + gprtBufferUnmap(rayHitBuffers_.hit); // required to sync buffer back on GPU? Maybe this second unmap isn't actually needed since we dont need to resyncrhonize after retrieving the data from device + + return; +} + +// Array version of ray_fire +void GPRTRayTracer::ray_fire(TreeID tree, + const Position* origins, + const Direction* directions, + const size_t num_rays, + double* hitDistances, + MeshID* surfaceIDs, + const double dist_limit, + HitOrientation orientation, + std::vector* const exclude_primitives) +{ + if (num_rays == 0) return; // no work to do. Early exit + + GPRTAccel volume = surface_volume_tree_to_accel_map.at(tree); + auto rayGen = rayGenPrograms_.at(RayGenType::RAY_FIRE); + dblRayGenData* rayGenData = gprtRayGenGetParameters(rayGen); + check_rayhit_buffer_capacity(num_rays); + + gprtBufferMap(rayHitBuffers_.ray); + dblRay* ray = gprtBufferGetHostPointer(rayHitBuffers_.ray); + // Set per ray values + for (size_t i = 0; i < num_rays; ++i) { + const auto& origin = origins[i]; + const auto& direction = directions[i]; + + ray[i].origin = {origin.x, origin.y, origin.z}; + ray[i].direction = {direction.x, direction.y, direction.z}; + ray[i].exclude_primitives = nullptr; // Not currently supported in batch version + ray[i].volume_mesh_id = surface_tree_to_volume_map_.at(tree); + ray[i].enabled = 1; // Ensure the ray is enabled + } + + gprtBufferUnmap(rayHitBuffers_.ray); // required to sync buffer back on GPU? + + // Set push constants (same for every ray) + dblRayFirePushConstants pushConstants; + pushConstants.hitOrientation = orientation; + pushConstants.tMax = dist_limit; + pushConstants.tMin = 0.0; + pushConstants.volume_accel = gprtAccelGetDeviceAddress(volume); + pushConstants.volume_tree = tree; + + // Launch the ray generation shader with push constants and buffer bindings + gprtRayGenLaunch1D(context_, rayGen, num_rays, pushConstants); + gprtGraphicsSynchronize(context_); + + // Retrieve the output from the ray output buffer + gprtBufferMap(rayHitBuffers_.hit); + dblHit* hit = gprtBufferGetHostPointer(rayHitBuffers_.hit); + // populate the result arrays + for (size_t i = 0; i < num_rays; ++i) { + const MeshID surfaceHit = hit[i].surf_id; + if (surfaceHit == ID_NONE) { + hitDistances[i] = INFTY; + surfaceIDs[i] = ID_NONE; + } + else { + hitDistances[i] = hit[i].distance; + surfaceIDs[i] = surfaceHit; + // TODO - handle exclude_primitives for batch version + } + } + gprtBufferUnmap(rayHitBuffers_.hit); // required to sync buffer back on GPU? Maybe this second unmap isn't actually needed since we dont need to resyncrhonize after retrieving the data from device + return; +} + +void +GPRTRayTracer::ray_fire_prepared(const size_t num_rays, + const double dist_limit, + HitOrientation orientation) +{ + if (num_rays == 0) return; // no work to do. Early exit + + check_rayhit_buffer_capacity(num_rays); + + auto rayGen = rayGenPrograms_.at(RayGenType::RAY_FIRE); + + dblRayFirePushConstants pushConstants; + pushConstants.tMax = dist_limit; + pushConstants.tMin = 0.0; + pushConstants.hitOrientation = orientation; // Set orientation for the ray + + gprtRayGenLaunch1D(context_, rayGen, num_rays, pushConstants); + gprtGraphicsSynchronize(context_); + return; +} + void GPRTRayTracer::create_global_surface_tree() { // Create a TLAS (Top-Level Acceleration Structure) for all the volumes @@ -341,16 +530,22 @@ void GPRTRayTracer::create_global_surface_tree() global_surface_accel_ = global_accel; } -void GPRTRayTracer::check_ray_buffer_capacity(size_t N) +void GPRTRayTracer::check_rayhit_buffer_capacity(const size_t N) { - if (N <= rayHitBuffers_.capacity) return; // current capacity is sufficient + if (N <= rayHitBuffers_.view.capacity) return; // current capacity is sufficient - // Resize buffers to accommodate N rays - size_t newCapacity = std::max(N, rayHitBuffers_.capacity * 2); // double the capacity or set to N, whichever is larger + // Resize buffers to accommodate N rays - double the capacity or set to N, whichever is larger + size_t newCapacity = std::max(N, rayHitBuffers_.view.capacity * 2); gprtBufferResize(context_, rayHitBuffers_.ray, newCapacity, false); gprtBufferResize(context_, rayHitBuffers_.hit, newCapacity, false); - rayHitBuffers_.capacity = newCapacity; + rayHitBuffers_.view.capacity = newCapacity; + + // Get fresh device pointers after resize + rayHitBuffers_.view.rayDevPtr = gprtBufferGetDevicePointer(rayHitBuffers_.ray); + rayHitBuffers_.view.hitDevPtr = gprtBufferGetDevicePointer(rayHitBuffers_.hit); + rayHitBuffers_.view.rayStride = sizeof(dblRay); + rayHitBuffers_.view.hitStride = sizeof(dblHit); // Since we have resized the ray buffers, we need to update the geom_data->rayIn pointers in all geometries too for (auto const& [surf, geom] : surface_to_geometry_map_) { @@ -363,9 +558,63 @@ void GPRTRayTracer::check_ray_buffer_capacity(size_t N) dblRayGenData* rayGenData = gprtRayGenGetParameters(rayGen); rayGenData->ray = gprtBufferGetDevicePointer(rayHitBuffers_.ray); rayGenData->hit = gprtBufferGetDevicePointer(rayHitBuffers_.hit); + rayGenData->meshid_to_accel_address = gprtBufferGetDevicePointer(tlas_handle_buffer_); } gprtBuildShaderBindingTable(context_, static_cast(GPRT_SBT_GEOM | GPRT_SBT_RAYGEN)); } +// Update the TLAS table (MeshID -> SurfaceAccelerationStructure) buffer on the device +void GPRTRayTracer::update_tlas_table_() +{ + gprtBufferResize(context_, tlas_handle_buffer_, tlas_handles_.size(), false); + gprtBufferMap(tlas_handle_buffer_); + std::copy(tlas_handles_.begin(), tlas_handles_.end(), gprtBufferGetHostPointer(tlas_handle_buffer_)); + gprtBufferUnmap(tlas_handle_buffer_); + + for (auto type : {RayGenType::RAY_FIRE, RayGenType::POINT_IN_VOLUME}) { + auto* raygendata = gprtRayGenGetParameters(rayGenPrograms_.at(type)); + raygendata->meshid_to_accel_address = gprtBufferGetDevicePointer(tlas_handle_buffer_); + } +} + +DeviceRayHitBuffers GPRTRayTracer::get_device_rayhit_buffers(const size_t N) +{ + check_rayhit_buffer_capacity(N); + return rayHitBuffers_.view; +} + +void GPRTRayTracer::populate_rays_external(size_t numRays, + const RayPopulationCallback& callback) +{ + if (numRays == 0) return; + + // Ensure device buffers are large enough + check_rayhit_buffer_capacity(numRays); + + // Use the user callback to populate the rays directly on the device + callback(rayHitBuffers_.view, numRays); + + // After callback returns, we assume the ray buffer is populated and ready to trace + // Note: The callback is responsible for synchronization if using an async API +} + +void GPRTRayTracer::download_hits(const size_t num_rays, + std::vector& hits) +{ + if (num_rays == 0) { + hits.clear(); + return; + } + if (num_rays > rayHitBuffers_.view.capacity) { + fatal_error("Requested {} hits, but hit buffer capacity is {}", num_rays, rayHitBuffers_.view.capacity); + } + + hits.resize(num_rays); + gprtBufferMap(rayHitBuffers_.hit); + dblHit* hit = gprtBufferGetHostPointer(rayHitBuffers_.hit); + std::copy(hit, hit + num_rays, hits.begin()); + gprtBufferUnmap(rayHitBuffers_.hit); +} + } // namespace xdg diff --git a/src/tetrahedron_contain.cpp b/src/tetrahedron_contain.cpp index 743ea1d0..98757768 100644 --- a/src/tetrahedron_contain.cpp +++ b/src/tetrahedron_contain.cpp @@ -13,7 +13,9 @@ bool plucker_tet_containment_test(const Position& point, const Position& v1, const Position& v2, const Position& v3) { - using namespace linalg::aliases; + using linalg::aliases::double3x3; + using linalg::aliases::double3; + using linalg::aliases::double4; // Create matrix T = [v1 - v0, v2 - v0, v3 - v0] Vec3da e0 = v1 - v0; Vec3da e1 = v2 - v0; diff --git a/src/xdg.cpp b/src/xdg.cpp index 371a9a1c..9cb8e749 100644 --- a/src/xdg.cpp +++ b/src/xdg.cpp @@ -9,6 +9,9 @@ #include "xdg/mesh_managers.h" #include "xdg/ray_tracers.h" +#ifdef XDG_ENABLE_GPRT +#include "xdg/gprt/ray.h" +#endif namespace xdg { @@ -52,6 +55,18 @@ void XDG::prepare_volume_for_raytracing(MeshID volume) { volume_to_point_location_tree_map_[volume] = volume_tree; } +#ifdef XDG_ENABLE_GPRT +void XDG::transfer_hits_buffer_to_host(const size_t num_rays, + std::vector& hits) +{ + auto gprt_rt = std::dynamic_pointer_cast(ray_tracing_interface()); + if (!gprt_rt) { + fatal_error("transfer_hits_buffer_to_host is only supported with the GPRT ray tracer"); + } + gprt_rt->download_hits(num_rays, hits); +} +#endif + std::shared_ptr XDG::create(MeshLibrary mesh_lib, RTLibrary ray_tracing_lib) { std::shared_ptr xdg = std::make_shared(); @@ -110,6 +125,17 @@ bool XDG::point_in_volume(MeshID volume, return ray_tracing_interface()->point_in_volume(tree, point, direction, exclude_primitives); } +void XDG::point_in_volume(MeshID volume, + const Position* points, + const size_t num_points, + uint8_t* results, + const Direction* directions, + std::vector* exclude_primitives) const +{ + TreeID tree = volume_to_surface_tree_map_.at(volume); + ray_tracing_interface()->point_in_volume(tree, points, num_points, results, directions, exclude_primitives); +} + MeshID XDG::find_volume(const Position& point, const Direction& direction) const { @@ -235,8 +261,32 @@ XDG::ray_fire(MeshID volume, HitOrientation orientation, std::vector* const exclude_primitives) const { - TreeID scene = volume_to_surface_tree_map_.at(volume); - return ray_tracing_interface()->ray_fire(scene, origin, direction, dist_limit, orientation, exclude_primitives); + TreeID tree = volume_to_surface_tree_map_.at(volume); + return ray_tracing_interface()->ray_fire(tree, origin, direction, dist_limit, orientation, exclude_primitives); +} + +// Array version of ray_fire +void +XDG::ray_fire(MeshID volume, + const Position* origins, + const Direction* directions, + const size_t num_rays, + double* hitDistances, + MeshID* surfaceIDs, + const double dist_limit, + HitOrientation orientation, + std::vector* const exclude_primitives) +{ + TreeID tree = volume_to_surface_tree_map_.at(volume); + return ray_tracing_interface()->ray_fire(tree, origins, directions, num_rays, hitDistances, surfaceIDs, dist_limit, orientation, exclude_primitives); +} + +void +XDG::ray_fire_prepared(const size_t num_rays, + const double dist_limit, + HitOrientation orientation) +{ + return ray_tracing_interface()->ray_fire_prepared(num_rays, dist_limit, orientation); } std::pair XDG::closest(MeshID volume, @@ -325,4 +375,4 @@ double XDG::measure_volume_area(MeshID volume) const return area; } -} // namespace xdg \ No newline at end of file +} // namespace xdg diff --git a/tests/test_files b/tests/test_files index a3caf0af..ca579198 160000 --- a/tests/test_files +++ b/tests/test_files @@ -1 +1 @@ -Subproject commit a3caf0af3f128944c4d6eac93b481df6e4efd97c +Subproject commit ca57919851224047ef86fab177a0bfe9fa920127 diff --git a/tests/test_point_in_volume.cpp b/tests/test_point_in_volume.cpp index ae34e823..23f559e4 100644 --- a/tests/test_point_in_volume.cpp +++ b/tests/test_point_in_volume.cpp @@ -24,7 +24,6 @@ TEMPLATE_TEST_CASE("Point-in-volume on MeshMock", "[piv][mock]", check_ray_tracer_supported(rt_backend); // skip if backend not enabled at configuration time auto rti = create_raytracer(rt_backend); REQUIRE(rti); - rti->init(); // Keep MeshMock usage consistent across backends auto mm = std::make_shared(false); diff --git a/vendor/GPRT b/vendor/GPRT index f1e95e41..405d9ee9 160000 --- a/vendor/GPRT +++ b/vendor/GPRT @@ -1 +1 @@ -Subproject commit f1e95e4188cde591547d6b4a33a70bf2afaeec59 +Subproject commit 405d9ee9f5ee8e1a0455f776f9e2c3adffb64160 From 53d963245537b56db028e0f9e01c7fca8b0f2eee Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Fri, 30 Jan 2026 11:51:57 +0000 Subject: [PATCH 51/62] Added tests for batch query API with GPRT --- tests/CMakeLists.txt | 17 +++ tests/test_direct_ray_buffer_access.cpp | 124 ++++++++++++++++++ ..._direct_ray_buffer_access_deviceCode.slang | 24 ++++ tests/test_direct_ray_buffer_access_shared.h | 14 ++ tests/test_files | 2 +- tests/test_point_in_volume.cpp | 85 +++++++++++- tests/test_ray_fire.cpp | 82 +++++++++++- tests/util.h | 17 +++ vendor/GPRT | 2 +- 9 files changed, 362 insertions(+), 5 deletions(-) create mode 100644 tests/test_direct_ray_buffer_access.cpp create mode 100644 tests/test_direct_ray_buffer_access_deviceCode.slang create mode 100644 tests/test_direct_ray_buffer_access_shared.h diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d7286555..8166eee9 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -37,6 +37,11 @@ if (XDG_ENABLE_MOAB) list(APPEND TEST_NAMES test_overlap_check) endif() +# This test really should be appended when any GPU library is enabled +if (XDG_ENABLE_GPRT) + list(APPEND TEST_NAMES test_direct_ray_buffer_access) +endif() + foreach(test ${TEST_NAMES}) add_executable(${test} ${test}.cpp) target_link_libraries(${test} xdg Catch2::Catch2WithMain) @@ -48,6 +53,18 @@ foreach(test ${TEST_NAMES}) TEST_PREFIX "${test}::") endforeach() +if (XDG_ENABLE_GPRT) + embed_devicecode( + OUTPUT_TARGET + test_direct_ray_buffer_access_deviceCode + HEADERS + ${CMAKE_CURRENT_SOURCE_DIR}/test_direct_ray_buffer_access_shared.h + SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/test_direct_ray_buffer_access_deviceCode.slang + ) + target_link_libraries(test_direct_ray_buffer_access test_direct_ray_buffer_access_deviceCode) +endif() + set( TEST_FILES diff --git a/tests/test_direct_ray_buffer_access.cpp b/tests/test_direct_ray_buffer_access.cpp new file mode 100644 index 00000000..309ce9e9 --- /dev/null +++ b/tests/test_direct_ray_buffer_access.cpp @@ -0,0 +1,124 @@ +// for testing +#include +#include +#include +#include + +// xdg includes +#include "xdg/constants.h" +#include "xdg/mesh_manager_interface.h" +#include "xdg/gprt/ray_tracer.h" +#include "xdg/xdg.h" +#include "mesh_mock.h" +#include "test_direct_ray_buffer_access_shared.h" +#include "util.h" +#include "gprt.h" + +#include + +using namespace xdg; +using namespace xdg::test; + +extern GPRTProgram test_direct_ray_buffer_access_deviceCode; + +// This is a GPU only test - skip if no GPU ray tracing backends are enabled +TEMPLATE_TEST_CASE("Ray Fire with external populated rays", "[rayfire][mock]", + GPRT_Raytracer) +{ + constexpr auto rt_backend = TestType::value; + + DYNAMIC_SECTION(fmt::format("Backend = {}", rt_backend)) { + check_ray_tracer_supported(rt_backend); + + auto rti = create_raytracer(rt_backend); + REQUIRE(rti); + + auto mm = std::make_shared(false); + mm->init(); + REQUIRE(mm->mesh_library() == MeshLibrary::MOCK); + + auto xdg = std::make_shared(); + xdg->set_mesh_manager_interface(mm); + xdg->set_ray_tracing_interface(rti); + xdg->prepare_raytracer(); + + std::vector origins; + std::vector directions; + size_t N = 64; + make_rays(N, origins, directions); + + auto gprt_rt = std::dynamic_pointer_cast(rti); + REQUIRE(gprt_rt); + + const MeshID volume_id = mm->volumes()[0]; + GPRTContext context = gprt_rt->context(); + GPRTModule module = gprtModuleCreate(context, test_direct_ray_buffer_access_deviceCode); + auto packRays = gprtComputeCreate(context, module, "pack_external_rays"); + + std::vector expected_distances(N, INFTY); + std::vector expected_surfaces(N, ID_NONE); + xdg->ray_fire(volume_id, + origins.data(), + directions.data(), + N, + expected_distances.data(), + expected_surfaces.data()); + + // Create callback to populate rays on device + RayPopulationCallback populate_callback = [&volume_id, &origins, &directions, &context, &packRays] + (const DeviceRayHitBuffers& buffer, size_t numRays) { + REQUIRE(origins.size() >= numRays); + REQUIRE(directions.size() >= numRays); + + // Convert to double3 for use on GPU + std::vector origins_device(numRays); + std::vector directions_device(numRays); + for (size_t i = 0; i < numRays; ++i) { + origins_device[i] = {origins[i].x, origins[i].y, origins[i].z}; + directions_device[i] = {directions[i].x, directions[i].y, directions[i].z}; + } + + auto origins_buffer = gprtDeviceBufferCreate(context, numRays, origins_device.data()); + auto directions_buffer = gprtDeviceBufferCreate(context, numRays, directions_device.data()); + + constexpr uint32_t threads_per_group = 256; + const uint32_t groups = static_cast((numRays + threads_per_group - 1) / threads_per_group); + + ExternalRayParams params = {}; + params.xdgRays = static_cast(buffer.rayDevPtr); + params.origins = gprtBufferGetDevicePointer(origins_buffer); + params.directions = gprtBufferGetDevicePointer(directions_buffer); + params.num_rays = static_cast(numRays); + params.total_threads = groups * threads_per_group; + params.volume_mesh_id = volume_id; + params.enabled = 1u; + + gprtComputeLaunch(packRays, + { groups, 1, 1 }, + { threads_per_group, 1, 1 }, + params); + gprtComputeSynchronize(context); + + gprtBufferDestroy(origins_buffer); + gprtBufferDestroy(directions_buffer); + }; + + // Populate rays via external API + xdg->populate_rays_external(N, populate_callback); + + xdg->ray_fire_prepared(volume_id, N); + std::vector hits; + xdg->transfer_hits_buffer_to_host(N, hits); + + REQUIRE(hits.size() == N); + for (size_t i = 0; i < N; ++i) { + REQUIRE(hits[i].surf_id == expected_surfaces[i]); + if (expected_surfaces[i] != ID_NONE) { + REQUIRE_THAT(hits[i].distance, Catch::Matchers::WithinAbs(expected_distances[i], 1e-6)); + } + } + + gprtComputeDestroy(packRays); + gprtModuleDestroy(module); + } +} diff --git a/tests/test_direct_ray_buffer_access_deviceCode.slang b/tests/test_direct_ray_buffer_access_deviceCode.slang new file mode 100644 index 00000000..a3e8729a --- /dev/null +++ b/tests/test_direct_ray_buffer_access_deviceCode.slang @@ -0,0 +1,24 @@ +#include "test_direct_ray_buffer_access_shared.h" + +[shader("compute")] +[numthreads(256, 1, 1)] +void pack_external_rays(uint3 DispatchThreadID: SV_DispatchThreadID, + uniform ExternalRayParams extParams) +{ + uint globalThreadID = DispatchThreadID.x; + uint stride = extParams.total_threads; // Groups * 256 + + // Grid-stride loop: each thread handles ray idx, idx+stride, idx+2*stride, ... + for (uint idx = globalThreadID; idx < extParams.num_rays; idx += stride) + { + xdg::dblRay r; + r.origin = extParams.origins[idx]; + r.direction = extParams.directions[idx]; + r.exclude_primitives = nullptr; + r.exclude_count = 0; + r.volume_mesh_id = extParams.volume_mesh_id; + r.enabled = extParams.enabled; + + extParams.xdgRays[idx] = r; + } +} diff --git a/tests/test_direct_ray_buffer_access_shared.h b/tests/test_direct_ray_buffer_access_shared.h new file mode 100644 index 00000000..a8e5d553 --- /dev/null +++ b/tests/test_direct_ray_buffer_access_shared.h @@ -0,0 +1,14 @@ +#include "gprt.h" + +#include "../include/xdg/gprt/ray.h" + +struct ExternalRayParams { + xdg::dblRay* xdgRays; + double3* origins; + double3* directions; + uint num_rays; + uint total_threads; + int volume_mesh_id; + uint enabled; +}; + diff --git a/tests/test_files b/tests/test_files index a3caf0af..ca579198 160000 --- a/tests/test_files +++ b/tests/test_files @@ -1 +1 @@ -Subproject commit a3caf0af3f128944c4d6eac93b481df6e4efd97c +Subproject commit ca57919851224047ef86fab177a0bfe9fa920127 diff --git a/tests/test_point_in_volume.cpp b/tests/test_point_in_volume.cpp index ae34e823..7e771499 100644 --- a/tests/test_point_in_volume.cpp +++ b/tests/test_point_in_volume.cpp @@ -12,6 +12,21 @@ using namespace xdg; using namespace xdg::test; +static void make_points(size_t N, + std::vector& points, + std::vector& directions) +{ + points.resize(N); + directions.resize(N); + for (size_t i = 0; i < N; ++i) { + // evens inside (origin), odds just outside +X + points[i] = (i % 2 == 0) ? xdg::Position{0,0,0} : xdg::Position{5.1,0,0}; + // alternate ±X directions + directions[i] = (i % 2 == 0) ? xdg::Direction{ 1,0,0} + : xdg::Direction{-1,0,0}; + } +} + // ---------- single test, sections per backend -------------------------------- TEMPLATE_TEST_CASE("Point-in-volume on MeshMock", "[piv][mock]", @@ -24,7 +39,6 @@ TEMPLATE_TEST_CASE("Point-in-volume on MeshMock", "[piv][mock]", check_ray_tracer_supported(rt_backend); // skip if backend not enabled at configuration time auto rti = create_raytracer(rt_backend); REQUIRE(rti); - rti->init(); // Keep MeshMock usage consistent across backends auto mm = std::make_shared(false); @@ -78,3 +92,72 @@ TEMPLATE_TEST_CASE("Point-in-volume on MeshMock", "[piv][mock]", REQUIRE(result == false); } } + +TEMPLATE_TEST_CASE("Batch API Point-in-volume on MeshMock", "[piv][mock][batch]", + Embree_Raytracer, + GPRT_Raytracer) +{ + constexpr auto rt_backend = TestType::value; + + DYNAMIC_SECTION(fmt::format("Backend = {}", rt_backend)) { + check_ray_tracer_supported(rt_backend); + if (rt_backend == RTLibrary::EMBREE) { + SKIP("Skipping PIV batch for Embree: batch API not implemented yet"); + } + + auto rti = create_raytracer(rt_backend); + REQUIRE(rti); + + auto mm = std::make_shared(false); + mm->init(); + REQUIRE(mm->mesh_library() == MeshLibrary::MOCK); + + auto [volume_tree, element_tree] = rti->register_volume(mm, mm->volumes()[0]); + REQUIRE(volume_tree != ID_NONE); + REQUIRE(element_tree == ID_NONE); + + rti->init(); + + std::vector points; + std::vector directions; + std::vector has_dir; + size_t N; + + SECTION("N=0 no-op") { + rti->point_in_volume(volume_tree, + nullptr, /*points*/ + 0, /*num_points*/ + nullptr /*results*/); + SUCCEED("N=0 completed without error"); + } + + SECTION("N=1") { + N = 1; + make_points(N, points, directions); + + auto scalar_result = static_cast(rti->point_in_volume(volume_tree, points[0], &directions[0])); + + std::vector batch_result(N, 0xFF); + rti->point_in_volume(volume_tree, points.data(), N, batch_result.data(), directions.data()); + REQUIRE(batch_result[0] == scalar_result); + } + + SECTION("N=64") { + N = 64; + make_points(N, points, directions); + + // Store results of scalar point_in_volume calls to verify batch against scalar + std::vector scalar_results(N, 0); + for (size_t i = 0; i < N; ++i) { + scalar_results[i] = static_cast(rti->point_in_volume(volume_tree, points[i], &directions[i])); + } + + std::vector batch_results(N, 0xFF); + rti->point_in_volume(volume_tree, points.data(), N, batch_results.data(), directions.data()); + for (size_t i = 0; i < points.size(); ++i) { + REQUIRE(batch_results[i] == scalar_results[i]); + } + } + } +} + diff --git a/tests/test_ray_fire.cpp b/tests/test_ray_fire.cpp index be816c36..552a4ed6 100644 --- a/tests/test_ray_fire.cpp +++ b/tests/test_ray_fire.cpp @@ -4,13 +4,14 @@ #include #include - // xdg includes #include "xdg/constants.h" #include "xdg/mesh_manager_interface.h" #include "mesh_mock.h" #include "util.h" +#include + using namespace xdg; using namespace xdg::test; @@ -109,4 +110,81 @@ TEMPLATE_TEST_CASE("Ray Fire on MeshMock (per-backend sections)", "[rayfire][moc intersection = rti->ray_fire(volume_tree, origin, direction, INFTY, HitOrientation::EXITING, &exclude_primitives); REQUIRE(intersection.second == ID_NONE); } -} \ No newline at end of file +} + +TEMPLATE_TEST_CASE("Batch API Ray Fire on MeshMock", "[rayfire][mock][batch]", + Embree_Raytracer, + GPRT_Raytracer) +{ + constexpr auto rt_backend = TestType::value; + + DYNAMIC_SECTION(fmt::format("Backend = {}", rt_backend)) { + check_ray_tracer_supported(rt_backend); + if (rt_backend == RTLibrary::EMBREE) { + SKIP("Skipping batch query mechanics test for Embree: batch API not implemented."); + } + + auto rti = create_raytracer(rt_backend); + REQUIRE(rti); + + auto mm = std::make_shared(false); + mm->init(); + REQUIRE(mm->mesh_library() == MeshLibrary::MOCK); + + auto [volume_tree, element_tree] = rti->register_volume(mm, mm->volumes()[0]); + REQUIRE(volume_tree != ID_NONE); + REQUIRE(element_tree == ID_NONE); + + rti->init(); + + std::vector origins; + std::vector directions; + size_t N; + + // ---- N = 0 ---- + SECTION("N=0 no-op") { + rti->ray_fire(volume_tree, nullptr, nullptr, 0, nullptr, nullptr, + INFTY, HitOrientation::EXITING, nullptr); + SUCCEED("N=0 completed without error"); + } + + // ---- N = 1 ---- + SECTION("N=1 equals scalar") { + N = 1; + make_rays(N, origins, directions); + + auto [dist_scalar, id_scalar] = rti->ray_fire(volume_tree, origins[0], directions[0], INFTY, HitOrientation::EXITING); + + double dist_batch = -1.0; + MeshID id_batch = ID_NONE; + rti->ray_fire(volume_tree, origins.data(), directions.data(), 1, + &dist_batch, &id_batch, INFTY, HitOrientation::EXITING, nullptr); + + REQUIRE(id_batch == id_scalar); + REQUIRE_THAT(dist_batch, Catch::Matchers::WithinAbs(dist_scalar, 1e-6)); + } + + // ---- N = 64 ---- + SECTION("N=64 matches scalar for all") { + N = 64; + make_rays(N, origins, directions); + + std::vector dist_scalar(64, INFTY); + std::vector id_scalar(64, ID_NONE); + for (size_t i = 0; i < 64; ++i) { + auto [d, id] = rti->ray_fire(volume_tree, origins[i], directions[i], INFTY, HitOrientation::EXITING); + dist_scalar[i] = d; id_scalar[i] = id; + } + + std::vector dist_batch(64, -1.0); + std::vector id_batch(64, ID_NONE); + rti->ray_fire(volume_tree, origins.data(), directions.data(), origins.size(), + dist_batch.data(), id_batch.data(), INFTY, HitOrientation::EXITING, nullptr); + + for (size_t i = 0; i < 64; ++i) { + REQUIRE(id_batch[i] == id_scalar[i]); + REQUIRE_THAT(dist_batch[i], Catch::Matchers::WithinAbs(dist_scalar[i], 1e-6)); + } + } + } +} diff --git a/tests/util.h b/tests/util.h index 579ff652..80964602 100644 --- a/tests/util.h +++ b/tests/util.h @@ -96,3 +96,20 @@ create_raytracer(xdg::RTLibrary rt) { return nullptr; } + +inline void make_rays(size_t N, std::vector& origins, std::vector& directions) +{ + origins.clear(); + directions.clear(); + origins.reserve(N); + directions.reserve(N); + for (size_t i = 0; i < N; ++i) { + int axis = static_cast(i % 3); + double s = (i % 2) ? 1.0 : -1.0; + origins.push_back({0.0, 0.0, 0.0}); + if (axis == 0) directions.push_back({s, 0.0, 0.0}); + else if (axis == 1) directions.push_back({0.0, s, 0.0}); + else directions.push_back({0.0, 0.0, s}); + } +} + diff --git a/vendor/GPRT b/vendor/GPRT index f1e95e41..405d9ee9 160000 --- a/vendor/GPRT +++ b/vendor/GPRT @@ -1 +1 @@ -Subproject commit f1e95e4188cde591547d6b4a33a70bf2afaeec59 +Subproject commit 405d9ee9f5ee8e1a0455f776f9e2c3adffb64160 From 663dad56d4c4307d67e86b7e50171cfec3f23499 Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Fri, 30 Jan 2026 12:14:55 +0000 Subject: [PATCH 52/62] Added wiring for point_in_volume_prepared() --- include/xdg/gprt/ray_tracer.h | 2 ++ include/xdg/ray_tracing_interface.h | 56 ++++------------------------- include/xdg/xdg.h | 22 ++++++++++++ src/gprt/dbl_deviceCode.slang | 7 ++-- src/gprt/ray_tracer.cpp | 18 ++++++++++ src/xdg.cpp | 6 ++++ 6 files changed, 59 insertions(+), 52 deletions(-) diff --git a/include/xdg/gprt/ray_tracer.h b/include/xdg/gprt/ray_tracer.h index 3001da33..83e0fa19 100644 --- a/include/xdg/gprt/ray_tracer.h +++ b/include/xdg/gprt/ray_tracer.h @@ -106,6 +106,8 @@ class GPRTRayTracer : public RayTracer { const double dist_limit = INFTY, HitOrientation orientation = HitOrientation::EXITING) override; + void point_in_volume_prepared(const size_t num_rays) override; + std::pair closest(TreeID scene, const Position& origin) override {}; diff --git a/include/xdg/ray_tracing_interface.h b/include/xdg/ray_tracing_interface.h index 6ab2334e..dbbefc8b 100644 --- a/include/xdg/ray_tracing_interface.h +++ b/include/xdg/ray_tracing_interface.h @@ -197,22 +197,6 @@ class RayTracer { // GPU Ray Tracing Support - - /** - * @brief Array based version of point_in_volume query - * - * This method performs a set of point_in_volume queries on a batch of rays defined by their origins and directions. - * It computes whether or not a point lies in a given volume for each point in the batch. With GPRT ray tracing - * this launches the RT pipeline with the number of rays provided. - * - * @param[in] tree The TreeID of the volume we are querying against - * @param[in] points An array of points to query - * @param[in] num_points The number of points to be processed in the batch - * @param[out] results An output array to store the computed results for each point (1 if inside volume, 0 if outside) - * @param[in] directions (optional) array of directions to launch rays in explicit directions per point - these must be non-zero length - * @param[in] exclude_primitives (optional) vector of surface element MeshIDs to exclude from intersection tests - * @return Void. Outputs stored in results array - */ virtual void point_in_volume(TreeID tree, const Position* points, const size_t num_points, @@ -222,24 +206,7 @@ class RayTracer { { fatal_error("GPU ray tracing not supported with this RayTracer backend"); } - /** - * @brief Array based version of ray_fire query - * - * This method performs a set of ray fire queries on a batch of rays defined by their origins and directions. - * It computes the intersection distances and surface IDs for each ray in the batch. With GPRT ray tracing - * this launches the RT pipeline with the number of rays provided. - * - * @param[in] tree The TreeID of the volume we are querying against - * @param[in] origins An array of Position objects representing the starting points of the rays - * @param[in] directions An array of Direction objects representing the directions of the rays - * @param[in] num_rays The number of rays to be processed in the batch - * @param[out] hitDistances An output array to store the computed intersection distances for each ray - * @param[out] surfaceIDs An output array to store the MeshIDs of the surfaces hit by each ray - * @param[in] dist_limit (optional) maximum distance to consider for intersections - * @param[in] orientation (optional) flag to consider whether Entering/Exiting hits should be rejected. Defaults to EXITING - * @param[in] exclude_primitives (optional) vector of surface element MeshIDs to exclude from intersection tests - * @return Void. Outputs stored in hitDistances and surfaceIDs arrays - */ + virtual void ray_fire(TreeID tree, const Position* origins, const Direction* directions, @@ -252,20 +219,7 @@ class RayTracer { { fatal_error("GPU ray tracing not supported with this RayTracer backend"); } - /** - * @brief Array based version of ray_fire query which assumes ray buffers are already populated on device - * - * This method assumes that ray buffers have been externally populated and simply calls the ray tracing pipeline - * to perform a set of ray fire queries on a batch of rays defined by their origins and directions. - * It computes the intersection distances and surface IDs for each ray in the batch. With GPRT ray tracing - * this launches the RT pipeline with the number of rays provided. The results are stored in the output arrays on device. - * - * @param[in] tree The TreeID of the volume we are querying against - * @param[in] num_rays The number of rays to be processed in the batch - * @param[in] dist_limit (optional) maximum distance to consider for intersections - * @param[in] orientation (optional) flag to consider whether Entering/Exiting hits should be rejected. Defaults to EXITING - * @return Void. Outputs stored in dblHit buffer on device - */ + virtual void ray_fire_prepared(const size_t num_rays, const double dist_limit = INFTY, HitOrientation orientation = HitOrientation::EXITING) @@ -273,6 +227,11 @@ class RayTracer { fatal_error("GPU ray tracing not supported with this RayTracer backend"); } + virtual void point_in_volume_prepared(const size_t num_points) + { + fatal_error("GPU ray tracing not supported with this RayTracer backend"); + } + /** * @brief Check whether the current ray buffer capacity is sufficient for the number of rays requested * @param[in] num_rays The number of rays to be processed @@ -301,7 +260,6 @@ class RayTracer { * 2. XDG passes device pointers to the callback * 3. User's callback populates the buffers using their preferred compute kernel/shader * 4. User's callback returns (XDG assumes buffers are now populated) - * 5. Call xdg::ray_fire_prepared() to trace the populated rays * * This avoids unnecessary host-device transfers by allowing users to write directly * to XDG's device buffers without any host-side transfers. diff --git a/include/xdg/xdg.h b/include/xdg/xdg.h index 4190c2ea..9dbc8ad2 100644 --- a/include/xdg/xdg.h +++ b/include/xdg/xdg.h @@ -156,10 +156,32 @@ void ray_fire(MeshID volume, HitOrientation orientation = HitOrientation::EXITING, std::vector* const exclude_primitives = nullptr); +/** + * @brief Call ray fire on pre-populated ray buffers + * + * This method performs a set of ray fire queries on a set of rays that have already been populated on device + * via the external ray population callback method. With GPRT ray tracing this launches the RT pipeline with the number of rays provided. + * + * @param[in] num_rays The number of rays to be processed in the batch + * @param[in] dist_limit (optional) maximum distance to consider for intersections + * @param[in] orientation (optional) flag to consider whether Entering/Exiting hits should be rejected. Defaults to EXITING + * @return Void. Outputs stored in dblHit buffer on device. And can be recovered on host via transfer_hits_buffer_to_host method + */ void ray_fire_prepared(const size_t num_rays, const double dist_limit = INFTY, HitOrientation orientation = HitOrientation::EXITING); +/** + * @brief Call point_in_volume on pre-populated ray buffers + * + * This method performs a set of point_in_volume queries on a set of points that have already been populated on device + * via the external ray population callback method. With GPRT ray tracing this launches the RT pipeline with the number of points provided. + * + * @param[in] num_points The number of points to be processed in the batch + * @return Void. Outputs stored in dblHit buffer on device. And can be recovered on host via transfer_hits_buffer_to_host method + */ +void point_in_volume_prepared(const size_t num_points); + std::pair closest(MeshID volume, const Position& origin) const; diff --git a/src/gprt/dbl_deviceCode.slang b/src/gprt/dbl_deviceCode.slang index b5332dce..8b7881f4 100644 --- a/src/gprt/dbl_deviceCode.slang +++ b/src/gprt/dbl_deviceCode.slang @@ -72,7 +72,7 @@ void ray_fire(uniform dblRayGenData record, uniform DPTriangleGeomData mesh) { payload.tlas = world; if (ray.enabled == 1u) { - TraceRay(PC.volume_accel, RAY_FLAG_NONE, 0xff, 0, 1, rayDesc, payload); + TraceRay(world, RAY_FLAG_NONE, 0xff, 0, 1, rayDesc, payload); } // Store the distance to the hit point and the surface ID in buffers for CPU @@ -94,8 +94,9 @@ void point_in_volume(uniform dblRayGenData record, uniform DPTriangleGeomData me rayDesc.TMin = float(PC.tMin); rayDesc.TMax = float(PC.tMax); - SurfaceAccelerationStructure world = PC.volume_accel; - + int mesh_id = ray.volume_mesh_id; + // Recover the TLAS we are tracing against for this ray + SurfaceAccelerationStructure world = record.meshid_to_accel_address[mesh_id]; // Pass the ray's origin and direction to the payload payload.surf_id = -1; payload.tlas = world; diff --git a/src/gprt/ray_tracer.cpp b/src/gprt/ray_tracer.cpp index 9c80b586..be346ca1 100644 --- a/src/gprt/ray_tracer.cpp +++ b/src/gprt/ray_tracer.cpp @@ -516,6 +516,24 @@ GPRTRayTracer::ray_fire_prepared(const size_t num_rays, return; } +void +GPRTRayTracer::point_in_volume_prepared(const size_t num_points) +{ + if (num_points == 0) return; // no work to do. Early exit + + check_rayhit_buffer_capacity(num_points); + auto rayGen = rayGenPrograms_.at(RayGenType::POINT_IN_VOLUME); + + dblRayFirePushConstants pushConstants; + pushConstants.tMax = INFTY; + pushConstants.tMin = 0.0; + pushConstants.hitOrientation = HitOrientation::ANY; // Set orientation for the ray + + gprtRayGenLaunch1D(context_, rayGen, num_points, pushConstants); + gprtGraphicsSynchronize(context_); + return; +} + void GPRTRayTracer::create_global_surface_tree() { // Create a TLAS (Top-Level Acceleration Structure) for all the volumes diff --git a/src/xdg.cpp b/src/xdg.cpp index 9cb8e749..d36866f7 100644 --- a/src/xdg.cpp +++ b/src/xdg.cpp @@ -289,6 +289,12 @@ XDG::ray_fire_prepared(const size_t num_rays, return ray_tracing_interface()->ray_fire_prepared(num_rays, dist_limit, orientation); } +void +XDG::point_in_volume_prepared(const size_t num_points) +{ + return ray_tracing_interface()->point_in_volume_prepared(num_points); +} + std::pair XDG::closest(MeshID volume, const Position& origin) const { From 3e41d897337bc43a59a4e0966e4e2e90443925d6 Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Fri, 30 Jan 2026 12:33:01 +0000 Subject: [PATCH 53/62] Added test for point_in_volume_prepared() codepath --- tests/test_direct_ray_buffer_access.cpp | 146 ++++++++++++++++++------ tests/test_point_in_volume.cpp | 16 --- tests/util.h | 12 ++ 3 files changed, 121 insertions(+), 53 deletions(-) diff --git a/tests/test_direct_ray_buffer_access.cpp b/tests/test_direct_ray_buffer_access.cpp index 309ce9e9..1f87797d 100644 --- a/tests/test_direct_ray_buffer_access.cpp +++ b/tests/test_direct_ray_buffer_access.cpp @@ -21,6 +21,50 @@ using namespace xdg::test; extern GPRTProgram test_direct_ray_buffer_access_deviceCode; +static RayPopulationCallback make_populate_callback(const std::vector& origins, + const std::vector& directions, + GPRTContext context, + GPRTComputeOf packRays, + MeshID volume_id) { + return [&origins, &directions, context, packRays, volume_id] + (const DeviceRayHitBuffers& buffer, size_t numRays) { + REQUIRE(origins.size() >= numRays); + REQUIRE(directions.size() >= numRays); + + // Convert to double3 for use on GPU + std::vector origins_device(numRays); + std::vector directions_device(numRays); + for (size_t i = 0; i < numRays; ++i) { + origins_device[i] = {origins[i].x, origins[i].y, origins[i].z}; + directions_device[i] = {directions[i].x, directions[i].y, directions[i].z}; + } + + auto origins_buffer = gprtDeviceBufferCreate(context, numRays, origins_device.data()); + auto directions_buffer = gprtDeviceBufferCreate(context, numRays, directions_device.data()); + + constexpr uint32_t threads_per_group = 256; + const uint32_t groups = static_cast((numRays + threads_per_group - 1) / threads_per_group); + + ExternalRayParams params = {}; + params.xdgRays = static_cast(buffer.rayDevPtr); + params.origins = gprtBufferGetDevicePointer(origins_buffer); + params.directions = gprtBufferGetDevicePointer(directions_buffer); + params.num_rays = static_cast(numRays); + params.total_threads = groups * threads_per_group; + params.volume_mesh_id = volume_id; + params.enabled = 1u; + + gprtComputeLaunch(packRays, + { groups, 1, 1 }, + { threads_per_group, 1, 1 }, + params); + gprtComputeSynchronize(context); + + gprtBufferDestroy(origins_buffer); + gprtBufferDestroy(directions_buffer); + }; +} + // This is a GPU only test - skip if no GPU ray tracing backends are enabled TEMPLATE_TEST_CASE("Ray Fire with external populated rays", "[rayfire][mock]", GPRT_Raytracer) @@ -65,43 +109,11 @@ TEMPLATE_TEST_CASE("Ray Fire with external populated rays", "[rayfire][mock]", expected_surfaces.data()); // Create callback to populate rays on device - RayPopulationCallback populate_callback = [&volume_id, &origins, &directions, &context, &packRays] - (const DeviceRayHitBuffers& buffer, size_t numRays) { - REQUIRE(origins.size() >= numRays); - REQUIRE(directions.size() >= numRays); - - // Convert to double3 for use on GPU - std::vector origins_device(numRays); - std::vector directions_device(numRays); - for (size_t i = 0; i < numRays; ++i) { - origins_device[i] = {origins[i].x, origins[i].y, origins[i].z}; - directions_device[i] = {directions[i].x, directions[i].y, directions[i].z}; - } - - auto origins_buffer = gprtDeviceBufferCreate(context, numRays, origins_device.data()); - auto directions_buffer = gprtDeviceBufferCreate(context, numRays, directions_device.data()); - - constexpr uint32_t threads_per_group = 256; - const uint32_t groups = static_cast((numRays + threads_per_group - 1) / threads_per_group); - - ExternalRayParams params = {}; - params.xdgRays = static_cast(buffer.rayDevPtr); - params.origins = gprtBufferGetDevicePointer(origins_buffer); - params.directions = gprtBufferGetDevicePointer(directions_buffer); - params.num_rays = static_cast(numRays); - params.total_threads = groups * threads_per_group; - params.volume_mesh_id = volume_id; - params.enabled = 1u; - - gprtComputeLaunch(packRays, - { groups, 1, 1 }, - { threads_per_group, 1, 1 }, - params); - gprtComputeSynchronize(context); - - gprtBufferDestroy(origins_buffer); - gprtBufferDestroy(directions_buffer); - }; + RayPopulationCallback populate_callback = make_populate_callback(origins, + directions, + context, + packRays, + volume_id); // Populate rays via external API xdg->populate_rays_external(N, populate_callback); @@ -122,3 +134,63 @@ TEMPLATE_TEST_CASE("Ray Fire with external populated rays", "[rayfire][mock]", gprtModuleDestroy(module); } } + +TEMPLATE_TEST_CASE("Point-in-volume with external populated rays", "[piv][mock]", + GPRT_Raytracer) +{ + constexpr auto rt_backend = TestType::value; + + DYNAMIC_SECTION(fmt::format("Backend = {}", rt_backend)) { + check_ray_tracer_supported(rt_backend); + + auto rti = create_raytracer(rt_backend); + REQUIRE(rti); + + auto mm = std::make_shared(false); + mm->init(); + REQUIRE(mm->mesh_library() == MeshLibrary::MOCK); + + auto xdg = std::make_shared(); + xdg->set_mesh_manager_interface(mm); + xdg->set_ray_tracing_interface(rti); + xdg->prepare_raytracer(); + + std::vector points; + std::vector directions; + size_t N = 64; + make_points(N, points, directions); + + auto gprt_rt = std::dynamic_pointer_cast(rti); + REQUIRE(gprt_rt); + + const MeshID volume_id = mm->volumes()[0]; + GPRTContext context = gprt_rt->context(); + GPRTModule module = gprtModuleCreate(context, test_direct_ray_buffer_access_deviceCode); + auto packRays = gprtComputeCreate(context, module, "pack_external_rays"); + + std::vector expected_piv(N, 0); + for (size_t i = 0; i < N; ++i) { + expected_piv[i] = static_cast(xdg->point_in_volume(volume_id, points[i], &directions[i])); + } + + RayPopulationCallback populate_callback = make_populate_callback(points, + directions, + context, + packRays, + volume_id); + xdg->populate_rays_external(N, populate_callback); + + xdg->point_in_volume_prepared(volume_id, N); + std::vector hits; + xdg->transfer_hits_buffer_to_host(N, hits); + + REQUIRE(hits.size() == N); + for (size_t i = 0; i < N; ++i) { + const auto expected = expected_piv[i] ? xdg::PointInVolume::INSIDE : xdg::PointInVolume::OUTSIDE; + REQUIRE(hits[i].piv == expected); + } + + gprtComputeDestroy(packRays); + gprtModuleDestroy(module); + } +} diff --git a/tests/test_point_in_volume.cpp b/tests/test_point_in_volume.cpp index 7e771499..acb62a41 100644 --- a/tests/test_point_in_volume.cpp +++ b/tests/test_point_in_volume.cpp @@ -12,21 +12,6 @@ using namespace xdg; using namespace xdg::test; -static void make_points(size_t N, - std::vector& points, - std::vector& directions) -{ - points.resize(N); - directions.resize(N); - for (size_t i = 0; i < N; ++i) { - // evens inside (origin), odds just outside +X - points[i] = (i % 2 == 0) ? xdg::Position{0,0,0} : xdg::Position{5.1,0,0}; - // alternate ±X directions - directions[i] = (i % 2 == 0) ? xdg::Direction{ 1,0,0} - : xdg::Direction{-1,0,0}; - } -} - // ---------- single test, sections per backend -------------------------------- TEMPLATE_TEST_CASE("Point-in-volume on MeshMock", "[piv][mock]", @@ -160,4 +145,3 @@ TEMPLATE_TEST_CASE("Batch API Point-in-volume on MeshMock", "[piv][mock][batch]" } } } - diff --git a/tests/util.h b/tests/util.h index 80964602..58d81d8f 100644 --- a/tests/util.h +++ b/tests/util.h @@ -113,3 +113,15 @@ inline void make_rays(size_t N, std::vector& origins, std::vector } } +inline void make_points(size_t N, std::vector& points, std::vector& directions) +{ + points.resize(N); + directions.resize(N); + for (size_t i = 0; i < N; ++i) { + // evens inside (origin), odds just outside +X + points[i] = (i % 2 == 0) ? xdg::Position{0.0, 0.0, 0.0} : xdg::Position{5.1, 0.0, 0.0}; + // alternate ±X directions + directions[i] = (i % 2 == 0) ? xdg::Direction{1.0, 0.0, 0.0} + : xdg::Direction{-1.0, 0.0, 0.0}; + } +} From d229e4f9576694350a910903881e568547d1baeb Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Fri, 30 Jan 2026 12:39:50 +0000 Subject: [PATCH 54/62] Fixed wrong function signature call and redefinition of helper function in tests --- tests/test_direct_ray_buffer_access.cpp | 2 +- tests/test_point_in_volume.cpp | 15 --------------- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/tests/test_direct_ray_buffer_access.cpp b/tests/test_direct_ray_buffer_access.cpp index 1f87797d..e4c7edde 100644 --- a/tests/test_direct_ray_buffer_access.cpp +++ b/tests/test_direct_ray_buffer_access.cpp @@ -180,7 +180,7 @@ TEMPLATE_TEST_CASE("Point-in-volume with external populated rays", "[piv][mock]" volume_id); xdg->populate_rays_external(N, populate_callback); - xdg->point_in_volume_prepared(volume_id, N); + xdg->point_in_volume_prepared(N); std::vector hits; xdg->transfer_hits_buffer_to_host(N, hits); diff --git a/tests/test_point_in_volume.cpp b/tests/test_point_in_volume.cpp index 1ad6d7d1..acb62a41 100644 --- a/tests/test_point_in_volume.cpp +++ b/tests/test_point_in_volume.cpp @@ -12,21 +12,6 @@ using namespace xdg; using namespace xdg::test; -static void make_points(size_t N, - std::vector& points, - std::vector& directions) -{ - points.resize(N); - directions.resize(N); - for (size_t i = 0; i < N; ++i) { - // evens inside (origin), odds just outside +X - points[i] = (i % 2 == 0) ? xdg::Position{0,0,0} : xdg::Position{5.1,0,0}; - // alternate ±X directions - directions[i] = (i % 2 == 0) ? xdg::Direction{ 1,0,0} - : xdg::Direction{-1,0,0}; - } -} - // ---------- single test, sections per backend -------------------------------- TEMPLATE_TEST_CASE("Point-in-volume on MeshMock", "[piv][mock]", From d88e7fce4ea245a9ddfab9bb50400b81049a7fbb Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Fri, 30 Jan 2026 18:29:05 +0000 Subject: [PATCH 55/62] Remove stale GPRT specific code from xdg.cpp --- src/xdg.cpp | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/xdg.cpp b/src/xdg.cpp index d36866f7..19b245c7 100644 --- a/src/xdg.cpp +++ b/src/xdg.cpp @@ -9,9 +9,6 @@ #include "xdg/mesh_managers.h" #include "xdg/ray_tracers.h" -#ifdef XDG_ENABLE_GPRT -#include "xdg/gprt/ray.h" -#endif namespace xdg { @@ -59,11 +56,7 @@ void XDG::prepare_volume_for_raytracing(MeshID volume) { void XDG::transfer_hits_buffer_to_host(const size_t num_rays, std::vector& hits) { - auto gprt_rt = std::dynamic_pointer_cast(ray_tracing_interface()); - if (!gprt_rt) { - fatal_error("transfer_hits_buffer_to_host is only supported with the GPRT ray tracer"); - } - gprt_rt->download_hits(num_rays, hits); + ray_tracing_interface()->download_hits(num_rays, hits); } #endif From bf001ab999ae1d15a63b5e0b57398c5c1fc8eb7e Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Tue, 3 Feb 2026 14:27:28 +0000 Subject: [PATCH 56/62] Updated direct_buffer_access test to also test multi-volume --- tests/test_direct_ray_buffer_access.cpp | 116 +++++++++--------- ..._direct_ray_buffer_access_deviceCode.slang | 10 +- tests/test_direct_ray_buffer_access_shared.h | 3 +- 3 files changed, 70 insertions(+), 59 deletions(-) diff --git a/tests/test_direct_ray_buffer_access.cpp b/tests/test_direct_ray_buffer_access.cpp index e4c7edde..2583815c 100644 --- a/tests/test_direct_ray_buffer_access.cpp +++ b/tests/test_direct_ray_buffer_access.cpp @@ -23,35 +23,38 @@ extern GPRTProgram test_direct_ray_buffer_access_deviceCode; static RayPopulationCallback make_populate_callback(const std::vector& origins, const std::vector& directions, + const std::vector& volume_ids, GPRTContext context, - GPRTComputeOf packRays, - MeshID volume_id) { - return [&origins, &directions, context, packRays, volume_id] - (const DeviceRayHitBuffers& buffer, size_t numRays) { - REQUIRE(origins.size() >= numRays); - REQUIRE(directions.size() >= numRays); + GPRTComputeOf packRays) { + return [&origins, &directions, volume_ids, context, packRays] + (const DeviceRayHitBuffers& buffer, size_t num_rays) { + + // When passing arrays to the callback, ensure they are the correct size + assert(origins.size() == num_rays); + assert(directions.size() == num_rays); // Convert to double3 for use on GPU - std::vector origins_device(numRays); - std::vector directions_device(numRays); - for (size_t i = 0; i < numRays; ++i) { + std::vector origins_device(num_rays); + std::vector directions_device(num_rays); + for (size_t i = 0; i < num_rays; ++i) { origins_device[i] = {origins[i].x, origins[i].y, origins[i].z}; directions_device[i] = {directions[i].x, directions[i].y, directions[i].z}; } - auto origins_buffer = gprtDeviceBufferCreate(context, numRays, origins_device.data()); - auto directions_buffer = gprtDeviceBufferCreate(context, numRays, directions_device.data()); + auto origins_buffer = gprtDeviceBufferCreate(context, num_rays, origins_device.data()); + auto directions_buffer = gprtDeviceBufferCreate(context, num_rays, directions_device.data()); + auto volume_ids_buffer = gprtDeviceBufferCreate(context, num_rays, volume_ids.data()); constexpr uint32_t threads_per_group = 256; - const uint32_t groups = static_cast((numRays + threads_per_group - 1) / threads_per_group); + const uint32_t groups = static_cast((num_rays + threads_per_group - 1) / threads_per_group); ExternalRayParams params = {}; params.xdgRays = static_cast(buffer.rayDevPtr); params.origins = gprtBufferGetDevicePointer(origins_buffer); params.directions = gprtBufferGetDevicePointer(directions_buffer); - params.num_rays = static_cast(numRays); + params.num_rays = static_cast(num_rays); params.total_threads = groups * threads_per_group; - params.volume_mesh_id = volume_id; + params.volume_mesh_ids = gprtBufferGetDevicePointer(volume_ids_buffer); params.enabled = 1u; gprtComputeLaunch(packRays, @@ -62,6 +65,9 @@ static RayPopulationCallback make_populate_callback(const std::vector& gprtBufferDestroy(origins_buffer); gprtBufferDestroy(directions_buffer); + if (volume_ids_buffer) { + gprtBufferDestroy(volume_ids_buffer); + } }; } @@ -72,18 +78,13 @@ TEMPLATE_TEST_CASE("Ray Fire with external populated rays", "[rayfire][mock]", constexpr auto rt_backend = TestType::value; DYNAMIC_SECTION(fmt::format("Backend = {}", rt_backend)) { - check_ray_tracer_supported(rt_backend); - - auto rti = create_raytracer(rt_backend); - REQUIRE(rti); - - auto mm = std::make_shared(false); - mm->init(); - REQUIRE(mm->mesh_library() == MeshLibrary::MOCK); - - auto xdg = std::make_shared(); - xdg->set_mesh_manager_interface(mm); - xdg->set_ray_tracing_interface(rti); + check_ray_tracer_supported(rt_backend); // skip if backend not enabled at configuration time + std::shared_ptr xdg = XDG::create(MeshLibrary::MOAB, rt_backend); + REQUIRE(xdg->ray_tracing_interface()->library() == rt_backend); + REQUIRE(xdg->mesh_manager()->mesh_library() == MeshLibrary::MOAB); + const auto& mesh_manager = xdg->mesh_manager(); + mesh_manager->load_file("jezebel.h5m"); + mesh_manager->init(); xdg->prepare_raytracer(); std::vector origins; @@ -91,29 +92,34 @@ TEMPLATE_TEST_CASE("Ray Fire with external populated rays", "[rayfire][mock]", size_t N = 64; make_rays(N, origins, directions); - auto gprt_rt = std::dynamic_pointer_cast(rti); + auto gprt_rt = std::dynamic_pointer_cast(xdg->ray_tracing_interface()); REQUIRE(gprt_rt); - const MeshID volume_id = mm->volumes()[0]; + std::vector volumes = mesh_manager->volumes(); + REQUIRE(volumes.size() >= 2); + const MeshID volume_id = volumes[0]; + const MeshID volume_id_alt = volumes[1]; GPRTContext context = gprt_rt->context(); GPRTModule module = gprtModuleCreate(context, test_direct_ray_buffer_access_deviceCode); auto packRays = gprtComputeCreate(context, module, "pack_external_rays"); std::vector expected_distances(N, INFTY); std::vector expected_surfaces(N, ID_NONE); - xdg->ray_fire(volume_id, - origins.data(), - directions.data(), - N, - expected_distances.data(), - expected_surfaces.data()); + + std::vector volume_ids(N, volume_id); + for (size_t i = 0; i < N; ++i) { + volume_ids[i] = (i % 2 == 0) ? volume_id : volume_id_alt; + const auto [dist, surf] = xdg->ray_fire(volume_ids[i], origins[i], directions[i]); + expected_distances[i] = dist; + expected_surfaces[i] = surf; + } // Create callback to populate rays on device RayPopulationCallback populate_callback = make_populate_callback(origins, - directions, - context, - packRays, - volume_id); + directions, + volume_ids, + context, + packRays); // Populate rays via external API xdg->populate_rays_external(N, populate_callback); @@ -141,18 +147,13 @@ TEMPLATE_TEST_CASE("Point-in-volume with external populated rays", "[piv][mock]" constexpr auto rt_backend = TestType::value; DYNAMIC_SECTION(fmt::format("Backend = {}", rt_backend)) { - check_ray_tracer_supported(rt_backend); - - auto rti = create_raytracer(rt_backend); - REQUIRE(rti); - - auto mm = std::make_shared(false); - mm->init(); - REQUIRE(mm->mesh_library() == MeshLibrary::MOCK); - - auto xdg = std::make_shared(); - xdg->set_mesh_manager_interface(mm); - xdg->set_ray_tracing_interface(rti); + check_ray_tracer_supported(rt_backend); // skip if backend not enabled at configuration time + std::shared_ptr xdg = XDG::create(MeshLibrary::MOAB, rt_backend); + REQUIRE(xdg->ray_tracing_interface()->library() == rt_backend); + REQUIRE(xdg->mesh_manager()->mesh_library() == MeshLibrary::MOAB); + const auto& mesh_manager = xdg->mesh_manager(); + mesh_manager->load_file("jezebel.h5m"); + mesh_manager->init(); xdg->prepare_raytracer(); std::vector points; @@ -160,24 +161,29 @@ TEMPLATE_TEST_CASE("Point-in-volume with external populated rays", "[piv][mock]" size_t N = 64; make_points(N, points, directions); - auto gprt_rt = std::dynamic_pointer_cast(rti); + auto gprt_rt = std::dynamic_pointer_cast(xdg->ray_tracing_interface()); REQUIRE(gprt_rt); - const MeshID volume_id = mm->volumes()[0]; + std::vector volumes = mesh_manager->volumes(); + REQUIRE(volumes.size() >= 2); + const MeshID volume_id = volumes[0]; + const MeshID volume_id_alt = volumes[1]; GPRTContext context = gprt_rt->context(); GPRTModule module = gprtModuleCreate(context, test_direct_ray_buffer_access_deviceCode); auto packRays = gprtComputeCreate(context, module, "pack_external_rays"); std::vector expected_piv(N, 0); + std::vector volume_ids(N, volume_id); for (size_t i = 0; i < N; ++i) { - expected_piv[i] = static_cast(xdg->point_in_volume(volume_id, points[i], &directions[i])); + volume_ids[i] = (i % 2 == 0) ? volume_id : volume_id_alt; + expected_piv[i] = static_cast(xdg->point_in_volume(volume_ids[i], points[i], &directions[i])); } RayPopulationCallback populate_callback = make_populate_callback(points, directions, + volume_ids, context, - packRays, - volume_id); + packRays); xdg->populate_rays_external(N, populate_callback); xdg->point_in_volume_prepared(N); diff --git a/tests/test_direct_ray_buffer_access_deviceCode.slang b/tests/test_direct_ray_buffer_access_deviceCode.slang index a3e8729a..8fae4b7c 100644 --- a/tests/test_direct_ray_buffer_access_deviceCode.slang +++ b/tests/test_direct_ray_buffer_access_deviceCode.slang @@ -16,9 +16,15 @@ void pack_external_rays(uint3 DispatchThreadID: SV_DispatchThreadID, r.direction = extParams.directions[idx]; r.exclude_primitives = nullptr; r.exclude_count = 0; - r.volume_mesh_id = extParams.volume_mesh_id; + r.volume_mesh_id = extParams.volume_mesh_ids[idx]; // Set volume mesh ID per ray r.enabled = extParams.enabled; - extParams.xdgRays[idx] = r; + printf("Ray %u: Origin=(%f, %f, %f), Direction=(%f, %f, %f), VolumeMeshID=%d\n", + idx, + r.origin.x, r.origin.y, r.origin.z, + r.direction.x, r.direction.y, r.direction.z, + r.volume_mesh_id); + + extParams.xdgRays[idx] = r; // Write to device ray buffer } } diff --git a/tests/test_direct_ray_buffer_access_shared.h b/tests/test_direct_ray_buffer_access_shared.h index a8e5d553..e8d5ceb3 100644 --- a/tests/test_direct_ray_buffer_access_shared.h +++ b/tests/test_direct_ray_buffer_access_shared.h @@ -8,7 +8,6 @@ struct ExternalRayParams { double3* directions; uint num_rays; uint total_threads; - int volume_mesh_id; + int32_t* volume_mesh_ids; uint enabled; }; - From dd3bda093413018a80e586fd7546ef5e8c298a50 Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Tue, 3 Feb 2026 14:31:19 +0000 Subject: [PATCH 57/62] Cleaned up some function names and added some warnings for no rays passed --- include/xdg/gprt/ray_tracer.h | 9 ++++++--- include/xdg/ray_tracing_interface.h | 8 ++++++++ src/gprt/ray_tracer.cpp | 22 ++++++++++++++++------ src/xdg.cpp | 2 +- 4 files changed, 31 insertions(+), 10 deletions(-) diff --git a/include/xdg/gprt/ray_tracer.h b/include/xdg/gprt/ray_tracer.h index 83e0fa19..23ed4b00 100644 --- a/include/xdg/gprt/ray_tracer.h +++ b/include/xdg/gprt/ray_tracer.h @@ -109,7 +109,10 @@ class GPRTRayTracer : public RayTracer { void point_in_volume_prepared(const size_t num_rays) override; std::pair closest(TreeID scene, - const Position& origin) override {}; + const Position& origin) override { + fatal_error("Closest queries are not currently supported with GPRT ray tracer"); + return {INFTY, ID_NONE}; + }; bool occluded(TreeID scene, const Position& origin, @@ -134,8 +137,8 @@ class GPRTRayTracer : public RayTracer { void populate_rays_external(size_t numRays, const RayPopulationCallback& callback) override; - void download_hits(const size_t num_rays, - std::vector& hits); + void transfer_hits_buffer_to_host(const size_t num_rays, + std::vector& hits) override; GPRTContext context() { diff --git a/include/xdg/ray_tracing_interface.h b/include/xdg/ray_tracing_interface.h index dbbefc8b..37266e4e 100644 --- a/include/xdg/ray_tracing_interface.h +++ b/include/xdg/ray_tracing_interface.h @@ -13,9 +13,12 @@ #include "xdg/primitive_ref.h" #include "xdg/geometry_data.h" + namespace xdg { +struct dblHit; // forward declaration for dblHit + /** * @brief Device ray/hit buffer descriptor * @@ -272,6 +275,11 @@ class RayTracer { fatal_error("GPU ray tracing not supported with this RayTracer backend"); } + virtual void transfer_hits_buffer_to_host(const size_t num_rays, + std::vector& hits) { + fatal_error("GPU ray tracing not supported with this RayTracer backend"); + } + protected: // Common functions across RayTracers const double bounding_box_bump(const std::shared_ptr mesh_manager, MeshID volume_id); // return a bump value based on the size of a bounding box (minimum 1e-3). Should this be a part of mesh_manager? diff --git a/src/gprt/ray_tracer.cpp b/src/gprt/ray_tracer.cpp index be346ca1..e1a389de 100644 --- a/src/gprt/ray_tracer.cpp +++ b/src/gprt/ray_tracer.cpp @@ -371,7 +371,10 @@ void GPRTRayTracer::point_in_volume(TreeID tree, const Direction* directions, std::vector* exclude_primitives) { - if (num_points == 0) return; // no work to do. Early exit + if (num_points == 0) { + warning("Warning number of points passed to point_in_volume is 0. No work to be done."); + return; + } GPRTAccel volume = surface_volume_tree_to_accel_map.at(tree); auto rayGen = rayGenPrograms_.at(RayGenType::POINT_IN_VOLUME); @@ -440,7 +443,10 @@ void GPRTRayTracer::ray_fire(TreeID tree, HitOrientation orientation, std::vector* const exclude_primitives) { - if (num_rays == 0) return; // no work to do. Early exit + if (num_rays == 0) { + warning("Warning number of rays passed to ray_fire is 0. No work to be done."); + return; + } GPRTAccel volume = surface_volume_tree_to_accel_map.at(tree); auto rayGen = rayGenPrograms_.at(RayGenType::RAY_FIRE); @@ -605,7 +611,10 @@ DeviceRayHitBuffers GPRTRayTracer::get_device_rayhit_buffers(const size_t N) void GPRTRayTracer::populate_rays_external(size_t numRays, const RayPopulationCallback& callback) { - if (numRays == 0) return; + if (numRays == 0) { + warning("Warning number of rays passed to populate_rays_external is 0. No work to be done."); + return; + } // Ensure device buffers are large enough check_rayhit_buffer_capacity(numRays); @@ -617,11 +626,12 @@ void GPRTRayTracer::populate_rays_external(size_t numRays, // Note: The callback is responsible for synchronization if using an async API } -void GPRTRayTracer::download_hits(const size_t num_rays, - std::vector& hits) +void GPRTRayTracer::transfer_hits_buffer_to_host(const size_t num_rays, + std::vector& hits) { + hits.clear(); // Ensure hits vector is empty before populating if (num_rays == 0) { - hits.clear(); + warning("Warning number of rays passed to transfer_hits_buffer_to_host is 0. No work to be done."); return; } if (num_rays > rayHitBuffers_.view.capacity) { diff --git a/src/xdg.cpp b/src/xdg.cpp index 19b245c7..8bc8a2ec 100644 --- a/src/xdg.cpp +++ b/src/xdg.cpp @@ -56,7 +56,7 @@ void XDG::prepare_volume_for_raytracing(MeshID volume) { void XDG::transfer_hits_buffer_to_host(const size_t num_rays, std::vector& hits) { - ray_tracing_interface()->download_hits(num_rays, hits); + ray_tracing_interface()->transfer_hits_buffer_to_host(num_rays, hits); } #endif From c0d180fa4f57ac9c0363ee2016134378dc3ba6b3 Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Tue, 3 Feb 2026 15:46:01 +0000 Subject: [PATCH 58/62] Fixed multi-volume tests --- tests/test_direct_ray_buffer_access.cpp | 9 ++++----- tests/test_direct_ray_buffer_access_deviceCode.slang | 6 ------ 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/tests/test_direct_ray_buffer_access.cpp b/tests/test_direct_ray_buffer_access.cpp index 2583815c..97a96a11 100644 --- a/tests/test_direct_ray_buffer_access.cpp +++ b/tests/test_direct_ray_buffer_access.cpp @@ -54,7 +54,7 @@ static RayPopulationCallback make_populate_callback(const std::vector& params.directions = gprtBufferGetDevicePointer(directions_buffer); params.num_rays = static_cast(num_rays); params.total_threads = groups * threads_per_group; - params.volume_mesh_ids = gprtBufferGetDevicePointer(volume_ids_buffer); + params.volume_mesh_ids = gprtBufferGetDevicePointer(volume_ids_buffer); // Pass array of volume IDs to compute shader params.enabled = 1u; gprtComputeLaunch(packRays, @@ -105,10 +105,9 @@ TEMPLATE_TEST_CASE("Ray Fire with external populated rays", "[rayfire][mock]", std::vector expected_distances(N, INFTY); std::vector expected_surfaces(N, ID_NONE); - std::vector volume_ids(N, volume_id); for (size_t i = 0; i < N; ++i) { - volume_ids[i] = (i % 2 == 0) ? volume_id : volume_id_alt; + volume_ids[i] = (i % 2 == 0) ? volume_id : volume_id_alt; // Volume IDs alternating between two volumes const auto [dist, surf] = xdg->ray_fire(volume_ids[i], origins[i], directions[i]); expected_distances[i] = dist; expected_surfaces[i] = surf; @@ -124,7 +123,7 @@ TEMPLATE_TEST_CASE("Ray Fire with external populated rays", "[rayfire][mock]", // Populate rays via external API xdg->populate_rays_external(N, populate_callback); - xdg->ray_fire_prepared(volume_id, N); + xdg->ray_fire_prepared(N); std::vector hits; xdg->transfer_hits_buffer_to_host(N, hits); @@ -192,7 +191,7 @@ TEMPLATE_TEST_CASE("Point-in-volume with external populated rays", "[piv][mock]" REQUIRE(hits.size() == N); for (size_t i = 0; i < N; ++i) { - const auto expected = expected_piv[i] ? xdg::PointInVolume::INSIDE : xdg::PointInVolume::OUTSIDE; + const auto expected = expected_piv[i] ? xdg::PointInVolume::INSIDE : xdg::PointInVolume::OUTSIDE; // convert back to enum REQUIRE(hits[i].piv == expected); } diff --git a/tests/test_direct_ray_buffer_access_deviceCode.slang b/tests/test_direct_ray_buffer_access_deviceCode.slang index 8fae4b7c..b61e6126 100644 --- a/tests/test_direct_ray_buffer_access_deviceCode.slang +++ b/tests/test_direct_ray_buffer_access_deviceCode.slang @@ -19,12 +19,6 @@ void pack_external_rays(uint3 DispatchThreadID: SV_DispatchThreadID, r.volume_mesh_id = extParams.volume_mesh_ids[idx]; // Set volume mesh ID per ray r.enabled = extParams.enabled; - printf("Ray %u: Origin=(%f, %f, %f), Direction=(%f, %f, %f), VolumeMeshID=%d\n", - idx, - r.origin.x, r.origin.y, r.origin.z, - r.direction.x, r.direction.y, r.direction.z, - r.volume_mesh_id); - extParams.xdgRays[idx] = r; // Write to device ray buffer } } From 82189385235fe2d6b63dbb4d8396e8379bbc7102 Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Tue, 3 Feb 2026 15:46:51 +0000 Subject: [PATCH 59/62] Added Device side MeshID to sense mapping to handle reverse sense for multi-volume_ --- include/xdg/gprt/ray_tracer.h | 20 +++++++++++++++++++- include/xdg/gprt/shared_structs.h | 1 + src/gprt/dbl_deviceCode.slang | 5 +++-- src/gprt/ray_tracer.cpp | 29 ++++++++++++++++++++--------- 4 files changed, 43 insertions(+), 12 deletions(-) diff --git a/include/xdg/gprt/ray_tracer.h b/include/xdg/gprt/ray_tracer.h index 23ed4b00..03580e42 100644 --- a/include/xdg/gprt/ray_tracer.h +++ b/include/xdg/gprt/ray_tracer.h @@ -189,9 +189,28 @@ class GPRTRayTracer : public RayTracer { std::unordered_map surface_tree_to_volume_map_; std::vector tlas_handles_; // Host side storage of TLAS device addresses GPRTBufferOf tlas_handle_buffer_; // Device buffer for TLAS addresses + std::vector meshid_to_sense_; // Host-side MeshID -> sense map + GPRTBufferOf meshid_to_sense_buffer_ {nullptr}; // Device buffer for MeshID -> sense map bool initialized_ {false}; // flag to indicate if init() has been called void update_tlas_table_(); + void update_meshid_to_sense_(); + + template + void upload_device_buffer_(GPRTBufferOf& buf, const std::vector& host_data) + { + if (host_data.empty()) return; + + if (!buf) { + buf = gprtDeviceBufferCreate(context_, host_data.size(), host_data.data()); + return; + } + + gprtBufferResize(context_, buf, host_data.size(), false); + gprtBufferMap(buf); + std::copy(host_data.begin(), host_data.end(), gprtBufferGetHostPointer(buf)); + gprtBufferUnmap(buf); + } // Global Tree IDs GPRTAccel global_surface_accel_ {nullptr}; @@ -200,5 +219,4 @@ class GPRTRayTracer : public RayTracer { }; } // namespace xdg - #endif // include guard diff --git a/include/xdg/gprt/shared_structs.h b/include/xdg/gprt/shared_structs.h index 755b48b3..01ede6ae 100644 --- a/include/xdg/gprt/shared_structs.h +++ b/include/xdg/gprt/shared_structs.h @@ -22,6 +22,7 @@ struct DPTriangleGeomData { int2 vols; int forward_vol; int reverse_vol; + int* meshid_to_sense; // MeshID -> sense (+1 forward, -1 reverse) xdg::dblRay *ray; // double precision rays xdg::HitOrientation hitOrientation; int forward_tree; // TreeID of the forward volume diff --git a/src/gprt/dbl_deviceCode.slang b/src/gprt/dbl_deviceCode.slang index 8b7881f4..0ba8a490 100644 --- a/src/gprt/dbl_deviceCode.slang +++ b/src/gprt/dbl_deviceCode.slang @@ -241,8 +241,9 @@ void DPTrianglePluckerIntersection(uniform DPTriangleGeomData record) double3 norm = record.normals[primID]; // recover double precision normal. TODO - Should we calculate from vertices instead? - // sense adjustment of normal - if (PC.volume_tree == record.reverse_tree) + // sense adjustment of normal (per MeshID) + int mesh_sense = record.meshid_to_sense[ray.volume_mesh_id]; // +1 forward, -1 reverse + if (mesh_sense < 0) { norm = -norm; } diff --git a/src/gprt/ray_tracer.cpp b/src/gprt/ray_tracer.cpp index e1a389de..7427892d 100644 --- a/src/gprt/ray_tracer.cpp +++ b/src/gprt/ray_tracer.cpp @@ -171,6 +171,7 @@ GPRTRayTracer::create_surface_tree(const std::shared_ptr& mesh_mana geom_data->normals = gprtBufferGetDevicePointer(normal_buffer); geom_data->primitive_refs = gprtBufferGetDevicePointer(primitive_refs_buffer); geom_data->num_faces = num_faces; + // meshid_to_sense pointer is set after meshid_to_sense_buffer_ is created constexpr uint32_t threadsPerGroup = 64; // must match [numthreads(64,1,1)] uint32_t numGroupsX = (num_faces + threadsPerGroup - 1) / threadsPerGroup; @@ -194,14 +195,14 @@ GPRTRayTracer::create_surface_tree(const std::shared_ptr& mesh_mana surfaceBlasInstances.push_back(instance); globalBlasInstances_.push_back(instance); - // Always update per-volume info + // Always update per-volume info and MeshID -> sparse sense mapping auto [forward_parent, reverse_parent] = mesh_manager->get_parent_volumes(surf); if (volume_id == forward_parent) { - geom_data->forward_vol = forward_parent; - geom_data->forward_tree = tree; + meshid_to_sense_.resize(static_cast(forward_parent) + 1, 1); + meshid_to_sense_[forward_parent] = 1; } else if (volume_id == reverse_parent) { - geom_data->reverse_vol = reverse_parent; - geom_data->reverse_tree = tree; + meshid_to_sense_.resize(static_cast(reverse_parent) + 1, 1); + meshid_to_sense_[reverse_parent] = -1; } else { fatal_error("Volume {} is not a parent of surface {}", volume_id, surf); } @@ -218,6 +219,8 @@ GPRTRayTracer::create_surface_tree(const std::shared_ptr& mesh_mana } tlas_handles_[volume_id] = gprtAccelGetDeviceAddress(volume_tlas); + update_meshid_to_sense_(); + if (initialized_) { update_tlas_table_(); gprtBuildShaderBindingTable(context_, GPRT_SBT_ALL); @@ -591,10 +594,7 @@ void GPRTRayTracer::check_rayhit_buffer_capacity(const size_t N) // Update the TLAS table (MeshID -> SurfaceAccelerationStructure) buffer on the device void GPRTRayTracer::update_tlas_table_() { - gprtBufferResize(context_, tlas_handle_buffer_, tlas_handles_.size(), false); - gprtBufferMap(tlas_handle_buffer_); - std::copy(tlas_handles_.begin(), tlas_handles_.end(), gprtBufferGetHostPointer(tlas_handle_buffer_)); - gprtBufferUnmap(tlas_handle_buffer_); + upload_device_buffer_(tlas_handle_buffer_, tlas_handles_); for (auto type : {RayGenType::RAY_FIRE, RayGenType::POINT_IN_VOLUME}) { auto* raygendata = gprtRayGenGetParameters(rayGenPrograms_.at(type)); @@ -602,6 +602,17 @@ void GPRTRayTracer::update_tlas_table_() } } +void GPRTRayTracer::update_meshid_to_sense_() +{ + upload_device_buffer_(meshid_to_sense_buffer_, meshid_to_sense_); + + for (auto const& [surf, geom] : surface_to_geometry_map_) { + DPTriangleGeomData* geom_data = gprtGeomGetParameters(geom); + geom_data->meshid_to_sense = gprtBufferGetDevicePointer(meshid_to_sense_buffer_); + } +} + + DeviceRayHitBuffers GPRTRayTracer::get_device_rayhit_buffers(const size_t N) { check_rayhit_buffer_capacity(N); From 422a386b72c57dccc3eda339470c8f10077d0653 Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Tue, 3 Feb 2026 15:51:21 +0000 Subject: [PATCH 60/62] Cleanup unused variables in shared host/device side structs --- include/xdg/gprt/shared_structs.h | 8 -------- src/gprt/ray_tracer.cpp | 8 -------- 2 files changed, 16 deletions(-) diff --git a/include/xdg/gprt/shared_structs.h b/include/xdg/gprt/shared_structs.h index 01ede6ae..915898f5 100644 --- a/include/xdg/gprt/shared_structs.h +++ b/include/xdg/gprt/shared_structs.h @@ -8,7 +8,6 @@ struct GPRTPrimitiveRef { int id; // ID of the primitive - int sense; }; @@ -19,14 +18,9 @@ struct DPTriangleGeomData { uint3 *index; // index buffer double3 *normals; // normals buffer int surf_id; - int2 vols; - int forward_vol; - int reverse_vol; int* meshid_to_sense; // MeshID -> sense (+1 forward, -1 reverse) xdg::dblRay *ray; // double precision rays xdg::HitOrientation hitOrientation; - int forward_tree; // TreeID of the forward volume - int reverse_tree; // TreeID of the reverse volume GPRTPrimitiveRef* primitive_refs; int num_faces; // Number of faces in the geometry }; @@ -43,8 +37,6 @@ struct dblRayGenData { struct dblRayFirePushConstants { double tMax; double tMin; - SurfaceAccelerationStructure volume_accel; - int volume_tree; xdg::HitOrientation hitOrientation; }; diff --git a/src/gprt/ray_tracer.cpp b/src/gprt/ray_tracer.cpp index 7427892d..1d76283e 100644 --- a/src/gprt/ray_tracer.cpp +++ b/src/gprt/ray_tracer.cpp @@ -284,8 +284,6 @@ bool GPRTRayTracer::point_in_volume(SurfaceTreeID tree, pushConstants.hitOrientation = HitOrientation::ANY; pushConstants.tMax = INFTY; pushConstants.tMin = 0.0; - pushConstants.volume_accel = gprtAccelGetDeviceAddress(volume); - pushConstants.volume_tree = tree; gprtRayGenLaunch1D(context_, rayGen, 1, pushConstants); // Launch raygen shader (entry point to RT pipeline) gprtGraphicsSynchronize(context_); // Ensure all GPU operations are complete before returning control flow to CPU @@ -346,8 +344,6 @@ std::pair GPRTRayTracer::ray_fire(SurfaceTreeID tree, pushConstants.hitOrientation = orientation; pushConstants.tMax = dist_limit; pushConstants.tMin = 0.0; - pushConstants.volume_accel = gprtAccelGetDeviceAddress(volume); - pushConstants.volume_tree = tree; gprtRayGenLaunch1D(context_, rayGen, 1, pushConstants); // Launch raygen shader (entry point to RT pipeline) gprtGraphicsSynchronize(context_); // Ensure all GPU operations are complete before returning control flow to CPU @@ -417,8 +413,6 @@ void GPRTRayTracer::point_in_volume(TreeID tree, pushConstants.hitOrientation = HitOrientation::ANY; pushConstants.tMax = INFTY; pushConstants.tMin = 0.0; - pushConstants.volume_accel = gprtAccelGetDeviceAddress(volume); - pushConstants.volume_tree = tree; gprtRayGenLaunch1D(context_, rayGen, num_points, pushConstants); gprtGraphicsSynchronize(context_); @@ -477,8 +471,6 @@ void GPRTRayTracer::ray_fire(TreeID tree, pushConstants.hitOrientation = orientation; pushConstants.tMax = dist_limit; pushConstants.tMin = 0.0; - pushConstants.volume_accel = gprtAccelGetDeviceAddress(volume); - pushConstants.volume_tree = tree; // Launch the ray generation shader with push constants and buffer bindings gprtRayGenLaunch1D(context_, rayGen, num_rays, pushConstants); From ea3d53c8344a291d03da4577ad5f451489e813e0 Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Tue, 3 Feb 2026 16:30:45 +0000 Subject: [PATCH 61/62] Implemented methods for directly populating ray buffers on device --- include/xdg/gprt/ray_tracer.h | 18 +- include/xdg/ray_tracing_interface.h | 30 +- include/xdg/xdg.h | 61 +--- src/gprt/ray_tracer.cpp | 133 -------- src/xdg.cpp | 35 --- tests/test_direct_ray_buffer_access.cpp | 4 +- tests/test_point_in_volume.cpp | 70 +---- tests/test_ray_fire.cpp | 79 +---- tools/CMakeLists.txt | 6 +- tools/batch_point_in_volume.cpp | 194 ------------ tools/batch_ray_fire.cpp | 226 ------------- tools/ray_benchmark/CMakeLists.txt | 33 -- tools/ray_benchmark/ray_benchmark.cpp | 231 -------------- tools/ray_benchmark/ray_benchmark.h | 97 ------ .../ray_benchmark_deviceCode.slang | 57 ---- tools/ray_benchmark/ray_benchmark_driver.py | 297 ------------------ tools/ray_benchmark/ray_benchmark_shared.h | 14 - 17 files changed, 10 insertions(+), 1575 deletions(-) delete mode 100644 tools/batch_point_in_volume.cpp delete mode 100644 tools/batch_ray_fire.cpp delete mode 100644 tools/ray_benchmark/CMakeLists.txt delete mode 100644 tools/ray_benchmark/ray_benchmark.cpp delete mode 100644 tools/ray_benchmark/ray_benchmark.h delete mode 100644 tools/ray_benchmark/ray_benchmark_deviceCode.slang delete mode 100644 tools/ray_benchmark/ray_benchmark_driver.py delete mode 100644 tools/ray_benchmark/ray_benchmark_shared.h diff --git a/include/xdg/gprt/ray_tracer.h b/include/xdg/gprt/ray_tracer.h index 03580e42..8965fc54 100644 --- a/include/xdg/gprt/ray_tracer.h +++ b/include/xdg/gprt/ray_tracer.h @@ -79,28 +79,12 @@ class GPRTRayTracer : public RayTracer { const Direction* direction = nullptr, const std::vector* exclude_primitives = nullptr) const override; - void point_in_volume(TreeID tree, - const Position* points, - const size_t num_points, - uint8_t* results, - const Direction* directions = nullptr, - std::vector* exclude_primitives = nullptr) override; - std::pair ray_fire(TreeID scene, const Position& origin, const Direction& direction, const double dist_limit = INFTY, HitOrientation orientation = HitOrientation::EXITING, std::vector* const exclude_primitives = nullptr) override; - void ray_fire(TreeID tree, - const Position* origins, - const Direction* directions, - const size_t num_rays, - double* hitDistances, - MeshID* surfaceIDs, - const double dist_limit = INFTY, - HitOrientation orientation = HitOrientation::EXITING, - std::vector* const exclude_primitives = nullptr) override; void ray_fire_prepared(const size_t num_rays, const double dist_limit = INFTY, @@ -138,7 +122,7 @@ class GPRTRayTracer : public RayTracer { const RayPopulationCallback& callback) override; void transfer_hits_buffer_to_host(const size_t num_rays, - std::vector& hits) override; + std::vector& hits); GPRTContext context() { diff --git a/include/xdg/ray_tracing_interface.h b/include/xdg/ray_tracing_interface.h index 37266e4e..b53902f6 100644 --- a/include/xdg/ray_tracing_interface.h +++ b/include/xdg/ray_tracing_interface.h @@ -200,29 +200,6 @@ class RayTracer { // GPU Ray Tracing Support - virtual void point_in_volume(TreeID tree, - const Position* points, - const size_t num_points, - uint8_t* results, - const Direction* directions = nullptr, - std::vector* exclude_primitives = nullptr) - { - fatal_error("GPU ray tracing not supported with this RayTracer backend"); - } - - virtual void ray_fire(TreeID tree, - const Position* origins, - const Direction* directions, - const size_t num_rays, - double* hitDistances, - MeshID* surfaceIDs, - const double dist_limit = INFTY, - HitOrientation orientation = HitOrientation::EXITING, - std::vector* const exclude_primitives = nullptr) - { - fatal_error("GPU ray tracing not supported with this RayTracer backend"); - } - virtual void ray_fire_prepared(const size_t num_rays, const double dist_limit = INFTY, HitOrientation orientation = HitOrientation::EXITING) @@ -274,12 +251,7 @@ class RayTracer { const RayPopulationCallback& callback) { fatal_error("GPU ray tracing not supported with this RayTracer backend"); } - - virtual void transfer_hits_buffer_to_host(const size_t num_rays, - std::vector& hits) { - fatal_error("GPU ray tracing not supported with this RayTracer backend"); - } - + protected: // Common functions across RayTracers const double bounding_box_bump(const std::shared_ptr mesh_manager, MeshID volume_id); // return a bump value based on the size of a bounding box (minimum 1e-3). Should this be a part of mesh_manager? diff --git a/include/xdg/xdg.h b/include/xdg/xdg.h index 9dbc8ad2..f25225d5 100644 --- a/include/xdg/xdg.h +++ b/include/xdg/xdg.h @@ -92,7 +92,7 @@ bool point_in_volume(MeshID volume, * a distance limit and whether Entering/Exiting hits should be rejected. * * @param[in] volume The MeshID of the volume we are querying against - * @param[in] origin An array of Position objects representing the starting points of the rays + * @param[in] origin Origin of the ray to be fired * @param[in] direction (optional) Direction object to launch a ray in a specified direction * @param[in] dist_limit (optional) maximum distance to consider for intersections * @param[in] orientation (optional) flag to consider whether Entering/Exiting hits should be rejected. Defaults to EXITING @@ -106,55 +106,6 @@ std::pair ray_fire(MeshID volume, HitOrientation orientation = HitOrientation::EXITING, std::vector* const exclude_primitives = nullptr) const; -/** - * @brief Array based version of point_in_volume query - * - * This method performs a set of point_in_volume queries on a batch of rays defined by their origins and directions. - * It computes whether or not a point lies in a given volume for each point in the batch. With GPRT ray tracing - * this launches the RT pipeline with the number of rays provided. - * - * @param[in] tree The TreeID of the volume we are querying against - * @param[in] points An array of points to query - * @param[in] num_points The number of points to be processed in the batch - * @param[out] results An output array to store the computed results for each point (1 if inside volume, 0 if outside) - * @param[in] directions (optional) array of directions to launch rays in explicit directions per point - these must be non-zero length - * @param[in] exclude_primitives (optional) vector of surface element MeshIDs to exclude from intersection tests - * @return Void. Outputs stored in results array - */ -void point_in_volume(MeshID volume, - const Position* points, - const size_t num_points, - uint8_t* results, - const Direction* directions = nullptr, - std::vector* exclude_primitives = nullptr) const; - -/** - * @brief Array based version of ray_fire query - * - * This method performs a set of ray fire queries on a batch of rays defined by their origins and directions. - * It computes the intersection distances and surface IDs for each ray in the batch. With GPRT ray tracing - * this launches the RT pipeline with the number of rays provided. - * - * @param[in] tree The TreeID of the volume we are querying against - * @param[in] origins An array of Position objects representing the starting points of the rays - * @param[in] directions An array of Direction objects representing the directions of the rays - * @param[in] num_rays The number of rays to be processed in the batch - * @param[out] hitDistances An output array to store the computed intersection distances for each ray - * @param[out] surfaceIDs An output array to store the MeshIDs of the surfaces hit by each ray - * @param[in] dist_limit (optional) maximum distance to consider for intersections - * @param[in] orientation (optional) flag to consider whether Entering/Exiting hits should be rejected. Defaults to EXITING - * @param[in] exclude_primitives (optional) vector of surface element MeshIDs to exclude from intersection tests - * @return Void. Outputs stored in hitDistances and surfaceIDs arrays - */ -void ray_fire(MeshID volume, - const Position* origins, - const Direction* directions, - const size_t num_rays, - double* hitDistances, - MeshID* surfaceIDs, - const double dist_limit = INFTY, - HitOrientation orientation = HitOrientation::EXITING, - std::vector* const exclude_primitives = nullptr); /** * @brief Call ray fire on pre-populated ray buffers @@ -165,7 +116,7 @@ void ray_fire(MeshID volume, * @param[in] num_rays The number of rays to be processed in the batch * @param[in] dist_limit (optional) maximum distance to consider for intersections * @param[in] orientation (optional) flag to consider whether Entering/Exiting hits should be rejected. Defaults to EXITING - * @return Void. Outputs stored in dblHit buffer on device. And can be recovered on host via transfer_hits_buffer_to_host method + * @return Void. Outputs stored in dblHit buffer on device. */ void ray_fire_prepared(const size_t num_rays, const double dist_limit = INFTY, @@ -178,7 +129,7 @@ void ray_fire_prepared(const size_t num_rays, * via the external ray population callback method. With GPRT ray tracing this launches the RT pipeline with the number of points provided. * * @param[in] num_points The number of points to be processed in the batch - * @return Void. Outputs stored in dblHit buffer on device. And can be recovered on host via transfer_hits_buffer_to_host method + * @return Void. Outputs stored in dblHit buffer on device. */ void point_in_volume_prepared(const size_t num_points); @@ -223,12 +174,6 @@ Direction surface_normal(MeshID surface, return ray_tracing_interface()->populate_rays_external(numRays, callback); } -// Device to host transfer of hit buffers (GPRT only for now) -#ifdef XDG_ENABLE_GPRT - void transfer_hits_buffer_to_host(const size_t num_rays, - std::vector& hits); -#endif - // Accessors const std::shared_ptr& ray_tracing_interface() const { return ray_tracing_interface_; diff --git a/src/gprt/ray_tracer.cpp b/src/gprt/ray_tracer.cpp index 1d76283e..5398dabf 100644 --- a/src/gprt/ray_tracer.cpp +++ b/src/gprt/ray_tracer.cpp @@ -362,140 +362,7 @@ std::pair GPRTRayTracer::ray_fire(SurfaceTreeID tree, if (exclude_primitives) exclude_primitives->push_back(primitive_id); return {distance, surface}; } - -void GPRTRayTracer::point_in_volume(TreeID tree, - const Position* points, - const size_t num_points, - uint8_t* results, - const Direction* directions, - std::vector* exclude_primitives) -{ - if (num_points == 0) { - warning("Warning number of points passed to point_in_volume is 0. No work to be done."); - return; - } - - GPRTAccel volume = surface_volume_tree_to_accel_map.at(tree); - auto rayGen = rayGenPrograms_.at(RayGenType::POINT_IN_VOLUME); - dblRayGenData* rayGenData = gprtRayGenGetParameters(rayGen); - check_rayhit_buffer_capacity(num_points); - - // TODO - handle exclude_primitives for batch version - - // Set a default direction to be used if no direction is provided - const Direction defaultDir = Direction{1. / std::sqrt(2.0), 1. / std::sqrt(2.0), 0.0}; - - // Map the region start - gprtBufferMap(rayHitBuffers_.ray); - dblRay* ray = gprtBufferGetHostPointer(rayHitBuffers_.ray); - - // Common ray params - for (size_t i = 0; i < num_points; ++i) { - ray[i].origin = {points[i].x, points[i].y, points[i].z}; - ray[i].exclude_primitives = nullptr; - ray[i].volume_mesh_id = surface_tree_to_volume_map_.at(tree); - ray[i].enabled = 1; // Ensure the ray is enabled - } - - // Directions - if (!directions) { - for (size_t i = 0; i < num_points; ++i) - ray[i].direction = double3{ defaultDir.x, defaultDir.y, defaultDir.z }; - } else { - for (size_t i = 0; i < num_points; ++i) - ray[i].direction = double3{ directions[i].x, directions[i].y, directions[i].z }; - } - - gprtBufferUnmap(rayHitBuffers_.ray); // required to sync buffer back on GPU? - - // Set push constants (same for every ray) - dblRayFirePushConstants pushConstants; - pushConstants.hitOrientation = HitOrientation::ANY; - pushConstants.tMax = INFTY; - pushConstants.tMin = 0.0; - - gprtRayGenLaunch1D(context_, rayGen, num_points, pushConstants); - gprtGraphicsSynchronize(context_); - - // Retrieve the output from the ray output buffer - gprtBufferMap(rayHitBuffers_.hit); - dblHit* hit = gprtBufferGetHostPointer(rayHitBuffers_.hit); - for (size_t i = 0; i < num_points; ++i) { - auto piv = hit[i].piv; // Point in volume check result - results[i] = static_cast(piv); - } - gprtBufferUnmap(rayHitBuffers_.hit); // required to sync buffer back on GPU? Maybe this second unmap isn't actually needed since we dont need to resyncrhonize after retrieving the data from device - - return; -} -// Array version of ray_fire -void GPRTRayTracer::ray_fire(TreeID tree, - const Position* origins, - const Direction* directions, - const size_t num_rays, - double* hitDistances, - MeshID* surfaceIDs, - const double dist_limit, - HitOrientation orientation, - std::vector* const exclude_primitives) -{ - if (num_rays == 0) { - warning("Warning number of rays passed to ray_fire is 0. No work to be done."); - return; - } - - GPRTAccel volume = surface_volume_tree_to_accel_map.at(tree); - auto rayGen = rayGenPrograms_.at(RayGenType::RAY_FIRE); - dblRayGenData* rayGenData = gprtRayGenGetParameters(rayGen); - check_rayhit_buffer_capacity(num_rays); - - gprtBufferMap(rayHitBuffers_.ray); - dblRay* ray = gprtBufferGetHostPointer(rayHitBuffers_.ray); - // Set per ray values - for (size_t i = 0; i < num_rays; ++i) { - const auto& origin = origins[i]; - const auto& direction = directions[i]; - - ray[i].origin = {origin.x, origin.y, origin.z}; - ray[i].direction = {direction.x, direction.y, direction.z}; - ray[i].exclude_primitives = nullptr; // Not currently supported in batch version - ray[i].volume_mesh_id = surface_tree_to_volume_map_.at(tree); - ray[i].enabled = 1; // Ensure the ray is enabled - } - - gprtBufferUnmap(rayHitBuffers_.ray); // required to sync buffer back on GPU? - - // Set push constants (same for every ray) - dblRayFirePushConstants pushConstants; - pushConstants.hitOrientation = orientation; - pushConstants.tMax = dist_limit; - pushConstants.tMin = 0.0; - - // Launch the ray generation shader with push constants and buffer bindings - gprtRayGenLaunch1D(context_, rayGen, num_rays, pushConstants); - gprtGraphicsSynchronize(context_); - - // Retrieve the output from the ray output buffer - gprtBufferMap(rayHitBuffers_.hit); - dblHit* hit = gprtBufferGetHostPointer(rayHitBuffers_.hit); - // populate the result arrays - for (size_t i = 0; i < num_rays; ++i) { - const MeshID surfaceHit = hit[i].surf_id; - if (surfaceHit == ID_NONE) { - hitDistances[i] = INFTY; - surfaceIDs[i] = ID_NONE; - } - else { - hitDistances[i] = hit[i].distance; - surfaceIDs[i] = surfaceHit; - // TODO - handle exclude_primitives for batch version - } - } - gprtBufferUnmap(rayHitBuffers_.hit); // required to sync buffer back on GPU? Maybe this second unmap isn't actually needed since we dont need to resyncrhonize after retrieving the data from device - return; -} - void GPRTRayTracer::ray_fire_prepared(const size_t num_rays, const double dist_limit, diff --git a/src/xdg.cpp b/src/xdg.cpp index 8bc8a2ec..f0687076 100644 --- a/src/xdg.cpp +++ b/src/xdg.cpp @@ -52,14 +52,6 @@ void XDG::prepare_volume_for_raytracing(MeshID volume) { volume_to_point_location_tree_map_[volume] = volume_tree; } -#ifdef XDG_ENABLE_GPRT -void XDG::transfer_hits_buffer_to_host(const size_t num_rays, - std::vector& hits) -{ - ray_tracing_interface()->transfer_hits_buffer_to_host(num_rays, hits); -} -#endif - std::shared_ptr XDG::create(MeshLibrary mesh_lib, RTLibrary ray_tracing_lib) { std::shared_ptr xdg = std::make_shared(); @@ -118,17 +110,6 @@ bool XDG::point_in_volume(MeshID volume, return ray_tracing_interface()->point_in_volume(tree, point, direction, exclude_primitives); } -void XDG::point_in_volume(MeshID volume, - const Position* points, - const size_t num_points, - uint8_t* results, - const Direction* directions, - std::vector* exclude_primitives) const -{ - TreeID tree = volume_to_surface_tree_map_.at(volume); - ray_tracing_interface()->point_in_volume(tree, points, num_points, results, directions, exclude_primitives); -} - MeshID XDG::find_volume(const Position& point, const Direction& direction) const { @@ -258,22 +239,6 @@ XDG::ray_fire(MeshID volume, return ray_tracing_interface()->ray_fire(tree, origin, direction, dist_limit, orientation, exclude_primitives); } -// Array version of ray_fire -void -XDG::ray_fire(MeshID volume, - const Position* origins, - const Direction* directions, - const size_t num_rays, - double* hitDistances, - MeshID* surfaceIDs, - const double dist_limit, - HitOrientation orientation, - std::vector* const exclude_primitives) -{ - TreeID tree = volume_to_surface_tree_map_.at(volume); - return ray_tracing_interface()->ray_fire(tree, origins, directions, num_rays, hitDistances, surfaceIDs, dist_limit, orientation, exclude_primitives); -} - void XDG::ray_fire_prepared(const size_t num_rays, const double dist_limit, diff --git a/tests/test_direct_ray_buffer_access.cpp b/tests/test_direct_ray_buffer_access.cpp index 97a96a11..8b75900c 100644 --- a/tests/test_direct_ray_buffer_access.cpp +++ b/tests/test_direct_ray_buffer_access.cpp @@ -125,7 +125,7 @@ TEMPLATE_TEST_CASE("Ray Fire with external populated rays", "[rayfire][mock]", xdg->ray_fire_prepared(N); std::vector hits; - xdg->transfer_hits_buffer_to_host(N, hits); + gprt_rt->transfer_hits_buffer_to_host(N, hits); REQUIRE(hits.size() == N); for (size_t i = 0; i < N; ++i) { @@ -187,7 +187,7 @@ TEMPLATE_TEST_CASE("Point-in-volume with external populated rays", "[piv][mock]" xdg->point_in_volume_prepared(N); std::vector hits; - xdg->transfer_hits_buffer_to_host(N, hits); + gprt_rt->transfer_hits_buffer_to_host(N, hits); REQUIRE(hits.size() == N); for (size_t i = 0; i < N; ++i) { diff --git a/tests/test_point_in_volume.cpp b/tests/test_point_in_volume.cpp index acb62a41..63acb882 100644 --- a/tests/test_point_in_volume.cpp +++ b/tests/test_point_in_volume.cpp @@ -76,72 +76,4 @@ TEMPLATE_TEST_CASE("Point-in-volume on MeshMock", "[piv][mock]", result = rti->point_in_volume(volume_tree, point, &dir); REQUIRE(result == false); } -} - -TEMPLATE_TEST_CASE("Batch API Point-in-volume on MeshMock", "[piv][mock][batch]", - Embree_Raytracer, - GPRT_Raytracer) -{ - constexpr auto rt_backend = TestType::value; - - DYNAMIC_SECTION(fmt::format("Backend = {}", rt_backend)) { - check_ray_tracer_supported(rt_backend); - if (rt_backend == RTLibrary::EMBREE) { - SKIP("Skipping PIV batch for Embree: batch API not implemented yet"); - } - - auto rti = create_raytracer(rt_backend); - REQUIRE(rti); - - auto mm = std::make_shared(false); - mm->init(); - REQUIRE(mm->mesh_library() == MeshLibrary::MOCK); - - auto [volume_tree, element_tree] = rti->register_volume(mm, mm->volumes()[0]); - REQUIRE(volume_tree != ID_NONE); - REQUIRE(element_tree == ID_NONE); - - rti->init(); - - std::vector points; - std::vector directions; - std::vector has_dir; - size_t N; - - SECTION("N=0 no-op") { - rti->point_in_volume(volume_tree, - nullptr, /*points*/ - 0, /*num_points*/ - nullptr /*results*/); - SUCCEED("N=0 completed without error"); - } - - SECTION("N=1") { - N = 1; - make_points(N, points, directions); - - auto scalar_result = static_cast(rti->point_in_volume(volume_tree, points[0], &directions[0])); - - std::vector batch_result(N, 0xFF); - rti->point_in_volume(volume_tree, points.data(), N, batch_result.data(), directions.data()); - REQUIRE(batch_result[0] == scalar_result); - } - - SECTION("N=64") { - N = 64; - make_points(N, points, directions); - - // Store results of scalar point_in_volume calls to verify batch against scalar - std::vector scalar_results(N, 0); - for (size_t i = 0; i < N; ++i) { - scalar_results[i] = static_cast(rti->point_in_volume(volume_tree, points[i], &directions[i])); - } - - std::vector batch_results(N, 0xFF); - rti->point_in_volume(volume_tree, points.data(), N, batch_results.data(), directions.data()); - for (size_t i = 0; i < points.size(); ++i) { - REQUIRE(batch_results[i] == scalar_results[i]); - } - } - } -} +} \ No newline at end of file diff --git a/tests/test_ray_fire.cpp b/tests/test_ray_fire.cpp index 552a4ed6..0e6816e3 100644 --- a/tests/test_ray_fire.cpp +++ b/tests/test_ray_fire.cpp @@ -110,81 +110,4 @@ TEMPLATE_TEST_CASE("Ray Fire on MeshMock (per-backend sections)", "[rayfire][moc intersection = rti->ray_fire(volume_tree, origin, direction, INFTY, HitOrientation::EXITING, &exclude_primitives); REQUIRE(intersection.second == ID_NONE); } -} - -TEMPLATE_TEST_CASE("Batch API Ray Fire on MeshMock", "[rayfire][mock][batch]", - Embree_Raytracer, - GPRT_Raytracer) -{ - constexpr auto rt_backend = TestType::value; - - DYNAMIC_SECTION(fmt::format("Backend = {}", rt_backend)) { - check_ray_tracer_supported(rt_backend); - if (rt_backend == RTLibrary::EMBREE) { - SKIP("Skipping batch query mechanics test for Embree: batch API not implemented."); - } - - auto rti = create_raytracer(rt_backend); - REQUIRE(rti); - - auto mm = std::make_shared(false); - mm->init(); - REQUIRE(mm->mesh_library() == MeshLibrary::MOCK); - - auto [volume_tree, element_tree] = rti->register_volume(mm, mm->volumes()[0]); - REQUIRE(volume_tree != ID_NONE); - REQUIRE(element_tree == ID_NONE); - - rti->init(); - - std::vector origins; - std::vector directions; - size_t N; - - // ---- N = 0 ---- - SECTION("N=0 no-op") { - rti->ray_fire(volume_tree, nullptr, nullptr, 0, nullptr, nullptr, - INFTY, HitOrientation::EXITING, nullptr); - SUCCEED("N=0 completed without error"); - } - - // ---- N = 1 ---- - SECTION("N=1 equals scalar") { - N = 1; - make_rays(N, origins, directions); - - auto [dist_scalar, id_scalar] = rti->ray_fire(volume_tree, origins[0], directions[0], INFTY, HitOrientation::EXITING); - - double dist_batch = -1.0; - MeshID id_batch = ID_NONE; - rti->ray_fire(volume_tree, origins.data(), directions.data(), 1, - &dist_batch, &id_batch, INFTY, HitOrientation::EXITING, nullptr); - - REQUIRE(id_batch == id_scalar); - REQUIRE_THAT(dist_batch, Catch::Matchers::WithinAbs(dist_scalar, 1e-6)); - } - - // ---- N = 64 ---- - SECTION("N=64 matches scalar for all") { - N = 64; - make_rays(N, origins, directions); - - std::vector dist_scalar(64, INFTY); - std::vector id_scalar(64, ID_NONE); - for (size_t i = 0; i < 64; ++i) { - auto [d, id] = rti->ray_fire(volume_tree, origins[i], directions[i], INFTY, HitOrientation::EXITING); - dist_scalar[i] = d; id_scalar[i] = id; - } - - std::vector dist_batch(64, -1.0); - std::vector id_batch(64, ID_NONE); - rti->ray_fire(volume_tree, origins.data(), directions.data(), origins.size(), - dist_batch.data(), id_batch.data(), INFTY, HitOrientation::EXITING, nullptr); - - for (size_t i = 0; i < 64; ++i) { - REQUIRE(id_batch[i] == id_scalar[i]); - REQUIRE_THAT(dist_batch[i], Catch::Matchers::WithinAbs(dist_scalar[i], 1e-6)); - } - } - } -} +} \ No newline at end of file diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 00d7ecd3..f23f871e 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -1,10 +1,8 @@ set(TOOL_NAMES particle_sim ray_fire -batch_ray_fire find_volume point_in_volume -batch_point_in_volume overlap_check walk_elements tally_segments @@ -32,6 +30,4 @@ foreach(tool ${TOOL_NAMES}) target_compile_definitions(${tool_exec} PUBLIC XDG_OPENMP) endif() install(TARGETS ${tool_exec} DESTINATION ${CMAKE_INSTALL_BINDIR}/tools) -endforeach() - -add_subdirectory(ray_benchmark) +endforeach() \ No newline at end of file diff --git a/tools/batch_point_in_volume.cpp b/tools/batch_point_in_volume.cpp deleted file mode 100644 index 56ab46c4..00000000 --- a/tools/batch_point_in_volume.cpp +++ /dev/null @@ -1,194 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include "xdg/error.h" -#include "xdg/mesh_manager_interface.h" -#include "xdg/vec3da.h" -#include "xdg/xdg.h" - -#include "argparse/argparse.hpp" - -using namespace xdg; - -int main(int argc, char** argv) { - - argparse::ArgumentParser args("XDG Batch Point In Volume Tool", "1.0", argparse::default_arguments::help); - - args.add_argument("filename") - .help("Path to the input file"); - - args.add_argument("volume") - .help("Volume ID to query").scan<'i', int>(); - - args.add_argument("-l", "--list") - .default_value(false) - .implicit_value(true) - .help("List all volumes in the file and exit"); - - args.add_argument("-o", "-p", "--origin", "--position") - .default_value(std::vector{0.0, 0.0, 0.0}) - .help("Ray origin/position. Repeat to supply multiple origins.") - .scan<'g', double>().nargs(3).append(); - - args.add_argument("-d", "--direction") - .default_value(std::vector{0.0, 0.0, 1.0}) - .help("Ray direction. Repeat to supply multiple directions.") - .scan<'g', double>().nargs(3).append(); - - - args.add_argument("-m", "--mesh-library") - .help("Mesh library to use. One of (MOAB, LIBMESH)") - .default_value("MOAB"); - - args.add_argument("-r", "--rt-library") - .help("Ray tracing library to use. One of (EMBREE, GPRT)") - .default_value("GPRT"); - - // High-level rules in the description - args.add_description( - "Directions are completely optional for this tool but the number provided will effect how the program runs: \n\n" - " Only points (mask all, device default dir used)\n" - " --origin 0 0 0 --origin 5.1 0 0 --origin 0 0 0\n\n" - " One direction (broadcast to all)\n" - " --origin 0 0 0 --origin 5.1 0 0 --direction 1 0 0\n\n" - " Several directions. Match to points and mask remainder\n" - " --origin 0 0 0 --origin 5.1 0 0 --origin 4.999999 0 0 \\\n" - " --direction 1 0 0 --direction -1 0 0\n" - ); - - try { - args.parse_args(argc, argv); - } - catch (const std::runtime_error& err) { - std::cout << err.what() << std::endl; - std::cout << args; - exit(0); - } - - std::string mesh_str = args.get("--mesh-library"); - std::string rt_str = args.get("--rt-library"); - - MeshLibrary mesh_lib; - if (mesh_str == "MOAB") mesh_lib = MeshLibrary::MOAB; - else if (mesh_str == "LIBMESH") fatal_error("LibMesh is not currently supported with GPRT"); - else fatal_error("Invalid mesh library '{}' specified", mesh_str); - - RTLibrary rt_lib; - if (rt_str == "EMBREE") rt_lib = RTLibrary::EMBREE; - else if (rt_str == "GPRT") rt_lib = RTLibrary::GPRT; - else fatal_error("Invalid ray tracing library '{}' specified", rt_str); - - // create a mesh manager - std::shared_ptr xdg = XDG::create(mesh_lib, rt_lib); - const auto& mm = xdg->mesh_manager(); - mm->load_file(args.get("filename")); - mm->init(); - mm->parse_metadata(); - - auto rti = xdg->ray_tracing_interface(); - - if (args.get("--list")) { - std::cout << "Volumes: " << std::endl; - for (auto volume : mm->volumes()) { - std::cout << volume << std::endl; - } - exit(0); - } - - MeshID volume = args.get("volume"); - xdg->prepare_volume_for_raytracing(volume); - - // Gather our inputs and determine which mode of operation the tool will be working in - auto flat_origins = args.get>("--origin"); - auto flat_directions = args.get>("--direction"); - - if (flat_origins.empty()) { - fatal_error("You must supply at least one --origin x y z"); - } - if (flat_origins.size() % 3 != 0) { - fatal_error("Origins must be supplied in groups of 3 numbers."); - } - - // group every 3 into Position / Direction - std::vector> args_origins; - for (size_t i = 0; i < flat_origins.size(); i += 3) { - args_origins.push_back({flat_origins[i], flat_origins[i+1], flat_origins[i+2]}); - } - - std::vector> args_directions; - for (size_t i = 0; i < flat_directions.size(); i += 3) { - args_directions.push_back({flat_directions[i], flat_directions[i+1], flat_directions[i+2]}); - } - - // helper lambdas to convert std::vector to xdg::Position and xdg::Direction types - auto vec_to_pos = [](const std::vector& v) { return Position{v[0], v[1], v[2]}; }; - auto vec_to_dir = [](const std::vector& v) { - Direction dir{v[0], v[1], v[2]}; - dir.normalize(); - return dir; - }; - - const size_t N = args_origins.size(); - size_t num_dirs = args_directions.size(); - - std::vector origins; - origins.reserve(N); - for (const auto& o : args_origins) origins.push_back(vec_to_pos(o)); - - std::vector directions; - std::vector has_dir; // mask to indicate which rays have directions - const Direction* directions_ptr = nullptr; - const uint8_t* has_dir_ptr = nullptr; - - if (num_dirs == 0) { - // No directions let batch API set default direction per point - directions_ptr = nullptr; - has_dir_ptr = nullptr; - } else if (num_dirs == 1) { - // Broadcast one direction to all points (no mask needed) - directions.assign(N, vec_to_dir(args_directions[0])); - directions_ptr = directions.data(); - has_dir_ptr = nullptr; - } else if (num_dirs < N) { - // First k get explicit directions; rest fall back to default via mask - const size_t k = num_dirs; - directions.resize(N); - has_dir.assign(N, 0); - for (size_t i = 0; i < k; ++i) { - directions[i] = vec_to_dir(args_directions[i]); - has_dir[i] = 1; - } - directions_ptr = directions.data(); - has_dir_ptr = has_dir.data(); - } else { - // ≥ N directions → use first N pairwise (no mask needed) - directions.reserve(N); - for (size_t i = 0; i < N; ++i) directions.push_back(vec_to_dir(args_directions[i])); - directions_ptr = directions.data(); - has_dir_ptr = nullptr; - } - - std::vector results(N, 0xFF); - - xdg->point_in_volume(volume, - origins.data(), - N, - results.data(), - directions.data()); - - std::cout << std::endl << "Printing Batch point in volume results..." << std::endl; - - std::cout << "\nPrinting Batch point-in-volume results...\n"; - for (size_t i = 0; i < N; ++i) { - const auto& p = origins[i]; - std::cout << "Point (" << p.x << ", " << p.y << ", " << p.z << ") " - << (results[i] ? "is in " : "is NOT in ") - << "Volume " << volume << "\n"; - } - - return 0; -} diff --git a/tools/batch_ray_fire.cpp b/tools/batch_ray_fire.cpp deleted file mode 100644 index a208ed74..00000000 --- a/tools/batch_ray_fire.cpp +++ /dev/null @@ -1,226 +0,0 @@ -#include -#include -#include -#include -#include - -#include "xdg/error.h" -#include "xdg/mesh_manager_interface.h" -#include "xdg/vec3da.h" -#include "xdg/xdg.h" - -#include "argparse/argparse.hpp" - -enum class BatchMode { - ORIGIN_BROADCAST, // 1 origin, many directions - DIRECTION_BROADCAST, // many origins, 1 direction - PAIRWISE // equal numbers of origins and directions -}; - -inline const char* to_string(BatchMode mode) { - switch (mode) { - case BatchMode::ORIGIN_BROADCAST: return "ORIGIN_BROADCAST"; - case BatchMode::DIRECTION_BROADCAST: return "DIRECTION_BROADCAST"; - case BatchMode::PAIRWISE: return "PAIRWISE"; - default: return "UNKNOWN"; - } -} - -inline BatchMode deduce_batch_mode(size_t num_origins, size_t num_directions) { - if (num_origins == 0 || num_directions == 0) { - throw std::runtime_error("At least one origin and one direction must be provided."); - } - - if (num_origins == 1 && num_directions > 1) { - return BatchMode::ORIGIN_BROADCAST; - } - else if (num_directions == 1 && num_origins > 1) { - return BatchMode::DIRECTION_BROADCAST; - } - else if (num_origins == num_directions) { - return BatchMode::PAIRWISE; - } - else { - throw std::runtime_error( - "Invalid combination: number of origins (" + std::to_string(num_origins) + - ") does not match number of directions (" + std::to_string(num_directions) + - ") for broadcast or pairwise mode." - ); - } -} - -using namespace xdg; - -int main(int argc, char** argv) { - - argparse::ArgumentParser args("XDG Batch Ray Fire Tool", "1.0", argparse::default_arguments::help); - - args.add_argument("filename") - .help("Path to the input file"); - - args.add_argument("volume") - .help("Volume ID to query").scan<'i', int>(); - - args.add_argument("-l", "--list") - .default_value(false) - .implicit_value(true) - .help("List all volumes in the file and exit"); - - args.add_argument("-o", "-p", "--origin", "--position") - .default_value(std::vector{0.0, 0.0, 0.0}) - .help("Ray origin/position. Repeat to supply multiple origins.") - .scan<'g', double>().nargs(3).append(); - - args.add_argument("-d", "--direction") - .default_value(std::vector{0.0, 0.0, 1.0}) - .help("Ray direction. Repeat to supply multiple directions.") - .scan<'g', double>().nargs(3).append(); - - - args.add_argument("-m", "--mesh-library") - .help("Mesh library to use. One of (MOAB, LIBMESH)") - .default_value("MOAB"); - - args.add_argument("-r", "--rt-library") - .help("Ray tracing library to use. One of (EMBREE, GPRT)") - .default_value("GPRT"); - - // High-level rules in the description - args.add_description( - "This tool supports two modes of operation for batch ray firing: 'Broadcast' and 'Pairwise'\n\n" - "To use 'Broadcast' mode, provide one origin and many directions, or one direction and many origins:\n" - " --origin x y z --direction u1 v1 w1 --direction u2 v2 w2 ...\n" - " --direction u v w --origin x1 y1 z1 --origin x2 y2 z2 ...\n\n" - "To use 'Pairwise' mode, each origin is paired with a corresponding direction in order:\n" - " --origin x1 y1 z1 --direction u1 v1 w1 --origin x2 y2 z2 --direction u2 v2 w2 ...\n" - ); - - try { - args.parse_args(argc, argv); - } - catch (const std::runtime_error& err) { - std::cout << err.what() << std::endl; - std::cout << args; - exit(0); - } - - std::string mesh_str = args.get("--mesh-library"); - std::string rt_str = args.get("--rt-library"); - - MeshLibrary mesh_lib; - if (mesh_str == "MOAB") mesh_lib = MeshLibrary::MOAB; - else if (mesh_str == "LIBMESH") fatal_error("LibMesh is not currently supported with GPRT"); - else fatal_error("Invalid mesh library '{}' specified", mesh_str); - - RTLibrary rt_lib; - if (rt_str == "EMBREE") rt_lib = RTLibrary::EMBREE; - else if (rt_str == "GPRT") rt_lib = RTLibrary::GPRT; - else fatal_error("Invalid ray tracing library '{}' specified", rt_str); - - // create a mesh manager - std::shared_ptr xdg = XDG::create(mesh_lib, rt_lib); - const auto& mm = xdg->mesh_manager(); - mm->load_file(args.get("filename")); - mm->init(); - mm->parse_metadata(); - - auto rti = xdg->ray_tracing_interface(); - - if (args.get("--list")) { - std::cout << "Volumes: " << std::endl; - for (auto volume : mm->volumes()) { - std::cout << volume << std::endl; - } - exit(0); - } - - MeshID volume = args.get("volume"); - xdg->prepare_volume_for_raytracing(volume); - - // Gather our inputs and determine which mode of operation the tool will be working in - auto flat_origins = args.get>("--origin"); - auto flat_directions = args.get>("--direction"); - - if (flat_origins.empty()) { - fatal_error("You must supply at least one --origin x y z"); - } - if (flat_origins.size() % 3 != 0) { - fatal_error("Origins must be supplied in groups of 3 numbers."); - } - if (flat_directions.size() % 3 != 0) { - fatal_error("Directions must be supplied in groups of 3 numbers."); - } - - // group every 3 into Position / Direction - std::vector> args_origins; - for (size_t i = 0; i < flat_origins.size(); i += 3) { - args_origins.push_back({flat_origins[i], flat_origins[i+1], flat_origins[i+2]}); - } - - std::vector> args_directions; - for (size_t i = 0; i < flat_directions.size(); i += 3) { - args_directions.push_back({flat_directions[i], flat_directions[i+1], flat_directions[i+2]}); - } - - // helper lambdas to convert std::vector to xdg::Position and xdg::Direction types - auto vec_to_pos = [](const std::vector& v) { return Position{v[0], v[1], v[2]}; }; - auto vec_to_dir = [](const std::vector& v) { - Direction dir{v[0], v[1], v[2]}; - dir.normalize(); - return dir; - }; - - size_t num_orig = args_origins.size(); - size_t num_dirs = args_directions.size(); - - auto mode = deduce_batch_mode(num_orig, num_dirs); - std::cout << "Running XDG Batch Ray Fire in " << to_string(mode) << " mode" << std::endl; - std::vector origins; - std::vector directions; - - switch (mode) - { - case BatchMode::ORIGIN_BROADCAST: - origins.assign(num_dirs, vec_to_pos(args_origins[0])); - directions.reserve(num_dirs); - for (const auto& dir : args_directions) directions.push_back(vec_to_dir(dir)); - break; - case BatchMode::DIRECTION_BROADCAST: - directions.assign(num_orig, vec_to_dir(args_directions[0])); - origins.reserve(num_orig); - for (const auto& origin : args_origins) origins.push_back(vec_to_pos(origin)); - break; - case BatchMode::PAIRWISE: - origins.reserve(num_orig); - directions.reserve(num_dirs); - for (size_t i = 0; i < num_orig; ++i) - { - origins.push_back(vec_to_pos(args_origins[i])); - directions.push_back(vec_to_dir(args_directions[i])); - } - break; - - default: - fatal_error("You must provide either a single origin and many directions. " - "A single direction and many origins. Or an equal number of origins and directions."); - } - - size_t num_rays = origins.size(); // get number of rays to fire from now aligned arrays - - std::vector hitDistances(num_rays); - std::vector surfacesHit(num_rays); - - xdg->ray_fire(volume, origins.data(), directions.data(), num_rays, hitDistances.data(), surfacesHit.data()); - - std::cout << std::endl << "Printing Batch Ray results..." << std::endl; - - for (size_t i = 0; i < num_rays; ++i) { - std::cout << "Ray[" << i << "] " - << "Origin=(" << origins[i].x << ", " << origins[i].y << ", " << origins[i].z << ") " - << "Dir=(" << directions[i].x << ", " << directions[i].y << ", " << directions[i].z << ") " - << "Distance=" << std::setprecision(17) << hitDistances[i] << " " - << "| Surface=" << surfacesHit[i] << "\n"; - } - - return 0; -} diff --git a/tools/ray_benchmark/CMakeLists.txt b/tools/ray_benchmark/CMakeLists.txt deleted file mode 100644 index 9566a620..00000000 --- a/tools/ray_benchmark/CMakeLists.txt +++ /dev/null @@ -1,33 +0,0 @@ -#=============================================================================== -# ray-benchmark (special case - requires linking directly to GPRT) -#=============================================================================== -if (XDG_ENABLE_GPRT) - # Embed and compile the device code - embed_devicecode( - OUTPUT_TARGET - ray_benchmark_deviceCode - HEADERS - ${CMAKE_CURRENT_SOURCE_DIR}/ray_benchmark_shared.h - SOURCES - ${CMAKE_CURRENT_SOURCE_DIR}/ray_benchmark_deviceCode.slang - ) - - # Create the ray-benchmark executable - add_executable(ray-benchmark ray_benchmark.cpp) - target_link_libraries(ray-benchmark xdg argparse ray_benchmark_deviceCode) - # Keep the runtime output alongside other tools for single- and multi-config generators. - get_filename_component(TOOLS_BIN_DIR "${CMAKE_CURRENT_BINARY_DIR}" DIRECTORY) - set_target_properties(ray-benchmark PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${TOOLS_BIN_DIR}" - ) - foreach(config DEBUG RELEASE RELWITHDEBINFO MINSIZEREL) - set_target_properties(ray-benchmark PROPERTIES - RUNTIME_OUTPUT_DIRECTORY_${config} "${TOOLS_BIN_DIR}" - ) - endforeach() - if (OpenMP_CXX_FOUND) - target_link_libraries(ray-benchmark OpenMP::OpenMP_CXX) - target_compile_definitions(ray-benchmark PUBLIC XDG_OPENMP) - endif() - install(TARGETS ray-benchmark DESTINATION ${CMAKE_INSTALL_BINDIR}/tools) -endif() diff --git a/tools/ray_benchmark/ray_benchmark.cpp b/tools/ray_benchmark/ray_benchmark.cpp deleted file mode 100644 index afc975c1..00000000 --- a/tools/ray_benchmark/ray_benchmark.cpp +++ /dev/null @@ -1,231 +0,0 @@ -#include -#include -#include -#include -#include - -#include "xdg/error.h" -#include "xdg/mesh_manager_interface.h" -#include "xdg/moab/mesh_manager.h" -#include "xdg/vec3da.h" -#include "xdg/xdg.h" -#include "xdg/ray_tracers.h" -#include "xdg/timer.h" - -#include "argparse/argparse.hpp" - -#include "ray_benchmark.h" - -#include - -using namespace xdg; - -int main(int argc, char** argv) { - - argparse::ArgumentParser args("XDG Ray Tracing throughput benchmarking tool", "1.0", argparse::default_arguments::help); - - args.add_argument("filename") - .help("Path to the input file"); - - args.add_argument("volume") - .help("Volume ID to query") - .scan<'i', int>(); - - args.add_argument("-n", "--num-rays") - .default_value(10'000'000) - .help("Number of rays to be cast for the benchmark (default - 10 million)") - .scan<'u', uint32_t>(); - - args.add_argument("-s", "--seed") - .default_value(12345) - .help("Seed for random number generator (default - 12345)") - .scan<'u', uint32_t>(); - - args.add_argument("-o", "-p", "--origin", "--position") - .default_value(std::vector{0.0, 0.0, 0.0}) - .help("Ray origin/position (default - {0.0, 0.0, 0.0} )") - .scan<'g', double>().nargs(3); - - args.add_argument("-m", "--mesh-library") - .help("Mesh library to use. One of (MOAB, LIBMESH)") - .default_value("MOAB"); - - args.add_argument("-rt", "--rt-library") - .help("Ray tracing library to use. One of (EMBREE, GPRT)") - .default_value("EMBREE"); - - args.add_argument("-l", "--list") - .default_value(false) - .implicit_value(true) - .help("List all volumes in the file and exit"); - - args.add_argument("-sr", "--source-radius") - .default_value(0.0) - .help("Radius of a scattered source blob around the origin (0.0 = point source)") - .scan<'g', double>(); - - args.add_description( - "This tool supports can be used to benchmark XDG ray tracing throughput on a given mesh against" - "a given volume \n." - "A single origin/seed point is provided and ray directions are randomly generated in 360 degrees from that position" - ); - - try { - args.parse_args(argc, argv); - } - catch (const std::runtime_error& err) { - std::cout << err.what() << std::endl; - std::cout << args; - return 1; - } - - std::string mesh_str = args.get("--mesh-library"); - std::string rt_str = args.get("--rt-library"); - - RTLibrary rt_lib; - if (rt_str == "EMBREE") - rt_lib = RTLibrary::EMBREE; - else if (rt_str == "GPRT") - rt_lib = RTLibrary::GPRT; - else - fatal_error("Invalid ray tracing library '{}' specified", rt_str); - - MeshLibrary mesh_lib; - if (mesh_str == "MOAB") { - mesh_lib = MeshLibrary::MOAB; - } else if (mesh_str == "LIBMESH") { - mesh_lib = MeshLibrary::LIBMESH; - if (rt_lib == RTLibrary::GPRT) - fatal_error("LibMesh is not currently supported with GPRT"); - } else { - fatal_error("Invalid mesh library '{}' specified", mesh_str); - } - - // Full wall-clock timer (post-argparse) - Timer wall_timer; - wall_timer.start(); - - // Separate timers for setup, generation, and tracing - Timer setup_timer; - Timer gen_timer; - Timer trace_timer; - - // -------------------------- - // XDG setup timing - // -------------------------- - setup_timer.start(); - - std::shared_ptr xdg = XDG::create(mesh_lib, rt_lib); - const auto& mm = xdg->mesh_manager(); - mm->load_file(args.get("filename")); - mm->init(); - - MeshID volume = args.get("volume"); - xdg->prepare_raytracer(); - xdg->prepare_volume_for_raytracing(volume); - auto rti = xdg->ray_tracing_interface(); - - setup_timer.stop(); - - std::size_t N = args.get("--num-rays"); - uint32_t seed = args.get("--seed"); - Position origin = args.get>("--origin"); - double source_radius = args.get("--source-radius"); - - std::cout << "Volume ID: " << volume << " with: " - << mm->num_volume_faces(volume) << " faces" << std::endl; - - - if (rt_lib == RTLibrary::EMBREE) { - int num_threads = omp_get_max_threads(); - rt_str += " (" + std::to_string(num_threads) + " CPU threads)"; - } - std::cout << "Starting ray fire benchmark with " << N << " rays" - << " using " << rt_str << ": \n" << std::endl; - - std::cout << "XDG initalisation Time = " << setup_timer.elapsed() << "s" << std::endl; - - std::shared_ptr gprt_rt; - if (rt_lib == RTLibrary::GPRT) { - // ---- Random ray generation on device via callback method ---- - gen_timer.start(); - - gprt_rt = std::dynamic_pointer_cast(xdg->ray_tracing_interface()); - auto generateRaysCallback = - tools::benchmark::make_generate_rays_callback(gprt_rt->context(), origin, source_radius, seed, volume); - - // Let XDG internally allocate buffers and invoke the callback to populate them - xdg->populate_rays_external(N, generateRaysCallback); - - gen_timer.stop(); - std::cout << "Random ray generation (via external compute shader) Time = " - << gen_timer.elapsed() << "s" << std::endl; - - // ---- Ray tracing on device ---- - trace_timer.start(); - xdg->ray_fire_prepared(N); // ray_fire against pre-packed rays on device - trace_timer.stop(); - - } - else { // EMBREE / CPU backend - - // ---- Random ray generation on host ---- - gen_timer.start(); - std::vector directions(N); - std::vector origins(N); - - #pragma omp parallel for schedule(static) - for (uint32_t i = 0; i < N; ++i) { - uint32_t state = seed ^ i; - auto [pos,dir] = tools::benchmark::random_spherical_source(origin, state, source_radius); - origins[i] = pos; - directions[i] = dir; - } - gen_timer.stop(); - - std::cout << "Random ray generation Time = " - << gen_timer.elapsed() << "s" << std::endl; - - // ---- Ray tracing on host ---- - trace_timer.start(); - #pragma omp parallel for schedule(static) - for (std::size_t i = 0; i < N; ++i) { - auto result = xdg->ray_fire(volume, origins[i], directions[i]); - } - trace_timer.stop(); - } - - // -------------------------- - // Final reporting - // -------------------------- - double setup_time = setup_timer.elapsed(); - double gen_time = gen_timer.elapsed(); - double trace_time = trace_timer.elapsed(); - - double trace_only_rps = (trace_time > 0.0) - ? static_cast(N) / trace_time - : 0.0; - - double end_to_end_time = gen_time + trace_time; - double end_to_end_rps = (end_to_end_time > 0.0) - ? static_cast(N) / end_to_end_time - : 0.0; - - wall_timer.stop(); - double wall_time = wall_timer.elapsed(); - - std::cout << "Generation + tracing time = " << end_to_end_time - << "s" << std::endl; - std::cout << "End-to-end throughput = " << end_to_end_rps - << " rays/s" << std::endl; - std::cout << "Full wall-clock time = " << wall_time - << "s (post-argparse)" << std::endl; - - std::cout << "----------------------------------------" << std::endl; - std::cout << "Ray Tracing Time (trace-only) = " << trace_time - << "s for " << N << " rays" << std::endl; - std::cout << "Trace-only throughput = " << trace_only_rps - << " rays/s" << std::endl; - std::cout << "---------------------------------------- \n" << std::endl; - return 0; -} diff --git a/tools/ray_benchmark/ray_benchmark.h b/tools/ray_benchmark/ray_benchmark.h deleted file mode 100644 index 52b29d62..00000000 --- a/tools/ray_benchmark/ray_benchmark.h +++ /dev/null @@ -1,97 +0,0 @@ -#ifndef _XDG_RAY_BENCHMARK_H -#define _XDG_RAY_BENCHMARK_H - -#include -#include -#include - -#include "gprt/gprt.h" -#include "xdg/gprt/ray.h" -#include "xdg/gprt/ray_tracer.h" -#include "xdg/vec3da.h" -#include "xdg/xdg.h" - -#include "ray_benchmark_shared.h" - -extern GPRTProgram ray_benchmark_deviceCode; - -namespace xdg::tools::benchmark { - -inline double rand01(uint32_t &state) -{ - state = state * 1664525u + 1013904223u; - return double(state) * (1.0 / 4294967296.0); -} - -inline Direction random_unit_dir_lcg(uint32_t &state) -{ - double x1, x2, s; - do { - x1 = rand01(state) * 2.0 - 1.0; - x2 = rand01(state) * 2.0 - 1.0; - s = x1 * x1 + x2 * x2; - } while (s <= 0.0 || s >= 1.0); - - double t = 2.0 * std::sqrt(1.0 - s); - return { x1 * t, x2 * t, 1.0 - 2.0 * s }; -} - -// Generates a random point cloud with radius (--source-radius) -inline std::pair random_spherical_source(const Position& origin, - std::uint32_t state, - double source_radius) -{ - // Always generate random direction - Direction dir = random_unit_dir_lcg(state); - Position pos = origin; - if (source_radius > 0.0) { - // random origins (spherical source) - double r = source_radius * std::cbrt(rand01(state)); // uniform in ball - pos += dir * r; - } - return {pos, dir}; -} - -// - User creates their own GPU compute API method to populate rays and passes that to XDG -// - In this miniapp we are using GPRT as a demonstration -// - This callback runs inside populate_rays_external and receives XDG's device buffers -inline RayPopulationCallback make_generate_rays_callback(GPRTContext gprt_context, - Position origin, - double source_radius, - uint32_t seed, - MeshID volume) -{ - return [gprt_context, origin, source_radius, seed, volume](const DeviceRayHitBuffers& buffer, size_t numRays) { - GPRTContext context = gprt_context; - GPRTModule module = gprtModuleCreate(context, ray_benchmark_deviceCode); - auto genRandomRays = gprtComputeCreate( - context, module, "generate_random_rays"); - - constexpr int threadsPerGroup = 64; - const int neededGroups = static_cast((numRays + threadsPerGroup - 1) / threadsPerGroup); - const int groups = std::min(neededGroups, WORKGROUP_LIMIT); - - GenerateRandomRayParams randomRayParams = {}; - randomRayParams.rays = static_cast(buffer.rayDevPtr); // Cast opaque pointer to typed dblRay* - randomRayParams.numRays = static_cast(numRays); - randomRayParams.source_radius = source_radius; - randomRayParams.origin = { origin.x, origin.y, origin.z }; - randomRayParams.seed = seed; - randomRayParams.total_threads = static_cast(groups * threadsPerGroup); - randomRayParams.volume_mesh_id = volume; - randomRayParams.enabled = 1u; - - gprtComputeLaunch(genRandomRays, - { static_cast(groups), 1, 1 }, - { static_cast(threadsPerGroup), 1, 1 }, - randomRayParams); - gprtComputeSynchronize(context); - - gprtComputeDestroy(genRandomRays); - gprtModuleDestroy(module); - }; -} - -} // namespace xdg::tools::benchmark - -#endif // _XDG_RAY_BENCHMARK_H diff --git a/tools/ray_benchmark/ray_benchmark_deviceCode.slang b/tools/ray_benchmark/ray_benchmark_deviceCode.slang deleted file mode 100644 index e9a6aefb..00000000 --- a/tools/ray_benchmark/ray_benchmark_deviceCode.slang +++ /dev/null @@ -1,57 +0,0 @@ -#include "ray_benchmark_shared.h" - -/* -For this simple benchmark case we are mocking what a downstream application would do in terms of populating -ray buffers. The idea is that the downstream application generates rays (origins + directions). -*/ -[shader("compute")] -[numthreads(64, 1, 1)] -void generate_random_rays(uint3 DispatchThreadID: SV_DispatchThreadID, - uniform GenerateRandomRayParams params) -{ - uint globalThreadID = DispatchThreadID.x; - uint stride = params.total_threads; - uint nRays = params.numRays; - - for (uint idx = globalThreadID; idx < nRays; idx += stride) - { - uint state = params.seed ^ idx; - - double3 dir = random_unit_dir_lcg(state); - - double3 pos = params.origin; - if (params.source_radius > 0.0) { - double u = float(rand01(state)); - float r = float(params.source_radius) * pow(float(u), 1.0f / 3.0f); // cbrt(u) - pos += dir * double(r); - } - - params.rays[idx].origin = pos; - params.rays[idx].direction = dir; - params.rays[idx].exclude_primitives = nullptr; - params.rays[idx].exclude_count = 0; - params.rays[idx].enabled = params.enabled; - params.rays[idx].volume_mesh_id = params.volume_mesh_id; - } -} - -// Simple LCG random number generator -double rand01(inout uint state) -{ - state = state * 1664525u + 1013904223u; - return double(state) * double(1.0 / 4294967296.0); -} - -// return random unit dir -double3 random_unit_dir_lcg(inout uint state) -{ - double x1, x2, s; - do { - x1 = rand01(state) * 2.0 - 1.0; - x2 = rand01(state) * 2.0 - 1.0; - s = x1 * x1 + x2 * x2; - } while (s <= 0.0 || s >= 1.0); - - double t = 2.0 * sqrt(1.0 - s); - return double3(x1 * t, x2 * t, 1.0 - 2.0 * s); -} \ No newline at end of file diff --git a/tools/ray_benchmark/ray_benchmark_driver.py b/tools/ray_benchmark/ray_benchmark_driver.py deleted file mode 100644 index 1839418f..00000000 --- a/tools/ray_benchmark/ray_benchmark_driver.py +++ /dev/null @@ -1,297 +0,0 @@ -#!/usr/bin/env python3 -import subprocess -import statistics -import sys -import csv -import os - -# --- CONFIG --- - -BENCHMARK = "./tools/ray-benchmark" -MESH_PATH = "../dagmc_xdg_test.h5m" -VOLUME_ID = "2" -NUM_RAYS = "80000000" -ORIGIN = ["-o", "180", "250", "-27"] # x y z as strings - -# --- PARSING HELPERS --- - -def parse_float_before_s(s: str) -> float: - """ - Given a string like 'XDG initalisation Time = 1.25017s', - pull out 1.25017 as float. - """ - try: - after_eq = s.split('=', 1)[1] - number_str = after_eq.split('s', 1)[0].strip() - return float(number_str) - except Exception as e: - raise ValueError(f"Failed to parse float from line: {s!r}") from e - -def parse_throughput_line(s: str) -> float: - """ - Given a string like 'Trace-only throughput = 2.64065e+09 rays/s', - pull out 2.64065e+09 as float. - """ - try: - after_eq = s.split('=', 1)[1] - number_str = after_eq.split('rays', 1)[0].strip() - return float(number_str) - except Exception as e: - raise ValueError(f"Failed to parse throughput from line: {s!r}") from e - -def parse_benchmark_output(output: str): - """ - Parse the benchmark stdout text and return a dict of metrics. - Expected keys: - - xdg_init - - gen - - gen_trace - - end_to_end - - wall_clock - - trace_only - - trace_only_throughput - """ - metrics = {} - - for line in output.splitlines(): - line = line.strip() - - if line.startswith("XDG initalisation Time"): - metrics["xdg_init"] = parse_float_before_s(line) - - elif line.startswith("Random ray generation"): - metrics["gen"] = parse_float_before_s(line) - - elif line.startswith("Generation + tracing time"): - metrics["gen_trace"] = parse_float_before_s(line) - - elif line.startswith("End-to-end throughput"): - metrics["end_to_end"] = parse_throughput_line(line) - - elif line.startswith("Full wall-clock time"): - metrics["wall_clock"] = parse_float_before_s(line) - - elif line.startswith("Ray Tracing Time (trace-only)"): - metrics["trace_only"] = parse_float_before_s(line) - - elif line.startswith("Trace-only throughput"): - metrics["trace_only_throughput"] = parse_throughput_line(line) - - required = [ - "xdg_init", "gen", "gen_trace", "end_to_end", - "wall_clock", "trace_only", "trace_only_throughput" - ] - missing = [k for k in required if k not in metrics] - if missing: - raise RuntimeError(f"Missing metrics in output: {missing}") - - return metrics - -# --- MAIN DRIVER --- - -def main(): - # Ask for backend - backend_in = input("Choose backend (embree/gprt): ").strip().lower() - if backend_in not in ("embree", "gprt"): - print("Invalid backend, please choose 'embree' or 'gprt'.") - sys.exit(1) - - base_backend = backend_in.upper() # what we pass to -r: EMBREE or GPRT - - # If GPRT, ask for which variant - if backend_in == "gprt": - mode_in = input( - "GPRT mode: [1] GPRT (FP64), [2] GPRT (FP32) + RT cores [1]: " - ).strip() - if mode_in == "2": - variant = "fp32_rt" - label = "GPRT (FP32) + RT cores" - else: - variant = "fp64" - label = "GPRT (FP64)" - else: - # Embree is effectively FP64 for your purposes - variant = "fp64" - label = "Embree" - - runs_str = input("How many runs? ").strip() - try: - num_runs = int(runs_str) - if num_runs <= 0: - raise ValueError - except ValueError: - print("Number of runs must be a positive integer.") - sys.exit(1) - - # Ask for CSV filename - csv_filename = input("CSV output file [benchmarks.csv]: ").strip() - if not csv_filename: - csv_filename = "benchmarks.csv" - - mesh_name = os.path.basename(MESH_PATH) - - all_metrics = { - "xdg_init": [], - "gen": [], - "gen_trace": [], - "end_to_end": [], - "wall_clock": [], - "trace_only": [], - "trace_only_throughput": [], - } - - # CSV header: machine-friendly backend/variant, plus pretty label - header = [ - "backend", # EMBREE / GPRT - "variant", # fp64 / fp32_rt - "label", # Embree / GPRT (FP64) / GPRT (FP32) + RT cores - "mesh_name", - "volume_id", - "num_rays", - "run_index", - "xdg_init", - "gen", - "gen_trace", - "end_to_end", - "wall_clock", - "trace_only", - "trace_only_throughput", - ] - - # Decide whether to append or overwrite - file_exists = os.path.exists(csv_filename) - write_header = False - file_mode = "w" - append_mode = False - - if file_exists: - choice = input( - f"File '{csv_filename}' already exists. " - "[o]verwrite, [a]ppend, or e[x]it? [a]: " - ).strip().lower() - - if choice in ("x", "q"): - print("Aborting, no benchmarks run.") - sys.exit(0) - elif choice in ("", "a"): - file_mode = "a" - write_header = False # assume header already there - append_mode = True - elif choice == "o": - file_mode = "w" - write_header = True - append_mode = False - else: - print("Unrecognized choice, aborting.") - sys.exit(1) - else: - # new file: write header - file_mode = "w" - write_header = True - append_mode = False - - csv_file = open(csv_filename, file_mode, newline="") - - # If appending, add a separation comment line so it's obvious this is a new batch - if append_mode: - csv_file.write( - f"\n# --- New benchmark batch: " - f"label={label}, backend={base_backend}, variant={variant}, " - f"mesh={mesh_name}, volume={VOLUME_ID}, " - f"rays={NUM_RAYS}, runs={num_runs} ---\n" - ) - - writer = csv.writer(csv_file) - - if write_header: - writer.writerow(header) - - try: - for i in range(1, num_runs + 1): - print(f"\n=== Run {i}/{num_runs} ({label}) ===") - - cmd = [ - BENCHMARK, - MESH_PATH, - VOLUME_ID, - "-r", base_backend, # EMBREE or GPRT - "-n", NUM_RAYS, - *ORIGIN, - ] - - print("Running:", " ".join(cmd)) - - try: - result = subprocess.run( - cmd, - check=True, - text=True, - capture_output=True, - ) - except subprocess.CalledProcessError as e: - print("Benchmark command failed!") - print("STDOUT:\n", e.stdout) - print("STDERR:\n", e.stderr) - sys.exit(1) - - try: - metrics = parse_benchmark_output(result.stdout) - except Exception as e: - print("Failed to parse benchmark output:", e) - print("Raw output:\n", result.stdout) - sys.exit(1) - - # store for averages - for k in all_metrics.keys(): - all_metrics[k].append(metrics[k]) - - # write CSV row - writer.writerow([ - base_backend, # backend - variant, # variant - label, # label - mesh_name, - VOLUME_ID, - NUM_RAYS, - i, # run_index - metrics["xdg_init"], - metrics["gen"], - metrics["gen_trace"], - metrics["end_to_end"], - metrics["wall_clock"], - metrics["trace_only"], - metrics["trace_only_throughput"], - ]) - - # per-run summary - print(f"XDG init : {metrics['xdg_init']:.6f} s") - print(f"Generation : {metrics['gen']:.6f} s") - print(f"Gen + trace : {metrics['gen_trace']:.6f} s") - print(f"End-to-end : {metrics['end_to_end']:.3e} rays/s") - print(f"Wall-clock : {metrics['wall_clock']:.6f} s") - print(f"Trace-only : {metrics['trace_only']:.6f} s") - print(f"Trace-only thrpt : {metrics['trace_only_throughput']:.3e} rays/s") - - finally: - csv_file.close() - - # Averages - print( - "\n=== Averages over", - num_runs, - f"runs (label: {label}) ===" - ) - - def avg(key): return statistics.mean(all_metrics[key]) - - print(f"Avg XDG init : {avg('xdg_init'):.6f} s") - print(f"Avg Generation : {avg('gen'):.6f} s") - print(f"Avg Gen + trace : {avg('gen_trace'):.6f} s") - print(f"Avg End-to-end : {avg('end_to_end'):.3e} rays/s") - print(f"Avg Wall-clock : {avg('wall_clock'):.6f} s") - print(f"Avg Trace-only : {avg('trace_only'):.6f} s") - print(f"Avg Trace-only thrpt : {avg('trace_only_throughput'):.3e} rays/s") - print(f"\nResults written to: {csv_filename}") - -if __name__ == "__main__": - main() diff --git a/tools/ray_benchmark/ray_benchmark_shared.h b/tools/ray_benchmark/ray_benchmark_shared.h deleted file mode 100644 index a3fffcf0..00000000 --- a/tools/ray_benchmark/ray_benchmark_shared.h +++ /dev/null @@ -1,14 +0,0 @@ -#include "gprt.h" - -#include "../../include/xdg/gprt/ray.h" - -struct GenerateRandomRayParams { - xdg::dblRay* rays; - uint numRays; - double3 origin; - uint seed; - uint total_threads; - double source_radius; - int volume_mesh_id; - uint enabled; -}; From 7290e6441154421b2d2124edf33ce4fd8294ac44 Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Tue, 17 Feb 2026 14:11:04 +0000 Subject: [PATCH 62/62] Some extra pre-review cleanups --- include/xdg/gprt/ray.h | 2 ++ include/xdg/gprt/ray_tracer.h | 18 ++---------- include/xdg/gprt/shared_structs.h | 7 ++--- include/xdg/ray_tracing_interface.h | 8 ++---- include/xdg/xdg.h | 1 + src/gprt/dbl_deviceCode.slang | 6 ++-- src/gprt/ray_tracer.cpp | 44 +++++++++++------------------ src/tetrahedron_contain.cpp | 1 + src/xdg.cpp | 2 +- tests/test_files | 2 +- tests/test_ray_fire.cpp | 2 -- 11 files changed, 35 insertions(+), 58 deletions(-) diff --git a/include/xdg/gprt/ray.h b/include/xdg/gprt/ray.h index d82eb8d5..023b1338 100644 --- a/include/xdg/gprt/ray.h +++ b/include/xdg/gprt/ray.h @@ -21,11 +21,13 @@ struct dblRay double3 direction; int volume_mesh_id; // MeshID of the volume this ray will be traced against uint enabled; // Flag to indicate if the ray is active + // TODO - Implement exclude primtives functionality. Right now these are essentially just stubs. int32_t* exclude_primitives; // Optional for excluding primitives int32_t exclude_count; // Number of excluded primitives }; +// TODO - Should we define separate hit structs for PIV and ray-fire or do we think its better to keep them together? struct dblHit { double distance; diff --git a/include/xdg/gprt/ray_tracer.h b/include/xdg/gprt/ray_tracer.h index 8965fc54..37e47898 100644 --- a/include/xdg/gprt/ray_tracer.h +++ b/include/xdg/gprt/ray_tracer.h @@ -129,16 +129,6 @@ class GPRTRayTracer : public RayTracer { return context_; } - SurfaceAccelerationStructure* tlas_handle_device_ptr() const - { - return gprtBufferGetDevicePointer(tlas_handle_buffer_); - } - - size_t tlas_handle_count() const - { - return tlas_handles_.size(); - } - private: // GPRT objects @@ -148,9 +138,8 @@ class GPRTRayTracer : public RayTracer { GPRTAccel world_; GPRTBuildParams buildParams_; //> rayGenPrograms_; - GPRTMissOf missProgram_; GPRTComputeOf aabbPopulationProgram_; // void upload_device_buffer_(GPRTBufferOf& buf, const std::vector& host_data) { @@ -199,8 +189,6 @@ class GPRTRayTracer : public RayTracer { // Global Tree IDs GPRTAccel global_surface_accel_ {nullptr}; GPRTAccel global_element_accel_ {nullptr}; - }; - } // namespace xdg -#endif // include guard +#endif // include guard \ No newline at end of file diff --git a/include/xdg/gprt/shared_structs.h b/include/xdg/gprt/shared_structs.h index 915898f5..1cff1751 100644 --- a/include/xdg/gprt/shared_structs.h +++ b/include/xdg/gprt/shared_structs.h @@ -8,6 +8,7 @@ struct GPRTPrimitiveRef { int id; // ID of the primitive + // TODO - What else do we need here? Perhaps a flag for exclude prims? }; @@ -20,7 +21,6 @@ struct DPTriangleGeomData { int surf_id; int* meshid_to_sense; // MeshID -> sense (+1 forward, -1 reverse) xdg::dblRay *ray; // double precision rays - xdg::HitOrientation hitOrientation; GPRTPrimitiveRef* primitive_refs; int num_faces; // Number of faces in the geometry }; @@ -33,11 +33,10 @@ struct dblRayGenData { /* A small structure of constants that can change every frame without rebuilding the shader binding table. (must be 128 bytes or less) */ - -struct dblRayFirePushConstants { +struct dblPushConstants { double tMax; double tMin; xdg::HitOrientation hitOrientation; }; -#endif +#endif \ No newline at end of file diff --git a/include/xdg/ray_tracing_interface.h b/include/xdg/ray_tracing_interface.h index b53902f6..30a53f17 100644 --- a/include/xdg/ray_tracing_interface.h +++ b/include/xdg/ray_tracing_interface.h @@ -40,8 +40,8 @@ struct DeviceRayHitBuffers { void* rayDevPtr; void* hitDevPtr; size_t capacity; // Number of rays the buffer can hold - size_t rayStride; // Bytes between ray elements - sizeof(dblRay) - size_t hitStride; // Bytes between hit elements - sizeof(dblHit) + size_t rayStride; // Bytes between ray elements - currently set to sizeof(dblRay) but in theory allows for future flexibility + size_t hitStride; // Bytes between hit elements - currently set to sizeof(dblHit) but in theory allows for future flexibility }; /** @@ -275,8 +275,6 @@ class RayTracer { ElementTreeID next_element_tree_id_ {0}; double numerical_precision_ {1e-3}; }; - } // namespace xdg - -#endif // include guard +#endif // include guard \ No newline at end of file diff --git a/include/xdg/xdg.h b/include/xdg/xdg.h index f25225d5..139f240d 100644 --- a/include/xdg/xdg.h +++ b/include/xdg/xdg.h @@ -163,6 +163,7 @@ Direction surface_normal(MeshID surface, ray_tracing_interface_ = ray_tracing_interface; } + // Resize buffers (if necessary) and return device pointers for ray and hit data DeviceRayHitBuffers get_device_rayhit_buffers(const size_t requiredCapacity) { return ray_tracing_interface()->get_device_rayhit_buffers(requiredCapacity); diff --git a/src/gprt/dbl_deviceCode.slang b/src/gprt/dbl_deviceCode.slang index 0ba8a490..360d19b5 100644 --- a/src/gprt/dbl_deviceCode.slang +++ b/src/gprt/dbl_deviceCode.slang @@ -1,7 +1,7 @@ #include "../../include/xdg/gprt/shared_structs.h" [[vk::push_constant]] -dblRayFirePushConstants PC; +dblPushConstants PC; struct RayFirePayload { double distance; // Distance to intersection @@ -71,7 +71,7 @@ void ray_fire(uniform dblRayGenData record, uniform DPTriangleGeomData mesh) { payload.surf_id = -1; payload.tlas = world; - if (ray.enabled == 1u) { + if (ray.enabled == 1u) { // skip traversal for rays that are marked as disabled TraceRay(world, RAY_FLAG_NONE, 0xff, 0, 1, rayDesc, payload); } @@ -102,7 +102,7 @@ void point_in_volume(uniform dblRayGenData record, uniform DPTriangleGeomData me payload.tlas = world; payload.piv = xdg::PointInVolume::OUTSIDE; // Initialize point in volume check result to outside (0) - if (ray.enabled == 1u) { // skip RT pipeline for rays that are marked as disabled + if (ray.enabled == 1u) { // skip traversal for rays that are marked as disabled TraceRay(world, RAY_FLAG_NONE, 0xff, 0, 1, rayDesc, payload); } diff --git a/src/gprt/ray_tracer.cpp b/src/gprt/ray_tracer.cpp index 5398dabf..d5567544 100644 --- a/src/gprt/ray_tracer.cpp +++ b/src/gprt/ray_tracer.cpp @@ -24,7 +24,6 @@ GPRTRayTracer::GPRTRayTracer() setup_shaders(); - // Bind the buffers to the RayGenData structure dblRayGenData* rayGenData = gprtRayGenGetParameters(rayGenPrograms_.at(RayGenType::RAY_FIRE)); rayGenData->ray = gprtBufferGetDevicePointer(rayHitBuffers_.ray); @@ -37,8 +36,6 @@ GPRTRayTracer::GPRTRayTracer() // Set up build parameters for acceleration structures buildParams_.buildMode = GPRT_BUILD_MODE_FAST_BUILD_NO_UPDATE; - - } GPRTRayTracer::~GPRTRayTracer() @@ -47,7 +44,6 @@ GPRTRayTracer::~GPRTRayTracer() gprtGraphicsSynchronize(context_); gprtComputeSynchronize(context_); - // Destroy TLAS structures for (const auto& [tree, accel] : surface_volume_tree_to_accel_map) { gprtAccelDestroy(accel); @@ -175,13 +171,10 @@ GPRTRayTracer::create_surface_tree(const std::shared_ptr& mesh_mana constexpr uint32_t threadsPerGroup = 64; // must match [numthreads(64,1,1)] uint32_t numGroupsX = (num_faces + threadsPerGroup - 1) / threadsPerGroup; - gprtComputeLaunch(aabbPopulationProgram_, {numGroupsX, 1, 1}, {threadsPerGroup, 1, 1}, *geom_data); GPRTAccel blas = gprtAABBAccelCreate(context_, triangleGeom, buildParams_.buildMode); - gprtAccelBuild(context_, blas, buildParams_); - gprt::Instance instance; instance = gprtAccelGetInstance(blas); // create instance of BLAS to be added to TLAS instance.mask = 0xff; // mask can be used to filter instances during ray traversal. 0xff ensures no filtering @@ -195,7 +188,8 @@ GPRTRayTracer::create_surface_tree(const std::shared_ptr& mesh_mana surfaceBlasInstances.push_back(instance); globalBlasInstances_.push_back(instance); - // Always update per-volume info and MeshID -> sparse sense mapping + // Ensure MeshID->sense lookup has an entry for this volume and record its + // orientation sign (+1 forward, -1 reverse) for normal flipping in shader. auto [forward_parent, reverse_parent] = mesh_manager->get_parent_volumes(surf); if (volume_id == forward_parent) { meshid_to_sense_.resize(static_cast(forward_parent) + 1, 1); @@ -241,7 +235,7 @@ bool GPRTRayTracer::point_in_volume(SurfaceTreeID tree, const Direction* direction, const std::vector* exclude_primitives) const { - GPRTAccel volume = surface_volume_tree_to_accel_map.at(tree); + MeshID volume = surface_tree_to_volume_map_.at(tree); // recover MeshID of volume to return GPRTAccel on device auto rayGen = rayGenPrograms_.at(RayGenType::POINT_IN_VOLUME); dblRayGenData* rayGenPIVData = gprtRayGenGetParameters(rayGen); @@ -251,18 +245,13 @@ bool GPRTRayTracer::point_in_volume(SurfaceTreeID tree, Direction directionUsed = (direction != nullptr) ? Direction{direction->x, direction->y, direction->z} : defaultDir; - // Catch directions with zero length - const double l2 = directionUsed.x*directionUsed.x - + directionUsed.y*directionUsed.y - + directionUsed.z*directionUsed.z; - if (l2 == 0.0) directionUsed = defaultDir; - - gprtBufferMap(rayHitBuffers_.ray); // Update the ray input buffer + // Host -> Device buffer mapping/population of raydata for raygen shader + gprtBufferMap(rayHitBuffers_.ray); dblRay* ray = gprtBufferGetHostPointer(rayHitBuffers_.ray); ray[0].origin = {point.x, point.y, point.z}; ray[0].direction = {directionUsed.x, directionUsed.y, directionUsed.z}; - ray[0].volume_mesh_id = surface_tree_to_volume_map_.at(tree); - ray[0].enabled = 1; // Ensure the ray is enabled + ray[0].volume_mesh_id = volume; + ray[0].enabled = 1; // Ensure the ray is enabled if (exclude_primitives) { if (!exclude_primitives->empty()) gprtBufferResize(context_, excludePrimitivesBuffer_, exclude_primitives->size(), false); @@ -280,7 +269,7 @@ bool GPRTRayTracer::point_in_volume(SurfaceTreeID tree, } gprtBufferUnmap(rayHitBuffers_.ray); // required to sync buffer back on GPU? - dblRayFirePushConstants pushConstants; + dblPushConstants pushConstants; pushConstants.hitOrientation = HitOrientation::ANY; pushConstants.tMax = INFTY; pushConstants.tMin = 0.0; @@ -288,7 +277,7 @@ bool GPRTRayTracer::point_in_volume(SurfaceTreeID tree, gprtRayGenLaunch1D(context_, rayGen, 1, pushConstants); // Launch raygen shader (entry point to RT pipeline) gprtGraphicsSynchronize(context_); // Ensure all GPU operations are complete before returning control flow to CPU - // Retrieve the hit from the dblHit buffer + // Device -> Host buffer mapping to retrieve hit result gprtBufferMap(rayHitBuffers_.hit); dblHit* hit = gprtBufferGetHostPointer(rayHitBuffers_.hit); auto surface = hit[0].surf_id; @@ -312,15 +301,16 @@ std::pair GPRTRayTracer::ray_fire(SurfaceTreeID tree, HitOrientation orientation, std::vector* const exclude_primitives) { - GPRTAccel volume = surface_volume_tree_to_accel_map.at(tree); + MeshID volume = surface_tree_to_volume_map_.at(tree); // recover MeshID of volume to return GPRTAccel on device auto rayGen = rayGenPrograms_.at(RayGenType::RAY_FIRE); dblRayGenData* rayGenData = gprtRayGenGetParameters(rayGen); - gprtBufferMap(rayHitBuffers_.ray); // Update the ray input buffer + // Host -> Device buffer mapping/population of raydata for raygen shader + gprtBufferMap(rayHitBuffers_.ray); dblRay* ray = gprtBufferGetHostPointer(rayHitBuffers_.ray); ray[0].origin = {origin.x, origin.y, origin.z}; ray[0].direction = {direction.x, direction.y, direction.z}; - ray[0].volume_mesh_id = surface_tree_to_volume_map_.at(tree); + ray[0].volume_mesh_id = volume; ray[0].enabled = 1; // Ensure the ray is enabled if (exclude_primitives) { @@ -340,7 +330,7 @@ std::pair GPRTRayTracer::ray_fire(SurfaceTreeID tree, gprtBufferUnmap(rayHitBuffers_.ray); // required to sync buffer back on GPU? // Set push constants (same for every ray) - dblRayFirePushConstants pushConstants; + dblPushConstants pushConstants; pushConstants.hitOrientation = orientation; pushConstants.tMax = dist_limit; pushConstants.tMin = 0.0; @@ -348,7 +338,7 @@ std::pair GPRTRayTracer::ray_fire(SurfaceTreeID tree, gprtRayGenLaunch1D(context_, rayGen, 1, pushConstants); // Launch raygen shader (entry point to RT pipeline) gprtGraphicsSynchronize(context_); // Ensure all GPU operations are complete before returning control flow to CPU - // Retrieve the hit from the dblHit buffer + // Device -> Host buffer mapping to retrieve hit result gprtBufferMap(rayHitBuffers_.hit); dblHit* hit = gprtBufferGetHostPointer(rayHitBuffers_.hit); auto distance = hit[0].distance; @@ -374,7 +364,7 @@ GPRTRayTracer::ray_fire_prepared(const size_t num_rays, auto rayGen = rayGenPrograms_.at(RayGenType::RAY_FIRE); - dblRayFirePushConstants pushConstants; + dblPushConstants pushConstants; pushConstants.tMax = dist_limit; pushConstants.tMin = 0.0; pushConstants.hitOrientation = orientation; // Set orientation for the ray @@ -392,7 +382,7 @@ GPRTRayTracer::point_in_volume_prepared(const size_t num_points) check_rayhit_buffer_capacity(num_points); auto rayGen = rayGenPrograms_.at(RayGenType::POINT_IN_VOLUME); - dblRayFirePushConstants pushConstants; + dblPushConstants pushConstants; pushConstants.tMax = INFTY; pushConstants.tMin = 0.0; pushConstants.hitOrientation = HitOrientation::ANY; // Set orientation for the ray diff --git a/src/tetrahedron_contain.cpp b/src/tetrahedron_contain.cpp index 98757768..c6f43a2a 100644 --- a/src/tetrahedron_contain.cpp +++ b/src/tetrahedron_contain.cpp @@ -13,6 +13,7 @@ bool plucker_tet_containment_test(const Position& point, const Position& v1, const Position& v2, const Position& v3) { + // explicit namespace usage to avoid clash with GPRT math types using linalg::aliases::double3x3; using linalg::aliases::double3; using linalg::aliases::double4; diff --git a/src/xdg.cpp b/src/xdg.cpp index f0687076..46cb7795 100644 --- a/src/xdg.cpp +++ b/src/xdg.cpp @@ -339,4 +339,4 @@ double XDG::measure_volume_area(MeshID volume) const return area; } -} // namespace xdg +} // namespace xdg \ No newline at end of file diff --git a/tests/test_files b/tests/test_files index ca579198..eb0334e7 160000 --- a/tests/test_files +++ b/tests/test_files @@ -1 +1 @@ -Subproject commit ca57919851224047ef86fab177a0bfe9fa920127 +Subproject commit eb0334e7bde416845bc28aeb91b192f44ee35a78 diff --git a/tests/test_ray_fire.cpp b/tests/test_ray_fire.cpp index 0e6816e3..a730c4c1 100644 --- a/tests/test_ray_fire.cpp +++ b/tests/test_ray_fire.cpp @@ -10,8 +10,6 @@ #include "mesh_mock.h" #include "util.h" -#include - using namespace xdg; using namespace xdg::test;