cmake_minimum_required(VERSION 3.24)

# Keep pyproject.toml as the single release-version source for both the
# Python distribution and the native extension.
file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/pyproject.toml"
    LESS_PYPROJECT_VERSION_LINE
    REGEX "^version = \"[0-9]+\\.[0-9]+\\.[0-9]+([A-Za-z]+[0-9]+)?\"$")
string(REGEX REPLACE "^version = \"([^\"]+)\"$" "\\1"
    LESS_RELEASE_VERSION "${LESS_PYPROJECT_VERSION_LINE}")
string(REGEX MATCH "^[0-9]+\\.[0-9]+\\.[0-9]+"
    LESS_PROJECT_VERSION "${LESS_RELEASE_VERSION}")
if(NOT LESS_RELEASE_VERSION OR NOT LESS_PROJECT_VERSION)
    message(FATAL_ERROR "Could not read project version from pyproject.toml")
endif()
project(LESS_GPU VERSION ${LESS_PROJECT_VERSION} LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Required on Linux: static libs linked into a shared .so must be PIC
set(CMAKE_POSITION_INDEPENDENT_CODE ON)

# ---- Options ----
option(LESS_ENABLE_OPTIX "Build NVIDIA OptiX/CUDA backend" ON)
option(LESS_ENABLE_EMBREE "Build Intel Embree CPU backend" ON)
option(LESS_ENABLE_VULKAN "Build Vulkan Ray Query backend" OFF)
option(LESS_USE_PRECOMPILED_SPV
    "Package checked-in SPIR-V shaders instead of invoking slangc" OFF)
set(OPTIX_ROOT "" CACHE PATH "Path to OptiX SDK (e.g. C:/ProgramData/NVIDIA Corporation/OptiX SDK 8.1.0)")

if(LESS_ENABLE_VULKAN)
    find_package(Vulkan 1.2 REQUIRED)
    message(STATUS "Vulkan headers: ${Vulkan_INCLUDE_DIRS}")
    message(STATUS "Vulkan library: ${Vulkan_LIBRARIES}")
endif()

if(LESS_ENABLE_OPTIX)
    enable_language(CUDA)
    set(CMAKE_CUDA_STANDARD 17)
    set(CMAKE_CUDA_STANDARD_REQUIRED ON)

    # ---- Find CUDA + OptiX ----
    find_package(CUDAToolkit REQUIRED)
    message(STATUS "CUDA version: ${CUDAToolkit_VERSION}")
    message(STATUS "CUDA include: ${CUDAToolkit_INCLUDE_DIRS}")
    include(cmake/FindOptiX.cmake)
endif()

# ---- Find pybind11 ----
find_package(pybind11 CONFIG QUIET)
if(NOT pybind11_FOUND)
    # Try to find pybind11 via pip installation
    execute_process(
        COMMAND python -m pybind11 --cmakedir
        OUTPUT_VARIABLE pybind11_DIR
        OUTPUT_STRIP_TRAILING_WHITESPACE
        ERROR_QUIET
    )
    if(pybind11_DIR)
        find_package(pybind11 CONFIG REQUIRED PATHS ${pybind11_DIR})
    else()
        message(FATAL_ERROR "pybind11 not found. Install with: pip install pybind11")
    endif()
endif()
message(STATUS "pybind11 version: ${pybind11_VERSION}")
# ---- Include directories ----
set(LESS_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include)
include(cmake/LESSRuntime.cmake)
include(cmake/LESSModels.cmake)
add_subdirectory(src/surfaces)
less_generate_surface_dispatch(
    OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated/surfaces")
add_subdirectory(src/media)
less_generate_phase_registry(
    OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated/phases")
less_generate_medium_registry(
    OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated/media")
add_subdirectory(src/illumination)
less_generate_illumination_registry(
    OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated/illumination")
add_subdirectory(src/sensors)
less_generate_sensor_registry(
    OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated/sensors")
add_subdirectory(src/integrators)
less_generate_integrator_registry(
    OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated/integrators")
add_subdirectory(src/products)
less_generate_product_registry(
    OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated/products")
add_subdirectory(src/processes)
less_generate_process_registry(
    OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated/processes")
less_configure_runtime_dependencies()
# ---- Compiler flags ----
include(cmake/CompilerFlags.cmake)

set(PTX_TARGETS "")
set(OPTIXIR_TARGETS "")
set(LESS_PHYSIOLOGY_CONTRACT
    "${CMAKE_CURRENT_SOURCE_DIR}/src/processes/physiology/farquhar/farquhar.h")
set(LESS_PHOTOSYNTHESIS_PROCESS_CONTRACT
    "${CMAKE_CURRENT_SOURCE_DIR}/src/processes/photosynthesis/photosynthesis.h")
set(LESS_PROCESS_CONTRACTS
    "${CMAKE_CURRENT_SOURCE_DIR}/src/processes/energy_balance/process.h" "${CMAKE_CURRENT_SOURCE_DIR}/src/processes/photosynthesis/process.h"
    "${CMAKE_CURRENT_SOURCE_DIR}/src/processes/physiology/process.h" "${CMAKE_CURRENT_SOURCE_DIR}/src/processes/sif/process.h")
set(LESS_TURBID_CONTRACTS
    "${CMAKE_CURRENT_SOURCE_DIR}/src/media/turbid/domain.h"
    "${CMAKE_CURRENT_SOURCE_DIR}/src/media/turbid/sampling.h"
    "${CMAKE_CURRENT_SOURCE_DIR}/src/media/turbid/state.h"
    "${CMAKE_CURRENT_SOURCE_DIR}/src/media/turbid/fields.h"
    "${CMAKE_CURRENT_SOURCE_DIR}/src/media/turbid/aggregation.h")
set(LESS_TURBID_SOBOL
    "${CMAKE_CURRENT_SOURCE_DIR}/src/media/turbid/sobol.h")
set(LESS_TURBID_ARTIFACT_ABI
    "${CMAKE_CURRENT_SOURCE_DIR}/src/media/turbid/sampling_layout.h"
    "${CMAKE_CURRENT_SOURCE_DIR}/src/media/turbid/sampling_layout.def")
set(LESS_TURBID_DEBUG_HOST
    "${CMAKE_CURRENT_SOURCE_DIR}/src/media/turbid/host/sampling.h")
set(LESS_TURBID_DOMAIN_STORAGE
    "${CMAKE_CURRENT_SOURCE_DIR}/src/media/turbid/host/domain.h")
set(LESS_TURBID_STATE_STORAGE
    "${CMAKE_CURRENT_SOURCE_DIR}/src/media/turbid/host/state.h")
set(LESS_TURBID_FIELD_STORAGE
    "${CMAKE_CURRENT_SOURCE_DIR}/src/media/turbid/host/fields.h")
set(LESS_TURBID_FIELD_DEVICE
    "${CMAKE_CURRENT_SOURCE_DIR}/src/media/turbid/field_device.h")
set(LESS_TURBID_PROCESS_CONTRACT
    "${CMAKE_CURRENT_SOURCE_DIR}/src/processes/energy_balance/turbid.h;${CMAKE_CURRENT_SOURCE_DIR}/src/processes/radiation_field/radiation_field.h;${CMAKE_CURRENT_SOURCE_DIR}/src/processes/photosynthesis/turbid.h")
set(LESS_TURBID_RADIATION_STORAGE
    "${CMAKE_CURRENT_SOURCE_DIR}/src/processes/radiation_field/storage.h")
set(LESS_TURBID_PHYSIOLOGY_STORAGE
    "${CMAKE_CURRENT_SOURCE_DIR}/src/processes/photosynthesis/turbid_storage.h")
set(LESS_TURBID_ENERGY_BALANCE_STORAGE
    "${CMAKE_CURRENT_SOURCE_DIR}/src/processes/energy_balance/turbid_storage.h")
set(LESS_OPTIX_MANAGER_LAYOUT
    "${CMAKE_CURRENT_SOURCE_DIR}/src/core/scene/scene_manager.h"
    "${LESS_TURBID_DOMAIN_STORAGE}"
    "${LESS_TURBID_STATE_STORAGE}"
    "${LESS_TURBID_FIELD_STORAGE}"
    "${LESS_TURBID_RADIATION_STORAGE}"
    "${LESS_TURBID_PHYSIOLOGY_STORAGE}"
    "${LESS_TURBID_ENERGY_BALANCE_STORAGE}"
    ${LESS_TURBID_CONTRACTS})
set(LESS_EMBREE_MANAGER_LAYOUT
    "${CMAKE_CURRENT_SOURCE_DIR}/src/backends/embree/embree_backend.h"
    "${LESS_TURBID_DOMAIN_STORAGE}"
    "${LESS_TURBID_STATE_STORAGE}"
    "${LESS_TURBID_FIELD_STORAGE}"
    "${LESS_TURBID_RADIATION_STORAGE}"
    "${LESS_TURBID_PHYSIOLOGY_STORAGE}"
    "${LESS_TURBID_ENERGY_BALANCE_STORAGE}"
    ${LESS_TURBID_CONTRACTS})
set(LESS_VULKAN_MANAGER_LAYOUT
    "${CMAKE_CURRENT_SOURCE_DIR}/src/backends/vulkan/vulkan_scene.h"
    "${LESS_TURBID_DOMAIN_STORAGE}"
    "${LESS_TURBID_STATE_STORAGE}"
    "${LESS_TURBID_FIELD_STORAGE}"
    "${LESS_TURBID_RADIATION_STORAGE}"
    "${LESS_TURBID_PHYSIOLOGY_STORAGE}"
    "${LESS_TURBID_ENERGY_BALANCE_STORAGE}"
    ${LESS_TURBID_CONTRACTS})

# Native contract tests are explicit build targets so wheel builds stay lean.
include(CTest)
if(BUILD_TESTING)
    less_add_runtime_native_tests()
    less_add_surface_native_tests()
    less_add_phase_native_tests()
    less_add_medium_native_tests()
    less_add_sensor_native_tests()
    less_add_integrator_native_tests()
    less_add_process_native_tests()


endif()

if(LESS_ENABLE_OPTIX)
# ---- OpenMP (used to parallelise per-primitive area scan in scene_manager) ----
find_package(OpenMP)
if(NOT OpenMP_CXX_FOUND)
    message(WARNING "OpenMP not found — populate_primitive_areas_mat_ids "
                    "will fall back to a serial loop. Build will work but "
                    "EB/process scene setup is single-threaded.")
endif()

# ---- OptiX host library (C++ host code) ----
add_library(less_optix STATIC
    src/core/scene/scene_manager.cpp
    src/core/mesh/mesh_loader.cpp
    src/core/simulation/simulator.cpp
    src/media/turbid/optix/state.cu
    src/media/turbid/optix/fields.cu
    src/backends/optix/host/optix_context.cpp
    ${less_runtime_optix_library_sources}
    ${less_integrator_optix_sources}
    ${less_product_optix_sources}
    ${less_process_optix_sources}
    ${less_illumination_optix_library_sources}
    ${less_sensor_optix_library_sources}
    ${less_medium_host_sources}
)
set(LESS_OPTIX_SIF_PROCESS_SOURCES
    "${CMAKE_CURRENT_SOURCE_DIR}/src/processes/sif/optix/sif_process.cu"
    ${LESS_PROCESS_CONTRACTS}
    "${less_runtime_field_program_contract}")
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS
    ${LESS_OPTIX_SIF_PROCESS_SOURCES})
set(LESS_OPTIX_SIF_PROCESS_DIGEST_INPUT "")
foreach(LESS_FIELD_SOURCE IN LISTS LESS_OPTIX_SIF_PROCESS_SOURCES)
    file(SHA256 "${LESS_FIELD_SOURCE}" LESS_FIELD_SOURCE_SHA256)
    string(APPEND LESS_OPTIX_SIF_PROCESS_DIGEST_INPUT
        "${LESS_FIELD_SOURCE_SHA256}")
endforeach()
string(SHA256 LESS_OPTIX_SIF_PROCESS_SOURCE_SHA256
    "${LESS_OPTIX_SIF_PROCESS_DIGEST_INPUT}")
target_compile_definitions(less_optix PUBLIC
    LESS_OPTIX_SIF_PROCESS_SOURCE_SHA256="${LESS_OPTIX_SIF_PROCESS_SOURCE_SHA256}")
set(LESS_OPTIX_PHOTOSYNTHESIS_PROCESS_SOURCES
    "${CMAKE_CURRENT_SOURCE_DIR}/src/processes/thermo_physiology/optix/processes.cu"
    ${LESS_PROCESS_CONTRACTS}
    "${less_runtime_field_program_contract}")
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS
    ${LESS_OPTIX_PHOTOSYNTHESIS_PROCESS_SOURCES})
set(LESS_OPTIX_PHOTOSYNTHESIS_PROCESS_DIGEST_INPUT "")
foreach(LESS_FIELD_SOURCE IN LISTS LESS_OPTIX_PHOTOSYNTHESIS_PROCESS_SOURCES)
    file(SHA256 "${LESS_FIELD_SOURCE}" LESS_FIELD_SOURCE_SHA256)
    string(APPEND LESS_OPTIX_PHOTOSYNTHESIS_PROCESS_DIGEST_INPUT
        "${LESS_FIELD_SOURCE_SHA256}")
endforeach()
string(SHA256 LESS_OPTIX_PHOTOSYNTHESIS_PROCESS_SOURCE_SHA256
    "${LESS_OPTIX_PHOTOSYNTHESIS_PROCESS_DIGEST_INPUT}")
target_compile_definitions(less_optix PUBLIC
    LESS_OPTIX_PHOTOSYNTHESIS_PROCESS_SOURCE_SHA256="${LESS_OPTIX_PHOTOSYNTHESIS_PROCESS_SOURCE_SHA256}")
set(LESS_OPTIX_ENERGY_BALANCE_PROCESS_SOURCES
    "${CMAKE_CURRENT_SOURCE_DIR}/src/processes/thermo_physiology/optix/processes.cu"
    "${CMAKE_CURRENT_SOURCE_DIR}/src/integrators/energy_balance_radiation/optix/raygen_eb_rrt.cu"
    "${CMAKE_CURRENT_SOURCE_DIR}/src/core/simulation/simulator.cpp"
    "${CMAKE_CURRENT_SOURCE_DIR}/src/processes/energy_balance/energy_balance_process.h"
    "${less_runtime_field_program_contract}")
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS
    ${LESS_OPTIX_ENERGY_BALANCE_PROCESS_SOURCES})
set(LESS_OPTIX_ENERGY_BALANCE_PROCESS_DIGEST_INPUT "")
foreach(LESS_FIELD_SOURCE IN LISTS LESS_OPTIX_ENERGY_BALANCE_PROCESS_SOURCES)
    file(SHA256 "${LESS_FIELD_SOURCE}" LESS_FIELD_SOURCE_SHA256)
    string(APPEND LESS_OPTIX_ENERGY_BALANCE_PROCESS_DIGEST_INPUT
        "${LESS_FIELD_SOURCE_SHA256}")
endforeach()
string(SHA256 LESS_OPTIX_ENERGY_BALANCE_PROCESS_SOURCE_SHA256
    "${LESS_OPTIX_ENERGY_BALANCE_PROCESS_DIGEST_INPUT}")
target_compile_definitions(less_optix PUBLIC
    LESS_OPTIX_ENERGY_BALANCE_PROCESS_SOURCE_SHA256="${LESS_OPTIX_ENERGY_BALANCE_PROCESS_SOURCE_SHA256}")
set(LESS_OPTIX_JOINT_THERMO_PHYSIOLOGY_SOURCES
    "${CMAKE_CURRENT_SOURCE_DIR}/src/processes/thermo_physiology/optix/joint_thermo_physiology.cu"
    "${CMAKE_CURRENT_SOURCE_DIR}/src/processes/thermo_physiology/optix/joint_thermo_physiology.cpp"
    "${CMAKE_CURRENT_SOURCE_DIR}/src/processes/thermo_physiology/joint_thermo_physiology.h"
    "${less_runtime_field_program_contract}")
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS
    ${LESS_OPTIX_JOINT_THERMO_PHYSIOLOGY_SOURCES})
set(LESS_OPTIX_JOINT_THERMO_PHYSIOLOGY_DIGEST_INPUT "")
foreach(LESS_FIELD_SOURCE IN LISTS LESS_OPTIX_JOINT_THERMO_PHYSIOLOGY_SOURCES)
    file(SHA256 "${LESS_FIELD_SOURCE}" LESS_FIELD_SOURCE_SHA256)
    string(APPEND LESS_OPTIX_JOINT_THERMO_PHYSIOLOGY_DIGEST_INPUT
        "${LESS_FIELD_SOURCE_SHA256}")
endforeach()
string(SHA256 LESS_OPTIX_JOINT_THERMO_PHYSIOLOGY_SOURCE_SHA256
    "${LESS_OPTIX_JOINT_THERMO_PHYSIOLOGY_DIGEST_INPUT}")
target_compile_definitions(less_optix PUBLIC
    LESS_OPTIX_JOINT_THERMO_PHYSIOLOGY_SOURCE_SHA256="${LESS_OPTIX_JOINT_THERMO_PHYSIOLOGY_SOURCE_SHA256}")
set(LESS_OPTIX_TURBID_PROCESS_SOURCES
    "${CMAKE_CURRENT_SOURCE_DIR}/src/processes/thermo_physiology/optix/turbid_processes.cpp"
    "${CMAKE_CURRENT_SOURCE_DIR}/src/core/simulation/simulator.cpp"
    "${CMAKE_CURRENT_SOURCE_DIR}/src/integrators/energy_balance_radiation/optix/raygen_eb_rrt.cu"
    "${CMAKE_CURRENT_SOURCE_DIR}/src/processes/photosynthesis/optix.cu"
    "${CMAKE_CURRENT_SOURCE_DIR}/src/processes/energy_balance/optix.cu"
    "${less_runtime_field_contract}"
    "${less_runtime_field_program_contract}"
    ${LESS_PROCESS_CONTRACTS})
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS
    ${LESS_OPTIX_TURBID_PROCESS_SOURCES})
set(LESS_OPTIX_TURBID_PROCESS_DIGEST_INPUT "")
foreach(LESS_FIELD_SOURCE IN LISTS LESS_OPTIX_TURBID_PROCESS_SOURCES)
    file(SHA256 "${LESS_FIELD_SOURCE}" LESS_FIELD_SOURCE_SHA256)
    string(APPEND LESS_OPTIX_TURBID_PROCESS_DIGEST_INPUT
        "${LESS_FIELD_SOURCE_SHA256}")
endforeach()
string(SHA256 LESS_OPTIX_TURBID_PROCESS_SOURCE_SHA256
    "${LESS_OPTIX_TURBID_PROCESS_DIGEST_INPUT}")
target_compile_definitions(less_optix PUBLIC
    LESS_OPTIX_TURBID_PROCESS_SOURCE_SHA256="${LESS_OPTIX_TURBID_PROCESS_SOURCE_SHA256}")
set_property(SOURCE src/core/scene/scene_manager.cpp APPEND PROPERTY
    OBJECT_DEPENDS
        "${LESS_OPTIX_MANAGER_LAYOUT};${LESS_TURBID_DEBUG_HOST}")
set_property(SOURCE src/core/simulation/simulator.cpp APPEND PROPERTY
    OBJECT_DEPENDS "${LESS_OPTIX_MANAGER_LAYOUT}")
set_source_files_properties(
    ${less_runtime_turbid_sampling_optix_sources}
    PROPERTIES OBJECT_DEPENDS
        "${LESS_TURBID_CONTRACTS};${LESS_TURBID_SOBOL};${LESS_TURBID_ARTIFACT_ABI};${LESS_TURBID_DEBUG_HOST};${CMAKE_CURRENT_SOURCE_DIR}/src/media/turbid/optix/sampling.h")
set_source_files_properties(
    src/processes/thermo_physiology/optix/turbid_processes.cpp
    PROPERTIES OBJECT_DEPENDS
        "${LESS_TURBID_PROCESS_CONTRACT};${LESS_TURBID_RADIATION_STORAGE};${LESS_TURBID_PHYSIOLOGY_STORAGE};${LESS_TURBID_ENERGY_BALANCE_STORAGE};${LESS_OPTIX_MANAGER_LAYOUT};${CMAKE_CURRENT_SOURCE_DIR}/src/processes/thermo_physiology/optix/turbid_processes.h;${CMAKE_CURRENT_SOURCE_DIR}/src/core/simulation/simulator.h")
set_source_files_properties(
    src/processes/thermo_physiology/optix/processes.cu
    PROPERTIES OBJECT_DEPENDS
        "${LESS_PHYSIOLOGY_CONTRACT};${LESS_PHOTOSYNTHESIS_PROCESS_CONTRACT};${LESS_PROCESS_CONTRACTS}")
set_source_files_properties(
    src/processes/sif/optix/sif_process.cu
    PROPERTIES OBJECT_DEPENDS "${LESS_PROCESS_CONTRACTS}")
set_source_files_properties(
    src/processes/sif/optix/sif_source_assembly.cu
    PROPERTIES OBJECT_DEPENDS
        "${CMAKE_CURRENT_SOURCE_DIR}/src/processes/sif/source_assembly.h;${CMAKE_CURRENT_SOURCE_DIR}/include/less/core/grid_mapping.h;${LESS_PROCESS_CONTRACTS}")
set_source_files_properties(
    src/media/turbid/optix/state.cu
    PROPERTIES OBJECT_DEPENDS
        "${LESS_TURBID_STATE_STORAGE};${LESS_TURBID_DOMAIN_STORAGE};${LESS_TURBID_CONTRACTS};${CMAKE_CURRENT_SOURCE_DIR}/src/media/turbid/optix/state.h")
set_source_files_properties(
    src/media/turbid/optix/fields.cu
    PROPERTIES OBJECT_DEPENDS
        "${LESS_TURBID_FIELD_DEVICE};${LESS_TURBID_FIELD_STORAGE};${LESS_TURBID_STATE_STORAGE};${LESS_TURBID_DOMAIN_STORAGE};${LESS_TURBID_CONTRACTS};${CMAKE_CURRENT_SOURCE_DIR}/src/media/turbid/optix/fields.h")
set_source_files_properties(
    src/processes/photosynthesis/optix.cu
    PROPERTIES OBJECT_DEPENDS
        "${LESS_PHYSIOLOGY_CONTRACT};${LESS_PHOTOSYNTHESIS_PROCESS_CONTRACT};${LESS_TURBID_PROCESS_CONTRACT};${LESS_TURBID_PHYSIOLOGY_STORAGE};${LESS_TURBID_STATE_STORAGE};${LESS_TURBID_DOMAIN_STORAGE};${LESS_TURBID_CONTRACTS};${CMAKE_CURRENT_SOURCE_DIR}/src/media/turbid/optix/state.h;${CMAKE_CURRENT_SOURCE_DIR}/src/processes/thermo_physiology/optix/turbid_processes.h")
set_source_files_properties(
    src/processes/energy_balance/optix.cu
    PROPERTIES OBJECT_DEPENDS
        "${LESS_PHYSIOLOGY_CONTRACT};${LESS_TURBID_PROCESS_CONTRACT};${LESS_TURBID_ENERGY_BALANCE_STORAGE};${LESS_TURBID_STATE_STORAGE};${LESS_TURBID_DOMAIN_STORAGE};${LESS_TURBID_CONTRACTS};${CMAKE_CURRENT_SOURCE_DIR}/src/media/turbid/optix/state.h;${CMAKE_CURRENT_SOURCE_DIR}/src/processes/thermo_physiology/optix/turbid_processes.h")
set_source_files_properties(
    src/processes/thermo_physiology/optix/joint_thermo_physiology.cu
    src/processes/thermo_physiology/optix/joint_thermo_physiology.cpp
    PROPERTIES OBJECT_DEPENDS
        "${CMAKE_CURRENT_SOURCE_DIR}/src/processes/thermo_physiology/joint_thermo_physiology.h;${CMAKE_CURRENT_SOURCE_DIR}/src/processes/energy_balance/joint_storage.h;${CMAKE_CURRENT_SOURCE_DIR}/src/processes/thermo_physiology/optix/joint_thermo_physiology.h;${LESS_OPTIX_MANAGER_LAYOUT};${CMAKE_CURRENT_SOURCE_DIR}/src/core/simulation/simulator.h")
target_include_directories(less_optix PUBLIC
    ${LESS_INCLUDE_DIR}
    ${less_surface_generated_include_dir}
    ${less_illumination_generated_include_dir} ${less_sensor_generated_include_dir}
    ${CMAKE_CURRENT_SOURCE_DIR}
    ${CUDAToolkit_INCLUDE_DIRS}
    ${OPTIX_INCLUDE_DIR}
    ${CMAKE_CURRENT_SOURCE_DIR}/src
)
target_link_libraries(less_optix PUBLIC CUDA::cudart_static CUDA::cuda_driver)
if(OpenMP_CXX_FOUND)
    target_link_libraries(less_optix PUBLIC OpenMP::OpenMP_CXX)
endif()

# ---- CUDA kernels (compiled to PTX via custom commands) ----
# OptiX programs must be compiled to PTX, not linked.
# We use custom commands because VS generator + CUDA_PTX_COMPILATION
# has issues with multi-arch in nvcc -ptx mode.
set(KERNEL_SOURCES
    src/integrators/backward_optical/optix/raygen_backward.cu
    src/integrators/forward_optical/optix/raygen_forward.cu
    src/integrators/forward_optical/optix/raygen_flux.cu    # IrradianceMapSensor GPU path
    src/integrators/time_resolved_active/optix/raygen_lidar.cu
    src/integrators/energy_balance_radiation/optix/raygen_eb_rrt.cu  # Phase 1.4: energy balance reverse RT
    src/backends/optix/device/optical/kernels/closesthit.cu
    src/backends/optix/device/optical/kernels/miss.cu
    src/integrators/homogeneous_water_lidar/optix/homogeneous_water_lidar.cu
    src/media/turbid/optix/sampling.cu
)

set(OPTIX_KERNEL_OUTPUT_DIR "${CMAKE_BINARY_DIR}/kernels/optix")
file(MAKE_DIRECTORY ${OPTIX_KERNEL_OUTPUT_DIR})

set(KERNEL_INCLUDE_DIRS
    -I${LESS_INCLUDE_DIR}
    -I${less_surface_generated_include_dir}
    -I${less_illumination_generated_include_dir} -I${less_sensor_generated_include_dir}
    -I${CMAKE_CURRENT_SOURCE_DIR}
    -I${OPTIX_INCLUDE_DIR}
    -I${CMAKE_CURRENT_SOURCE_DIR}/src/backends/optix/device/optical
    -I${CMAKE_CURRENT_SOURCE_DIR}/src/backends
)
set(OPTICAL_PTX_ABI_HEADERS
    "${CMAKE_CURRENT_SOURCE_DIR}/src/backends/optix/device/optical/launch_params.h"
    "${CMAKE_CURRENT_SOURCE_DIR}/include/less/core/grid_mapping.h"
    "${less_runtime_field_contract}"
    "${less_runtime_texture_footprint}"
    "${CMAKE_CURRENT_SOURCE_DIR}/include/less/fields/data.h"
    "${less_runtime_scene_field_evaluator}"
    "${CMAKE_CURRENT_SOURCE_DIR}/include/less/runtime/scene_data.h"
    ${LESS_PROCESS_CONTRACTS}
    "${LESS_TURBID_SOBOL}"
    "${LESS_TURBID_ARTIFACT_ABI}"
)
# ---- Microwave CUDA kernels (independent from optical) ----
set(MW_KERNEL_SOURCES
    src/integrators/passive_microwave/optix/raygen_passive.cu
    src/integrators/active_microwave/optix/raygen_active.cu
    src/backends/optix/device/microwave/kernels/closesthit_mw.cu
    src/backends/optix/device/microwave/kernels/miss_mw.cu
)
set(MW_PERIODIC_HEADER
    "${CMAKE_CURRENT_SOURCE_DIR}/src/backends/optix/device/microwave/kernels/periodic_xy.cuh")

set(MW_KERNEL_INCLUDE_DIRS
    -I${LESS_INCLUDE_DIR}
    -I${less_surface_generated_include_dir}
    -I${less_illumination_generated_include_dir}
    -I${less_sensor_generated_include_dir}
    -I${CMAKE_CURRENT_SOURCE_DIR}
    -I${OPTIX_INCLUDE_DIR}
    -I${CMAKE_CURRENT_SOURCE_DIR}/src/backends/optix/device/microwave
    -I${CMAKE_CURRENT_SOURCE_DIR}/src/backends
)

foreach(KERNEL_SRC ${KERNEL_SOURCES})
    get_filename_component(KERNEL_NAME ${KERNEL_SRC} NAME_WE)
    set(KERNEL_OUTPUT_NAME "${KERNEL_NAME}")
    if(KERNEL_NAME STREQUAL "raygen_backward")
        set(KERNEL_OUTPUT_NAME "backward_optical")
    elseif(KERNEL_NAME STREQUAL "raygen_forward")
        set(KERNEL_OUTPUT_NAME "forward_optical")
    elseif(KERNEL_NAME STREQUAL "raygen_flux")
        set(KERNEL_OUTPUT_NAME "forward_flux")
    elseif(KERNEL_NAME STREQUAL "raygen_lidar")
        set(KERNEL_OUTPUT_NAME "time_resolved_active")
    elseif(KERNEL_NAME STREQUAL "raygen_eb_rrt")
        set(KERNEL_OUTPUT_NAME "energy_balance_radiation")
    elseif(KERNEL_NAME STREQUAL "closesthit")
        set(KERNEL_OUTPUT_NAME "optical_hit")
    elseif(KERNEL_NAME STREQUAL "miss")
        set(KERNEL_OUTPUT_NAME "optical_miss")
    elseif(KERNEL_NAME STREQUAL "sampling")
        set(KERNEL_OUTPUT_NAME "turbid_sampling")
    endif()
    set(PTX_FILE "${OPTIX_KERNEL_OUTPUT_DIR}/${KERNEL_OUTPUT_NAME}.ptx")
    set(KERNEL_DEFINES "")
    set(KERNEL_DEPENDS ${OPTICAL_PTX_ABI_HEADERS})
    less_append_backward_optix_dependencies(KERNEL_DEPENDS "${KERNEL_NAME}")
    less_append_forward_optix_dependencies(KERNEL_DEPENDS "${KERNEL_NAME}")
    less_append_radiation_field_optix_dependencies(
        KERNEL_DEPENDS "${KERNEL_NAME}")
    less_append_brf_product_optix_dependencies(
        KERNEL_DEPENDS "${KERNEL_NAME}")
    less_append_lidar_waveform_optix_dependencies(
        KERNEL_DEPENDS "${KERNEL_NAME}")
    less_append_lidar_acquisition_optix_dependencies(
        KERNEL_DEPENDS "${KERNEL_NAME}")
    less_append_time_resolved_active_optix_dependencies(
        KERNEL_DEPENDS "${KERNEL_NAME}")
    less_append_irradiance_map_optix_dependencies(
        KERNEL_DEPENDS "${KERNEL_NAME}")
    less_append_energy_balance_optix_dependencies(KERNEL_DEPENDS "${KERNEL_NAME}")
    less_append_optical_surface_optix_dependencies(KERNEL_DEPENDS "${KERNEL_NAME}")
    less_append_registered_surface_optix_dependencies(KERNEL_DEPENDS "${KERNEL_NAME}")
    less_append_turbid_leaf_medium_optix_dependencies(
        KERNEL_DEPENDS "${KERNEL_NAME}")
    less_append_phase_optix_dependencies(KERNEL_DEPENDS "${KERNEL_NAME}")
    if(KERNEL_NAME STREQUAL "raygen_backward")
        list(APPEND KERNEL_DEFINES
            -DLESS_OPTIX_DYNAMIC_SPECTRUM
            -DLESS_OPTIX_ENABLE_REGISTERED_SURFACES)
    elseif(KERNEL_NAME STREQUAL "raygen_forward")
        list(APPEND KERNEL_DEFINES
            -DLESS_OPTIX_ENABLE_REGISTERED_SURFACES)
    endif()

    add_custom_command(
        OUTPUT ${PTX_FILE}
        COMMAND ${CUDAToolkit_NVCC_EXECUTABLE}
            ${KERNEL_INCLUDE_DIRS}
            --machine 64
            -ptx
            -O3
            --std=c++17
            --use_fast_math
            -arch=compute_75
            -DNDEBUG
            -D_CRT_SECURE_NO_WARNINGS
            -DNOMINMAX
            ${KERNEL_DEFINES}
            -o ${PTX_FILE}
            ${CMAKE_CURRENT_SOURCE_DIR}/${KERNEL_SRC}
        DEPENDS
            ${CMAKE_CURRENT_SOURCE_DIR}/${KERNEL_SRC}
            ${KERNEL_DEPENDS}
        COMMENT "Compiling PTX: ${KERNEL_OUTPUT_NAME}.cu"
        VERBATIM
    )
    list(APPEND PTX_TARGETS ${PTX_FILE})

    # OptiX modules are distributed as OptiX IR to avoid repeating the much
    # slower PTX-to-OptiX translation in every fresh driver cache. The
    # standalone turbid sampler is loaded through the CUDA Driver API and
    # therefore remains PTX-only.
    if(NOT KERNEL_NAME STREQUAL "sampling")
        set(OPTIXIR_FILE "${OPTIX_KERNEL_OUTPUT_DIR}/${KERNEL_OUTPUT_NAME}.optixir")
        add_custom_command(
            OUTPUT ${OPTIXIR_FILE}
            COMMAND ${CUDAToolkit_NVCC_EXECUTABLE}
                ${KERNEL_INCLUDE_DIRS}
                --machine 64
                --optix-ir
                -O3
                --std=c++17
                --use_fast_math
                -arch=compute_75
                -DNDEBUG
                -D_CRT_SECURE_NO_WARNINGS
                -DNOMINMAX
                ${KERNEL_DEFINES}
                -o ${OPTIXIR_FILE}
                ${CMAKE_CURRENT_SOURCE_DIR}/${KERNEL_SRC}
            DEPENDS
                ${CMAKE_CURRENT_SOURCE_DIR}/${KERNEL_SRC}
                ${KERNEL_DEPENDS}
            COMMENT "Compiling OptiX IR: ${KERNEL_OUTPUT_NAME}.cu"
            VERBATIM
        )
        list(APPEND OPTIXIR_TARGETS ${OPTIXIR_FILE})
    endif()
endforeach()

# Runtime-sized Turbid transport keeps one narrow module instead of the old
# 4/8/16/32/64/128 capacity matrix. Separating its ray-generation program
# from the mesh entry point preserves arbitrary band counts without loading
# unreachable mesh transport code into each Turbid pipeline.
set(BACKWARD_DYNAMIC_TURBID_PTX
    "${OPTIX_KERNEL_OUTPUT_DIR}/backward_optical_turbid.ptx")
set(BACKWARD_DYNAMIC_TURBID_DEPENDS ${OPTICAL_PTX_ABI_HEADERS})
less_append_backward_optix_dependencies(
    BACKWARD_DYNAMIC_TURBID_DEPENDS "raygen_backward")
add_custom_command(
    OUTPUT ${BACKWARD_DYNAMIC_TURBID_PTX}
    COMMAND ${CUDAToolkit_NVCC_EXECUTABLE}
        ${KERNEL_INCLUDE_DIRS}
        --machine 64
        -ptx
        -O3
        --std=c++17
        --use_fast_math
        -arch=compute_75
        -DNDEBUG
        -D_CRT_SECURE_NO_WARNINGS
        -DNOMINMAX
        -DLESS_OPTIX_DYNAMIC_SPECTRUM
        -DLESS_OPTIX_BACKWARD_TURBID_ONLY
        -DLESS_OPTIX_ENABLE_REGISTERED_SURFACES
        -o ${BACKWARD_DYNAMIC_TURBID_PTX}
        ${CMAKE_CURRENT_SOURCE_DIR}/src/integrators/backward_optical/optix/raygen_backward.cu
    DEPENDS
        ${CMAKE_CURRENT_SOURCE_DIR}/src/integrators/backward_optical/optix/raygen_backward.cu
        ${BACKWARD_DYNAMIC_TURBID_DEPENDS}
    COMMENT "Compiling PTX: backward_optical_turbid.cu"
    VERBATIM
)
list(APPEND PTX_TARGETS ${BACKWARD_DYNAMIC_TURBID_PTX})

set(BACKWARD_DYNAMIC_TURBID_OPTIXIR
    "${OPTIX_KERNEL_OUTPUT_DIR}/backward_optical_turbid.optixir")
add_custom_command(
    OUTPUT ${BACKWARD_DYNAMIC_TURBID_OPTIXIR}
    COMMAND ${CUDAToolkit_NVCC_EXECUTABLE}
        ${KERNEL_INCLUDE_DIRS}
        --machine 64
        --optix-ir
        -O3
        --std=c++17
        --use_fast_math
        -arch=compute_75
        -DNDEBUG
        -D_CRT_SECURE_NO_WARNINGS
        -DNOMINMAX
        -DLESS_OPTIX_DYNAMIC_SPECTRUM
        -DLESS_OPTIX_BACKWARD_TURBID_ONLY
        -DLESS_OPTIX_ENABLE_REGISTERED_SURFACES
        -o ${BACKWARD_DYNAMIC_TURBID_OPTIXIR}
        ${CMAKE_CURRENT_SOURCE_DIR}/src/integrators/backward_optical/optix/raygen_backward.cu
    DEPENDS
        ${CMAKE_CURRENT_SOURCE_DIR}/src/integrators/backward_optical/optix/raygen_backward.cu
        ${BACKWARD_DYNAMIC_TURBID_DEPENDS}
    COMMENT "Compiling OptiX IR: backward_optical_turbid.cu"
    VERBATIM
)
list(APPEND OPTIXIR_TARGETS ${BACKWARD_DYNAMIC_TURBID_OPTIXIR})

# Common reflected-solar Turbid scenes with constant Lambertian or diffuse-
# transmitting closures do not need registered-surface dispatch, Texture2D
# fields, StateFields, fluorescence, or thermal source branches.  Keep this a
# physics/profile specialization; its spectrum remains runtime-sized.
set(BACKWARD_DYNAMIC_TURBID_DIFFUSE_PTX
    "${OPTIX_KERNEL_OUTPUT_DIR}/backward_optical_turbid_diffuse.ptx")
add_custom_command(
    OUTPUT ${BACKWARD_DYNAMIC_TURBID_DIFFUSE_PTX}
    COMMAND ${CUDAToolkit_NVCC_EXECUTABLE}
        ${KERNEL_INCLUDE_DIRS}
        --machine 64
        -ptx
        -O3
        --std=c++17
        --use_fast_math
        -arch=compute_75
        -DNDEBUG
        -D_CRT_SECURE_NO_WARNINGS
        -DNOMINMAX
        -DLESS_OPTIX_DYNAMIC_SPECTRUM
        -DLESS_OPTIX_BACKWARD_TURBID_ONLY
        -DLESS_FIELD_CONSTANT_ONLY -DLESS_FIELD_DISABLE_STATE
        -DLESS_OPTIX_REFLECTED_SOLAR_ONLY -DLESS_OPTIX_DIFFUSE_ONLY
        -o ${BACKWARD_DYNAMIC_TURBID_DIFFUSE_PTX}
        ${CMAKE_CURRENT_SOURCE_DIR}/src/integrators/backward_optical/optix/raygen_backward.cu
    DEPENDS
        ${CMAKE_CURRENT_SOURCE_DIR}/src/integrators/backward_optical/optix/raygen_backward.cu
        ${BACKWARD_DYNAMIC_TURBID_DEPENDS}
    COMMENT "Compiling PTX: backward_optical_turbid_diffuse.cu"
    VERBATIM
)
list(APPEND PTX_TARGETS ${BACKWARD_DYNAMIC_TURBID_DIFFUSE_PTX})

set(BACKWARD_DYNAMIC_TURBID_DIFFUSE_OPTIXIR
    "${OPTIX_KERNEL_OUTPUT_DIR}/backward_optical_turbid_diffuse.optixir")
add_custom_command(
    OUTPUT ${BACKWARD_DYNAMIC_TURBID_DIFFUSE_OPTIXIR}
    COMMAND ${CUDAToolkit_NVCC_EXECUTABLE}
        ${KERNEL_INCLUDE_DIRS}
        --machine 64
        --optix-ir
        -O3
        --std=c++17
        --use_fast_math
        -arch=compute_75
        -DNDEBUG
        -D_CRT_SECURE_NO_WARNINGS
        -DNOMINMAX
        -DLESS_OPTIX_DYNAMIC_SPECTRUM
        -DLESS_OPTIX_BACKWARD_TURBID_ONLY
        -DLESS_FIELD_CONSTANT_ONLY -DLESS_FIELD_DISABLE_STATE
        -DLESS_OPTIX_REFLECTED_SOLAR_ONLY -DLESS_OPTIX_DIFFUSE_ONLY
        -o ${BACKWARD_DYNAMIC_TURBID_DIFFUSE_OPTIXIR}
        ${CMAKE_CURRENT_SOURCE_DIR}/src/integrators/backward_optical/optix/raygen_backward.cu
    DEPENDS
        ${CMAKE_CURRENT_SOURCE_DIR}/src/integrators/backward_optical/optix/raygen_backward.cu
        ${BACKWARD_DYNAMIC_TURBID_DEPENDS}
    COMMENT "Compiling OptiX IR: backward_optical_turbid_diffuse.cu"
    VERBATIM
)
list(APPEND OPTIXIR_TARGETS ${BACKWARD_DYNAMIC_TURBID_DIFFUSE_OPTIXIR})

# Mesh multi-band RadiationField uses the same energy-balance source and
# closures, but excludes the single-band, longwave and Turbid virtual-leaf
# entry points. Loading the combined module made an ordinary mesh workflow
# pay the driver-JIT cost of every unrelated energy-balance program.
set(EB_RRT_MESH_PTX
    "${OPTIX_KERNEL_OUTPUT_DIR}/energy_balance_radiation_mesh.ptx")
add_custom_command(
    OUTPUT ${EB_RRT_MESH_PTX}
    COMMAND ${CUDAToolkit_NVCC_EXECUTABLE}
        ${KERNEL_INCLUDE_DIRS}
        --machine 64
        -ptx
        -O3
        --std=c++17
        --use_fast_math
        -arch=compute_75
        -DNDEBUG
        -D_CRT_SECURE_NO_WARNINGS
        -DNOMINMAX
        -DLESS_OPTIX_EB_MULTIBAND_MESH_ONLY
        -o ${EB_RRT_MESH_PTX}
        ${CMAKE_CURRENT_SOURCE_DIR}/src/integrators/energy_balance_radiation/optix/raygen_eb_rrt.cu
    DEPENDS
        ${CMAKE_CURRENT_SOURCE_DIR}/src/integrators/energy_balance_radiation/optix/raygen_eb_rrt.cu
        ${OPTICAL_PTX_ABI_HEADERS} ${less_runtime_energy_balance_optix_dependencies}
    COMMENT "Compiling PTX: raygen_eb_rrt_mesh.cu"
    VERBATIM
)
list(APPEND PTX_TARGETS ${EB_RRT_MESH_PTX})

set(EB_RRT_MESH_OPTIXIR
    "${OPTIX_KERNEL_OUTPUT_DIR}/energy_balance_radiation_mesh.optixir")
add_custom_command(
    OUTPUT ${EB_RRT_MESH_OPTIXIR}
    COMMAND ${CUDAToolkit_NVCC_EXECUTABLE}
        ${KERNEL_INCLUDE_DIRS}
        --machine 64
        --optix-ir
        -O3
        --std=c++17
        --use_fast_math
        -arch=compute_75
        -DNDEBUG
        -D_CRT_SECURE_NO_WARNINGS
        -DNOMINMAX
        -DLESS_OPTIX_EB_MULTIBAND_MESH_ONLY
        -o ${EB_RRT_MESH_OPTIXIR}
        ${CMAKE_CURRENT_SOURCE_DIR}/src/integrators/energy_balance_radiation/optix/raygen_eb_rrt.cu
    DEPENDS
        ${CMAKE_CURRENT_SOURCE_DIR}/src/integrators/energy_balance_radiation/optix/raygen_eb_rrt.cu
        ${OPTICAL_PTX_ABI_HEADERS} ${less_runtime_energy_balance_optix_dependencies}
    COMMENT "Compiling OptiX IR: raygen_eb_rrt_mesh.cu"
    VERBATIM
)
list(APPEND OPTIXIR_TARGETS ${EB_RRT_MESH_OPTIXIR})

# Fixed optical workflows share their transport source with the complete
# Field program, but constant-property scenes compile out Texture2D and State
# evaluators.  This keeps physics identical while avoiding whole-program CUDA
# inlining of MIP/RGB2Spec/spectral interpolation into every call site.
set(FIXED_FIELD_CONSTANT_VARIANTS
    raygen_forward
    raygen_flux
    raygen_lidar
    raygen_eb_rrt_mesh
)
foreach(FIXED_CONSTANT_NAME ${FIXED_FIELD_CONSTANT_VARIANTS})
    if(FIXED_CONSTANT_NAME STREQUAL "raygen_forward")
        set(FIXED_CONSTANT_OUTPUT_NAME "forward_optical_constant")
    elseif(FIXED_CONSTANT_NAME STREQUAL "raygen_flux")
        set(FIXED_CONSTANT_OUTPUT_NAME "forward_flux_constant")
    elseif(FIXED_CONSTANT_NAME STREQUAL "raygen_lidar")
        set(FIXED_CONSTANT_OUTPUT_NAME "time_resolved_active_constant")
    elseif(FIXED_CONSTANT_NAME STREQUAL "raygen_eb_rrt_mesh")
        set(FIXED_CONSTANT_OUTPUT_NAME "energy_balance_radiation_mesh_constant")
    endif()
    set(FIXED_CONSTANT_DEPENDS ${OPTICAL_PTX_ABI_HEADERS})
    less_append_forward_optix_dependencies(
        FIXED_CONSTANT_DEPENDS "${FIXED_CONSTANT_NAME}")
    less_append_radiation_field_optix_dependencies(
        FIXED_CONSTANT_DEPENDS "${FIXED_CONSTANT_NAME}")
    less_append_brf_product_optix_dependencies(
        FIXED_CONSTANT_DEPENDS "${FIXED_CONSTANT_NAME}")
    less_append_lidar_waveform_optix_dependencies(
        FIXED_CONSTANT_DEPENDS "${FIXED_CONSTANT_NAME}")
    less_append_lidar_acquisition_optix_dependencies(
        FIXED_CONSTANT_DEPENDS "${FIXED_CONSTANT_NAME}")
    less_append_time_resolved_active_optix_dependencies(
        FIXED_CONSTANT_DEPENDS "${FIXED_CONSTANT_NAME}")
    less_append_irradiance_map_optix_dependencies(
        FIXED_CONSTANT_DEPENDS "${FIXED_CONSTANT_NAME}")
    less_append_energy_balance_optix_dependencies(FIXED_CONSTANT_DEPENDS "${FIXED_CONSTANT_NAME}")
    less_append_optical_surface_optix_dependencies(
        FIXED_CONSTANT_DEPENDS "${FIXED_CONSTANT_NAME}")
    less_append_registered_surface_optix_dependencies(
        FIXED_CONSTANT_DEPENDS "${FIXED_CONSTANT_NAME}")
    if(FIXED_CONSTANT_NAME STREQUAL "raygen_eb_rrt_mesh")
        set(FIXED_CONSTANT_SOURCE
            "${CMAKE_CURRENT_SOURCE_DIR}/src/integrators/energy_balance_radiation/optix/raygen_eb_rrt.cu")
        set(FIXED_CONSTANT_DEFINES -DLESS_OPTIX_EB_MULTIBAND_MESH_ONLY)
    elseif(FIXED_CONSTANT_NAME STREQUAL "raygen_forward")
        set(FIXED_CONSTANT_SOURCE
            "${CMAKE_CURRENT_SOURCE_DIR}/src/integrators/forward_optical/optix/raygen_forward.cu")
        set(FIXED_CONSTANT_DEFINES
            -DLESS_OPTIX_FORWARD_MESH_ONLY
            -DLESS_OPTIX_ENABLE_REGISTERED_SURFACES)
    elseif(FIXED_CONSTANT_NAME STREQUAL "raygen_flux")
        set(FIXED_CONSTANT_SOURCE
            "${CMAKE_CURRENT_SOURCE_DIR}/src/integrators/forward_optical/optix/raygen_flux.cu")
        set(FIXED_CONSTANT_DEFINES "")
    elseif(FIXED_CONSTANT_NAME STREQUAL "raygen_lidar")
        set(FIXED_CONSTANT_SOURCE
            "${CMAKE_CURRENT_SOURCE_DIR}/src/integrators/time_resolved_active/optix/raygen_lidar.cu")
        set(FIXED_CONSTANT_DEFINES "")
    else()
        set(FIXED_CONSTANT_SOURCE
            "${CMAKE_CURRENT_SOURCE_DIR}/src/backends/optix/device/optical/kernels/${FIXED_CONSTANT_NAME}.cu")
        set(FIXED_CONSTANT_DEFINES "")
    endif()

    set(FIXED_CONSTANT_PTX
        "${OPTIX_KERNEL_OUTPUT_DIR}/${FIXED_CONSTANT_OUTPUT_NAME}.ptx")
    add_custom_command(
        OUTPUT ${FIXED_CONSTANT_PTX}
        COMMAND ${CUDAToolkit_NVCC_EXECUTABLE}
            ${KERNEL_INCLUDE_DIRS}
            --machine 64
            -ptx
            -O3
            --std=c++17
            --use_fast_math
            -arch=compute_75
            -DNDEBUG
            -D_CRT_SECURE_NO_WARNINGS
            -DNOMINMAX
            ${FIXED_CONSTANT_DEFINES}
            -DLESS_FIELD_CONSTANT_ONLY
            -DLESS_FIELD_DISABLE_STATE
            -o ${FIXED_CONSTANT_PTX}
            ${FIXED_CONSTANT_SOURCE}
        DEPENDS
            ${FIXED_CONSTANT_SOURCE}
            ${FIXED_CONSTANT_DEPENDS}
        COMMENT "Compiling PTX: ${FIXED_CONSTANT_NAME}_constant.cu"
        VERBATIM
    )
    list(APPEND PTX_TARGETS ${FIXED_CONSTANT_PTX})

    set(FIXED_CONSTANT_OPTIXIR
        "${OPTIX_KERNEL_OUTPUT_DIR}/${FIXED_CONSTANT_OUTPUT_NAME}.optixir")
    add_custom_command(
        OUTPUT ${FIXED_CONSTANT_OPTIXIR}
        COMMAND ${CUDAToolkit_NVCC_EXECUTABLE}
            ${KERNEL_INCLUDE_DIRS}
            --machine 64
            --optix-ir
            -O3
            --std=c++17
            --use_fast_math
            -arch=compute_75
            -DNDEBUG
            -D_CRT_SECURE_NO_WARNINGS
            -DNOMINMAX
            ${FIXED_CONSTANT_DEFINES}
            -DLESS_FIELD_CONSTANT_ONLY
            -DLESS_FIELD_DISABLE_STATE
            -o ${FIXED_CONSTANT_OPTIXIR}
            ${FIXED_CONSTANT_SOURCE}
        DEPENDS
            ${FIXED_CONSTANT_SOURCE}
            ${FIXED_CONSTANT_DEPENDS}
        COMMENT "Compiling OptiX IR: ${FIXED_CONSTANT_NAME}_constant.cu"
        VERBATIM
    )
    list(APPEND OPTIXIR_TARGETS ${FIXED_CONSTANT_OPTIXIR})
endforeach()

# Runtime-sized backward optical spectra keep path geometry shared across an
# arbitrary wavelength domain. The generic module retains every registered
# Surface and Turbid workflow; the diffuse profile compiles the common
# Lambertian/DiffuseTransmitter closure directly without changing its physics.
list(APPEND OPTICAL_PTX_ABI_HEADERS
    ${less_sensor_optix_dependencies} ${less_runtime_backward_integrator_optix_dependencies} ${less_surface_optix_dependencies}
    ${less_runtime_turbid_leaf_medium_optix_dependencies} ${less_phase_optix_dependencies}
    ${less_product_optical_image_optix_dependencies} ${less_illumination_optix_dependencies} ${less_runtime_source_composition_optix_dependencies} ${less_runtime_surface_fluorescence_emission_optix_dependencies} ${less_runtime_thermal_surface_emission_optix_dependencies}
    ${less_surface_optix_dependencies})
set(BACKWARD_DYNAMIC_DIFFUSE_PTX
    "${OPTIX_KERNEL_OUTPUT_DIR}/backward_optical_diffuse.ptx")
add_custom_command(
    OUTPUT ${BACKWARD_DYNAMIC_DIFFUSE_PTX}
    COMMAND ${CUDAToolkit_NVCC_EXECUTABLE}
        ${KERNEL_INCLUDE_DIRS}
        --machine 64
        -ptx
        -O3
        --std=c++17
        --use_fast_math
        -arch=compute_75
        -DNDEBUG
        -D_CRT_SECURE_NO_WARNINGS
        -DNOMINMAX
        -DLESS_OPTIX_DYNAMIC_SPECTRUM
        -DLESS_OPTIX_ENABLE_REGISTERED_SURFACES
        -DLESS_OPTIX_BACKWARD_STANDARD_ONLY
        -DLESS_FIELD_CONSTANT_ONLY -DLESS_FIELD_DISABLE_STATE
        -DLESS_OPTIX_REFLECTED_SOLAR_ONLY -DLESS_OPTIX_DIFFUSE_ONLY
        -o ${BACKWARD_DYNAMIC_DIFFUSE_PTX}
        ${CMAKE_CURRENT_SOURCE_DIR}/src/integrators/backward_optical/optix/raygen_backward.cu
    DEPENDS
        ${CMAKE_CURRENT_SOURCE_DIR}/src/integrators/backward_optical/optix/raygen_backward.cu
        ${OPTICAL_PTX_ABI_HEADERS}
        ${less_surface_optix_dependencies}
    COMMENT "Compiling PTX: raygen_backward_dynamic_diffuse.cu"
    VERBATIM
)
list(APPEND PTX_TARGETS ${BACKWARD_DYNAMIC_DIFFUSE_PTX})

set(BACKWARD_DYNAMIC_DIFFUSE_OPTIXIR
    "${OPTIX_KERNEL_OUTPUT_DIR}/backward_optical_diffuse.optixir")
add_custom_command(
    OUTPUT ${BACKWARD_DYNAMIC_DIFFUSE_OPTIXIR}
    COMMAND ${CUDAToolkit_NVCC_EXECUTABLE}
        ${KERNEL_INCLUDE_DIRS}
        --machine 64
        --optix-ir
        -O3
        --std=c++17
        --use_fast_math
        -arch=compute_75
        -DNDEBUG
        -D_CRT_SECURE_NO_WARNINGS
        -DNOMINMAX
        -DLESS_OPTIX_DYNAMIC_SPECTRUM
        -DLESS_OPTIX_ENABLE_REGISTERED_SURFACES
        -DLESS_OPTIX_BACKWARD_STANDARD_ONLY
        -DLESS_FIELD_CONSTANT_ONLY -DLESS_FIELD_DISABLE_STATE
        -DLESS_OPTIX_REFLECTED_SOLAR_ONLY -DLESS_OPTIX_DIFFUSE_ONLY
        -o ${BACKWARD_DYNAMIC_DIFFUSE_OPTIXIR}
        ${CMAKE_CURRENT_SOURCE_DIR}/src/integrators/backward_optical/optix/raygen_backward.cu
    DEPENDS
        ${CMAKE_CURRENT_SOURCE_DIR}/src/integrators/backward_optical/optix/raygen_backward.cu
        ${OPTICAL_PTX_ABI_HEADERS}
        ${less_surface_optix_dependencies}
    COMMENT "Compiling OptiX IR: raygen_backward_dynamic_diffuse.cu"
    VERBATIM
)
list(APPEND OPTIXIR_TARGETS ${BACKWARD_DYNAMIC_DIFFUSE_OPTIXIR})

# Measured-spectral Texture Fields do not need the nonlinear RGB2Spec
# reconstruction branch. Keep the same transport source and compile only that
# representation out, so the exact sampled profile does not pay for the
# reconstructed superset during OptiX module creation.
set(BACKWARD_SAMPLED_PTX
    "${OPTIX_KERNEL_OUTPUT_DIR}/backward_optical_passive_sampled.ptx")
add_custom_command(
    OUTPUT ${BACKWARD_SAMPLED_PTX}
    COMMAND ${CUDAToolkit_NVCC_EXECUTABLE}
        ${KERNEL_INCLUDE_DIRS}
        --machine 64
        -ptx
        -O3
        --std=c++17
        --use_fast_math
        -arch=compute_75
        -DNDEBUG
        -D_CRT_SECURE_NO_WARNINGS
        -DNOMINMAX
        -DLESS_OPTIX_BACKWARD_STANDARD_ONLY
        -DLESS_FIELD_ENABLE_RGB2SPEC=0
        -DLESS_OPTIX_ENABLE_REGISTERED_SURFACES
        -o ${BACKWARD_SAMPLED_PTX}
        ${CMAKE_CURRENT_SOURCE_DIR}/src/integrators/backward_optical/optix/raygen_backward.cu
    DEPENDS
        ${CMAKE_CURRENT_SOURCE_DIR}/src/integrators/backward_optical/optix/raygen_backward.cu
        ${OPTICAL_PTX_ABI_HEADERS}
        ${less_surface_optix_dependencies}
    COMMENT "Compiling PTX: raygen_backward_sampled.cu"
    VERBATIM
)
list(APPEND PTX_TARGETS ${BACKWARD_SAMPLED_PTX})

set(BACKWARD_SAMPLED_OPTIXIR
    "${OPTIX_KERNEL_OUTPUT_DIR}/backward_optical_passive_sampled.optixir")
add_custom_command(
    OUTPUT ${BACKWARD_SAMPLED_OPTIXIR}
    COMMAND ${CUDAToolkit_NVCC_EXECUTABLE}
        ${KERNEL_INCLUDE_DIRS}
        --machine 64
        --optix-ir
        -O3
        --std=c++17
        --use_fast_math
        -arch=compute_75
        -DNDEBUG
        -D_CRT_SECURE_NO_WARNINGS
        -DNOMINMAX
        -DLESS_OPTIX_BACKWARD_STANDARD_ONLY
        -DLESS_FIELD_ENABLE_RGB2SPEC=0
        -DLESS_OPTIX_ENABLE_REGISTERED_SURFACES
        -o ${BACKWARD_SAMPLED_OPTIXIR}
        ${CMAKE_CURRENT_SOURCE_DIR}/src/integrators/backward_optical/optix/raygen_backward.cu
    DEPENDS
        ${CMAKE_CURRENT_SOURCE_DIR}/src/integrators/backward_optical/optix/raygen_backward.cu
        ${OPTICAL_PTX_ABI_HEADERS}
        ${less_surface_optix_dependencies}
    COMMENT "Compiling OptiX IR: raygen_backward_sampled.cu"
    VERBATIM
)
list(APPEND OPTIXIR_TARGETS ${BACKWARD_SAMPLED_OPTIXIR})

# Reflected-solar sampled and reconstructed profiles compile out the thermal
# and fluorescence source branches.  The generic sampled/reconstructed
# modules remain available for textured thermal and SIF workflows, which must
# never reuse a module specialized for the wrong passive source.
set(BACKWARD_OPTICAL_SAMPLED_PTX
    "${OPTIX_KERNEL_OUTPUT_DIR}/backward_optical_reflected_sampled.ptx")
add_custom_command(
    OUTPUT ${BACKWARD_OPTICAL_SAMPLED_PTX}
    COMMAND ${CUDAToolkit_NVCC_EXECUTABLE}
        ${KERNEL_INCLUDE_DIRS}
        --machine 64
        -ptx
        -O3
        --std=c++17
        --use_fast_math
        -arch=compute_75
        -DNDEBUG
        -D_CRT_SECURE_NO_WARNINGS
        -DNOMINMAX
        -DLESS_OPTIX_BACKWARD_STANDARD_ONLY
        -DLESS_FIELD_ENABLE_RGB2SPEC=0
        -DLESS_OPTIX_REFLECTED_SOLAR_ONLY
        -DLESS_OPTIX_ENABLE_REGISTERED_SURFACES
        -o ${BACKWARD_OPTICAL_SAMPLED_PTX}
        ${CMAKE_CURRENT_SOURCE_DIR}/src/integrators/backward_optical/optix/raygen_backward.cu
    DEPENDS
        ${CMAKE_CURRENT_SOURCE_DIR}/src/integrators/backward_optical/optix/raygen_backward.cu
        ${OPTICAL_PTX_ABI_HEADERS}
        ${less_surface_optix_dependencies}
    COMMENT "Compiling PTX: raygen_backward_optical_sampled.cu"
    VERBATIM
)
list(APPEND PTX_TARGETS ${BACKWARD_OPTICAL_SAMPLED_PTX})

set(BACKWARD_OPTICAL_SAMPLED_OPTIXIR
    "${OPTIX_KERNEL_OUTPUT_DIR}/backward_optical_reflected_sampled.optixir")
add_custom_command(
    OUTPUT ${BACKWARD_OPTICAL_SAMPLED_OPTIXIR}
    COMMAND ${CUDAToolkit_NVCC_EXECUTABLE}
        ${KERNEL_INCLUDE_DIRS}
        --machine 64
        --optix-ir
        -O3
        --std=c++17
        --use_fast_math
        -arch=compute_75
        -DNDEBUG
        -D_CRT_SECURE_NO_WARNINGS
        -DNOMINMAX
        -DLESS_OPTIX_BACKWARD_STANDARD_ONLY
        -DLESS_FIELD_ENABLE_RGB2SPEC=0
        -DLESS_OPTIX_REFLECTED_SOLAR_ONLY
        -DLESS_OPTIX_ENABLE_REGISTERED_SURFACES
        -o ${BACKWARD_OPTICAL_SAMPLED_OPTIXIR}
        ${CMAKE_CURRENT_SOURCE_DIR}/src/integrators/backward_optical/optix/raygen_backward.cu
    DEPENDS
        ${CMAKE_CURRENT_SOURCE_DIR}/src/integrators/backward_optical/optix/raygen_backward.cu
        ${OPTICAL_PTX_ABI_HEADERS}
        ${less_surface_optix_dependencies}
    COMMENT "Compiling OptiX IR: raygen_backward_optical_sampled.cu"
    VERBATIM
)
list(APPEND OPTIXIR_TARGETS ${BACKWARD_OPTICAL_SAMPLED_OPTIXIR})

set(BACKWARD_OPTICAL_RECONSTRUCTED_PTX
    "${OPTIX_KERNEL_OUTPUT_DIR}/backward_optical_reflected_reconstructed.ptx")
add_custom_command(
    OUTPUT ${BACKWARD_OPTICAL_RECONSTRUCTED_PTX}
    COMMAND ${CUDAToolkit_NVCC_EXECUTABLE}
        ${KERNEL_INCLUDE_DIRS}
        --machine 64
        -ptx
        -O3
        --std=c++17
        --use_fast_math
        -arch=compute_75
        -DNDEBUG
        -D_CRT_SECURE_NO_WARNINGS
        -DNOMINMAX
        -DLESS_OPTIX_BACKWARD_STANDARD_ONLY
        -DLESS_OPTIX_REFLECTED_SOLAR_ONLY
        -DLESS_OPTIX_ENABLE_REGISTERED_SURFACES
        -o ${BACKWARD_OPTICAL_RECONSTRUCTED_PTX}
        ${CMAKE_CURRENT_SOURCE_DIR}/src/integrators/backward_optical/optix/raygen_backward.cu
    DEPENDS
        ${CMAKE_CURRENT_SOURCE_DIR}/src/integrators/backward_optical/optix/raygen_backward.cu
        ${OPTICAL_PTX_ABI_HEADERS}
        ${less_surface_optix_dependencies}
    COMMENT "Compiling PTX: raygen_backward_optical_reconstructed.cu"
    VERBATIM
)
list(APPEND PTX_TARGETS ${BACKWARD_OPTICAL_RECONSTRUCTED_PTX})

set(BACKWARD_OPTICAL_RECONSTRUCTED_OPTIXIR
    "${OPTIX_KERNEL_OUTPUT_DIR}/backward_optical_reflected_reconstructed.optixir")
add_custom_command(
    OUTPUT ${BACKWARD_OPTICAL_RECONSTRUCTED_OPTIXIR}
    COMMAND ${CUDAToolkit_NVCC_EXECUTABLE}
        ${KERNEL_INCLUDE_DIRS}
        --machine 64
        --optix-ir
        -O3
        --std=c++17
        --use_fast_math
        -arch=compute_75
        -DNDEBUG
        -D_CRT_SECURE_NO_WARNINGS
        -DNOMINMAX
        -DLESS_OPTIX_BACKWARD_STANDARD_ONLY
        -DLESS_OPTIX_REFLECTED_SOLAR_ONLY
        -DLESS_OPTIX_ENABLE_REGISTERED_SURFACES
        -o ${BACKWARD_OPTICAL_RECONSTRUCTED_OPTIXIR}
        ${CMAKE_CURRENT_SOURCE_DIR}/src/integrators/backward_optical/optix/raygen_backward.cu
    DEPENDS
        ${CMAKE_CURRENT_SOURCE_DIR}/src/integrators/backward_optical/optix/raygen_backward.cu
        ${OPTICAL_PTX_ABI_HEADERS}
        ${less_surface_optix_dependencies}
    COMMENT "Compiling OptiX IR: raygen_backward_optical_reconstructed.cu"
    VERBATIM
)
list(APPEND OPTIXIR_TARGETS ${BACKWARD_OPTICAL_RECONSTRUCTED_OPTIXIR})

# SIF imaging consumes primitive fluorescence StateFields but does not need
# Texture2D reconstruction, reflected-solar NEE, or thermal emission. Keep a
# dedicated module so enabling process state cannot make every mesh scene pay
# for the complete backward program at driver JIT time.
set(BACKWARD_STATE_SIF_PTX
    "${OPTIX_KERNEL_OUTPUT_DIR}/backward_optical_sif_state.ptx")
add_custom_command(
    OUTPUT ${BACKWARD_STATE_SIF_PTX}
    COMMAND ${CUDAToolkit_NVCC_EXECUTABLE}
        ${KERNEL_INCLUDE_DIRS}
        --machine 64
        -ptx
        -O3
        --std=c++17
        --use_fast_math
        -arch=compute_75
        -DNDEBUG
        -D_CRT_SECURE_NO_WARNINGS
        -DNOMINMAX
        -DLESS_OPTIX_BACKWARD_STANDARD_ONLY
        -DLESS_FIELD_CONSTANT_ONLY
        -DLESS_OPTIX_SIF_ONLY
        -o ${BACKWARD_STATE_SIF_PTX}
        ${CMAKE_CURRENT_SOURCE_DIR}/src/integrators/backward_optical/optix/raygen_backward.cu
    DEPENDS
        ${CMAKE_CURRENT_SOURCE_DIR}/src/integrators/backward_optical/optix/raygen_backward.cu
        ${OPTICAL_PTX_ABI_HEADERS}
    COMMENT "Compiling PTX: raygen_backward_state_sif.cu"
    VERBATIM
)
list(APPEND PTX_TARGETS ${BACKWARD_STATE_SIF_PTX})

set(BACKWARD_STATE_SIF_OPTIXIR
    "${OPTIX_KERNEL_OUTPUT_DIR}/backward_optical_sif_state.optixir")
add_custom_command(
    OUTPUT ${BACKWARD_STATE_SIF_OPTIXIR}
    COMMAND ${CUDAToolkit_NVCC_EXECUTABLE}
        ${KERNEL_INCLUDE_DIRS}
        --machine 64
        --optix-ir
        -O3
        --std=c++17
        --use_fast_math
        -arch=compute_75
        -DNDEBUG
        -D_CRT_SECURE_NO_WARNINGS
        -DNOMINMAX
        -DLESS_OPTIX_BACKWARD_STANDARD_ONLY
        -DLESS_FIELD_CONSTANT_ONLY
        -DLESS_OPTIX_SIF_ONLY
        -o ${BACKWARD_STATE_SIF_OPTIXIR}
        ${CMAKE_CURRENT_SOURCE_DIR}/src/integrators/backward_optical/optix/raygen_backward.cu
    DEPENDS
        ${CMAKE_CURRENT_SOURCE_DIR}/src/integrators/backward_optical/optix/raygen_backward.cu
        ${OPTICAL_PTX_ABI_HEADERS}
    COMMENT "Compiling OptiX IR: raygen_backward_state_sif.cu"
    VERBATIM
)
list(APPEND OPTIXIR_TARGETS ${BACKWARD_STATE_SIF_OPTIXIR})

# Thermal imaging uses the same Constant/State Field contract but none of the
# reflected-solar or fluorescence source branches.  It receives an equally
# narrow module so live temperature publication cannot inflate optical/SIF
# startup and vice versa.
set(BACKWARD_THERMAL_PTX
    "${OPTIX_KERNEL_OUTPUT_DIR}/backward_optical_thermal.ptx")
add_custom_command(
    OUTPUT ${BACKWARD_THERMAL_PTX}
    COMMAND ${CUDAToolkit_NVCC_EXECUTABLE}
        ${KERNEL_INCLUDE_DIRS}
        --machine 64
        -ptx
        -O3
        --std=c++17
        --use_fast_math
        -arch=compute_75
        -DNDEBUG
        -D_CRT_SECURE_NO_WARNINGS
        -DNOMINMAX
        -DLESS_OPTIX_BACKWARD_STANDARD_ONLY
        -DLESS_FIELD_CONSTANT_ONLY
        -DLESS_OPTIX_THERMAL_ONLY
        -o ${BACKWARD_THERMAL_PTX}
        ${CMAKE_CURRENT_SOURCE_DIR}/src/integrators/backward_optical/optix/raygen_backward.cu
    DEPENDS
        ${CMAKE_CURRENT_SOURCE_DIR}/src/integrators/backward_optical/optix/raygen_backward.cu
        ${OPTICAL_PTX_ABI_HEADERS}
    COMMENT "Compiling PTX: raygen_backward_thermal.cu"
    VERBATIM
)
list(APPEND PTX_TARGETS ${BACKWARD_THERMAL_PTX})

set(BACKWARD_THERMAL_OPTIXIR
    "${OPTIX_KERNEL_OUTPUT_DIR}/backward_optical_thermal.optixir")
add_custom_command(
    OUTPUT ${BACKWARD_THERMAL_OPTIXIR}
    COMMAND ${CUDAToolkit_NVCC_EXECUTABLE}
        ${KERNEL_INCLUDE_DIRS}
        --machine 64
        --optix-ir
        -O3
        --std=c++17
        --use_fast_math
        -arch=compute_75
        -DNDEBUG
        -D_CRT_SECURE_NO_WARNINGS
        -DNOMINMAX
        -DLESS_OPTIX_BACKWARD_STANDARD_ONLY
        -DLESS_FIELD_CONSTANT_ONLY
        -DLESS_OPTIX_THERMAL_ONLY
        -o ${BACKWARD_THERMAL_OPTIXIR}
        ${CMAKE_CURRENT_SOURCE_DIR}/src/integrators/backward_optical/optix/raygen_backward.cu
    DEPENDS
        ${CMAKE_CURRENT_SOURCE_DIR}/src/integrators/backward_optical/optix/raygen_backward.cu
        ${OPTICAL_PTX_ABI_HEADERS}
    COMMENT "Compiling OptiX IR: raygen_backward_thermal.cu"
    VERBATIM
)
list(APPEND OPTIXIR_TARGETS ${BACKWARD_THERMAL_OPTIXIR})

# Measured temperature/emissivity Texture Fields need the thermal source path
# and sampled interpolation, but neither RGB reconstruction nor reflected
# solar/SIF branches.  Keeping this distinct from the generic sampled module
# prevents thermal first use from paying for unreachable passive sources.
set(BACKWARD_THERMAL_SAMPLED_PTX
    "${OPTIX_KERNEL_OUTPUT_DIR}/backward_optical_thermal_sampled.ptx")
add_custom_command(
    OUTPUT ${BACKWARD_THERMAL_SAMPLED_PTX}
    COMMAND ${CUDAToolkit_NVCC_EXECUTABLE}
        ${KERNEL_INCLUDE_DIRS}
        --machine 64
        -ptx
        -O3
        --std=c++17
        --use_fast_math
        -arch=compute_75
        -DNDEBUG
        -D_CRT_SECURE_NO_WARNINGS
        -DNOMINMAX
        -DLESS_OPTIX_BACKWARD_STANDARD_ONLY
        -DLESS_FIELD_ENABLE_RGB2SPEC=0
        -DLESS_OPTIX_THERMAL_ONLY
        -o ${BACKWARD_THERMAL_SAMPLED_PTX}
        ${CMAKE_CURRENT_SOURCE_DIR}/src/integrators/backward_optical/optix/raygen_backward.cu
    DEPENDS
        ${CMAKE_CURRENT_SOURCE_DIR}/src/integrators/backward_optical/optix/raygen_backward.cu
        ${OPTICAL_PTX_ABI_HEADERS}
    COMMENT "Compiling PTX: raygen_backward_thermal_sampled.cu"
    VERBATIM
)
list(APPEND PTX_TARGETS ${BACKWARD_THERMAL_SAMPLED_PTX})

set(BACKWARD_THERMAL_SAMPLED_OPTIXIR
    "${OPTIX_KERNEL_OUTPUT_DIR}/backward_optical_thermal_sampled.optixir")
add_custom_command(
    OUTPUT ${BACKWARD_THERMAL_SAMPLED_OPTIXIR}
    COMMAND ${CUDAToolkit_NVCC_EXECUTABLE}
        ${KERNEL_INCLUDE_DIRS}
        --machine 64
        --optix-ir
        -O3
        --std=c++17
        --use_fast_math
        -arch=compute_75
        -DNDEBUG
        -D_CRT_SECURE_NO_WARNINGS
        -DNOMINMAX
        -DLESS_OPTIX_BACKWARD_STANDARD_ONLY
        -DLESS_FIELD_ENABLE_RGB2SPEC=0
        -DLESS_OPTIX_THERMAL_ONLY
        -o ${BACKWARD_THERMAL_SAMPLED_OPTIXIR}
        ${CMAKE_CURRENT_SOURCE_DIR}/src/integrators/backward_optical/optix/raygen_backward.cu
    DEPENDS
        ${CMAKE_CURRENT_SOURCE_DIR}/src/integrators/backward_optical/optix/raygen_backward.cu
        ${OPTICAL_PTX_ABI_HEADERS}
    COMMENT "Compiling OptiX IR: raygen_backward_thermal_sampled.cu"
    VERBATIM
)
list(APPEND OPTIXIR_TARGETS ${BACKWARD_THERMAL_SAMPLED_OPTIXIR})

# Turbid and mesh reverse transport share the runtime-sized generic module.
# The entry point remains specialized, but spectral capacity is request-owned
# and no longer creates a matrix of compiled artifacts.
list(REMOVE_ITEM OPTICAL_PTX_ABI_HEADERS
    ${less_sensor_optix_dependencies} ${less_surface_optix_dependencies}
    ${less_runtime_turbid_leaf_medium_optix_dependencies} ${less_phase_optix_dependencies}
    ${less_product_optical_image_optix_dependencies} ${less_illumination_optix_dependencies} ${less_runtime_source_composition_optix_dependencies} ${less_runtime_surface_fluorescence_emission_optix_dependencies} ${less_runtime_thermal_surface_emission_optix_dependencies})

# ---- Microwave PTX compilation (same pattern, independent include paths) ----
foreach(KERNEL_SRC ${MW_KERNEL_SOURCES})
    get_filename_component(KERNEL_NAME ${KERNEL_SRC} NAME_WE)
    if(KERNEL_NAME STREQUAL "raygen_passive")
        set(KERNEL_OUTPUT_NAME "passive_microwave")
    elseif(KERNEL_NAME STREQUAL "raygen_active")
        set(KERNEL_OUTPUT_NAME "active_microwave")
    elseif(KERNEL_NAME STREQUAL "closesthit_mw")
        set(KERNEL_OUTPUT_NAME "microwave_hit")
    elseif(KERNEL_NAME STREQUAL "miss_mw")
        set(KERNEL_OUTPUT_NAME "microwave_miss")
    endif()
    set(PTX_FILE "${OPTIX_KERNEL_OUTPUT_DIR}/${KERNEL_OUTPUT_NAME}.ptx")
    set(MW_KERNEL_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${KERNEL_SRC}
        ${MW_PERIODIC_HEADER} ${less_surface_microwave_dependencies})
    less_append_microwave_acquisition_optix_dependencies(MW_KERNEL_DEPENDS "${KERNEL_NAME}")

    add_custom_command(
        OUTPUT ${PTX_FILE}
        COMMAND ${CUDAToolkit_NVCC_EXECUTABLE}
            ${MW_KERNEL_INCLUDE_DIRS}
            --machine 64
            -ptx
            -O3
            --std=c++17
            --use_fast_math
            -arch=compute_75
            -DNDEBUG
            -D_CRT_SECURE_NO_WARNINGS
            -DNOMINMAX
            -o ${PTX_FILE}
            ${CMAKE_CURRENT_SOURCE_DIR}/${KERNEL_SRC}
        DEPENDS ${MW_KERNEL_DEPENDS}
        COMMENT "Compiling MW PTX: ${KERNEL_NAME}.cu"
        VERBATIM
    )
    list(APPEND PTX_TARGETS ${PTX_FILE})

    set(OPTIXIR_FILE "${OPTIX_KERNEL_OUTPUT_DIR}/${KERNEL_OUTPUT_NAME}.optixir")
    add_custom_command(
        OUTPUT ${OPTIXIR_FILE}
        COMMAND ${CUDAToolkit_NVCC_EXECUTABLE}
            ${MW_KERNEL_INCLUDE_DIRS}
            --machine 64
            --optix-ir
            -O3
            --std=c++17
            --use_fast_math
            -arch=compute_75
            -DNDEBUG
            -D_CRT_SECURE_NO_WARNINGS
            -DNOMINMAX
            -o ${OPTIXIR_FILE}
            ${CMAKE_CURRENT_SOURCE_DIR}/${KERNEL_SRC}
        DEPENDS ${MW_KERNEL_DEPENDS}
        COMMENT "Compiling MW OptiX IR: ${KERNEL_NAME}.cu"
        VERBATIM
    )
    list(APPEND OPTIXIR_TARGETS ${OPTIXIR_FILE})
endforeach()

add_custom_target(optix_ptx_modules ALL DEPENDS ${PTX_TARGETS})
add_custom_target(optix_ir_modules ALL DEPENDS ${OPTIXIR_TARGETS})
endif()

# ---- Vulkan Ray Query backend ----
if(LESS_ENABLE_VULKAN)
    if(LESS_USE_PRECOMPILED_SPV)
        set(VULKAN_KERNEL_OUTPUT_DIR
            "${CMAKE_CURRENT_SOURCE_DIR}/src/backends/vulkan/precompiled")
        set(VULKAN_TRACE_SPV "${VULKAN_KERNEL_OUTPUT_DIR}/ray_query_trace.spv")
        set(VULKAN_BACKWARD_SPV_TARGETS
            "${VULKAN_KERNEL_OUTPUT_DIR}/backward_optical_constant.spv"
            "${VULKAN_KERNEL_OUTPUT_DIR}/backward_optical_state.spv"
            "${VULKAN_KERNEL_OUTPUT_DIR}/backward_optical_sampled.spv"
            "${VULKAN_KERNEL_OUTPUT_DIR}/backward_optical.spv"
            "${VULKAN_KERNEL_OUTPUT_DIR}/backward_optical_thermal_constant.spv"
            "${VULKAN_KERNEL_OUTPUT_DIR}/backward_optical_thermal_sampled.spv"
            "${VULKAN_KERNEL_OUTPUT_DIR}/backward_optical_thermal.spv"
            "${VULKAN_KERNEL_OUTPUT_DIR}/backward_optical_turbid_reflected_sampled.spv"
            "${VULKAN_KERNEL_OUTPUT_DIR}/backward_optical_turbid_reflected.spv"
            "${VULKAN_KERNEL_OUTPUT_DIR}/backward_optical_turbid.spv"
            "${VULKAN_KERNEL_OUTPUT_DIR}/backward_optical_wavefront_trace.spv"
            "${VULKAN_KERNEL_OUTPUT_DIR}/backward_optical_wavefront_diffuse_trace.spv"
            "${VULKAN_KERNEL_OUTPUT_DIR}/backward_optical_wavefront_resolve.spv")
        set(VULKAN_FLUX_SPV "${VULKAN_KERNEL_OUTPUT_DIR}/forward_flux.spv")
        set(VULKAN_FLUX_CONSTANT_SPV
            "${VULKAN_KERNEL_OUTPUT_DIR}/forward_flux_constant.spv")
        set(VULKAN_EB_SPV "${VULKAN_KERNEL_OUTPUT_DIR}/energy_balance_radiation.spv")
        set(VULKAN_EB_CONSTANT_SPV
            "${VULKAN_KERNEL_OUTPUT_DIR}/energy_balance_radiation_constant.spv")
        set(VULKAN_EB_PARTITION_SPV
            "${VULKAN_KERNEL_OUTPUT_DIR}/energy_balance_partition.spv")
        set(VULKAN_MICROWAVE_SPV
            "${VULKAN_KERNEL_OUTPUT_DIR}/active_microwave.spv")
        set(VULKAN_TURBID_RRT_SPV
            "${VULKAN_KERNEL_OUTPUT_DIR}/turbid_leaf_rrt.spv")
        set(VULKAN_TURBID_SAMPLING_SPV
            "${VULKAN_KERNEL_OUTPUT_DIR}/turbid_sampling.spv")
        set(VULKAN_TURBID_STATE_SPV
            "${VULKAN_KERNEL_OUTPUT_DIR}/turbid_state.spv")
        set(VULKAN_TURBID_FIELD_ASSEMBLE_SPV
            "${VULKAN_KERNEL_OUTPUT_DIR}/turbid_field_assemble.spv")
        set(VULKAN_TURBID_FIELD_LOOKUP_SPV
            "${VULKAN_KERNEL_OUTPUT_DIR}/turbid_field_lookup.spv")
        set(VULKAN_TURBID_OBSERVATION_SOURCE_SPV
            "${VULKAN_KERNEL_OUTPUT_DIR}/turbid_observation_source.spv")
        set(VULKAN_TURBID_RADIATION_ASSEMBLE_SPV
            "${VULKAN_KERNEL_OUTPUT_DIR}/turbid_radiation_assemble.spv")
        set(VULKAN_TURBID_PHYSIOLOGY_ASSEMBLE_SPV
            "${VULKAN_KERNEL_OUTPUT_DIR}/turbid_physiology_assemble.spv")
        set(VULKAN_TURBID_ENERGY_BALANCE_ASSEMBLE_SPV
            "${VULKAN_KERNEL_OUTPUT_DIR}/turbid_energy_balance_assemble.spv")
        set(VULKAN_JOINT_THERMO_PHYSIOLOGY_SPV
            "${VULKAN_KERNEL_OUTPUT_DIR}/joint_thermo_physiology.spv")
        set(VULKAN_LIDAR_SPV
            "${VULKAN_KERNEL_OUTPUT_DIR}/time_resolved_active.spv")
        set(VULKAN_LIDAR_CONSTANT_SPV
            "${VULKAN_KERNEL_OUTPUT_DIR}/time_resolved_active_constant.spv")
        set(VULKAN_LIDAR_DIRECT_CONSTANT_SPV
            "${VULKAN_KERNEL_OUTPUT_DIR}/time_resolved_active_direct_constant.spv")
        set(VULKAN_PHYSIOLOGY_SPV
            "${VULKAN_KERNEL_OUTPUT_DIR}/physiology.spv")
        set(VULKAN_EB_PROCESS_SPV
            "${VULKAN_KERNEL_OUTPUT_DIR}/energy_balance_process.spv")
        set(VULKAN_PHOTOSYNTHESIS_PROCESS_SPV
            "${VULKAN_KERNEL_OUTPUT_DIR}/photosynthesis_process.spv")
        set(VULKAN_SIF_PROCESS_SPV
            "${VULKAN_KERNEL_OUTPUT_DIR}/sif_process.spv")
        set(VULKAN_SIF_ASSEMBLY_SPV
            "${VULKAN_KERNEL_OUTPUT_DIR}/sif_source_assembly.spv")
        set(VULKAN_MESH_SIF_ASSEMBLY_SPV
            "${VULKAN_KERNEL_OUTPUT_DIR}/mesh_sif_source_assembly.spv")
        set(VULKAN_SPV_TARGETS "${VULKAN_TRACE_SPV}"
            ${VULKAN_BACKWARD_SPV_TARGETS}
            "${VULKAN_FLUX_SPV}" "${VULKAN_FLUX_CONSTANT_SPV}"
            "${VULKAN_EB_SPV}" "${VULKAN_EB_CONSTANT_SPV}"
            "${VULKAN_EB_PARTITION_SPV}"
            "${VULKAN_MICROWAVE_SPV}" "${VULKAN_TURBID_RRT_SPV}"
            "${VULKAN_TURBID_SAMPLING_SPV}"
            "${VULKAN_TURBID_STATE_SPV}"
            "${VULKAN_TURBID_FIELD_ASSEMBLE_SPV}"
            "${VULKAN_TURBID_FIELD_LOOKUP_SPV}"
            "${VULKAN_TURBID_OBSERVATION_SOURCE_SPV}"
            "${VULKAN_TURBID_RADIATION_ASSEMBLE_SPV}"
            "${VULKAN_TURBID_PHYSIOLOGY_ASSEMBLE_SPV}"
            "${VULKAN_TURBID_ENERGY_BALANCE_ASSEMBLE_SPV}"
            "${VULKAN_JOINT_THERMO_PHYSIOLOGY_SPV}"
            "${VULKAN_LIDAR_SPV}" "${VULKAN_LIDAR_CONSTANT_SPV}"
            "${VULKAN_LIDAR_DIRECT_CONSTANT_SPV}"
            "${VULKAN_PHYSIOLOGY_SPV}"
            "${VULKAN_EB_PROCESS_SPV}"
            "${VULKAN_PHOTOSYNTHESIS_PROCESS_SPV}"
            "${VULKAN_SIF_PROCESS_SPV}"
            "${VULKAN_SIF_ASSEMBLY_SPV}"
            "${VULKAN_MESH_SIF_ASSEMBLY_SPV}")
        foreach(SPV_FILE IN LISTS VULKAN_SPV_TARGETS)
            if(NOT EXISTS "${SPV_FILE}")
                message(FATAL_ERROR "Precompiled SPIR-V shader missing: ${SPV_FILE}")
            endif()
        endforeach()
        add_custom_target(vulkan_kernel_modules ALL DEPENDS ${VULKAN_SPV_TARGETS})
    else()
    find_program(LESS_SLANGC_EXECUTABLE NAMES slangc HINTS
        "$ENV{VULKAN_SDK}/Bin"
        "${Vulkan_INCLUDE_DIRS}/../Bin"
        REQUIRED)
    set(VULKAN_KERNEL_OUTPUT_DIR "${CMAKE_BINARY_DIR}/kernels/vulkan")
    file(MAKE_DIRECTORY "${VULKAN_KERNEL_OUTPUT_DIR}")
    set(VULKAN_TRACE_SHADER
        "${CMAKE_CURRENT_SOURCE_DIR}/src/backends/vulkan/shaders/trace_closest.slang")
    set(VULKAN_TRACE_SPV "${VULKAN_KERNEL_OUTPUT_DIR}/ray_query_trace.spv")
    set(VULKAN_BACKWARD_SHADER
        "${CMAKE_CURRENT_SOURCE_DIR}/src/integrators/backward_optical/vulkan/kernel.slang")
    set(VULKAN_BACKWARD_SLANGC "${LESS_SLANGC_EXECUTABLE}"

        -I "${less_surface_generated_shader_dir}"
        -I "${less_illumination_generated_shader_dir}"
        -I "${less_sensor_generated_shader_dir}"
        -I "${CMAKE_CURRENT_SOURCE_DIR}/src/backends/vulkan/shaders"
        -I "${CMAKE_CURRENT_SOURCE_DIR}")
    set(VULKAN_TRANSPORT_COMMON "${CMAKE_CURRENT_SOURCE_DIR}/src/backends/vulkan/shaders/transport_common.slang")
    set(VULKAN_TRANSPORT_COMMON_DEPENDS "${VULKAN_TRANSPORT_COMMON}" ${less_runtime_turbid_leaf_medium_vulkan_dependencies} ${less_phase_vulkan_dependencies})
    set(VULKAN_FIELD_CONTRACT
        "${CMAKE_CURRENT_SOURCE_DIR}/src/backends/vulkan/shaders/field.slang")
    set(VULKAN_BACKWARD_DEPENDS "${VULKAN_BACKWARD_SHADER}"
        ${VULKAN_TRANSPORT_COMMON_DEPENDS} "${VULKAN_FIELD_CONTRACT}"
        ${less_runtime_backward_integrator_vulkan_dependencies}
        ${less_sensor_vulkan_dependencies}
        ${less_surface_vulkan_dependencies}
        ${less_surface_vulkan_dependencies} ${less_illumination_vulkan_dependencies} ${less_runtime_source_composition_vulkan_dependencies} ${less_runtime_surface_fluorescence_emission_vulkan_dependencies} ${less_runtime_thermal_surface_emission_vulkan_dependencies} ${less_product_optical_image_vulkan_dependencies})
    set(VULKAN_BACKWARD_SPV_TARGETS)
    set(OPTICAL_GROUP_X 8)
    set(OPTICAL_GROUP_Y 8)
        set(VULKAN_BACKWARD_SPV
            "${VULKAN_KERNEL_OUTPUT_DIR}/backward_optical.spv")
        set(VULKAN_BACKWARD_CONSTANT_SPV
            "${VULKAN_KERNEL_OUTPUT_DIR}/backward_optical_constant.spv")
        set(VULKAN_BACKWARD_STATE_SPV
            "${VULKAN_KERNEL_OUTPUT_DIR}/backward_optical_state.spv")
        set(VULKAN_BACKWARD_SAMPLED_SPV
            "${VULKAN_KERNEL_OUTPUT_DIR}/backward_optical_sampled.spv")
        set(VULKAN_BACKWARD_THERMAL_SPV
            "${VULKAN_KERNEL_OUTPUT_DIR}/backward_optical_thermal.spv")
        set(VULKAN_BACKWARD_THERMAL_CONSTANT_SPV
            "${VULKAN_KERNEL_OUTPUT_DIR}/backward_optical_thermal_constant.spv")
        set(VULKAN_BACKWARD_THERMAL_SAMPLED_SPV
            "${VULKAN_KERNEL_OUTPUT_DIR}/backward_optical_thermal_sampled.spv")
        set(VULKAN_BACKWARD_TURBID_SPV
            "${VULKAN_KERNEL_OUTPUT_DIR}/backward_optical_turbid.spv")
        set(VULKAN_BACKWARD_TURBID_OPTICAL_SPV
            "${VULKAN_KERNEL_OUTPUT_DIR}/backward_optical_turbid_reflected.spv")
        set(VULKAN_BACKWARD_TURBID_OPTICAL_SAMPLED_SPV
            "${VULKAN_KERNEL_OUTPUT_DIR}/backward_optical_turbid_reflected_sampled.spv")
        set(VULKAN_WAVEFRONT_TRACE_SPV
            "${VULKAN_KERNEL_OUTPUT_DIR}/backward_optical_wavefront_trace.spv")
        set(VULKAN_WAVEFRONT_DIFFUSE_TRACE_SPV
            "${VULKAN_KERNEL_OUTPUT_DIR}/backward_optical_wavefront_diffuse_trace.spv")
        set(VULKAN_WAVEFRONT_RESOLVE_SPV
            "${VULKAN_KERNEL_OUTPUT_DIR}/backward_optical_wavefront_resolve.spv")
        add_custom_command(
            OUTPUT
                "${VULKAN_BACKWARD_CONSTANT_SPV}"
                "${VULKAN_WAVEFRONT_TRACE_SPV}"
                "${VULKAN_WAVEFRONT_DIFFUSE_TRACE_SPV}"
                "${VULKAN_WAVEFRONT_RESOLVE_SPV}"
            COMMAND ${VULKAN_BACKWARD_SLANGC}
                "${VULKAN_BACKWARD_SHADER}"
                -entry backwardOptical
                -stage compute
                -target spirv
                -profile spirv_1_5
                -capability spvRayQueryKHR
                -fvk-use-entrypoint-name
                -DLESS_OPTICAL_ENABLE_TURBID=0
                -DLESS_VULKAN_REFLECTED_SOLAR_ONLY=1
                -DLESS_FIELD_CONSTANT_ONLY=1
                -DLESS_FIELD_DYNAMIC_PACKET_LOOPS=1
                -DLESS_GROUP_X=${OPTICAL_GROUP_X}
                -DLESS_GROUP_Y=${OPTICAL_GROUP_Y}
                -O3
                -o "${VULKAN_BACKWARD_CONSTANT_SPV}"
            COMMAND ${VULKAN_BACKWARD_SLANGC}
                "${VULKAN_BACKWARD_SHADER}"
                -entry wavefrontOpticalTrace
                -stage compute
                -target spirv
                -profile spirv_1_5
                -capability spvRayQueryKHR
                -fvk-use-entrypoint-name
                -DLESS_OPTICAL_ENABLE_TURBID=0
                -DLESS_VULKAN_REFLECTED_SOLAR_ONLY=1
                -DLESS_FIELD_CONSTANT_ONLY=1
                -DLESS_GROUP_X=8
                -DLESS_GROUP_Y=8
                -O3
                -o "${VULKAN_WAVEFRONT_TRACE_SPV}"
            COMMAND ${VULKAN_BACKWARD_SLANGC}
                "${VULKAN_BACKWARD_SHADER}"
                -entry wavefrontOpticalTrace
                -stage compute
                -target spirv
                -profile spirv_1_5
                -capability spvRayQueryKHR
                -fvk-use-entrypoint-name
                -DLESS_OPTICAL_ENABLE_TURBID=0
                -DLESS_VULKAN_REFLECTED_SOLAR_ONLY=1
                -DLESS_FIELD_CONSTANT_ONLY=1
                -DLESS_WAVEFRONT_DIFFUSE_ONLY=1
                -DLESS_GROUP_X=8
                -DLESS_GROUP_Y=8
                -O3
                -o "${VULKAN_WAVEFRONT_DIFFUSE_TRACE_SPV}"
            COMMAND ${VULKAN_BACKWARD_SLANGC}
                "${VULKAN_BACKWARD_SHADER}"
                -entry wavefrontOpticalResolve
                -stage compute
                -target spirv
                -profile spirv_1_5
                -capability spvRayQueryKHR
                -fvk-use-entrypoint-name
                -DLESS_OPTICAL_ENABLE_TURBID=0
                -DLESS_VULKAN_REFLECTED_SOLAR_ONLY=1
                -DLESS_FIELD_CONSTANT_ONLY=1
                -DLESS_GROUP_X=8
                -DLESS_GROUP_Y=8
                -O3
                -o "${VULKAN_WAVEFRONT_RESOLVE_SPV}"
            DEPENDS ${VULKAN_BACKWARD_DEPENDS}
            COMMENT "Compiling Vulkan constant/dynamic wavefront optical SPIR-V"
            VERBATIM
        )
        add_custom_command(
            OUTPUT "${VULKAN_BACKWARD_SAMPLED_SPV}"
            COMMAND ${VULKAN_BACKWARD_SLANGC}
                "${VULKAN_BACKWARD_SHADER}"
                -entry backwardOptical
                -stage compute
                -target spirv
                -profile spirv_1_5
                -capability spvRayQueryKHR
                -fvk-use-entrypoint-name
                -DLESS_OPTICAL_ENABLE_TURBID=0
                -DLESS_VULKAN_REFLECTED_SOLAR_ONLY=1
                -DLESS_FIELD_ENABLE_RGB2SPEC=0
                -DLESS_FIELD_INLINE_SPATIAL_CLOSURES=1
                -DLESS_FIELD_DYNAMIC_PACKET_LOOPS=1
                -DLESS_GROUP_X=${OPTICAL_GROUP_X}
                -DLESS_GROUP_Y=${OPTICAL_GROUP_Y}
                -O3
                -o "${VULKAN_BACKWARD_SAMPLED_SPV}"
            DEPENDS ${VULKAN_BACKWARD_DEPENDS}
            COMMENT "Compiling Vulkan SPIR-V: backward_optical.slang (sampled, dynamic bands)"
            VERBATIM
        )
        add_custom_command(
            OUTPUT "${VULKAN_BACKWARD_THERMAL_CONSTANT_SPV}"
            COMMAND ${VULKAN_BACKWARD_SLANGC}
                "${VULKAN_BACKWARD_SHADER}"
                -entry backwardOptical
                -stage compute
                -target spirv
                -profile spirv_1_5
                -capability spvRayQueryKHR
                -fvk-use-entrypoint-name
                -DLESS_OPTICAL_ENABLE_TURBID=0
                -DLESS_VULKAN_THERMAL_ONLY=1
                -DLESS_FIELD_CONSTANT_ONLY=1
                -DLESS_FIELD_DYNAMIC_PACKET_LOOPS=1
                -DLESS_GROUP_X=${OPTICAL_GROUP_X}
                -DLESS_GROUP_Y=${OPTICAL_GROUP_Y}
                -O3
                -o "${VULKAN_BACKWARD_THERMAL_CONSTANT_SPV}"
            DEPENDS ${VULKAN_BACKWARD_DEPENDS}
            COMMENT "Compiling Vulkan SPIR-V: backward_optical.slang (thermal constant, dynamic bands)"
            VERBATIM
        )
        add_custom_command(
            OUTPUT "${VULKAN_BACKWARD_THERMAL_SAMPLED_SPV}"
            COMMAND ${VULKAN_BACKWARD_SLANGC}
                "${VULKAN_BACKWARD_SHADER}"
                -entry backwardOptical
                -stage compute
                -target spirv
                -profile spirv_1_5
                -capability spvRayQueryKHR
                -fvk-use-entrypoint-name
                -DLESS_OPTICAL_ENABLE_TURBID=0
                -DLESS_VULKAN_THERMAL_ONLY=1
                -DLESS_FIELD_ENABLE_RGB2SPEC=0
                -DLESS_FIELD_INLINE_SPATIAL_CLOSURES=1
                -DLESS_FIELD_DYNAMIC_PACKET_LOOPS=1
                -DLESS_GROUP_X=${OPTICAL_GROUP_X}
                -DLESS_GROUP_Y=${OPTICAL_GROUP_Y}
                -O3
                -o "${VULKAN_BACKWARD_THERMAL_SAMPLED_SPV}"
            DEPENDS ${VULKAN_BACKWARD_DEPENDS}
            COMMENT "Compiling Vulkan SPIR-V: backward_optical.slang (thermal sampled, dynamic bands)"
            VERBATIM
        )
        add_custom_command(
            OUTPUT "${VULKAN_BACKWARD_STATE_SPV}"
            COMMAND ${VULKAN_BACKWARD_SLANGC}
                "${VULKAN_BACKWARD_SHADER}"
                -entry backwardOptical
                -stage compute
                -target spirv
                -profile spirv_1_5
                -capability spvRayQueryKHR
                -fvk-use-entrypoint-name
                -DLESS_OPTICAL_ENABLE_TURBID=0
                -DLESS_FIELD_STATE_ONLY=1
                -DLESS_GROUP_X=${OPTICAL_GROUP_X}
                -DLESS_GROUP_Y=${OPTICAL_GROUP_Y}
                -O3
                -o "${VULKAN_BACKWARD_STATE_SPV}"
            DEPENDS ${VULKAN_BACKWARD_DEPENDS}
            COMMENT "Compiling Vulkan SPIR-V: backward_optical.slang (state, dynamic bands)"
            VERBATIM
        )
        add_custom_command(
            OUTPUT "${VULKAN_BACKWARD_SPV}"
            COMMAND ${VULKAN_BACKWARD_SLANGC}
                "${VULKAN_BACKWARD_SHADER}"
                -entry backwardOptical
                -stage compute
                -target spirv
                -profile spirv_1_5
                -capability spvRayQueryKHR
                -fvk-use-entrypoint-name
                -DLESS_OPTICAL_ENABLE_TURBID=0
                -DLESS_VULKAN_REFLECTED_SOLAR_ONLY=1
                -DLESS_FIELD_INLINE_SPATIAL_CLOSURES=1
                -DLESS_FIELD_DYNAMIC_PACKET_LOOPS=1
                -DLESS_GROUP_X=${OPTICAL_GROUP_X}
                -DLESS_GROUP_Y=${OPTICAL_GROUP_Y}
                -O3
                -o "${VULKAN_BACKWARD_SPV}"
            DEPENDS ${VULKAN_BACKWARD_DEPENDS}
            COMMENT "Compiling Vulkan SPIR-V: backward_optical.slang (dynamic bands)"
            VERBATIM
        )
        add_custom_command(
            OUTPUT "${VULKAN_BACKWARD_THERMAL_SPV}"
            COMMAND ${VULKAN_BACKWARD_SLANGC}
                "${VULKAN_BACKWARD_SHADER}"
                -entry backwardOptical
                -stage compute
                -target spirv
                -profile spirv_1_5
                -capability spvRayQueryKHR
                -fvk-use-entrypoint-name
                -DLESS_OPTICAL_ENABLE_TURBID=0
                -DLESS_VULKAN_THERMAL_ONLY=1
                -DLESS_FIELD_INLINE_SPATIAL_CLOSURES=1
                -DLESS_FIELD_DYNAMIC_PACKET_LOOPS=1
                -DLESS_GROUP_X=${OPTICAL_GROUP_X}
                -DLESS_GROUP_Y=${OPTICAL_GROUP_Y}
                -O3
                -o "${VULKAN_BACKWARD_THERMAL_SPV}"
            DEPENDS ${VULKAN_BACKWARD_DEPENDS}
            COMMENT "Compiling Vulkan SPIR-V: backward_optical.slang (thermal, dynamic bands)"
            VERBATIM
        )
        add_custom_command(
            OUTPUT "${VULKAN_BACKWARD_TURBID_OPTICAL_SAMPLED_SPV}"
            COMMAND ${VULKAN_BACKWARD_SLANGC}
                "${VULKAN_BACKWARD_SHADER}"
                -entry backwardOpticalTurbid
                -stage compute
                -target spirv
                -profile spirv_1_5
                -capability spvRayQueryKHR
                -fvk-use-entrypoint-name
                -DLESS_OPTICAL_ENABLE_TURBID=1
                -DLESS_VULKAN_REFLECTED_SOLAR_ONLY=1
                -DLESS_FIELD_ENABLE_RGB2SPEC=0
                -DLESS_GROUP_X=${OPTICAL_GROUP_X}
                -DLESS_GROUP_Y=${OPTICAL_GROUP_Y}
                -O3
                -o "${VULKAN_BACKWARD_TURBID_OPTICAL_SAMPLED_SPV}"
            DEPENDS ${VULKAN_BACKWARD_DEPENDS}
            COMMENT "Compiling Vulkan SPIR-V: backward_optical.slang (Turbid optical sampled, dynamic bands)"
            VERBATIM
        )
        add_custom_command(
            OUTPUT "${VULKAN_BACKWARD_TURBID_OPTICAL_SPV}"
            COMMAND ${VULKAN_BACKWARD_SLANGC}
                "${VULKAN_BACKWARD_SHADER}"
                -entry backwardOpticalTurbid
                -stage compute
                -target spirv
                -profile spirv_1_5
                -capability spvRayQueryKHR
                -fvk-use-entrypoint-name
                -DLESS_OPTICAL_ENABLE_TURBID=1
                -DLESS_VULKAN_REFLECTED_SOLAR_ONLY=1
                -DLESS_GROUP_X=${OPTICAL_GROUP_X}
                -DLESS_GROUP_Y=${OPTICAL_GROUP_Y}
                -O3
                -o "${VULKAN_BACKWARD_TURBID_OPTICAL_SPV}"
            DEPENDS ${VULKAN_BACKWARD_DEPENDS}
            COMMENT "Compiling Vulkan SPIR-V: backward_optical.slang (Turbid optical, dynamic bands)"
            VERBATIM
        )
        add_custom_command(
            OUTPUT "${VULKAN_BACKWARD_TURBID_SPV}"
            COMMAND ${VULKAN_BACKWARD_SLANGC}
                "${VULKAN_BACKWARD_SHADER}"
                -entry backwardOpticalTurbid
                -stage compute
                -target spirv
                -profile spirv_1_5
                -capability spvRayQueryKHR
                -fvk-use-entrypoint-name
                -DLESS_OPTICAL_ENABLE_TURBID=1
                -DLESS_GROUP_X=${OPTICAL_GROUP_X}
                -DLESS_GROUP_Y=${OPTICAL_GROUP_Y}
                -O3
                -o "${VULKAN_BACKWARD_TURBID_SPV}"
            DEPENDS ${VULKAN_BACKWARD_DEPENDS}
            COMMENT "Compiling Vulkan SPIR-V: backward_optical.slang (Turbid, dynamic bands)"
            VERBATIM
        )
        list(APPEND VULKAN_BACKWARD_SPV_TARGETS
            "${VULKAN_BACKWARD_CONSTANT_SPV}"
            "${VULKAN_BACKWARD_STATE_SPV}"
            "${VULKAN_BACKWARD_SAMPLED_SPV}"
            "${VULKAN_BACKWARD_SPV}"
            "${VULKAN_BACKWARD_THERMAL_CONSTANT_SPV}"
            "${VULKAN_BACKWARD_THERMAL_SAMPLED_SPV}"
            "${VULKAN_BACKWARD_THERMAL_SPV}"
            "${VULKAN_BACKWARD_TURBID_OPTICAL_SAMPLED_SPV}"
            "${VULKAN_BACKWARD_TURBID_OPTICAL_SPV}"
            "${VULKAN_BACKWARD_TURBID_SPV}"
            "${VULKAN_WAVEFRONT_TRACE_SPV}"
            "${VULKAN_WAVEFRONT_DIFFUSE_TRACE_SPV}"
            "${VULKAN_WAVEFRONT_RESOLVE_SPV}")
    set(VULKAN_FLUX_SHADER
        "${CMAKE_CURRENT_SOURCE_DIR}/src/integrators/forward_optical/vulkan/kernel.slang")
    set(VULKAN_FLUX_SPV "${VULKAN_KERNEL_OUTPUT_DIR}/forward_flux.spv")
    set(VULKAN_FLUX_CONSTANT_SPV
        "${VULKAN_KERNEL_OUTPUT_DIR}/forward_flux_constant.spv")
    set(VULKAN_EB_SHADER
        "${CMAKE_CURRENT_SOURCE_DIR}/src/integrators/energy_balance_radiation/vulkan/kernel.slang")
    set(VULKAN_EB_SPV "${VULKAN_KERNEL_OUTPUT_DIR}/energy_balance_radiation.spv")
    set(VULKAN_EB_CONSTANT_SPV "${VULKAN_KERNEL_OUTPUT_DIR}/energy_balance_radiation_constant.spv")
    set(VULKAN_EB_PARTITION_SPV "${VULKAN_KERNEL_OUTPUT_DIR}/energy_balance_partition.spv")
    set(VULKAN_MICROWAVE_SHADER
        "${CMAKE_CURRENT_SOURCE_DIR}/src/backends/vulkan/shaders/microwave_wavefront.slang")
    set(VULKAN_MICROWAVE_SPV
        "${VULKAN_KERNEL_OUTPUT_DIR}/active_microwave.spv")
    set(VULKAN_TURBID_RRT_SHADER
        "${CMAKE_CURRENT_SOURCE_DIR}/src/integrators/energy_balance_radiation/vulkan/leaf_rrt.slang")
    set(VULKAN_TURBID_RRT_SPV
        "${VULKAN_KERNEL_OUTPUT_DIR}/turbid_leaf_rrt.spv")
    set(VULKAN_TURBID_SAMPLING_SHADER
        "${CMAKE_CURRENT_SOURCE_DIR}/src/media/turbid/vulkan/sampling.slang")
    set(VULKAN_TURBID_SAMPLING_SPV
        "${VULKAN_KERNEL_OUTPUT_DIR}/turbid_sampling.spv")
    set(VULKAN_TURBID_STATE_SHADER
        "${CMAKE_CURRENT_SOURCE_DIR}/src/media/turbid/vulkan/state.slang")
    set(VULKAN_TURBID_STATE_SPV
        "${VULKAN_KERNEL_OUTPUT_DIR}/turbid_state.spv")
    set(VULKAN_TURBID_FIELD_ASSEMBLE_SHADER
        "${CMAKE_CURRENT_SOURCE_DIR}/src/media/turbid/vulkan/field_assemble.slang")
    set(VULKAN_TURBID_FIELD_ASSEMBLE_SPV
        "${VULKAN_KERNEL_OUTPUT_DIR}/turbid_field_assemble.spv")
    set(VULKAN_TURBID_FIELD_LOOKUP_SHADER
        "${CMAKE_CURRENT_SOURCE_DIR}/src/media/turbid/vulkan/field_lookup.slang")
    set(VULKAN_TURBID_FIELD_LOOKUP_SPV
        "${VULKAN_KERNEL_OUTPUT_DIR}/turbid_field_lookup.spv")
    set(VULKAN_TURBID_OBSERVATION_SOURCE_SHADER
        "${CMAKE_CURRENT_SOURCE_DIR}/src/media/turbid/vulkan/observation_source.slang")
    set(VULKAN_TURBID_OBSERVATION_SOURCE_SPV
        "${VULKAN_KERNEL_OUTPUT_DIR}/turbid_observation_source.spv")
    set(VULKAN_TURBID_RADIATION_ASSEMBLE_SHADER
        "${CMAKE_CURRENT_SOURCE_DIR}/src/processes/radiation_field/vulkan.slang")
    set(VULKAN_TURBID_RADIATION_ASSEMBLE_SPV
        "${VULKAN_KERNEL_OUTPUT_DIR}/turbid_radiation_assemble.spv")
    set(VULKAN_TURBID_PHYSIOLOGY_ASSEMBLE_SHADER
        "${CMAKE_CURRENT_SOURCE_DIR}/src/processes/photosynthesis/vulkan/turbid_assemble.slang")
    set(VULKAN_TURBID_PHYSIOLOGY_ASSEMBLE_SPV
        "${VULKAN_KERNEL_OUTPUT_DIR}/turbid_physiology_assemble.spv")
    set(VULKAN_TURBID_ENERGY_BALANCE_ASSEMBLE_SHADER
        "${CMAKE_CURRENT_SOURCE_DIR}/src/processes/energy_balance/vulkan/turbid_assemble.slang")
    set(VULKAN_TURBID_ENERGY_BALANCE_ASSEMBLE_SPV
        "${VULKAN_KERNEL_OUTPUT_DIR}/turbid_energy_balance_assemble.spv")
    set(VULKAN_JOINT_THERMO_PHYSIOLOGY_SHADER
        "${CMAKE_CURRENT_SOURCE_DIR}/src/processes/thermo_physiology/vulkan/joint_thermo_physiology.slang")
    set(VULKAN_FARQUHAR_FORMULATION_CONTRACT
        "${CMAKE_CURRENT_SOURCE_DIR}/src/processes/physiology/farquhar/vulkan/formulation.slang")
    set(VULKAN_FARQUHAR_LEAF_SOLVER
        "${CMAKE_CURRENT_SOURCE_DIR}/src/processes/physiology/farquhar/vulkan/farquhar_leaf_solver.slang")
    set(VULKAN_JOINT_THERMO_PHYSIOLOGY_SPV
        "${VULKAN_KERNEL_OUTPUT_DIR}/joint_thermo_physiology.spv")
    set(VULKAN_LIDAR_SHADER
        "${CMAKE_CURRENT_SOURCE_DIR}/src/integrators/time_resolved_active/vulkan/kernel.slang")
    set(VULKAN_LIDAR_SPV
        "${VULKAN_KERNEL_OUTPUT_DIR}/time_resolved_active.spv")
    set(VULKAN_LIDAR_CONSTANT_SPV
        "${VULKAN_KERNEL_OUTPUT_DIR}/time_resolved_active_constant.spv")
    set(VULKAN_LIDAR_DIRECT_CONSTANT_SPV
        "${VULKAN_KERNEL_OUTPUT_DIR}/time_resolved_active_direct_constant.spv")
    set(VULKAN_PHYSIOLOGY_SHADER
        "${CMAKE_CURRENT_SOURCE_DIR}/src/processes/physiology/vulkan.slang")
    set(VULKAN_PHYSIOLOGY_SPV
        "${VULKAN_KERNEL_OUTPUT_DIR}/physiology.spv")
    set(VULKAN_EB_PROCESS_SHADER
        "${CMAKE_CURRENT_SOURCE_DIR}/src/processes/energy_balance/vulkan/process.slang")
    set(VULKAN_EB_PROCESS_SPV
        "${VULKAN_KERNEL_OUTPUT_DIR}/energy_balance_process.spv")
    set(VULKAN_PHOTOSYNTHESIS_PROCESS_SHADER
        "${CMAKE_CURRENT_SOURCE_DIR}/src/processes/photosynthesis/vulkan/process.slang")
    set(VULKAN_PHOTOSYNTHESIS_PROCESS_SPV
        "${VULKAN_KERNEL_OUTPUT_DIR}/photosynthesis_process.spv")
    set(VULKAN_SIF_PROCESS_SHADER
        "${CMAKE_CURRENT_SOURCE_DIR}/src/processes/sif/vulkan/sif_process.slang")
    set(VULKAN_SIF_PROCESS_SPV
        "${VULKAN_KERNEL_OUTPUT_DIR}/sif_process.spv")
    set(VULKAN_SIF_ASSEMBLY_SHADER
        "${CMAKE_CURRENT_SOURCE_DIR}/src/processes/sif/vulkan/sif_source_assembly.slang")
    set(VULKAN_SIF_ASSEMBLY_SPV
        "${VULKAN_KERNEL_OUTPUT_DIR}/sif_source_assembly.spv")
    set(VULKAN_MESH_SIF_ASSEMBLY_SHADER
        "${CMAKE_CURRENT_SOURCE_DIR}/src/processes/sif/vulkan/mesh_sif_source_assembly.slang")
    set(VULKAN_MESH_SIF_ASSEMBLY_SPV
        "${VULKAN_KERNEL_OUTPUT_DIR}/mesh_sif_source_assembly.spv")
    add_custom_command(
        OUTPUT "${VULKAN_TRACE_SPV}"
        COMMAND "${LESS_SLANGC_EXECUTABLE}"
            "${VULKAN_TRACE_SHADER}"
            -entry traceClosest
            -stage compute
            -target spirv
            -profile spirv_1_5
            -capability spvRayQueryKHR
            -O3
            -o "${VULKAN_TRACE_SPV}"
        DEPENDS "${VULKAN_TRACE_SHADER}"
        COMMENT "Compiling Vulkan SPIR-V: trace_closest.slang"
        VERBATIM
    )
    add_custom_command(
        OUTPUT "${VULKAN_FLUX_SPV}"
        COMMAND "${LESS_SLANGC_EXECUTABLE}"

            -I "${less_surface_generated_shader_dir}"
            -I "${less_illumination_generated_shader_dir}"
            -I "${less_sensor_generated_shader_dir}"
            -I "${CMAKE_CURRENT_SOURCE_DIR}/src/backends/vulkan/shaders"
            -I "${CMAKE_CURRENT_SOURCE_DIR}"
            "${VULKAN_FLUX_SHADER}"
            -entry forwardFlux
            -stage compute
            -target spirv
            -profile spirv_1_5
            -capability spvRayQueryKHR
            -O3
            -o "${VULKAN_FLUX_SPV}"
        DEPENDS "${VULKAN_FLUX_SHADER}" ${VULKAN_TRANSPORT_COMMON_DEPENDS}
            "${VULKAN_FIELD_CONTRACT}" ${less_phase_vulkan_dependencies} ${less_runtime_volume_event_vulkan_dependencies} ${less_runtime_medium_event_vulkan_dependencies} ${less_runtime_forward_integrator_vulkan_dependencies} ${less_runtime_flux_integrator_vulkan_dependencies} ${less_product_irradiance_map_vulkan_dependencies} ${less_product_radiation_field_vulkan_dependencies} ${less_illumination_vulkan_dependencies} ${less_surface_vulkan_dependencies}
        COMMENT "Compiling Vulkan SPIR-V: forward_flux.slang"
        VERBATIM
    )
    add_custom_command(
        OUTPUT "${VULKAN_FLUX_CONSTANT_SPV}"
        COMMAND "${LESS_SLANGC_EXECUTABLE}"

            -I "${less_surface_generated_shader_dir}"
            -I "${less_illumination_generated_shader_dir}"
            -I "${less_sensor_generated_shader_dir}"
            -I "${CMAKE_CURRENT_SOURCE_DIR}/src/backends/vulkan/shaders"
            -I "${CMAKE_CURRENT_SOURCE_DIR}"
            "${VULKAN_FLUX_SHADER}"
            -entry forwardFlux
            -stage compute
            -target spirv
            -profile spirv_1_5
            -capability spvRayQueryKHR
            -DLESS_FIELD_CONSTANT_ONLY=1
            -O3
            -o "${VULKAN_FLUX_CONSTANT_SPV}"
        DEPENDS "${VULKAN_FLUX_SHADER}" ${VULKAN_TRANSPORT_COMMON_DEPENDS}
            "${VULKAN_FIELD_CONTRACT}" ${less_phase_vulkan_dependencies} ${less_runtime_volume_event_vulkan_dependencies} ${less_runtime_medium_event_vulkan_dependencies} ${less_runtime_forward_integrator_vulkan_dependencies} ${less_runtime_flux_integrator_vulkan_dependencies} ${less_product_irradiance_map_vulkan_dependencies} ${less_product_radiation_field_vulkan_dependencies} ${less_illumination_vulkan_dependencies} ${less_surface_vulkan_dependencies}
        COMMENT "Compiling Vulkan SPIR-V: forward_flux_constant.slang"
        VERBATIM
    )
    add_custom_command(
        OUTPUT "${VULKAN_EB_SPV}"
        COMMAND ${VULKAN_BACKWARD_SLANGC}
            "${VULKAN_EB_SHADER}"
            -entry energyBalance
            -stage compute
            -target spirv
            -profile spirv_1_5
            -capability spvRayQueryKHR
            -O3
            -o "${VULKAN_EB_SPV}"
        DEPENDS "${VULKAN_EB_SHADER}" ${VULKAN_TRANSPORT_COMMON_DEPENDS}
            "${VULKAN_FIELD_CONTRACT}" ${less_illumination_vulkan_dependencies} ${less_runtime_energy_balance_vulkan_dependencies}
        COMMENT "Compiling Vulkan SPIR-V: energy_balance.slang"
        VERBATIM
    )
    add_custom_command(
        OUTPUT "${VULKAN_EB_CONSTANT_SPV}"
        COMMAND ${VULKAN_BACKWARD_SLANGC}
            "${VULKAN_EB_SHADER}"
            -entry energyBalance
            -stage compute
            -target spirv
            -profile spirv_1_5
            -capability spvRayQueryKHR
            -DLESS_FIELD_CONSTANT_ONLY=1
            -O3
            -o "${VULKAN_EB_CONSTANT_SPV}"
        DEPENDS "${VULKAN_EB_SHADER}" ${VULKAN_TRANSPORT_COMMON_DEPENDS}
            "${VULKAN_FIELD_CONTRACT}" ${less_illumination_vulkan_dependencies} ${less_runtime_energy_balance_vulkan_dependencies}
        COMMENT "Compiling Vulkan SPIR-V: energy_balance_constant.slang"
        VERBATIM
    )
    add_custom_command(
        OUTPUT "${VULKAN_MICROWAVE_SPV}"
        COMMAND "${LESS_SLANGC_EXECUTABLE}"
            "${VULKAN_MICROWAVE_SHADER}"
            -entry microwaveWavefront
            -stage compute
            -target spirv
            -profile spirv_1_5
            -capability spvRayQueryKHR
            -O3
            -o "${VULKAN_MICROWAVE_SPV}"
        DEPENDS "${VULKAN_MICROWAVE_SHADER}" ${VULKAN_TRANSPORT_COMMON_DEPENDS}
        COMMENT "Compiling Vulkan SPIR-V: microwave_wavefront.slang"
        VERBATIM
    )
    add_custom_command(
        OUTPUT "${VULKAN_TURBID_RRT_SPV}"
        COMMAND ${VULKAN_BACKWARD_SLANGC}
            "${VULKAN_TURBID_RRT_SHADER}"
            -entry turbidLeafRrt
            -stage compute
            -target spirv
            -profile spirv_1_5
            -capability spvRayQueryKHR
            -O3
            -o "${VULKAN_TURBID_RRT_SPV}"
        DEPENDS "${VULKAN_TURBID_RRT_SHADER}" ${VULKAN_TRANSPORT_COMMON_DEPENDS}
            "${VULKAN_FIELD_CONTRACT}"
        COMMENT "Compiling Vulkan SPIR-V: turbid_leaf_rrt.slang"
        VERBATIM
    )
    add_custom_command(
        OUTPUT "${VULKAN_LIDAR_SPV}"
        COMMAND ${VULKAN_BACKWARD_SLANGC}
            "${VULKAN_LIDAR_SHADER}"
            -entry lidar
            -stage compute
            -target spirv
            -profile spirv_1_5
            -capability spvRayQueryKHR
            -O3
            -o "${VULKAN_LIDAR_SPV}"
        DEPENDS "${VULKAN_LIDAR_SHADER}" ${VULKAN_TRANSPORT_COMMON_DEPENDS}
            "${VULKAN_FIELD_CONTRACT}" ${less_product_lidar_waveform_vulkan_dependencies} ${less_sensor_vulkan_dependencies} ${less_runtime_time_resolved_active_vulkan_dependencies} ${less_medium_vulkan_dependencies} ${less_surface_vulkan_dependencies}
        COMMENT "Compiling Vulkan SPIR-V: lidar.slang"
        VERBATIM
    )
    add_custom_command(
        OUTPUT "${VULKAN_LIDAR_CONSTANT_SPV}"
        COMMAND ${VULKAN_BACKWARD_SLANGC}
            "${VULKAN_LIDAR_SHADER}"
            -entry lidar
            -stage compute
            -target spirv
            -profile spirv_1_5
            -capability spvRayQueryKHR
            -DLESS_FIELD_CONSTANT_ONLY=1
            -O3
            -o "${VULKAN_LIDAR_CONSTANT_SPV}"
        DEPENDS "${VULKAN_LIDAR_SHADER}" ${VULKAN_TRANSPORT_COMMON_DEPENDS}
            "${VULKAN_FIELD_CONTRACT}" ${less_product_lidar_waveform_vulkan_dependencies} ${less_sensor_vulkan_dependencies} ${less_runtime_time_resolved_active_vulkan_dependencies} ${less_medium_vulkan_dependencies} ${less_surface_vulkan_dependencies}
        COMMENT "Compiling Vulkan SPIR-V: lidar_constant.slang"
        VERBATIM
    )
    add_custom_command(
        OUTPUT "${VULKAN_LIDAR_DIRECT_CONSTANT_SPV}"
        COMMAND ${VULKAN_BACKWARD_SLANGC}
            "${VULKAN_LIDAR_SHADER}"
            -entry lidar
            -stage compute
            -target spirv
            -profile spirv_1_5
            -capability spvRayQueryKHR
            -DLESS_FIELD_CONSTANT_ONLY=1
            -DLESS_LIDAR_DIRECT_ONLY=1
            -O3
            -o "${VULKAN_LIDAR_DIRECT_CONSTANT_SPV}"
        DEPENDS "${VULKAN_LIDAR_SHADER}" ${VULKAN_TRANSPORT_COMMON_DEPENDS}
            "${VULKAN_FIELD_CONTRACT}" ${less_product_lidar_waveform_vulkan_dependencies} ${less_sensor_vulkan_dependencies} ${less_runtime_time_resolved_active_vulkan_dependencies} ${less_medium_vulkan_dependencies} ${less_surface_vulkan_dependencies}
        COMMENT "Compiling Vulkan SPIR-V: lidar_direct_constant.slang"
        VERBATIM
    )
    add_custom_command(
        OUTPUT "${VULKAN_TURBID_SAMPLING_SPV}"
        COMMAND "${LESS_SLANGC_EXECUTABLE}"
            "${VULKAN_TURBID_SAMPLING_SHADER}"
            -entry turbidSampling
            -stage compute
            -target spirv
            -profile spirv_1_5
            -I "${LESS_INCLUDE_DIR}"
            -O3
            -o "${VULKAN_TURBID_SAMPLING_SPV}"
        DEPENDS "${VULKAN_TURBID_SAMPLING_SHADER}" "${LESS_TURBID_SOBOL}"
        COMMENT "Compiling Vulkan SPIR-V: turbid_sampling.slang"
        VERBATIM
    )
    add_custom_command(
        OUTPUT "${VULKAN_TURBID_STATE_SPV}"
        COMMAND "${LESS_SLANGC_EXECUTABLE}"
            "${VULKAN_TURBID_STATE_SHADER}"
            -entry turbidStateAggregate
            -stage compute
            -target spirv
            -profile spirv_1_5
            -O3
            -o "${VULKAN_TURBID_STATE_SPV}"
        DEPENDS "${VULKAN_TURBID_STATE_SHADER}"
            "${LESS_TURBID_STATE_STORAGE}"
            "${CMAKE_CURRENT_SOURCE_DIR}/src/media/turbid/vulkan/state.h"
        COMMENT "Compiling Vulkan SPIR-V: turbid_state.slang"
        VERBATIM
    )
    add_custom_command(
        OUTPUT "${VULKAN_TURBID_FIELD_ASSEMBLE_SPV}"
        COMMAND "${LESS_SLANGC_EXECUTABLE}"
            "${VULKAN_TURBID_FIELD_ASSEMBLE_SHADER}"
            -entry turbidFieldAssemble
            -stage compute
            -target spirv
            -profile spirv_1_5
            -O3
            -o "${VULKAN_TURBID_FIELD_ASSEMBLE_SPV}"
        DEPENDS "${VULKAN_TURBID_FIELD_ASSEMBLE_SHADER}"
            "${LESS_TURBID_FIELD_STORAGE}"
            "${CMAKE_CURRENT_SOURCE_DIR}/src/media/turbid/field_device.h"
            "${CMAKE_CURRENT_SOURCE_DIR}/src/media/turbid/vulkan/field.h"
        COMMENT "Compiling Vulkan SPIR-V: turbid_field_assemble.slang"
        VERBATIM
    )
    add_custom_command(
        OUTPUT "${VULKAN_TURBID_FIELD_LOOKUP_SPV}"
        COMMAND "${LESS_SLANGC_EXECUTABLE}"
            "${VULKAN_TURBID_FIELD_LOOKUP_SHADER}"
            -entry turbidFieldLookup
            -stage compute
            -target spirv
            -profile spirv_1_5
            -O3
            -o "${VULKAN_TURBID_FIELD_LOOKUP_SPV}"
        DEPENDS "${VULKAN_TURBID_FIELD_LOOKUP_SHADER}"
            "${LESS_TURBID_FIELD_STORAGE}"
            "${CMAKE_CURRENT_SOURCE_DIR}/src/media/turbid/field_device.h"
            "${CMAKE_CURRENT_SOURCE_DIR}/src/media/turbid/vulkan/field.h"
        COMMENT "Compiling Vulkan SPIR-V: turbid_field_lookup.slang"
        VERBATIM
    )
    add_custom_command(
        OUTPUT "${VULKAN_TURBID_OBSERVATION_SOURCE_SPV}"
        COMMAND "${LESS_SLANGC_EXECUTABLE}"
            "${VULKAN_TURBID_OBSERVATION_SOURCE_SHADER}"
            -entry turbidObservationSource
            -stage compute
            -target spirv
            -profile spirv_1_5
            -O3
            -o "${VULKAN_TURBID_OBSERVATION_SOURCE_SPV}"
        DEPENDS "${VULKAN_TURBID_OBSERVATION_SOURCE_SHADER}"
            "${LESS_TURBID_FIELD_STORAGE}"
            "${CMAKE_CURRENT_SOURCE_DIR}/src/media/turbid/field_device.h"
            "${CMAKE_CURRENT_SOURCE_DIR}/src/media/turbid/vulkan/field.h"
        COMMENT "Compiling Vulkan SPIR-V: turbid_observation_source.slang"
        VERBATIM
    )
    add_custom_command(
        OUTPUT "${VULKAN_TURBID_RADIATION_ASSEMBLE_SPV}"
        COMMAND "${LESS_SLANGC_EXECUTABLE}"
            "${VULKAN_TURBID_RADIATION_ASSEMBLE_SHADER}"
            -entry turbidRadiationAssemble
            -stage compute
            -target spirv
            -profile spirv_1_5
            -O3
            -o "${VULKAN_TURBID_RADIATION_ASSEMBLE_SPV}"
        DEPENDS "${VULKAN_TURBID_RADIATION_ASSEMBLE_SHADER}"
            "${LESS_TURBID_RADIATION_STORAGE}"
            "${LESS_TURBID_STATE_STORAGE}"
            "${LESS_TURBID_DOMAIN_STORAGE}"
        COMMENT "Compiling Vulkan SPIR-V: turbid_radiation_assemble.slang"
        VERBATIM
    )
    add_custom_command(
        OUTPUT "${VULKAN_TURBID_PHYSIOLOGY_ASSEMBLE_SPV}"
        COMMAND "${LESS_SLANGC_EXECUTABLE}"
            "${VULKAN_TURBID_PHYSIOLOGY_ASSEMBLE_SHADER}"
            -entry turbidStaticPhysiology
            -stage compute
            -target spirv
            -profile spirv_1_5
            -O3
            -o "${VULKAN_TURBID_PHYSIOLOGY_ASSEMBLE_SPV}"
        DEPENDS "${VULKAN_TURBID_PHYSIOLOGY_ASSEMBLE_SHADER}"
            "${VULKAN_FARQUHAR_LEAF_SOLVER}"
            "${VULKAN_FARQUHAR_FORMULATION_CONTRACT}"
            "${LESS_TURBID_PHYSIOLOGY_STORAGE}"
            "${LESS_TURBID_STATE_STORAGE}"
            "${LESS_TURBID_DOMAIN_STORAGE}"
        COMMENT "Compiling Vulkan SPIR-V: turbid_physiology_assemble.slang"
        VERBATIM
    )
    add_custom_command(
        OUTPUT "${VULKAN_TURBID_ENERGY_BALANCE_ASSEMBLE_SPV}"
        COMMAND "${LESS_SLANGC_EXECUTABLE}"
            "${VULKAN_TURBID_ENERGY_BALANCE_ASSEMBLE_SHADER}"
            -entry turbidEnergyBalance
            -stage compute
            -target spirv
            -profile spirv_1_5
            -O3
            -o "${VULKAN_TURBID_ENERGY_BALANCE_ASSEMBLE_SPV}"
        DEPENDS "${VULKAN_TURBID_ENERGY_BALANCE_ASSEMBLE_SHADER}"
            "${LESS_TURBID_ENERGY_BALANCE_STORAGE}"
            "${LESS_TURBID_PROCESS_CONTRACT}"
            "${LESS_TURBID_STATE_STORAGE}"
            "${LESS_TURBID_DOMAIN_STORAGE}"
        COMMENT "Compiling Vulkan SPIR-V: turbid_energy_balance_assemble.slang"
        VERBATIM
    )
    add_custom_command(
        OUTPUT "${VULKAN_JOINT_THERMO_PHYSIOLOGY_SPV}"
        COMMAND "${LESS_SLANGC_EXECUTABLE}"
            "${VULKAN_JOINT_THERMO_PHYSIOLOGY_SHADER}"
            -entry main
            -stage compute
            -target spirv
            -profile spirv_1_5
            -O3
            -o "${VULKAN_JOINT_THERMO_PHYSIOLOGY_SPV}"
        DEPENDS "${VULKAN_JOINT_THERMO_PHYSIOLOGY_SHADER}"
            "${VULKAN_FARQUHAR_LEAF_SOLVER}"
            "${VULKAN_FARQUHAR_FORMULATION_CONTRACT}"
            "${CMAKE_CURRENT_SOURCE_DIR}/src/processes/thermo_physiology/joint_thermo_physiology.h"
            "${CMAKE_CURRENT_SOURCE_DIR}/src/processes/energy_balance/joint_storage.h"
        COMMENT "Compiling Vulkan SPIR-V: joint_thermo_physiology.slang"
        VERBATIM
    )
    add_custom_command(
        OUTPUT "${VULKAN_PHYSIOLOGY_SPV}"
        COMMAND "${LESS_SLANGC_EXECUTABLE}"
            "${VULKAN_PHYSIOLOGY_SHADER}"
            -entry physiology
            -stage compute
            -target spirv
            -profile spirv_1_5
            -O3
            -o "${VULKAN_PHYSIOLOGY_SPV}"
        DEPENDS "${VULKAN_PHYSIOLOGY_SHADER}"
        COMMENT "Compiling Vulkan SPIR-V: physiology.slang"
        VERBATIM
    )
    add_custom_command(
        OUTPUT "${VULKAN_EB_PROCESS_SPV}"
        COMMAND "${LESS_SLANGC_EXECUTABLE}"
            "${VULKAN_EB_PROCESS_SHADER}"
            -entry energyBalanceProcess
            -stage compute
            -target spirv
            -profile spirv_1_5
            -O3
            -o "${VULKAN_EB_PROCESS_SPV}"
        DEPENDS "${VULKAN_EB_PROCESS_SHADER}"
        COMMENT "Compiling Vulkan SPIR-V: energy_balance_process.slang"
        VERBATIM
    )
    add_custom_command(
        OUTPUT "${VULKAN_PHOTOSYNTHESIS_PROCESS_SPV}"
        COMMAND "${LESS_SLANGC_EXECUTABLE}"
            -I "${CMAKE_CURRENT_SOURCE_DIR}/src/backends/vulkan/shaders"
            "${VULKAN_PHOTOSYNTHESIS_PROCESS_SHADER}"
            -entry photosynthesisProcess
            -stage compute
            -target spirv
            -profile spirv_1_5
            -O3
            -o "${VULKAN_PHOTOSYNTHESIS_PROCESS_SPV}"
        DEPENDS "${VULKAN_PHOTOSYNTHESIS_PROCESS_SHADER}"
            "${VULKAN_FARQUHAR_FORMULATION_CONTRACT}"
        COMMENT "Compiling Vulkan SPIR-V: photosynthesis_process.slang"
        VERBATIM
    )
    add_custom_command(
        OUTPUT "${VULKAN_SIF_PROCESS_SPV}"
        COMMAND "${LESS_SLANGC_EXECUTABLE}"
            "${VULKAN_SIF_PROCESS_SHADER}"
            -entry sifProcess
            -stage compute
            -target spirv
            -profile spirv_1_5
            -O3
            -o "${VULKAN_SIF_PROCESS_SPV}"
        DEPENDS "${VULKAN_SIF_PROCESS_SHADER}"
        COMMENT "Compiling Vulkan SPIR-V: sif_process.slang"
        VERBATIM
    )
    add_custom_command(
        OUTPUT "${VULKAN_SIF_ASSEMBLY_SPV}"
        COMMAND "${LESS_SLANGC_EXECUTABLE}"
            "${VULKAN_SIF_ASSEMBLY_SHADER}"
            -entry sifSourceAssembly
            -stage compute
            -target spirv
            -profile spirv_1_5
            -O3
            -o "${VULKAN_SIF_ASSEMBLY_SPV}"
        DEPENDS "${VULKAN_SIF_ASSEMBLY_SHADER}"
        COMMENT "Compiling Vulkan SPIR-V: sif_source_assembly.slang"
        VERBATIM
    )
    add_custom_command(
        OUTPUT "${VULKAN_EB_PARTITION_SPV}"
        COMMAND ${VULKAN_BACKWARD_SLANGC}
            "${VULKAN_EB_SHADER}"
            -entry energyBalance
            -stage compute
            -target spirv
            -profile spirv_1_5
            -capability spvRayQueryKHR
            -DLESS_ENABLE_ABSORPTION_PARTITION=1
            -O3
            -o "${VULKAN_EB_PARTITION_SPV}"
        DEPENDS "${VULKAN_EB_SHADER}" ${VULKAN_TRANSPORT_COMMON_DEPENDS}
            "${VULKAN_FIELD_CONTRACT}" ${less_illumination_vulkan_dependencies} ${less_runtime_energy_balance_vulkan_dependencies}
        COMMENT "Compiling Vulkan SPIR-V: energy_balance_partition.slang"
        VERBATIM
    )
    add_custom_command(
        OUTPUT "${VULKAN_MESH_SIF_ASSEMBLY_SPV}"
        COMMAND "${LESS_SLANGC_EXECUTABLE}"
            "${VULKAN_MESH_SIF_ASSEMBLY_SHADER}"
            -entry meshSifSourceAssembly
            -stage compute
            -target spirv
            -profile spirv_1_5
            -O3
            -o "${VULKAN_MESH_SIF_ASSEMBLY_SPV}"
        DEPENDS "${VULKAN_MESH_SIF_ASSEMBLY_SHADER}"
        COMMENT "Compiling Vulkan SPIR-V: mesh_sif_source_assembly.slang"
        VERBATIM
    )
    set(VULKAN_SPV_TARGETS "${VULKAN_TRACE_SPV}"
        ${VULKAN_BACKWARD_SPV_TARGETS}
        "${VULKAN_FLUX_SPV}" "${VULKAN_FLUX_CONSTANT_SPV}"
        "${VULKAN_EB_SPV}" "${VULKAN_EB_CONSTANT_SPV}"
        "${VULKAN_EB_PARTITION_SPV}"
        "${VULKAN_MICROWAVE_SPV}" "${VULKAN_TURBID_RRT_SPV}"
        "${VULKAN_TURBID_SAMPLING_SPV}"
        "${VULKAN_TURBID_STATE_SPV}"
        "${VULKAN_TURBID_FIELD_ASSEMBLE_SPV}"
        "${VULKAN_TURBID_FIELD_LOOKUP_SPV}"
        "${VULKAN_TURBID_OBSERVATION_SOURCE_SPV}"
        "${VULKAN_TURBID_RADIATION_ASSEMBLE_SPV}"
        "${VULKAN_TURBID_PHYSIOLOGY_ASSEMBLE_SPV}"
        "${VULKAN_TURBID_ENERGY_BALANCE_ASSEMBLE_SPV}"
        "${VULKAN_JOINT_THERMO_PHYSIOLOGY_SPV}"
        "${VULKAN_LIDAR_SPV}" "${VULKAN_LIDAR_CONSTANT_SPV}"
        "${VULKAN_LIDAR_DIRECT_CONSTANT_SPV}"
        "${VULKAN_PHYSIOLOGY_SPV}"
        "${VULKAN_EB_PROCESS_SPV}"
        "${VULKAN_PHOTOSYNTHESIS_PROCESS_SPV}"
        "${VULKAN_SIF_PROCESS_SPV}"
        "${VULKAN_SIF_ASSEMBLY_SPV}"
        "${VULKAN_MESH_SIF_ASSEMBLY_SPV}")
    add_custom_target(vulkan_kernel_modules ALL DEPENDS ${VULKAN_SPV_TARGETS})
    endif()
    less_add_homogeneous_water_lidar_vulkan_artifact()

    # The passive optical variants are part of the shipped Turbid thermal/SIF
    # path.  A source build must prove that every checked-in precompiled
    # variant is byte-identical instead of silently certifying a stale ON
    # package.
    if(NOT LESS_USE_PRECOMPILED_SPV)
        set(VULKAN_RELEASE_BACKWARD_SPV_NAMES
            backward_optical_constant.spv
            backward_optical_state.spv
            backward_optical_sampled.spv
            backward_optical.spv
            backward_optical_thermal_constant.spv
            backward_optical_thermal_sampled.spv
            backward_optical_thermal.spv
            backward_optical_turbid_reflected_sampled.spv
            backward_optical_turbid_reflected.spv
            backward_optical_turbid.spv
            backward_optical_wavefront_trace.spv
            backward_optical_wavefront_diffuse_trace.spv
            backward_optical_wavefront_resolve.spv)
        set(VULKAN_RELEASE_BACKWARD_PARITY_COMMANDS)
        foreach(SPV_NAME IN LISTS VULKAN_RELEASE_BACKWARD_SPV_NAMES)
            list(APPEND VULKAN_RELEASE_BACKWARD_PARITY_COMMANDS
                COMMAND "${CMAKE_COMMAND}" -E compare_files
                    "${VULKAN_KERNEL_OUTPUT_DIR}/${SPV_NAME}"
                    "${CMAKE_CURRENT_SOURCE_DIR}/src/backends/vulkan/precompiled/${SPV_NAME}")
        endforeach()
        add_custom_target(vulkan_release_artifact_gate ALL
            ${VULKAN_RELEASE_BACKWARD_PARITY_COMMANDS}
            COMMAND "${CMAKE_COMMAND}" -E compare_files
                "${VULKAN_FLUX_CONSTANT_SPV}"
                "${CMAKE_CURRENT_SOURCE_DIR}/src/backends/vulkan/precompiled/forward_flux_constant.spv"
            COMMAND "${CMAKE_COMMAND}" -E compare_files
                "${VULKAN_EB_SPV}"
                "${CMAKE_CURRENT_SOURCE_DIR}/src/backends/vulkan/precompiled/energy_balance_radiation.spv"
            COMMAND "${CMAKE_COMMAND}" -E compare_files
                "${VULKAN_EB_CONSTANT_SPV}"
                "${CMAKE_CURRENT_SOURCE_DIR}/src/backends/vulkan/precompiled/energy_balance_radiation_constant.spv"
            COMMAND "${CMAKE_COMMAND}" -E compare_files
                "${VULKAN_EB_PARTITION_SPV}"
                "${CMAKE_CURRENT_SOURCE_DIR}/src/backends/vulkan/precompiled/energy_balance_partition.spv"
            COMMAND "${CMAKE_COMMAND}" -E compare_files
                "${VULKAN_LIDAR_CONSTANT_SPV}"
                "${CMAKE_CURRENT_SOURCE_DIR}/src/backends/vulkan/precompiled/time_resolved_active_constant.spv"
            COMMAND "${CMAKE_COMMAND}" -E compare_files
                "${VULKAN_LIDAR_DIRECT_CONSTANT_SPV}"
                "${CMAKE_CURRENT_SOURCE_DIR}/src/backends/vulkan/precompiled/time_resolved_active_direct_constant.spv"
            COMMAND "${CMAKE_COMMAND}" -E compare_files
                "${VULKAN_PHYSIOLOGY_SPV}"
                "${CMAKE_CURRENT_SOURCE_DIR}/src/backends/vulkan/precompiled/physiology.spv"
            COMMAND "${CMAKE_COMMAND}" -E compare_files
                "${VULKAN_TURBID_RRT_SPV}"
                "${CMAKE_CURRENT_SOURCE_DIR}/src/backends/vulkan/precompiled/turbid_leaf_rrt.spv"
            COMMAND "${CMAKE_COMMAND}" -E compare_files
                "${VULKAN_PHOTOSYNTHESIS_PROCESS_SPV}"
                "${CMAKE_CURRENT_SOURCE_DIR}/src/backends/vulkan/precompiled/photosynthesis_process.spv"
            DEPENDS ${VULKAN_BACKWARD_SPV_TARGETS}
                "${VULKAN_FLUX_CONSTANT_SPV}"
                "${VULKAN_EB_SPV}" "${VULKAN_EB_CONSTANT_SPV}"
                "${VULKAN_EB_PARTITION_SPV}"
                "${VULKAN_LIDAR_CONSTANT_SPV}"
                "${VULKAN_LIDAR_DIRECT_CONSTANT_SPV}"
                "${VULKAN_PHYSIOLOGY_SPV}"
                "${VULKAN_TURBID_RRT_SPV}"
                "${VULKAN_PHOTOSYNTHESIS_PROCESS_SPV}"
            COMMENT "Verifying release source/precompiled SPIR-V parity"
            VERBATIM)
    endif()

    add_library(less_vulkan STATIC
        src/backends/vulkan/vulkan_buffer.cpp
        src/backends/vulkan/vulkan_context.cpp
        src/backends/vulkan/vulkan_scene.cpp
        ${less_runtime_vulkan_library_sources}
        ${less_integrator_vulkan_sources}
        ${less_product_vulkan_sources}
        ${less_process_vulkan_sources}
        ${less_illumination_vulkan_library_sources}
        ${less_sensor_vulkan_library_sources}
        ${less_medium_host_sources}
        src/media/turbid/vulkan/state.cpp
        src/media/turbid/vulkan/field.cpp
    )
    set_source_files_properties(
        ${less_runtime_turbid_sampling_vulkan_sources}
        PROPERTIES OBJECT_DEPENDS
            "${LESS_TURBID_CONTRACTS};${LESS_TURBID_SOBOL};${LESS_TURBID_ARTIFACT_ABI};${LESS_TURBID_DEBUG_HOST}")
    set_source_files_properties(
        src/media/turbid/vulkan/state.cpp
        PROPERTIES OBJECT_DEPENDS
            "${LESS_TURBID_STATE_STORAGE};${LESS_TURBID_DOMAIN_STORAGE};${LESS_TURBID_CONTRACTS};${CMAKE_CURRENT_SOURCE_DIR}/src/media/turbid/vulkan/state.h;${CMAKE_CURRENT_SOURCE_DIR}/src/media/turbid/vulkan/resident_artifact.h")
    set_source_files_properties(
        src/media/turbid/vulkan/field.cpp
        PROPERTIES OBJECT_DEPENDS
            "${LESS_TURBID_FIELD_DEVICE};${LESS_TURBID_FIELD_STORAGE};${LESS_TURBID_STATE_STORAGE};${LESS_TURBID_DOMAIN_STORAGE};${LESS_TURBID_CONTRACTS};${CMAKE_CURRENT_SOURCE_DIR}/src/media/turbid/vulkan/field.h;${CMAKE_CURRENT_SOURCE_DIR}/src/media/turbid/vulkan/resident_artifact.h")
    set_source_files_properties(
        src/processes/thermo_physiology/vulkan/turbid_processes.cpp
        PROPERTIES OBJECT_DEPENDS
            "${LESS_TURBID_PROCESS_CONTRACT};${LESS_TURBID_RADIATION_STORAGE};${LESS_TURBID_PHYSIOLOGY_STORAGE};${LESS_TURBID_ENERGY_BALANCE_STORAGE};${LESS_VULKAN_MANAGER_LAYOUT};${CMAKE_CURRENT_SOURCE_DIR}/src/processes/thermo_physiology/vulkan/turbid_processes.h")
    set_property(SOURCE src/backends/vulkan/vulkan_scene.cpp APPEND PROPERTY
        OBJECT_DEPENDS
            "${LESS_PROCESS_CONTRACTS};${LESS_VULKAN_MANAGER_LAYOUT};${LESS_TURBID_DEBUG_HOST}")
    target_include_directories(less_vulkan PUBLIC
        ${LESS_INCLUDE_DIR}
        ${less_surface_generated_include_dir}
        ${less_illumination_generated_include_dir} ${less_sensor_generated_include_dir}
        ${CMAKE_CURRENT_SOURCE_DIR}
        ${CMAKE_CURRENT_SOURCE_DIR}/src
    )
    target_link_libraries(less_vulkan PUBLIC Vulkan::Vulkan)
    if(OpenMP_CXX_FOUND)
        target_link_libraries(less_vulkan PUBLIC OpenMP::OpenMP_CXX)
    endif()
    target_compile_definitions(less_vulkan PUBLIC LESS_HAS_VULKAN=1)
endif()

# Task 4's private native diagnostic is usable only when its device artifacts
# match the checked source, shared algorithm header, reflected ABI, and each
# other.  This gate intentionally runs for source and precompiled SPIR-V builds.
set(LESS_TURBID_ARTIFACT_MANIFEST
    "${CMAKE_CURRENT_SOURCE_DIR}/python/less/_runtime/artifacts/turbid/sampling_manifest.json")
set(LESS_TURBID_ARTIFACT_GATE_TARGETS "")
if(LESS_ENABLE_OPTIX)
    add_custom_target(turbid_sampling_optix_artifact_gate ALL
        COMMAND "${PYTHON_EXECUTABLE}"
            "${CMAKE_CURRENT_SOURCE_DIR}/cmake/verify_turbid_sampling_artifacts.py"
            --backend optix
            --root "${CMAKE_CURRENT_SOURCE_DIR}"
            --ptx "${OPTIX_KERNEL_OUTPUT_DIR}/turbid_sampling.ptx"
            --manifest "${LESS_TURBID_ARTIFACT_MANIFEST}"
        DEPENDS
            "${OPTIX_KERNEL_OUTPUT_DIR}/turbid_sampling.ptx"
            "${CMAKE_CURRENT_SOURCE_DIR}/src/media/turbid/optix/sampling.cu"
            "${LESS_TURBID_SOBOL}"
            "${LESS_TURBID_ARTIFACT_ABI}"
            "${LESS_TURBID_ARTIFACT_MANIFEST}"
            "${CMAKE_CURRENT_SOURCE_DIR}/cmake/verify_turbid_sampling_artifacts.py"
            "${CMAKE_CURRENT_SOURCE_DIR}/python/less/_runtime/artifacts/turbid/sampling.py"
        COMMENT "Verifying OptiX Turbid sampling artifact ABI and provenance"
        VERBATIM)
    list(APPEND LESS_TURBID_ARTIFACT_GATE_TARGETS
        turbid_sampling_optix_artifact_gate)
endif()
if(LESS_ENABLE_VULKAN)
    add_custom_target(turbid_sampling_vulkan_artifact_gate ALL
        COMMAND "${PYTHON_EXECUTABLE}"
            "${CMAKE_CURRENT_SOURCE_DIR}/cmake/verify_turbid_sampling_artifacts.py"
            --backend vulkan
            --root "${CMAKE_CURRENT_SOURCE_DIR}"
            --spv "${VULKAN_TURBID_SAMPLING_SPV}"
            --precompiled-spv
                "${CMAKE_CURRENT_SOURCE_DIR}/src/backends/vulkan/precompiled/turbid_sampling.spv"
            --manifest "${LESS_TURBID_ARTIFACT_MANIFEST}"
        DEPENDS
            "${VULKAN_TURBID_SAMPLING_SPV}"
            "${CMAKE_CURRENT_SOURCE_DIR}/src/backends/vulkan/precompiled/turbid_sampling.spv"
            "${CMAKE_CURRENT_SOURCE_DIR}/src/media/turbid/vulkan/sampling.slang"
            "${LESS_TURBID_SOBOL}"
            "${LESS_TURBID_ARTIFACT_ABI}"
            "${LESS_TURBID_ARTIFACT_MANIFEST}"
            "${CMAKE_CURRENT_SOURCE_DIR}/cmake/verify_turbid_sampling_artifacts.py"
            "${CMAKE_CURRENT_SOURCE_DIR}/python/less/_runtime/artifacts/turbid/sampling.py"
        COMMENT "Verifying Vulkan Turbid sampling artifact ABI and provenance"
        VERBATIM)
    list(APPEND LESS_TURBID_ARTIFACT_GATE_TARGETS
        turbid_sampling_vulkan_artifact_gate)
endif()
if(LESS_TURBID_ARTIFACT_GATE_TARGETS)
    add_custom_target(turbid_sampling_artifact_gate
        DEPENDS ${LESS_TURBID_ARTIFACT_GATE_TARGETS})
endif()

# Task 6/7's three resident Vulkan kernels are a single versioned contract.
# Source and precompiled builds must use byte-identical, reflected artifacts
# pinned to both their Slang sources and the native layout headers.
if(LESS_ENABLE_VULKAN)
    set(LESS_TURBID_RESIDENT_MANIFEST
        "${CMAKE_CURRENT_SOURCE_DIR}/python/less/_runtime/artifacts/turbid/resident_manifest.json")
    set(LESS_TURBID_RESIDENT_PRECOMPILED
        "${CMAKE_CURRENT_SOURCE_DIR}/src/backends/vulkan/precompiled")
    add_custom_target(turbid_resident_artifact_gate ALL
        COMMAND "${PYTHON_EXECUTABLE}"
            "${CMAKE_CURRENT_SOURCE_DIR}/cmake/verify_turbid_resident_artifacts.py"
            --root "${CMAKE_CURRENT_SOURCE_DIR}"
            --spv-dir "${VULKAN_KERNEL_OUTPUT_DIR}"
            --precompiled-dir "${LESS_TURBID_RESIDENT_PRECOMPILED}"
            --manifest "${LESS_TURBID_RESIDENT_MANIFEST}"
        DEPENDS
            "${VULKAN_TURBID_STATE_SPV}"
            "${VULKAN_TURBID_FIELD_ASSEMBLE_SPV}"
            "${VULKAN_TURBID_FIELD_LOOKUP_SPV}"
            "${VULKAN_TURBID_OBSERVATION_SOURCE_SPV}"
            "${VULKAN_TURBID_RADIATION_ASSEMBLE_SPV}"
            "${VULKAN_TURBID_PHYSIOLOGY_ASSEMBLE_SPV}"
            "${VULKAN_TURBID_ENERGY_BALANCE_ASSEMBLE_SPV}"
            "${LESS_TURBID_RESIDENT_PRECOMPILED}/turbid_state.spv"
            "${LESS_TURBID_RESIDENT_PRECOMPILED}/turbid_field_assemble.spv"
            "${LESS_TURBID_RESIDENT_PRECOMPILED}/turbid_field_lookup.spv"
            "${LESS_TURBID_RESIDENT_PRECOMPILED}/turbid_observation_source.spv"
            "${LESS_TURBID_RESIDENT_PRECOMPILED}/turbid_radiation_assemble.spv"
            "${LESS_TURBID_RESIDENT_PRECOMPILED}/turbid_physiology_assemble.spv"
            "${LESS_TURBID_RESIDENT_PRECOMPILED}/turbid_energy_balance_assemble.spv"
            "${CMAKE_CURRENT_SOURCE_DIR}/src/media/turbid/vulkan/state.slang"
            "${CMAKE_CURRENT_SOURCE_DIR}/src/media/turbid/vulkan/field_assemble.slang"
            "${CMAKE_CURRENT_SOURCE_DIR}/src/media/turbid/vulkan/field_lookup.slang"
            "${CMAKE_CURRENT_SOURCE_DIR}/src/media/turbid/vulkan/observation_source.slang"
            "${CMAKE_CURRENT_SOURCE_DIR}/src/processes/radiation_field/vulkan.slang"
            "${CMAKE_CURRENT_SOURCE_DIR}/src/processes/photosynthesis/vulkan/turbid_assemble.slang"
            "${CMAKE_CURRENT_SOURCE_DIR}/src/processes/physiology/farquhar/vulkan/farquhar_leaf_solver.slang"
            "${CMAKE_CURRENT_SOURCE_DIR}/src/processes/physiology/farquhar/vulkan/formulation.slang"
            "${CMAKE_CURRENT_SOURCE_DIR}/src/processes/energy_balance/vulkan/turbid_assemble.slang"
            "${LESS_TURBID_STATE_STORAGE}"
            "${LESS_TURBID_FIELD_STORAGE}"
            "${LESS_TURBID_ENERGY_BALANCE_STORAGE}"
            "${LESS_TURBID_PROCESS_CONTRACT}"
            "${CMAKE_CURRENT_SOURCE_DIR}/src/media/turbid/state.h"
            "${CMAKE_CURRENT_SOURCE_DIR}/src/media/turbid/field_device.h"
            "${CMAKE_CURRENT_SOURCE_DIR}/src/media/turbid/vulkan/state.h"
            "${CMAKE_CURRENT_SOURCE_DIR}/src/media/turbid/vulkan/field.h"
            "${CMAKE_CURRENT_SOURCE_DIR}/src/media/turbid/vulkan/resident_artifact.h"
            "${LESS_TURBID_RESIDENT_MANIFEST}"
            "${CMAKE_CURRENT_SOURCE_DIR}/cmake/verify_turbid_resident_artifacts.py"
            "${CMAKE_CURRENT_SOURCE_DIR}/python/less/_runtime/artifacts/turbid/resident.py"
        COMMENT "Verifying resident Turbid state/field SPIR-V ABI and provenance"
        VERBATIM)
endif()

# The mixed thermo-physiology process is shipped only with one exact reflected
# source/precompiled SPIR-V contract.
if(LESS_ENABLE_VULKAN)
    set(LESS_JOINT_THERMO_PHYSIOLOGY_MANIFEST
        "${CMAKE_CURRENT_SOURCE_DIR}/python/less/_runtime/artifacts/process/thermo_physiology_manifest.json")
    set(LESS_JOINT_THERMO_PHYSIOLOGY_PRECOMPILED
        "${CMAKE_CURRENT_SOURCE_DIR}/src/backends/vulkan/precompiled/joint_thermo_physiology.spv")
    add_custom_target(joint_thermo_physiology_artifact_gate ALL
        COMMAND "${PYTHON_EXECUTABLE}"
            "${CMAKE_CURRENT_SOURCE_DIR}/cmake/verify_joint_thermo_physiology_artifact.py"
            --root "${CMAKE_CURRENT_SOURCE_DIR}"
            --artifact "${VULKAN_JOINT_THERMO_PHYSIOLOGY_SPV}"
            --precompiled "${LESS_JOINT_THERMO_PHYSIOLOGY_PRECOMPILED}"
            --manifest "${LESS_JOINT_THERMO_PHYSIOLOGY_MANIFEST}"
        DEPENDS
            "${VULKAN_JOINT_THERMO_PHYSIOLOGY_SPV}"
            "${LESS_JOINT_THERMO_PHYSIOLOGY_PRECOMPILED}"
            "${CMAKE_CURRENT_SOURCE_DIR}/src/processes/thermo_physiology/vulkan/joint_thermo_physiology.slang"
            "${CMAKE_CURRENT_SOURCE_DIR}/src/processes/physiology/farquhar/vulkan/farquhar_leaf_solver.slang"
            "${CMAKE_CURRENT_SOURCE_DIR}/src/processes/physiology/farquhar/vulkan/formulation.slang"
            "${CMAKE_CURRENT_SOURCE_DIR}/src/processes/thermo_physiology/joint_thermo_physiology.h"
            "${CMAKE_CURRENT_SOURCE_DIR}/src/processes/energy_balance/joint_storage.h"
            "${LESS_JOINT_THERMO_PHYSIOLOGY_MANIFEST}"
            "${CMAKE_CURRENT_SOURCE_DIR}/cmake/verify_joint_thermo_physiology_artifact.py"
            "${CMAKE_CURRENT_SOURCE_DIR}/python/less/_runtime/artifacts/process/thermo_physiology.py"
        COMMENT "Verifying joint thermo-physiology SPIR-V ABI and provenance"
        VERBATIM)
endif()

# ---- Python binding source dependencies ----
set_property(SOURCE
    src/media/turbid/bindings/domain.cpp
    APPEND PROPERTY
    OBJECT_DEPENDS
        "${LESS_TURBID_DOMAIN_STORAGE};${LESS_TURBID_CONTRACTS};${LESS_TURBID_SOBOL};${LESS_TURBID_DEBUG_HOST};${CMAKE_CURRENT_SOURCE_DIR}/src/media/turbid/bindings/sampling_debug.h;${CMAKE_CURRENT_SOURCE_DIR}/src/core/scene/scene_manager.h;${CMAKE_CURRENT_SOURCE_DIR}/src/backends/embree/embree_backend.h;${CMAKE_CURRENT_SOURCE_DIR}/src/backends/vulkan/vulkan_scene.h")
set_property(SOURCE
    src/media/turbid/bindings/state.cpp
    APPEND PROPERTY
    OBJECT_DEPENDS
        "${CMAKE_CURRENT_SOURCE_DIR}/src/media/turbid/host/state.h;${LESS_TURBID_DOMAIN_STORAGE};${LESS_TURBID_CONTRACTS};${CMAKE_CURRENT_SOURCE_DIR}/src/core/scene/scene_manager.h;${CMAKE_CURRENT_SOURCE_DIR}/src/backends/embree/embree_backend.h;${CMAKE_CURRENT_SOURCE_DIR}/src/backends/vulkan/vulkan_scene.h")
set_property(SOURCE
    src/media/turbid/bindings/fields.cpp
    APPEND PROPERTY
    OBJECT_DEPENDS
        "${LESS_TURBID_FIELD_STORAGE};${LESS_TURBID_STATE_STORAGE};${LESS_TURBID_DOMAIN_STORAGE};${LESS_TURBID_CONTRACTS};${CMAKE_CURRENT_SOURCE_DIR}/src/core/scene/scene_manager.h;${CMAKE_CURRENT_SOURCE_DIR}/src/backends/embree/embree_backend.h;${CMAKE_CURRENT_SOURCE_DIR}/src/backends/vulkan/vulkan_scene.h")
set_property(SOURCE
    src/processes/energy_balance/bindings.cpp
    APPEND PROPERTY
    OBJECT_DEPENDS
        "${LESS_PROCESS_CONTRACTS};${LESS_TURBID_PROCESS_CONTRACT};${LESS_TURBID_ENERGY_BALANCE_STORAGE};${LESS_TURBID_STATE_STORAGE};${CMAKE_CURRENT_SOURCE_DIR}/src/processes/thermo_physiology/embree/turbid_processes.h;${CMAKE_CURRENT_SOURCE_DIR}/src/processes/thermo_physiology/optix/turbid_processes.h;${CMAKE_CURRENT_SOURCE_DIR}/src/processes/thermo_physiology/vulkan/turbid_processes.h;${CMAKE_CURRENT_SOURCE_DIR}/src/core/scene/scene_manager.h;${CMAKE_CURRENT_SOURCE_DIR}/src/backends/embree/embree_backend.h;${CMAKE_CURRENT_SOURCE_DIR}/src/backends/vulkan/vulkan_scene.h")
set_property(SOURCE
    src/processes/thermo_physiology/bindings/joint_thermo_physiology_bindings.cpp
    APPEND PROPERTY OBJECT_DEPENDS
        "${LESS_PROCESS_CONTRACTS};${LESS_OPTIX_MANAGER_LAYOUT};${LESS_EMBREE_MANAGER_LAYOUT};${LESS_VULKAN_MANAGER_LAYOUT};${CMAKE_CURRENT_SOURCE_DIR}/src/processes/energy_balance/joint_storage.h;${CMAKE_CURRENT_SOURCE_DIR}/src/processes/thermo_physiology/optix/joint_thermo_physiology.h;${CMAKE_CURRENT_SOURCE_DIR}/src/processes/thermo_physiology/embree/joint_thermo_physiology.h")

# ---- Embree CPU backend (optional) ----
if(LESS_ENABLE_EMBREE)
    find_package(embree 4 QUIET)
    if(embree_FOUND)
        message(STATUS "Embree ${embree_VERSION} found: ${embree_DIR}")

        add_library(less_cpu STATIC
            src/backends/embree/embree_backend.cpp
            src/media/turbid/embree/domain.cpp
            ${less_runtime_embree_library_sources}
            ${less_integrator_embree_sources}
            ${less_product_embree_sources}
            ${less_process_embree_sources}
            ${less_illumination_embree_library_sources}
            ${less_sensor_embree_library_sources}
            ${less_medium_host_sources}
        )
        set_source_files_properties(
            ${less_runtime_turbid_sampling_embree_sources}
            PROPERTIES OBJECT_DEPENDS
                "${LESS_TURBID_CONTRACTS};${LESS_TURBID_SOBOL};${LESS_TURBID_DEBUG_HOST}")
        set_property(SOURCE src/backends/embree/embree_backend.cpp APPEND PROPERTY
            OBJECT_DEPENDS
                "${LESS_EMBREE_MANAGER_LAYOUT};${LESS_TURBID_DEBUG_HOST};${less_runtime_backward_integrator_embree_dependencies};${less_runtime_forward_integrator_embree_dependencies};${less_runtime_flux_integrator_embree_dependencies};${less_runtime_energy_balance_embree_dependencies}")
        set_property(SOURCE src/media/turbid/embree/domain.cpp APPEND PROPERTY
            OBJECT_DEPENDS
                "${LESS_EMBREE_MANAGER_LAYOUT};${LESS_TURBID_DEBUG_HOST}")
        set_property(SOURCE src/processes/thermo_physiology/embree/turbid_processes.cpp APPEND PROPERTY
            OBJECT_DEPENDS
                "${LESS_TURBID_PROCESS_CONTRACT};${LESS_TURBID_RADIATION_STORAGE};${LESS_TURBID_PHYSIOLOGY_STORAGE};${LESS_TURBID_ENERGY_BALANCE_STORAGE};${LESS_EMBREE_MANAGER_LAYOUT};${CMAKE_CURRENT_SOURCE_DIR}/src/processes/thermo_physiology/embree/turbid_processes.h")
        set_source_files_properties(
            src/processes/thermo_physiology/embree/processes.cpp
            PROPERTIES OBJECT_DEPENDS
                "${LESS_PHYSIOLOGY_CONTRACT};${LESS_PHOTOSYNTHESIS_PROCESS_CONTRACT};${LESS_PROCESS_CONTRACTS};${LESS_EMBREE_MANAGER_LAYOUT}")
        set_source_files_properties(
            src/processes/sif/embree/sif_process.cpp
            PROPERTIES OBJECT_DEPENDS
                "${LESS_PROCESS_CONTRACTS};${LESS_EMBREE_MANAGER_LAYOUT}")
        set_source_files_properties(
            src/processes/sif/embree/sif_source_assembly.cpp
            PROPERTIES OBJECT_DEPENDS
                "${LESS_PROCESS_CONTRACTS};${LESS_EMBREE_MANAGER_LAYOUT}")
        target_include_directories(less_cpu PUBLIC
            ${LESS_INCLUDE_DIR}
            ${less_surface_generated_include_dir}
            ${less_illumination_generated_include_dir} ${less_sensor_generated_include_dir}
            ${CMAKE_CURRENT_SOURCE_DIR}
            ${CMAKE_CURRENT_SOURCE_DIR}/src
        )
        target_link_libraries(less_cpu PUBLIC embree)
        if(OpenMP_CXX_FOUND)
            target_link_libraries(less_cpu PUBLIC OpenMP::OpenMP_CXX)
        endif()
        set(LESS_EMBREE_FIELD_PROGRAM_SOURCES
            "${CMAKE_CURRENT_SOURCE_DIR}/src/backends/embree/embree_backend.cpp"
            "${CMAKE_CURRENT_SOURCE_DIR}/src/processes/thermo_physiology/embree/turbid_processes.cpp"
            "${CMAKE_CURRENT_SOURCE_DIR}/src/processes/thermo_physiology/embree/joint_thermo_physiology.cpp"
            "${CMAKE_CURRENT_SOURCE_DIR}/src/processes/thermo_physiology/embree/processes.cpp"
            "${CMAKE_CURRENT_SOURCE_DIR}/src/processes/sif/embree/sif_process.cpp"
            "${less_runtime_field_contract}"
            "${less_runtime_field_program_contract}"
            "${less_runtime_scene_field_evaluator}"
            "${less_runtime_texture_footprint}")
        set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS
            ${LESS_EMBREE_FIELD_PROGRAM_SOURCES})
        set(LESS_EMBREE_FIELD_PROGRAM_DIGEST_INPUT "")
        foreach(LESS_FIELD_SOURCE IN LISTS LESS_EMBREE_FIELD_PROGRAM_SOURCES)
            file(SHA256 "${LESS_FIELD_SOURCE}" LESS_FIELD_SOURCE_SHA256)
            string(APPEND LESS_EMBREE_FIELD_PROGRAM_DIGEST_INPUT
                "${LESS_FIELD_SOURCE_SHA256}")
        endforeach()
        string(SHA256 LESS_EMBREE_FIELD_PROGRAM_SOURCE_SHA256
            "${LESS_EMBREE_FIELD_PROGRAM_DIGEST_INPUT}")
        target_compile_definitions(less_cpu PUBLIC
            LESS_HAS_EMBREE=1
            LESS_EMBREE_FIELD_PROGRAM_SOURCE_SHA256=\"${LESS_EMBREE_FIELD_PROGRAM_SOURCE_SHA256}\")

        # Find Embree runtime DLLs for packaging
        get_target_property(EMBREE_LOCATION embree IMPORTED_LOCATION_RELEASE)
        if(NOT EMBREE_LOCATION)
            get_target_property(EMBREE_LOCATION embree IMPORTED_LOCATION)
        endif()
        if(EMBREE_LOCATION)
            get_filename_component(EMBREE_BIN_DIR "${EMBREE_LOCATION}" DIRECTORY)
            # Install Embree + TBB runtime libraries
            file(GLOB EMBREE_DLLS
                "${EMBREE_BIN_DIR}/embree4${CMAKE_SHARED_LIBRARY_SUFFIX}"
                "${EMBREE_BIN_DIR}/tbb12${CMAKE_SHARED_LIBRARY_SUFFIX}"
                "${EMBREE_BIN_DIR}/tbbmalloc${CMAKE_SHARED_LIBRARY_SUFFIX}"
                "${EMBREE_BIN_DIR}/libembree4${CMAKE_SHARED_LIBRARY_SUFFIX}*"
                "${EMBREE_BIN_DIR}/libtbb${CMAKE_SHARED_LIBRARY_SUFFIX}*"
            )
            install(FILES ${EMBREE_DLLS} DESTINATION less)
            message(STATUS "Embree DLLs to install: ${EMBREE_DLLS}")
        endif()

        message(STATUS "Embree CPU backend: ENABLED")
    else()
        message(WARNING "Embree not found. CPU backend disabled. "
                "Install Embree 4: https://github.com/RenderKit/embree")
    endif()
endif()

# ---- Mutually exclusive Python binding modules ----
set(LESS_BINDING_SOURCES
    src/bindings/py_less.cpp
    src/bindings/core/model_registry.cpp
    ${LESS_PROCESS_CONTRACTS})

function(add_less_native_module target backend_macro backend_library)
    pybind11_add_module(${target} ${LESS_BINDING_SOURCES} ${ARGN})
    target_compile_definitions(${target} PRIVATE
        LESS_VERSION="${LESS_RELEASE_VERSION}"
        LESS_NATIVE_MODULE=${target}
        ${backend_macro}=1)
    target_include_directories(${target} PRIVATE ${LESS_INCLUDE_DIR}

        ${less_surface_generated_include_dir} ${less_medium_generated_include_dir}
        ${less_illumination_generated_include_dir} ${less_sensor_generated_include_dir}
        ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/src)
    target_link_libraries(${target} PRIVATE ${backend_library})
    install(TARGETS ${target} LIBRARY DESTINATION less)
endfunction()

set(LESS_NATIVE_TARGETS "")

if(TARGET less_optix)
    add_less_native_module(_native_optix LESS_HAS_OPTIX less_optix
        src/bindings/backend/optix_module.cpp
        src/bindings/backend/optix/optical_bindings.cpp
        src/bindings/backend/optix/process_bindings.cpp
        src/bindings/backend/optix/active_bindings.cpp
        src/bindings/backend/optix/diagnostics_bindings.cpp
        ${less_surface_binding_sources}
        ${less_medium_binding_sources}
        ${less_sensor_optix_binding_sources}
        ${less_process_binding_sources}
        ${less_runtime_optix_binding_sources}
        ${less_integrator_binding_sources})
    target_link_libraries(_native_optix PRIVATE less_optix)
    target_include_directories(_native_optix PRIVATE
        ${CUDAToolkit_INCLUDE_DIRS}
        ${OPTIX_INCLUDE_DIR})
    add_dependencies(_native_optix optix_ptx_modules)
    add_dependencies(_native_optix optix_ir_modules)
    list(APPEND LESS_NATIVE_TARGETS _native_optix)
endif()

if(TARGET less_vulkan)
    add_less_native_module(_native_vulkan LESS_HAS_VULKAN less_vulkan
        src/bindings/backend/vulkan_module.cpp
        src/bindings/backend/vulkan/scene_bindings.cpp
        src/bindings/backend/vulkan/optical_bindings.cpp
        src/bindings/backend/vulkan/process_bindings.cpp
        src/bindings/backend/vulkan/active_bindings.cpp
        src/bindings/backend/vulkan/diagnostics_bindings.cpp
        ${less_surface_binding_sources}
        ${less_medium_binding_sources}
        ${less_sensor_vulkan_binding_sources}
        ${less_process_binding_sources}
        ${less_runtime_vulkan_binding_sources}
        ${less_integrator_binding_sources})
    target_link_libraries(_native_vulkan PRIVATE less_vulkan)
    add_dependencies(_native_vulkan vulkan_kernel_modules)
    list(APPEND LESS_NATIVE_TARGETS _native_vulkan)
endif()

if(TARGET less_cpu)
    add_less_native_module(_native_embree LESS_HAS_EMBREE less_cpu
        src/bindings/backend/embree_module.cpp
        src/bindings/backend/embree/optical_bindings.cpp
        src/bindings/backend/embree/process_bindings.cpp
        src/bindings/backend/embree/active_bindings.cpp
        src/bindings/backend/embree/diagnostics_bindings.cpp
        ${less_surface_binding_sources}
        ${less_medium_binding_sources}
        ${less_sensor_embree_binding_sources}
        ${less_process_binding_sources}
        ${less_runtime_embree_binding_sources}
        ${less_integrator_binding_sources})
    target_link_libraries(_native_embree PRIVATE less_cpu)
    list(APPEND LESS_NATIVE_TARGETS _native_embree)
endif()

add_custom_target(less_native_modules)
add_dependencies(less_native_modules ${LESS_NATIVE_TARGETS})

# ---- Install targets (for pip/scikit-build) ----
install(FILES
    "${CMAKE_CURRENT_SOURCE_DIR}/python/less/_runtime/artifacts/turbid/sampling_manifest.json"
    "${CMAKE_CURRENT_SOURCE_DIR}/python/less/_runtime/artifacts/turbid/resident_manifest.json"
    DESTINATION less/_runtime/artifacts/turbid)
install(FILES
    "${CMAKE_CURRENT_SOURCE_DIR}/python/less/_runtime/artifacts/process/thermo_physiology_manifest.json"
    DESTINATION less/_runtime/artifacts/process)
install(FILES
    "${CMAKE_CURRENT_SOURCE_DIR}/python/less/data/srgb.coeff"
    "${CMAKE_CURRENT_SOURCE_DIR}/python/less/data/srgb.coeff.provenance.json"
    DESTINATION less/data)
if(LESS_ENABLE_OPTIX)
    # PTX and OptiX-IR are installed as one build-owned runtime resource set.
    # OptiX prefers IR; PTX remains available for driver/runtime diagnostics
    # and standalone CUDA Driver API kernels.
    install(FILES ${PTX_TARGETS} DESTINATION less/kernels/optix)
    install(FILES ${OPTIXIR_TARGETS} DESTINATION less/kernels/optix)
endif()
if(LESS_ENABLE_VULKAN)
    install(FILES ${VULKAN_SPV_TARGETS} DESTINATION less/kernels/vulkan)
endif()

message(STATUS "LESS build configured successfully")
