diff --git a/CMakeLists.txt b/CMakeLists.txt index f683e417..c047795a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,6 +27,13 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") include(FersOptions) if (NOT FERS_DOCS_ONLY) + # Embree is an optional vcpkg manifest feature (see vcpkg.json) so it is only + # fetched/built when actually requested. This must be set before the vcpkg + # toolchain file runs, i.e. before project() below. + if (FERS_ENABLE_EMBREE) + list(APPEND VCPKG_MANIFEST_FEATURES "embree") + endif () + # Auto-detect vcpkg toolchain before project() so dependency resolution works in CLion and CLI. # Note: when building via `cargo build` (the Tauri path), CMAKE_TOOLCHAIN_FILE is always passed # explicitly by build.rs, so this block is effectively dead in that path. It is retained here diff --git a/Testing/Temporary/CTestCostData.txt b/Testing/Temporary/CTestCostData.txt new file mode 100644 index 00000000..ed97d539 --- /dev/null +++ b/Testing/Temporary/CTestCostData.txt @@ -0,0 +1 @@ +--- diff --git a/Testing/Temporary/LastTest.log b/Testing/Temporary/LastTest.log new file mode 100644 index 00000000..7b07d106 --- /dev/null +++ b/Testing/Temporary/LastTest.log @@ -0,0 +1,3 @@ +Start testing: Sep 30 21:27 SAST +---------------------------------------------------------- +End testing: Sep 30 21:27 SAST diff --git a/cmake/FersEmbedFile.cmake b/cmake/FersEmbedFile.cmake index 3a2efc79..2b9a342a 100644 --- a/cmake/FersEmbedFile.cmake +++ b/cmake/FersEmbedFile.cmake @@ -14,27 +14,10 @@ file(READ "${INPUT}" _fers_embed_hex HEX) string(LENGTH "${_fers_embed_hex}" _fers_embed_hex_length) math(EXPR _fers_embed_byte_count "${_fers_embed_hex_length} / 2") -set(_fers_embed_body) +# Convert the string to a C-style array of byte values. +set(_fers_embed_body "${_fers_embed_hex}") if (_fers_embed_byte_count GREATER 0) - math(EXPR _fers_embed_last_index "${_fers_embed_byte_count} - 1") - foreach (_fers_embed_index RANGE 0 ${_fers_embed_last_index}) - math(EXPR _fers_embed_pos "${_fers_embed_index} * 2") - string(SUBSTRING "${_fers_embed_hex}" ${_fers_embed_pos} 2 _fers_embed_byte) - - if (_fers_embed_index GREATER 0) - string(APPEND _fers_embed_body ",") - endif () - - math(EXPR _fers_embed_col "${_fers_embed_index} % 12") - if (_fers_embed_col EQUAL 0) - string(APPEND _fers_embed_body "\n\t") - else () - string(APPEND _fers_embed_body " ") - endif () - - string(APPEND _fers_embed_body "0x${_fers_embed_byte}") - endforeach () - string(APPEND _fers_embed_body "\n") + string(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1, " _fers_embed_body "${_fers_embed_body}") endif () file(WRITE "${OUTPUT}" diff --git a/cmake/FersEmbree.cmake b/cmake/FersEmbree.cmake new file mode 100644 index 00000000..a1aaad5d --- /dev/null +++ b/cmake/FersEmbree.cmake @@ -0,0 +1,25 @@ +# FERS Embree CPU ray-tracing backend. +# +# Included (once) from packages/libfers/src/CMakeLists.txt when +# FERS_ENABLE_EMBREE is ON. Embree is sourced via vcpkg's "embree" manifest +# feature (see vcpkg.json) and exports its own CMake config package, so no +# custom Find module is needed (unlike OptiX). +# +# Sets, in the caller's scope: +# FERS_EMBREE_LINK_LIBRARIES - libraries to add to FERS_NATIVE_LINK_DEPENDENCIES + +find_package(embree CONFIG REQUIRED) + +set(_fers_embree_dir ${CMAKE_CURRENT_SOURCE_DIR}/src/propagation/raytracing/embree) + +set(FERS_EMBREE_PRIVATE_HEADERS + ${_fers_embree_dir}/kernel.h + ${_fers_embree_dir}/embree.h + ${_fers_embree_dir}/utils.h +) +set(FERS_EMBREE_SOURCES + ${_fers_embree_dir}/kernel.cpp + ${_fers_embree_dir}/embree.cpp +) + +set(FERS_EMBREE_LINK_LIBRARIES unofficial::embree::embree) diff --git a/cmake/FersOptiX.cmake b/cmake/FersOptiX.cmake new file mode 100644 index 00000000..3894702f --- /dev/null +++ b/cmake/FersOptiX.cmake @@ -0,0 +1,88 @@ +# FERS OptiX GPU ray-tracing backend. +# +# Included (once) from packages/libfers/src/CMakeLists.txt when +# FERS_ENABLE_OPTIX is ON. Locates OptiX + CUDA, compiles kernel.cu to PTX with +# nvcc, and embeds the PTX into a generated header (via FersEmbedFile.cmake, +# the same tool used for the XML schema headers) so optix.cpp can hand the +# module bytes straight to optixModuleCreate() without FERS needing to locate +# a loose .ptx file next to the binary at runtime. +# +# Expects GENERATED_HEADERS_DIR to already be set by the including +# CMakeLists.txt. Sets, in the caller's scope: +# FERS_OPTIX_SOURCES - .cpp files to add to LIBFERS_SOURCES +# FERS_OPTIX_PRIVATE_HEADERS - headers to add to LIBFERS_PRIVATE_HEADERS +# FERS_OPTIX_LINK_LIBRARIES - libraries to add to FERS_NATIVE_LINK_DEPENDENCIES +# FERS_OPTIX_GENERATE_TARGET - target the library target must add_dependencies() on + +# Find OptiX with cmake/FindOptiX.cmake +find_package(OptiX REQUIRED) +# Find the CUDA toolkit so we can compile device programs to PTX. +find_package(CUDAToolkit REQUIRED) + +set(_fers_optix_dir ${CMAKE_CURRENT_SOURCE_DIR}/src/propagation/raytracing/optix) + +set(FERS_OPTIX_PRIVATE_HEADERS + ${_fers_optix_dir}/optix.h +) +set(FERS_OPTIX_SOURCES + ${_fers_optix_dir}/optix.cpp +) +set(FERS_OPTIX_LINK_LIBRARIES + OptiX::OptiX + CUDA::cuda_driver + CUDA::cudart +) + +# -------------------- PTX Generation -------------------- # + +set(_fers_optix_kernel_cu ${_fers_optix_dir}/kernel.cu) +set(_fers_optix_kernel_ptx ${CMAKE_CURRENT_BINARY_DIR}/optix_ptx/kernel.ptx) +set(_fers_optix_kernel_ptx_header ${GENERATED_HEADERS_DIR}/kernel_ptx.h) + +add_custom_command( + OUTPUT ${_fers_optix_kernel_ptx} + COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/optix_ptx + COMMAND "${CUDAToolkit_NVCC_EXECUTABLE}" + --ptx + --expt-relaxed-constexpr + -lineinfo + -std=c++${CMAKE_CXX_STANDARD} + -Xcompiler -Wall,-Wextra,-Wshadow,-Wconversion,-Wsign-conversion,-Wold-style-cast + --Wreorder --Wext-lambda-captures-this --Wdefault-stream-launch + -I "${OptiX_INCLUDE_DIR}" + -I "${_fers_optix_dir}" + -I "${CMAKE_CURRENT_SOURCE_DIR}/src" + -o "${_fers_optix_kernel_ptx}" + "${_fers_optix_kernel_cu}" + DEPENDS + ${_fers_optix_kernel_cu} + ${_fers_optix_dir}/kernel_defs.h + ${_fers_optix_dir}/utils.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/propagation/raytracing/sbr_shared.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/propagation/raytracing/sbr_impl.h + ${CMAKE_CURRENT_SOURCE_DIR}/src/propagation/math.h + COMMENT "Compiling kernel.cu to PTX with nvcc" + VERBATIM +) + +add_custom_command( + OUTPUT ${_fers_optix_kernel_ptx_header} + COMMAND ${CMAKE_COMMAND} + "-DINPUT=${_fers_optix_kernel_ptx}" + "-DOUTPUT=${_fers_optix_kernel_ptx_header}" + -DVAR=devicePrograms_ptx + -P "${CMAKE_SOURCE_DIR}/cmake/FersEmbedFile.cmake" + DEPENDS ${_fers_optix_kernel_ptx} "${CMAKE_SOURCE_DIR}/cmake/FersEmbedFile.cmake" + COMMENT "Embedding kernel.ptx into kernel_ptx.h" +) + +# ALL so that a plain `cmake --build` regenerates it even before anything +# depends on it yet (matches generate_schemas' behaviour below in the parent +# CMakeLists.txt) - useful for e.g. warming up an IDE's build directory. +add_custom_target(generate_optix_ptx ALL DEPENDS ${_fers_optix_kernel_ptx_header}) +set(FERS_OPTIX_GENERATE_TARGET generate_optix_ptx) + +unset(_fers_optix_dir) +unset(_fers_optix_kernel_cu) +unset(_fers_optix_kernel_ptx) +unset(_fers_optix_kernel_ptx_header) diff --git a/cmake/FersOptions.cmake b/cmake/FersOptions.cmake index f32fcbde..69d72b8c 100644 --- a/cmake/FersOptions.cmake +++ b/cmake/FersOptions.cmake @@ -21,6 +21,8 @@ option(FERS_BUILD_DOCS "Enable building Doxygen documentation" OFF) option(FERS_DOCS_ONLY "Configure only for documentation generation" OFF) option(FERS_BUILD_TESTS "Build unit tests" OFF) option(FERS_ENABLE_COVERAGE "Enable code coverage generation" OFF) +option(FERS_ENABLE_OPTIX "Enable the OptiX GPU ray tracing backend (requires the OptiX SDK and CUDA)" OFF) +option(FERS_ENABLE_EMBREE "Enable the Embree CPU ray tracing backend (sourced via vcpkg)" OFF) # Ensure at least one library type is selected. if (NOT FERS_BUILD_SHARED_LIBS AND NOT FERS_BUILD_STATIC_LIBS) diff --git a/cmake/FindOptiX.cmake b/cmake/FindOptiX.cmake new file mode 100644 index 00000000..cd53bd00 --- /dev/null +++ b/cmake/FindOptiX.cmake @@ -0,0 +1,24 @@ +# Searches OPTIX_ROOT, environment variable, and default install locations +find_path(OptiX_INCLUDE_DIR + NAMES optix.h + PATHS + ${OPTIX_ROOT} + $ENV{OPTIX_ROOT} + /opt/optix + /opt/nvidia/optix + "C:/ProgramData/NVIDIA Corporation/OptiX SDK 9.1.0" + "C:/ProgramData/NVIDIA Corporation/OptiX SDK 8.0.0" + "C:/ProgramData/NVIDIA Corporation/OptiX SDK 7.7.0" + PATH_SUFFIXES include +) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(OptiX + REQUIRED_VARS OptiX_INCLUDE_DIR + VERSION_VAR OptiX_VERSION +) + +if(OptiX_FOUND AND NOT TARGET OptiX::OptiX) + add_library(OptiX::OptiX INTERFACE IMPORTED) + target_include_directories(OptiX::OptiX INTERFACE ${OptiX_INCLUDE_DIR}) +endif() diff --git a/docs/wiki/FERS-CLI.md b/docs/wiki/FERS-CLI.md index 049cfec1..3dcc1997 100644 --- a/docs/wiki/FERS-CLI.md +++ b/docs/wiki/FERS-CLI.md @@ -31,6 +31,7 @@ Windows: | `--vita49-queue-depth ` | Optional VITA sender queue depth. Must be greater than zero. Default is `1024`. | | `--log-level=` | Set logging detail. Use `TRACE`, `DEBUG`, `INFO`, `WARNING`, `ERROR`, or `FATAL`. | | `--log-file=` | Write logs to a `.log` or `.txt` file as well as the terminal. | +| `--rt-engine=` | Force the ray-tracing engine for the SBR propagation model: `auto`, `optix`, or `embree` (case-insensitive). Defaults to `auto`. | | `-n=` | Choose how many worker threads to use. | If `--out-dir` is not supplied, results are written beside the scenario file. @@ -84,6 +85,16 @@ Tune VITA UDP packet sizing and queue depth: For the packet format, metadata fields, counters, and source-backed implementation contract, see [[VITA49 Streaming Implementation]]. +## Choosing a Ray-Tracing Engine + +SBR scenarios (`` in the `.fersxml`) auto-detect between OptiX and Embree by default, preferring OptiX. Force a specific engine with `--rt-engine=`: + +```bash +./build/release/packages/fers-cli/fers-cli scenario.fersxml --rt-engine=embree +``` + +Accepted values are `auto`, `optix`, and `embree`, matched case-insensitively. Requesting an engine that wasn't compiled into this build fails with an error when the simulation starts. + ## Choosing Thread Count Use `-n=` to control the worker pool: diff --git a/docs/wiki/Simulation-Pipelines.md b/docs/wiki/Simulation-Pipelines.md index a669c2d0..e9de63e4 100644 --- a/docs/wiki/Simulation-Pipelines.md +++ b/docs/wiki/Simulation-Pipelines.md @@ -182,6 +182,7 @@ Important CW settings: | Antenna patterns and platform rotation | Directional gain over time. | | `nodirect` | Removes direct transmitter-to-receiver path. | | `nopropagationloss` | Removes path-loss scaling for debugging. | +| `` | The CW analogue of ``. Sets the propagation/geometry sampling rate. | ## FMCW Pipeline Without Built-In Dechirp diff --git a/docs/wiki/Using-FERS.md b/docs/wiki/Using-FERS.md index 91671cd2..3b8cebdf 100644 --- a/docs/wiki/Using-FERS.md +++ b/docs/wiki/Using-FERS.md @@ -126,6 +126,7 @@ The most important rates are: | --- | --- | | `` | Base output sample rate in Hz. Raw CW/FMCW/SFCW streaming output is written at this rate. | | `` | Rate used for geometry interpolation in pulsed response generation. | +| `` | Rate used for geometry interpolation in CW-family sampling. Disabled by default; propagation modelled at ` * `. | | `` | Internal oversampling multiplier. FMCW aliasing checks and legacy full-rate dechirped IF output use ` * `. | | `` | Optional receiver-local FMCW IF output rate after dechirping and resampling. | diff --git a/docs/wiki/XML-Schema-Reference.md b/docs/wiki/XML-Schema-Reference.md index 6371eb54..09b7681f 100644 --- a/docs/wiki/XML-Schema-Reference.md +++ b/docs/wiki/XML-Schema-Reference.md @@ -68,12 +68,14 @@ Optional children, in order: | --- | --- | --- | --- | | `` | m/s | `299792458` | Propagation speed. Use the default for free-space radio propagation. | | `` | Hz | `1000` | Geometry interpolation rate used while building pulsed responses. Increase it when fast platform motion needs finer geometry sampling. | +| `` | Hz | disabled | Geometry interpolation rate used while simulating CW/streaming propagation, instead of every output sample (default). Increase for past motion, decrease for expensive propagation modelling, e.g. high density ray tracing. | | `` | integer | random | Seed for repeatable noise and random models. Set this when you want repeatable runs. | | `` | bits | `0` | ADC quantization depth. `0` disables quantization and stores normalized floating-point samples. | | `` | multiplier | `1` | Internal oversampling factor. Valid range is `1` to `8`. FMCW waveform aliasing checks use ` * `. Most final output is still written at ``, but legacy dechirped FMCW output without `` is written at ` * `. | | `` | `deg` or `rad` | `deg` | Unit used by platform rotation values. | | `` | attributes | built-in KML default | Geodetic reference used only by ENU KML/geospatial export. It does not affect signal simulation geometry. | | `` | attributes | `ENU` | Coordinate frame used when converting platform coordinates to geodetic coordinates for KML/geospatial export. It does not transform positions during simulation. | +| `` | `` or `` sub-element | `pointscatter` model | Selects the propagation model and its model-specific parameters. | ### `` @@ -119,6 +121,46 @@ Frames: FERS does not accept platform latitude/longitude waypoints. Latitude and longitude appear only in ``, where they define the geodetic reference used by ENU KML export. +### `` + +`` selects the propagation model as a choice of exactly one sub-element: ``, carrying its polarimetric-transport flag, or ``, carrying its ray-sampling parameters. Omitting `` entirely defaults to `pointscatter` with polarimetric transport disabled. + +```xml + + + +``` + +```xml + + + +``` + +`` attributes: + +| Attribute | Required | Unit | Default | Meaning | +| --- | --- | --- | --- | --- | +| `polarimetric` | No | boolean | `false` | Enables full polarimetric transport: antenna Jones-vector polarisation coupling and target scattering matrices on reflected paths. When `false`, direct and reflected paths compute scalar amplitudes. | + +`` attributes: + +| Attribute | Required | Unit | Default | Meaning | +| --- | --- | --- | --- | --- | +| `scatter_limit` | No | integer bounces | `4` | Maximum number of GO bounces per ray path (max ray depth is `scatter_limit + 1`). | +| `boresight_width` | No | `` | `0.3` rad (~17.2°) | Full angular width of the ray-sampling cone centered on each source antenna's boresight. The cone's half-angle is `boresight_width / 2`. Must be less than a full turn (360°/2π rad). | +| `boresight_tube_solid_angle` | No | steradians | `2e-8` | Target ray-tube solid angle for rays sampled within the boresight cone. | +| `off_boresight_tube_solid_angle` | No | steradians | `1e-5` | Target ray-tube solid angle for rays sampled outside the boresight cone. | +| `prune_below_max_db` | No | dB (power, positive magnitude) | unset (no pruning) | For each receiver at a given time, drops paths more than this many dB weaker than that group's strongest path, for performance. No pruning if disabled. | + +`rays_per_source` and the cone's ray count are not set directly - they're derived from `boresight_width` and the two tube-solid-angle attributes and logged at the start of the simulation. + +`prune_below_max_db` culls paths to a receiver below the maximum dB path. It's generally safe to set this to something like "60" depending on your SNR. Dropped paths are individually below the kept power by at least that many dB, but enough of them summing in phase could in principle raise the dropped total above the per-path floor. Pick a threshold with enough margin over your expected noise floor/dynamic range that this isn't a concern for your scenario. + +Each ray tube's footprint on a hit facet is approximately `R^2 * S / cos(theta)`, where `R` is the range to the facet, `S` is the tube's solid angle, and `theta` is the incidence angle off the facet normal. A tube whose footprint exceeds a facet's area does not fully resolve that facet. To choose `boresight_tube_solid_angle` or `off_boresight_tube_solid_angle`, pick the smallest facet area `A_facet` that regime needs to resolve, and the shortest range and most oblique incidence angle targets in that regime will be seen at, then solve for the tube solid angle: `S <= A_facet * cos(theta) / R^2`. A smaller `S` resolves finer detail at the cost of more rays (and compute). + ## `` A waveform defines what a transmitter emits. @@ -369,6 +411,27 @@ Optional child parameters: | `` | `gaussian` | Azimuth spread for Gaussian pattern. | | `` | `gaussian` | Elevation spread for Gaussian pattern. | | `` | `isotropic`, `sinc`, `squarehorn`, `parabolic`, `xml`, `file` | Linear gain multiplier applied to the computed antenna gain. Defaults to `1`. | +| `` | all | Boresight polarisation Jones vector (Ludwig-3). See below. Defaults to purely horizontal if omitted. | + +### `` + +```xml + + + +``` + +| Attribute | Required | Meaning | +| --- | --- | --- | +| `h` | No | Horizontal component of the Jones vector, as a complex number literal. Defaults to `0+0i`. | +| `v` | No | Vertical component of the Jones vector, as a complex number literal. Defaults to `0+0i`. | + +The vector is normalised on load, so only the ratio between `h` and `v` matters. + +A complex number literal is a real term, an imaginary term, or both (real first). An +imaginary term's unit marker (`i` or `j`, case-insensitive) may come before or after its +magnitude, and whitespace around signs and units is ignored. For example, all of +`1.0+0.0i`, `1.0 + j0.5`, `-0.5 - i2`, `0.5j`, and `1e-3+2e-4i` are valid. Pattern notes: @@ -484,7 +547,7 @@ Contains one or more `` elements. | Element | Unit | Meaning | | --- | --- | --- | -| `` | `` | Platform azimuth. | +| `` | `` | Platform compass azimuth (left-handed angle from north). | | `` | `` | Platform elevation. | | `