// Test-only execution of the exact CUDA kernel source, one logical thread at a time. // Linked solely into sar3d_math_fixture; never used as a production backend. #include "../processing/processors/src/sar3d_gpu.hpp" #include #include #include #include #include #include #include #define __global__ using std::isfinite; using std::min; struct Index { unsigned x; }; Index blockIdx{0}, blockDim{1}, threadIdx{0}; struct float2 { float x, y; }; auto make_float2(float x, float y) -> float2 { return {x, y}; } void sincospif(float x, float* sine, float* cosine) { *sine = std::sin(x * 3.14159265358979323846F); *cosine = std::cos(x * 3.14159265358979323846F); } auto __powf(float x, float y) -> float { return std::pow(x, y); } auto __float_as_uint(float x) -> unsigned int { return std::bit_cast(x); } auto __double2int_rn(double x) -> int { return static_cast(std::nearbyint(x)); } void atomicMax(unsigned int* destination, unsigned int value) { *destination = std::max(*destination, value); } #include "sar3d_kernels.inc" auto probe_ascan_upper_bound() -> float { blockIdx.x = 0; const double x[]{1073741824.0}, zero[]{0.0}; const ::Contribution contribution{{}, {}, 1.0F, 1.0F}; float volume = -1.0F; ::backproject(nullptr, INT_MAX, &contribution, 1, x, zero, zero, 1, 1, 1, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0F, 0, 0.0F, 0.0F, &volume); return volume; } namespace { template void each(int count, Operation operation) { for (blockIdx.x = 0; blockIdx.x < static_cast(count); ++blockIdx.x) { operation(); } } auto float_pairs(std::span> values) -> std::vector { std::vector pairs; pairs.reserve(values.size()); for (const auto value : values) { pairs.push_back({value.real(), value.imag()}); } return pairs; } } namespace radar::processing::sar3d { namespace { #include "sar3d_helpers.inc" } struct Engine::Impl { std::vector taps; }; Engine::Engine(double sigma) : impl_(std::make_unique()) { impl_->taps = gaussian_taps(sigma); } Engine::~Engine() = default; auto Engine::inverse_dft(std::span> bands, std::size_t points, std::size_t first_bin, std::size_t size) -> std::vector> { if (points == 0U || bands.empty() || bands.size() % points != 0U || !std::has_single_bit(size) || size > kMaxIndexedItems || first_bin >= size || points > size - first_bin) { throw std::runtime_error("sar3d invalid inverse FFT dimensions"); } const auto count = checked_product(bands.size() / points, size, kMaxIndexedItems, "A-scan stack"); const auto input = float_pairs(bands); std::vector output(count); each(static_cast(count), [&] { ::inverse_dft(input.data(), points, first_bin, size, count, output.data()); }); std::vector> result; result.reserve(count); for (const auto value : output) { result.emplace_back(value.x, value.y); } return result; } auto Engine::project(const VolumeRequest& request) -> Projections { const int nx = request.x.size(), ny = request.y.size(), nz = request.z.size(); const int plane = checked_product(nx, nz, kMaxIndexedItems, "grid plane"); const int voxels = checked_product(plane, ny, kMaxIndexedItems, "grid"); std::vector<::Contribution> contributions; for (const auto& term : request.contributions) { contributions.push_back(std::bit_cast<::Contribution>(term)); } const auto ascans = float_pairs(request.ascans); std::vector volume(voxels), scratch(voxels), levels(ny); std::vector indices(ny); each(voxels, [&] { ::backproject(ascans.data(), request.samples_per_ascan, contributions.data(), contributions.size(), request.x.data(), request.y.data(), request.z.data(), nx, nz, voxels, std::cos(request.pitch_rad), std::sin(request.pitch_rad), request.ground_depth_m, request.min_depth_m, request.max_depth_m, request.samples_per_metre, request.use_compensation, request.comp_range_power, request.comp_weight_max, volume.data()); }); each(ny, [&] { ::peak_planes(volume.data(), plane, ny, levels.data(), indices.data()); }); Projections raw; choose_peak(raw, request, levels); const int radius = impl_->taps.size() / 2; each(voxels, [&] { ::smooth(volume.data(), scratch.data(), impl_->taps.data(), radius, nx, nz, voxels, 1); }); each(voxels, [&] { ::smooth(scratch.data(), volume.data(), impl_->taps.data(), radius, nx, nz, voxels, 0); }); each(ny, [&] { ::peak_planes(volume.data(), plane, ny, levels.data(), indices.data()); }); Projections result; result.raw_level = raw.level; choose_peak(result, request, levels, indices); result.front.resize(ny * nx); result.side.resize(ny * nz); result.top.resize(plane); const auto& view = request.oblique; std::vector oblique(checked_product(view.nu, view.nv, kMaxIndexedItems, "oblique image")); each(result.front.size(), [&] { ::max_project(volume.data(), result.front.data(), result.front.size(), nx, plane, 1, nz, nx); }); each(result.side.size(), [&] { ::max_project(volume.data(), result.side.data(), result.side.size(), nz, plane, nx, nx, 1); }); each(result.top.size(), [&] { ::max_project(volume.data(), result.top.data(), result.top.size(), nx, nx, 1, ny, plane); }); each(voxels, [&] { ::project_oblique(volume.data(), request.x.data(), request.y.data(), request.z.data(), nx, nz, voxels, view.u[0], view.u[1], view.u[2], view.v[0], view.v[1], view.v[2], view.u0, view.v0, 1 / view.pixel_m, view.nu, view.nv, oblique.data()); }); result.oblique.reserve(oblique.size()); for (const auto value : oblique) { result.oblique.push_back(std::bit_cast(value)); } return result; } }