Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down
201 changes: 201 additions & 0 deletions tests/test_direct_ray_buffer_access.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
// for testing
#include <catch2/catch_test_macros.hpp>
#include <catch2/catch_template_test_macros.hpp>
#include <catch2/matchers/catch_matchers_floating_point.hpp>
#include <catch2/generators/catch_generators.hpp>

// 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 <chrono>

using namespace xdg;
using namespace xdg::test;

extern GPRTProgram test_direct_ray_buffer_access_deviceCode;

static RayPopulationCallback make_populate_callback(const std::vector<Position>& origins,
const std::vector<Direction>& directions,
const std::vector<MeshID>& volume_ids,
GPRTContext context,
GPRTComputeOf<ExternalRayParams> 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<double3> origins_device(num_rays);
std::vector<double3> 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<double3>(context, num_rays, origins_device.data());
auto directions_buffer = gprtDeviceBufferCreate<double3>(context, num_rays, directions_device.data());
auto volume_ids_buffer = gprtDeviceBufferCreate<int32_t>(context, num_rays, volume_ids.data());

constexpr uint32_t threads_per_group = 256;
const uint32_t groups = static_cast<uint32_t>((num_rays + threads_per_group - 1) / threads_per_group);

ExternalRayParams params = {};
params.xdgRays = static_cast<dblRay*>(buffer.rayDevPtr);
params.origins = gprtBufferGetDevicePointer(origins_buffer);
params.directions = gprtBufferGetDevicePointer(directions_buffer);
params.num_rays = static_cast<uint32_t>(num_rays);
params.total_threads = groups * threads_per_group;
params.volume_mesh_ids = gprtBufferGetDevicePointer(volume_ids_buffer); // Pass array of volume IDs to compute shader
params.enabled = 1u;

gprtComputeLaunch(packRays,
{ groups, 1, 1 },
{ threads_per_group, 1, 1 },
params);
gprtComputeSynchronize(context);

gprtBufferDestroy(origins_buffer);
gprtBufferDestroy(directions_buffer);
if (volume_ids_buffer) {
gprtBufferDestroy(volume_ids_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)
{
constexpr auto rt_backend = TestType::value;

DYNAMIC_SECTION(fmt::format("Backend = {}", rt_backend)) {
check_ray_tracer_supported(rt_backend); // skip if backend not enabled at configuration time
std::shared_ptr<XDG> 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<Position> origins;
std::vector<Direction> directions;
size_t N = 64;
make_rays(N, origins, directions);

auto gprt_rt = std::dynamic_pointer_cast<GPRTRayTracer>(xdg->ray_tracing_interface());
REQUIRE(gprt_rt);

std::vector<MeshID> 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<ExternalRayParams>(context, module, "pack_external_rays");

std::vector<double> expected_distances(N, INFTY);
std::vector<MeshID> expected_surfaces(N, ID_NONE);
std::vector<MeshID> 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 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;
}

// Create callback to populate rays on device
RayPopulationCallback populate_callback = make_populate_callback(origins,
directions,
volume_ids,
context,
packRays);

// Populate rays via external API
xdg->populate_rays_external(N, populate_callback);

xdg->ray_fire_prepared(N);
std::vector<dblHit> 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);
}
}

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); // skip if backend not enabled at configuration time
std::shared_ptr<XDG> 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<Position> points;
std::vector<Direction> directions;
size_t N = 64;
make_points(N, points, directions);

auto gprt_rt = std::dynamic_pointer_cast<GPRTRayTracer>(xdg->ray_tracing_interface());
REQUIRE(gprt_rt);

std::vector<MeshID> 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<ExternalRayParams>(context, module, "pack_external_rays");

std::vector<uint8_t> expected_piv(N, 0);
std::vector<MeshID> volume_ids(N, volume_id);
for (size_t i = 0; i < N; ++i) {
volume_ids[i] = (i % 2 == 0) ? volume_id : volume_id_alt;
expected_piv[i] = static_cast<uint8_t>(xdg->point_in_volume(volume_ids[i], points[i], &directions[i]));
}

RayPopulationCallback populate_callback = make_populate_callback(points,
directions,
volume_ids,
context,
packRays);
xdg->populate_rays_external(N, populate_callback);

xdg->point_in_volume_prepared(N);
std::vector<dblHit> 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; // convert back to enum
REQUIRE(hits[i].piv == expected);
}

gprtComputeDestroy(packRays);
gprtModuleDestroy(module);
}
}
24 changes: 24 additions & 0 deletions tests/test_direct_ray_buffer_access_deviceCode.slang
Original file line number Diff line number Diff line change
@@ -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_ids[idx]; // Set volume mesh ID per ray
r.enabled = extParams.enabled;

extParams.xdgRays[idx] = r; // Write to device ray buffer
}
}
13 changes: 13 additions & 0 deletions tests/test_direct_ray_buffer_access_shared.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#include "gprt.h"

#include "../include/xdg/gprt/ray.h"

struct ExternalRayParams {
xdg::dblRay* xdgRays;
double3* origins;
double3* directions;
uint num_rays;
uint total_threads;
int32_t* volume_mesh_ids;
uint enabled;
};
2 changes: 1 addition & 1 deletion tests/test_files
69 changes: 68 additions & 1 deletion tests/test_point_in_volume.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<MeshMock>(false);
Expand Down Expand Up @@ -78,3 +77,71 @@ 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<MeshMock>(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<Position> points;
std::vector<Direction> directions;
std::vector<uint8_t> 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<uint8_t>(rti->point_in_volume(volume_tree, points[0], &directions[0]));

std::vector<uint8_t> 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<uint8_t> scalar_results(N, 0);
for (size_t i = 0; i < N; ++i) {
scalar_results[i] = static_cast<uint8_t>(rti->point_in_volume(volume_tree, points[i], &directions[i]));
}

std::vector<uint8_t> 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]);
}
}
}
}
Loading
Loading