A real-time, GPU-accelerated active-radar simulator — from first-principles detection physics to a full signal-processing chain running on CUDA, with a live tactical display and statistical charts.
Why "PHAROS"? A double meaning. It's the acronym — Phased / Pulse-Doppler Active Radar Operations Simulator — and a nod to the Pharos of Alexandria, the ancient lighthouse whose rotating beam swept the darkness to reveal what was out there. Which is exactly what a radar's PPI sweep does.
Built in modern C++20 with CUDA, OpenGL, Dear ImGui and ImPlot. It models an active monostatic surveillance radar: targets move through a 2D tactical scene, the antenna sweeps, and detections are produced both from the radar range equation (analytic) and from a range-Doppler signal-processing pipeline (matched filter → Doppler FFT → CFAR) executed on the GPU.
The live dashboard: rotating PPI scope (top-centre), GPU range-Doppler map (bottom-centre), SNR/Pd/detection charts (right), and full parameter control (left).
It sits at the intersection of several domains I wanted to demonstrate end to end:
- Radar / DSP — the radar equation, probability of detection, LFM pulse compression, range-Doppler processing, and constant-false-alarm-rate detection.
- GPU computing — custom CUDA kernels, batched/strided cuFFT, and CUDA-OpenGL interop that paints the heatmap straight from device memory with no host round-trip.
- Real-time graphics & UX — an interactive 60 fps dashboard (PPI scope, range-Doppler map, live charts, full parameter control).
- Engineering discipline — a clean layered architecture, a dependency-free unit-tested physics core, and a build that degrades gracefully when no GPU toolkit is present.
| Area | What it does |
|---|---|
| Tactical sim | Fixed-timestep kinematics; targets with position, velocity and RCS; rotating antenna scan. |
| Detection physics | Radar range equation SNR ∝ Pt·G²·λ²·σ / (R⁴…); probability of detection via Albersheim's approximation; measurement noise; Poisson false alarms. |
| PPI scope | Rotating sweep with afterglow, range rings, azimuth spokes, fading detection blips, optional ground-truth overlay. |
| GPU signal chain | LFM chirp synthesis → matched-filter pulse compression → Hann-windowed Doppler FFT → CA-CFAR detection, all on the GPU. |
| Range-Doppler map | Live heatmap painted by a CUDA surface-write kernel via OpenGL interop; range/velocity axes; CFAR markers; noise-floor-relative colour scaling. |
| Signal mode | Overlays the GPU pipeline's CFAR detections back onto the PPI for comparison with the analytic detector. |
| Live analytics | ImPlot charts: SNR-vs-range, Pd-vs-range (with per-target markers and Pd=0.5/0.9 range lines), detections-per-scan. |
| Full interactivity | Every radar parameter on sliders; add/move/remove targets live; play/pause/time-scale. |
Single-look signal-to-noise ratio from the monostatic radar range equation:
Pt · G² · λ² · σ · N
SNR = ───────────────────────────
(4π)³ · R⁴ · k·T·B·F · L
Probability of detection is obtained from SNR with Albersheim's equation (closed-form, invertible), which also yields the "Pd = 0.9 range" style readouts.
For the beam's current pointing direction the pipeline builds a synthetic range × pulse data cube and processes it entirely on the GPU:
- Waveform — linear-FM (chirp) reference, time-bandwidth product ≈ 40.
- Echo synthesis — each in-beam target's delayed, Doppler-shifted, antenna- pattern-weighted echo, plus complex thermal noise.
- Matched filter —
IFFT( FFT(x) · conj(H) )for pulse compression, using stridedcufftPlanManyso the range and Doppler transforms run on the same cube layout with no explicit transpose. - Doppler FFT — Hann-windowed FFT across pulses → range-Doppler map.
- CA-CFAR — cell-averaging constant-false-alarm-rate detection in range, producing a detection list with per-cell SNR.
The result is colour-mapped (relative to the estimated noise floor) directly into an OpenGL texture through CUDA graphics interop.
The core physics is deliberately free of any GPU/GL dependency, so it is fully unit-testable and the whole project builds and runs even without a CUDA toolkit.
src/
core/ Math, entities, polar/Cartesian geometry (no deps)
radar/ Radar equation, Pd, antenna pattern, detector (no deps)
sim/ SimEngine: fixed-step scan + detection history (no deps)
dsp/ CUDA pipeline: chirp, matched filter, RD FFT, CFAR (CUDA, optional)
render/ PPI scope + range-Doppler heatmap view (OpenGL/ImGui)
ui/ Control panels + ImPlot charts (ImGui/ImPlot)
app/ Window, sim loop, glue
tests/ Catch2 — physics + GPU pipeline validation
CUDA is optional and auto-detected. With a toolkit present the DSP layer and range-Doppler map are enabled; without one, the application still builds and runs as the analytic radar simulator.
C++20 · CMake · CUDA + cuFFT · OpenGL 4.5 · GLFW · glad · Dear ImGui ·
ImPlot · GLM · Catch2. All third-party libraries except the CUDA Toolkit are
fetched automatically by CMake (FetchContent).
Prerequisites: CMake ≥ 3.24, a C++20 compiler, and network access on first configure (to fetch dependencies).
cmake -S . -B build
cmake --build build --config Release
ctest --test-dir build -C Release --output-on-failureRun it:
./build/bin/Release/radar_app # path varies by generator/configglad loader: the OpenGL loader is generated at build time with Python and needs the
jinja2package. If CMake selects the wrong Python, point it at a real interpreter:pip install jinja2thencmake -S . -B build -DPython_EXECUTABLE=/path/to/python.
Requires a CUDA Toolkit (12.x or 13.x) and a host compiler it supports
(CUDA 13 needs Visual Studio 2022). Point CMake at nvcc if it isn't on PATH:
cmake -S . -B build -DCMAKE_CUDA_COMPILER="/path/to/nvcc"The target GPU architecture defaults to sm_86 (Ampere / RTX 30-series);
override with -DCMAKE_CUDA_ARCHITECTURES=<arch>. The required CUDA runtime
DLLs are copied next to the executable automatically, so no PATH changes are
needed to run. Force-disable with -DRADAR_ENABLE_CUDA=OFF.
Catch2 suite run via ctest. The physics core is validated independently of the
GPU (R⁻⁴ falloff, RCS scaling, Pd monotonicity, detection-range inversion,
geometry round-trips, end-to-end sim detection). When CUDA is enabled, two extra
tests assert that the range-Doppler peak lands at the physically-expected
range and Doppler bin and that CFAR detects a known target with few false
alarms — the GPU pipeline is checked against the analytics, not just smoke-tested.
- M0 — build system, window, optional CUDA smoke test
- M1 — kinematic sim, radar-equation detection, PPI scope, live charts
- M2 — GPU range-Doppler chain (chirp → matched filter → FFT → CFAR) + interop heatmap
- M3 — multi-target tracker with track trails, ground/rain clutter, noise jammer, scenario save/load, recorded demo GIFs
MIT © Patrik Kalabus · PHAROS
Educational radar-simulation project. All models are textbook formulations implemented from public-domain physics; no classified or proprietary data is used.

