+# The gmx_detect_gpu() macro aims to detect GPUs available in the build machine
+# and provide the number, names, and compute-capabilities of these devices.
+#
+# The current version is limited to checking the availability of NVIDIA GPUs
+# without compute-capability information.
+#
+# The current detection relies on the following checks in the order of listing:
+# - output of nvidia-smi (if available);
+# - presence and content of of /proc/driver/nvidia/gpus/*/information (Linux)
+# - output of lspci (Linux)
+#
+# If any of the checks succeeds in finding devices, consecutive checks will not
+# be carried out. Additionally, when lspci is used and a device with unknown
+# PCI ID is encountered, lspci tries to check the online PCI ID database. If
+# this is not possible or the device is simply not recognized, no device names
+# will be available.
+#
+# The following advanced variables are defined:
+# - GMX_DETECT_GPU_AVAILABLE - TRUE if any GPUs were detected, otherwise FALSE
+# - GMX_DETECT_GPU_COUNT - # of GPUs detected
+# - GMX_DETECT_GPU_INFO - list of information strings of the detected GPUs
+#
+# NOTE: The proper solution is to detect hardware compatible with the native
+# GPU acceleration. However, this requires checking the compute capability
+# of the device which is not possible with the current checks and requires
+# interfacing with the CUDA driver API.
+#
+
+# check whether the number of GPUs machetes the number of elements in the GPU info list
+macro(check_num_gpu_info NGPU GPU_INFO)
+ list(LENGTH ${GPU_INFO} _len)
+ if (NOT NGPU EQUAL _len)
+ list(APPEND ${GMX_DETECT_GPU_INFO} "NOTE: information about some GPU(s) missing!")
+ endif()
+endmacro()
+
+macro(gmx_detect_gpu)
+
+ if (NOT DEFINED GMX_DETECT_GPU_COUNT OR NOT DEFINED GMX_DETECT_GPU_INFO)
+
+ set(GMX_DETECT_GPU_COUNT 0)
+ set(GMX_DETECT_GPU_INFO "")
+
+ message(STATUS "Looking for NVIDIA GPUs present in the system")
+
+ # nvidia-smi-based detection.
+ # Requires the nvidia-smi tool to be installed and available in the path
+ # or in one of the default search locations
+ if (NOT DEFINED GMX_DETECT_GPU_COUNT_NVIDIA_SMI)
+ # try to find the nvidia-smi binary
+ # TODO add location hints
+ find_program(_nvidia_smi "nvidia-smi")
+ if (_nvidia_smi)
+ set(GMX_DETECT_GPU_COUNT_NVIDIA_SMI 0)
+ # execute nvidia-smi -L to get a short list of GPUs available
+ exec_program(${_nvidia_smi_path} ARGS -L
+ OUTPUT_VARIABLE _nvidia_smi_out
+ RETURN_VALUE _nvidia_smi_ret)
+ # process the stdout of nvidia-smi
+ if (_nvidia_smi_ret EQUAL 0)
+ # convert string with newlines to list of strings
+ string(REGEX REPLACE "\n" ";" _nvidia_smi_out "${_nvidia_smi_out}")
+ foreach(_line ${_nvidia_smi_out})
+ if (_line MATCHES "^GPU [0-9]+:")
+ math(EXPR GMX_DETECT_GPU_COUNT_NVIDIA_SMI "${GMX_DETECT_GPU_COUNT_NVIDIA_SMI}+1")
+ # the UUID is not very useful for the user, remove it
+ string(REGEX REPLACE " \\(UUID:.*\\)" "" _gpu_info "${_line}")
+ if (NOT _gpu_info STREQUAL "")
+ list(APPEND GMX_DETECT_GPU_INFO "${_gpu_info}")
+ endif()
+ endif()
+ endforeach()
+
+ check_num_gpu_info(${GMX_DETECT_GPU_COUNT_NVIDIA_SMI} GMX_DETECT_GPU_INFO)
+ set(GMX_DETECT_GPU_COUNT ${GMX_DETECT_GPU_COUNT_NVIDIA_SMI})
+ endif()
+ endif()
+
+ unset(_nvidia_smi CACHE)
+ unset(_nvidia_smi_ret)
+ unset(_nvidia_smi_out)
+ unset(_gpu_name)
+ unset(_line)
+ endif()
+
+ if (UNIX AND NOT (APPLE OR CYGWIN))
+ # /proc/driver/nvidia/gpus/*/information-based detection.
+ # Requires the NVDIA closed source driver to be installed and loaded
+ if (NOT DEFINED GMX_DETECT_GPU_COUNT_PROC AND GMX_DETECT_GPU_COUNT EQUAL 0)
+ set(GMX_DETECT_GPU_COUNT_PROC 0)
+ file(GLOB _proc_nv_gpu_info "/proc/driver/nvidia/gpus/*/information")
+ foreach (_file ${_proc_nv_gpu_info})
+ math(EXPR GMX_DETECT_GPU_COUNT_PROC "${GMX_DETECT_GPU_COUNT_PROC}+1")
+ # assemble information strings similar to the nvidia-smi output
+ # GPU ID = directory name on /proc/driver/nvidia/gpus/
+ string(REGEX REPLACE "/proc/driver/nvidia/gpus.*([0-9]+).*information" "\\1" _gpu_id ${_file})
+ # GPU name
+ file(STRINGS ${_file} _gpu_name LIMIT_COUNT 1 REGEX "^Model:.*" NO_HEX_CONVERSION)
+ string(REGEX REPLACE "^Model:[ \t]*(.*)" "\\1" _gpu_name "${_gpu_name}")
+ if (NOT _gpu_id STREQUAL "" AND NOT _gpu_name STREQUAL "")
+ list(APPEND GMX_DETECT_GPU_INFO "GPU ${_gpu_id}: ${_gpu_name}")
+ endif()
+ endforeach()
+
+ check_num_gpu_info(${GMX_DETECT_GPU_COUNT_PROC} GMX_DETECT_GPU_INFO)
+ set(GMX_DETECT_GPU_COUNT ${GMX_DETECT_GPU_COUNT_PROC})
+
+ unset(_proc_nv_gpu_info)
+ unset(_gpu_name)
+ unset(_gpu_id)
+ unset(_file)
+ endif()
+
+ # lspci-based detection (does not provide GPU information).
+ # Requires lspci and for GPU names to be fetched from the central
+ # PCI ID db if not available locally.
+ if (NOT DEFINED GMX_DETECT_GPU_COUNT_LSPCI AND GMX_DETECT_GPU_COUNT EQUAL 0)
+ set(GMX_DETECT_GPU_COUNT_LSPCI 0)
+ exec_program(lspci ARGS -q
+ OUTPUT_VARIABLE _lspci_out
+ RETURN_VALUE _lspci_ret)
+ # prehaps -q is not supported, try running without
+ if (NOT RETURN_VALUE EQUAL 0)
+ exec_program(lspci
+ OUTPUT_VARIABLE _lspci_out
+ RETURN_VALUE _lspci_ret)
+ endif()
+ if (_lspci_ret EQUAL 0)
+ # convert string with newlines to list of strings
+ STRING(REGEX REPLACE ";" "\\\\;" _lspci_out "${_lspci_out}")
+ string(REGEX REPLACE "\n" ";" _lspci_out "${_lspci_out}")
+ foreach(_line ${_lspci_out})
+ string(TOUPPER "${_line}" _line_upper)
+ if (_line_upper MATCHES ".*VGA.*NVIDIA.*" OR _line_upper MATCHES ".*3D.*NVIDIA.*")
+ math(EXPR GMX_DETECT_GPU_COUNT_LSPCI "${GMX_DETECT_GPU_COUNT_LSPCI}+1")
+ # Try to parse out the device name which should be
+ # included in the lspci -q output between []-s
+ string(REGEX REPLACE ".*\\[(.*)\\].*" "\\1" _gpu_name "${_line}")
+ if (NOT _gpu_name EQUAL "")
+ list(APPEND GMX_DETECT_GPU_INFO "${_gpu_name}")
+ endif()
+ endif()
+ endforeach()
+
+ check_num_gpu_info(${GMX_DETECT_GPU_COUNT_LSPCI} GMX_DETECT_GPU_INFO)
+ set(GMX_DETECT_GPU_COUNT ${GMX_DETECT_GPU_COUNT_LSPCI})
+ endif()
+
+ unset(_lspci_ret)
+ unset(_lspci_out)
+ unset(_gpu_name)
+ unset(_line)
+ unset(_line_upper)
+ endif()
+ endif()
+
+ if (GMX_DETECT_GPU_COUNT GREATER 0)
+ set(GMX_DETECT_GPU_AVAILABLE YES)
+ else()
+ set(GMX_DETECT_GPU_AVAILABLE NO)
+ endif()
+ set(GMX_DETECT_GPU_AVAILABLE YES CACHE BOOL "Whether any NVIDIA GPU was detected" FORCE)
+
+ set(GMX_DETECT_GPU_COUNT ${GMX_DETECT_GPU_COUNT}
+ CACHE STRING "Number of NVIDIA GPUs detected")
+ set(GMX_DETECT_GPU_INFO ${GMX_DETECT_GPU_INFO}
+ CACHE STRING "basic information on the detected NVIDIA GPUs")
+
+ set(GMX_DETECT_GPU_COUNT_NVIDIA_SMI ${GMX_DETECT_GPU_COUNT_NVIDIA_SMI}
+ CACHE INTERNAL "Number of NVIDIA GPUs detected using nvidia-smi")
+ set(GMX_DETECT_GPU_COUNT_PROC ${GMX_DETECT_GPU_COUNT_PROC}
+ CACHE INTERNAL "Number of NVIDIA GPUs detected in /proc/driver/nvidia/gpus")
+ set(GMX_DETECT_GPU_COUNT_LSPCI ${GMX_DETECT_GPU_COUNT_LSPCI}
+ CACHE INTERNAL "Number of NVIDIA GPUs detected using lspci")
+
+ mark_as_advanced(GMX_DETECT_GPU_AVAILABLE
+ GMX_DETECT_GPU_COUNT
+ GMX_DETECT_GPU_INFO)
+
+ if (GMX_DETECT_GPU_AVAILABLE)
+ message(STATUS "Number of NVIDIA GPUs detected: ${GMX_DETECT_GPU_COUNT} ")
+ else()
+ message(STATUS "Could not detect NVIDIA GPUs")
+ endif()
+
+ endif (NOT DEFINED GMX_DETECT_GPU_COUNT OR NOT DEFINED GMX_DETECT_GPU_INFO)
+endmacro(gmx_detect_gpu)