Open3D (C++ API)  0.20.0
Loading...
Searching...
No Matches
BallQuerySYCL.h
Go to the documentation of this file.
1// ----------------------------------------------------------------------------
2// - Open3D: www.open3d.org -
3// ----------------------------------------------------------------------------
4// Copyright (c) 2018-2026 www.open3d.org
5// SPDX-License-Identifier: MIT
6// ----------------------------------------------------------------------------
7
8// SYCL implementation of BallQuery — ports BallQuery.cu (ball_query_kernel).
9// One work-group per (batch, query); work-items grid-stride over candidates
10// and use exclusive_scan_over_group to claim output slots. When more than
11// nsample points are in range, the in-range neighbor set is correct but slot
12// order may differ from CUDA; python/test/ml_ops/test_query_pts.py compares
13// neighbor sets, not order. A serial-scan kernel would preserve CUDA index
14// order but is not required today. Padding when total matches < nsample still
15// duplicates idx_out[0] into remaining slots (CUDA contract).
16#pragma once
17
18#include <sycl/sycl.hpp>
19
20namespace open3d {
21namespace ml {
22namespace contrib {
23
24namespace {
25// Work-group size: one work-group per query point, work-items grid-stride
26// over the n candidate points. Best-guess default (matches the
27// work-group-per-output-point size used by the conv FillColumn kernels,
28// which is itself a mirror of the CUDA warp-per-point design); not yet tuned
29// on target HW for this op's access pattern.
30constexpr size_t kBallQueryWGSize = 32;
31} // namespace
32
46inline void BallQuerySYCL(sycl::queue& queue,
47 int b,
48 int n,
49 int m,
50 float radius,
51 int nsample,
52 const float* const new_xyz,
53 const float* const xyz,
54 int* const idx) {
55 if (b <= 0 || m <= 0) return;
56 const float radius2 = radius * radius;
57 const size_t wg = kBallQueryWGSize;
58
59 queue.submit([&](sycl::handler& cgh) {
60 // Per-work-item running match count, used to derive each work-item's
61 // base output slot via an exclusive scan before writing.
62 cgh.parallel_for(
63 sycl::nd_range<1>(sycl::range<1>(size_t(b) * m * wg),
64 sycl::range<1>(wg)),
65 // Distinct buffers — safe for [[intel::kernel_args_restrict]].
66 [=](sycl::nd_item<1> item) [[intel::kernel_args_restrict]] {
67 const size_t group_id = item.get_group(0);
68 const int bs_idx = static_cast<int>(group_id / m);
69 const int pt_idx = static_cast<int>(group_id % m);
70 const size_t lid = item.get_local_id(0);
71 auto group = item.get_group();
72
73 const float* const nxyz =
74 new_xyz + bs_idx * m * 3 + pt_idx * 3;
75 const float* const xyz_batch = xyz + bs_idx * n * 3;
76 int* const idx_out =
77 idx + bs_idx * m * nsample + pt_idx * nsample;
78
79 const float new_x = nxyz[0];
80 const float new_y = nxyz[1];
81 const float new_z = nxyz[2];
82
83 // Local match count for this work-item's strided slice.
84 int local_count = 0;
85 for (int k = static_cast<int>(lid); k < n;
86 k += static_cast<int>(wg)) {
87 const float x = xyz_batch[k * 3 + 0];
88 const float y = xyz_batch[k * 3 + 1];
89 const float z = xyz_batch[k * 3 + 2];
90 const float d2 = (new_x - x) * (new_x - x) +
91 (new_y - y) * (new_y - y) +
92 (new_z - z) * (new_z - z);
93 if (d2 < radius2) ++local_count;
94 }
95
96 // Exclusive scan of local_count across the work-group
97 // gives each work-item's base output slot; the group's
98 // total (via a matching inclusive/exclusive-plus-local
99 // sum) is used below to detect the zero-match case.
100 const int base_slot = sycl::exclusive_scan_over_group(
101 group, local_count, sycl::plus<int>());
102 const int total_count = sycl::reduce_over_group(
103 group, local_count, sycl::plus<int>());
104
105 if (total_count == 0) {
106 // No candidate in range for this query: idx_out is
107 // already zero-initialized by the caller, matching
108 // the original serial kernel leaving it untouched.
109 return;
110 }
111
112 // Second pass: re-scan this work-item's slice, writing
113 // matches at [base_slot, base_slot+1, ...) capped at
114 // nsample, mirroring the serial kernel's cnt/idx_out[cnt]
115 // bookkeeping (but slots are now assigned by the scan
116 // instead of a running scalar counter).
117 int slot = base_slot;
118 for (int k = static_cast<int>(lid); k < n && slot < nsample;
119 k += static_cast<int>(wg)) {
120 const float x = xyz_batch[k * 3 + 0];
121 const float y = xyz_batch[k * 3 + 1];
122 const float z = xyz_batch[k * 3 + 2];
123 const float d2 = (new_x - x) * (new_x - x) +
124 (new_y - y) * (new_y - y) +
125 (new_z - z) * (new_z - z);
126 if (d2 < radius2) {
127 idx_out[slot] = k;
128 ++slot;
129 }
130 }
131
132 // Pad slots [total_count, nsample) with idx_out[0]; barrier
133 // so work-item 0 reads a value another lane may have
134 // written.
135 sycl::group_barrier(group);
136 if (lid == 0 && total_count < nsample) {
137 const int first_value = idx_out[0];
138 for (int l = total_count; l < nsample; ++l) {
139 idx_out[l] = first_value;
140 }
141 }
142 });
143 });
144 queue.wait_and_throw();
145}
146
147} // namespace contrib
148} // namespace ml
149} // namespace open3d
std::int64_t y
Definition NormalDistributionsTransform.cpp:43
std::int64_t x
Definition NormalDistributionsTransform.cpp:42
std::int64_t z
Definition NormalDistributionsTransform.cpp:44
sycl::queue queue
Definition SYCLContext.cpp:88
void BallQuerySYCL(sycl::queue &queue, int b, int n, int m, float radius, int nsample, const float *const new_xyz, const float *const xyz, int *const idx)
Definition BallQuerySYCL.h:46
Definition PinholeCameraIntrinsic.cpp:16