From 5f930ad96bcedf46e8d09a7343c440cd1250e4e2 Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Fri, 30 Jan 2026 11:46:20 +0000 Subject: [PATCH 1/8] 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 663dad56d4c4307d67e86b7e50171cfec3f23499 Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Fri, 30 Jan 2026 12:14:55 +0000 Subject: [PATCH 2/8] 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 b2bcc4e98e7574900ee2fbd3b279d9243edf1fb8 Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Fri, 30 Jan 2026 12:58:32 +0000 Subject: [PATCH 3/8] Removed method to expose rayhit buffers directly since we use callback instead --- include/xdg/gprt/ray_tracer.h | 13 ------------- include/xdg/ray_tracing_interface.h | 9 --------- include/xdg/xdg.h | 5 ----- src/gprt/ray_tracer.cpp | 6 ------ 4 files changed, 33 deletions(-) diff --git a/include/xdg/gprt/ray_tracer.h b/include/xdg/gprt/ray_tracer.h index 83e0fa19..a85d3608 100644 --- a/include/xdg/gprt/ray_tracer.h +++ b/include/xdg/gprt/ray_tracer.h @@ -122,9 +122,6 @@ class GPRTRayTracer : public RayTracer { // 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 * @@ -142,16 +139,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 diff --git a/include/xdg/ray_tracing_interface.h b/include/xdg/ray_tracing_interface.h index dbbefc8b..a7854630 100644 --- a/include/xdg/ray_tracing_interface.h +++ b/include/xdg/ray_tracing_interface.h @@ -240,15 +240,6 @@ class RayTracer { 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 * diff --git a/include/xdg/xdg.h b/include/xdg/xdg.h index 9dbc8ad2..843782d2 100644 --- a/include/xdg/xdg.h +++ b/include/xdg/xdg.h @@ -212,11 +212,6 @@ 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) { diff --git a/src/gprt/ray_tracer.cpp b/src/gprt/ray_tracer.cpp index be346ca1..4c9fb980 100644 --- a/src/gprt/ray_tracer.cpp +++ b/src/gprt/ray_tracer.cpp @@ -596,12 +596,6 @@ void GPRTRayTracer::update_tlas_table_() } } -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) { From e58b20b9cd7100b94d13d7a036c2cd5e65f95d0b Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Fri, 30 Jan 2026 18:28:22 +0000 Subject: [PATCH 4/8] 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 40c7e6bcca81abd1b7de9485e63a24e8ebe19504 Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Tue, 3 Feb 2026 14:31:19 +0000 Subject: [PATCH 5/8] 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 a85d3608..40f71477 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, @@ -131,8 +134,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 a7854630..7bdc5dbd 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 * @@ -263,6 +266,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 4c9fb980..b8456c40 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); @@ -599,7 +605,10 @@ void GPRTRayTracer::update_tlas_table_() 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); @@ -611,11 +620,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 53d8b94a222e35fe65a57648b89dd80297a3e56c Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Tue, 3 Feb 2026 15:46:51 +0000 Subject: [PATCH 6/8] 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 | 35 +++++++++++++++++++++++-------- 4 files changed, 49 insertions(+), 12 deletions(-) diff --git a/include/xdg/gprt/ray_tracer.h b/include/xdg/gprt/ray_tracer.h index 40f71477..c9623c20 100644 --- a/include/xdg/gprt/ray_tracer.h +++ b/include/xdg/gprt/ray_tracer.h @@ -176,9 +176,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}; @@ -187,5 +206,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 b8456c40..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,23 @@ 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); + return rayHitBuffers_.view; +} + void GPRTRayTracer::populate_rays_external(size_t numRays, const RayPopulationCallback& callback) { From 242be27666740fed6be278391ff1165c67b48053 Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Tue, 3 Feb 2026 15:51:21 +0000 Subject: [PATCH 7/8] 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 060efd1ed9d35d4a946ff5e73cb6430b08bc5c56 Mon Sep 17 00:00:00 2001 From: waqar-ukaea Date: Tue, 3 Feb 2026 16:02:49 +0000 Subject: [PATCH 8/8] Fixed missing definitions for get_device_rayhit_buffers --- include/xdg/gprt/ray_tracer.h | 3 +++ include/xdg/ray_tracing_interface.h | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/include/xdg/gprt/ray_tracer.h b/include/xdg/gprt/ray_tracer.h index c9623c20..7cdea83e 100644 --- a/include/xdg/gprt/ray_tracer.h +++ b/include/xdg/gprt/ray_tracer.h @@ -125,6 +125,9 @@ class GPRTRayTracer : public RayTracer { // 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 * diff --git a/include/xdg/ray_tracing_interface.h b/include/xdg/ray_tracing_interface.h index 7bdc5dbd..37266e4e 100644 --- a/include/xdg/ray_tracing_interface.h +++ b/include/xdg/ray_tracing_interface.h @@ -243,6 +243,15 @@ class RayTracer { 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 *