From c22b2e2e49669d4d205f48eaf1bb0caa056aa9d7 Mon Sep 17 00:00:00 2001 From: Robert Chisholm Date: Wed, 5 Jul 2023 15:03:58 +0100 Subject: [PATCH] Make GLM include directory portable GLM include files and license are now embedded into Python wheels when enabled. pyflamegpu.GLM now returns whether GLM support is enabled within the wheel. Added GLM_INC_DIR as portable way to locate GLM include dir --- README.md | 5 +-- src/flamegpu/detail/JitifyCache.cu | 50 +++++++++++++++++++++++--- swig/python/CMakeLists.txt | 56 +++++++++++++++++++++++++++--- swig/python/__init__.py.in | 14 ++++++-- swig/python/flamegpu.i | 7 ++++ swig/python/setup.py.in | 2 +- 6 files changed, 119 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index e0efc9d00..b405b374b 100644 --- a/README.md +++ b/README.md @@ -175,7 +175,7 @@ cmake --build . --target all | `FLAMEGPU_RTC_DISK_CACHE` | `ON`/`OFF` | Enable/Disable caching of RTC functions to disk. Default `ON`. | | `FLAMEGPU_VERBOSE_PTXAS` | `ON`/`OFF` | Enable verbose PTXAS output during compilation. Default `OFF`. | | `FLAMEGPU_CURAND_ENGINE` | `XORWOW` / `PHILOX` / `MRG` | Select the CUDA random engine. Default `XORWOW` | -| `FLAMEGPU_ENABLE_GLM` | `ON`/`OFF` | Experimental feature for GLM type support in RTC models. Default `OFF`. | +| `FLAMEGPU_ENABLE_GLM` | `ON`/`OFF` | Experimental feature for GLM type support within models. Default `OFF`. | | `FLAMEGPU_SHARE_USAGE_STATISTICS` | `ON`/`OFF` | Share usage statistics ([telemetry](https://docs.flamegpu.com/guide/telemetry)) to support evidencing usage/impact of the software. Default `ON`. | | `FLAMEGPU_TELEMETRY_SUPPRESS_NOTICE` | `ON`/`OFF` | Suppress notice encouraging telemetry to be enabled, which is emitted once per binary execution if telemetry is disabled. Defaults to `OFF`, or the value of a system environment variable of the same name. | | `FLAMEGPU_TELEMETRY_TEST_MODE` | `ON`/`OFF` | Submit telemetry values to the test mode of TelemetryDeck. Intended for use during development of FLAMEGPU rather than use. Defaults to `OFF`, or the value of a system environment variable of the same name.| @@ -247,7 +247,8 @@ Several environmental variables are used or required by FLAME GPU 2. | `FLAMEGPU_RTC_INCLUDE_DIRS` | A list of include directories that should be provided to the RTC compiler, these should be separated using `;` (Windows) or `:` (Linux). If this variable is not found, the working directory will be used as a default. | | `FLAMEGPU_SHARE_USAGE_STATISTICS` | Enable / Disable sending of telemetry data, when set to `ON` or `OFF` respectively. | | `FLAMEGPU_TELEMETRY_SUPPRESS_NOTICE` | Enable / Disable a once per execution notice encouraging the use of telemetry, if telemetry is disabled, when set to `ON` or `OFF` respectively. | -| `FLAMEGPU_TELEMETRY_TEST_MODE` | Enable / Disable sending telemetry data to a test endpoint, for FLAMEGPU develepoment to separate user statistics from developer statistics. Set to `ON` or `OFF`. | +| `FLAMEGPU_TELEMETRY_TEST_MODE` | Enable / Disable sending telemetry data to a test endpoint, for FLAMEGPU development to separate user statistics from developer statistics. Set to `ON` or `OFF`. | +| `FLAMEGPU_GLM_INC_DIR` | When RTC compilation is required and GLM support has been enabled, if the location of the GLM include directory cannot be found it must be specified using the `FLAMEGPU_GLM_INC_DIR` environment variable. | ## Running the Test Suite(s) diff --git a/src/flamegpu/detail/JitifyCache.cu b/src/flamegpu/detail/JitifyCache.cu index 0813fd983..554589dd0 100644 --- a/src/flamegpu/detail/JitifyCache.cu +++ b/src/flamegpu/detail/JitifyCache.cu @@ -152,12 +152,12 @@ std::string getFLAMEGPUIncludeDir(std::string &env_var_used) { break; } } catch (...) { } - // Throw if the value is not empty, but it does not exist. Outside the try catch excplicityly. + // Throw if the value is not empty, but it does not exist. Outside the try catch explicitly. THROW flamegpu::exception::InvalidFilePath("Error environment variable %s (%s) does not contain flamegpu/flamegpu.h. Please correct this environment variable.", env_var.c_str(), env_value.c_str()); } } - // If no appropriate environmental variables were found, check upwards for N levels (assuming the default filestructure is in use) + // If no appropriate environmental variables were found, check upwards for N levels (assuming the default file structure is in use) if (include_dir_str.empty()) { // Start with the current working directory std::filesystem::path test_dir("."); @@ -209,13 +209,54 @@ break_flamegpu_inc_dir_loop: return include_dir_str; } +#ifdef FLAMEGPU_USE_GLM +/** + * Get the GLM include directory via the environment variables. + * @return the GLM include directory. + */ +std::string getGLMIncludeDir() { + const std::string env_var = "FLAMEGPU_GLM_INC_DIR"; + const std::string test_file = "glm/glm.hpp"; + // Check the environment variable to see whether glm/glm.hpp exists + { + // If the environment variable exists + std::string env_value = std::getenv(env_var.c_str()) ? std::getenv(env_var.c_str()) : ""; + // If it's a value, check if the path exists, and if any expected files are found. + if (!env_value.empty()) { + std::filesystem::path check_file = std::filesystem::path(env_value) / test_file; + // Use try catch to suppress file permission exceptions etc + try { + if (std::filesystem::exists(check_file)) { + return env_value; + } + } + catch (...) {} + // Throw if the value is not empty, but it does not exist. Outside the try catch explicitly. + THROW flamegpu::exception::InvalidFilePath("Error environment variable %s (%s) does not contain %s. Please correct this environment variable.", env_var.c_str(), env_value.c_str(), test_file.c_str()); + } + } + + // If no appropriate environmental variables were found, check the compile time path to GLM + std::filesystem::path check_file = std::filesystem::path(FLAMEGPU_GLM_PATH) / test_file; + // Use try catch to suppress file permission exceptions etc + try { + if (std::filesystem::exists(check_file)) { + return FLAMEGPU_GLM_PATH; + } + } + catch (...) {} + // Throw if header wasn't found. Outside the try catch explicitly. + THROW flamegpu::exception::InvalidAgentFunc("Error compiling runtime agent function: Unable to automatically determine location of GLM include directory and %s environment variable not set", env_var.c_str()); +} +#endif + /** * Confirm that include directory version header matches the version of the static library. * This only compares up to the pre-release version number. Build metadata is only used for the RTC cache. * @param flamegpuIncludeDir path to the flamegpu include directory to check. * @return boolean indicator of success. */ -bool confirmFLAMEGPUHeaderVersion(const std::string flamegpuIncludeDir, const std::string envVariable) { +bool confirmFLAMEGPUHeaderVersion(const std::string &flamegpuIncludeDir, const std::string &envVariable) { static bool header_version_confirmed = false; if (!header_version_confirmed) { @@ -293,7 +334,8 @@ std::unique_ptr JitifyCache::compileKernel(const std::strin #ifdef FLAMEGPU_USE_GLM // GLM headers increase build time ~5x, so only enable glm if user is using it if (kernel_src.find("glm") != std::string::npos) { - options.push_back(std::string("-I") + FLAMEGPU_GLM_PATH); + static const std::string glm_include_dir = getGLMIncludeDir(); + options.push_back(std::string("-I") + glm_include_dir); options.push_back(std::string("-DFLAMEGPU_USE_GLM")); } #endif diff --git a/swig/python/CMakeLists.txt b/swig/python/CMakeLists.txt index 5a7db1736..0b2965de1 100644 --- a/swig/python/CMakeLists.txt +++ b/swig/python/CMakeLists.txt @@ -156,20 +156,48 @@ set(PYTHON_CODEGEN_SRC_FILES ${CMAKE_CURRENT_SOURCE_DIR}/codegen/codegen.py ) -# cleanup the flamegpu include file paths, so they're relative (begin `include/`) and seperated by `", "` +# cleanup the flamegpu include file paths, so they're relative (begin `include/`) and separated by `", "` foreach(FLAMEGPU_INC_FILE IN LISTS FLAMEGPU_INCLUDE) file(RELATIVE_PATH FLAMEGPU_INC_FILE_CLEAN "${FLAMEGPU_ROOT}" "${FLAMEGPU_INC_FILE}") set(FLAMEGPU_INCLUDE_CLEAN "${FLAMEGPU_INCLUDE_CLEAN}'${FLAMEGPU_INC_FILE_CLEAN}', ") unset(FLAMEGPU_INC_FILE_CLEAN) endforeach() -# cleanup code generator module files, so they're seperated by `", "` (file list is already using relative paths) +# cleanup code generator module files, so they're separated by `", "` (file list is already using relative paths) foreach(PYTHON_CODEGEN_FILE IN LISTS PYTHON_CODEGEN_SRC_FILES) file(RELATIVE_PATH PYTHON_CODEGEN_FILE_CLEAN "${CMAKE_CURRENT_SOURCE_DIR}" "${PYTHON_CODEGEN_FILE}") set(FLAMEGPU_CODEGEN_INCLUDE_CLEAN "${FLAMEGPU_CODEGEN_INCLUDE_CLEAN}'${FPYTHON_CODEGEN_FILE_CLEAN}', ") unset(PYTHON_CODEGEN_FILE_CLEAN) endforeach() +# Locate and cleanup GLM include files, so they're separated by `", "` +if(FLAMEGPU_ENABLE_GLM) + FetchContent_GetProperties(glm POPULATED glm_POPULATED SOURCE_DIR glm_SOURCE_DIR) + if (glm_POPULATED) + # Locate the root header to find the header directory + find_path(glm_ROOT + NAMES + glm/glm.hpp + PATHS + ${glm_SOURCE_DIR} + NO_CACHE + ) + # Build a list of all files in include dir + FILE(GLOB_RECURSE glm_INC_FILES "${glm_ROOT}glm/*") + # Add license to that list + list(APPEND glm_INC_FILES "${glm_ROOT}copying.txt") + # Clean, add separator and setup file copy + unset(glm_POPULATED) + unset(glm_SOURCE_DIR) + foreach(glm_INC_FILE IN LISTS glm_INC_FILES) + file(RELATIVE_PATH glm_INC_FILE_CLEAN "${glm_ROOT}" "${glm_INC_FILE}") + set(GLM_INCLUDE_CLEAN "${GLM_INCLUDE_CLEAN}'glm/${glm_INC_FILE_CLEAN}', ") # This var is used by setup.py template + endforeach() + else() + message(FATAL_ERROR "Python cmake can't find glm") + endif() +endif() + # Build a list of OS specific python package_data entries. set(FLAMEGPU_PYTHON_PACKAGE_DATA_OS_SPECIFIC "") if (FLAMEGPU_VISUALISATION) @@ -232,7 +260,7 @@ flamegpu_search_python_module(wheel) flamegpu_search_python_module(build) ## ------ -# Define custom commands to produce files in the current cmake directory, a custom target which the user invokes to build the python wheel with appropraite dependencies configured, and any post-build steps required. +# Define custom commands to produce files in the current cmake directory, a custom target which the user invokes to build the python wheel with appropriate dependencies configured, and any post-build steps required. ## ------ set(PYTHON_FLAMEGPU_LIB_OUTPUT_MODULE_DIR "${PYTHON_LIB_OUTPUT_DIRECTORY}/src/${PYTHON_MODULE_NAME}") # Only expliclty create the directory under linux, msbuild emits warnings and is fine without. @@ -258,7 +286,7 @@ foreach(FLAMEGPU_INC_FILE IN LISTS FLAMEGPU_INCLUDE) endforeach() # Create the codegen directory, and copy the codegen files in. -# Only expliclty create the directory under linux, msbuild emits warnings and is fine without. +# Only explicitly create the directory under linux, msbuild emits warnings and is fine without. if(NOT WIN32) add_custom_command( OUTPUT "${PYTHON_FLAMEGPU_LIB_OUTPUT_MODULE_DIR}/codegen" @@ -267,7 +295,7 @@ if(NOT WIN32) list(APPEND PYTHON_MODULE_TARGET_NAME_DEPENDS "${PYTHON_FLAMEGPU_LIB_OUTPUT_MODULE_DIR}/codegen") endif() -# Copy each codegen file into the pthon module directory, and append the filename to the list of python wheel dependencies. +# Copy each codegen file into the python module directory, and append the filename to the list of python wheel dependencies. foreach(PYTHON_CODEGEN_FILE IN LISTS PYTHON_CODEGEN_SRC_FILES) file(RELATIVE_PATH PYTHON_CODEGEN_FILE_CLEAN "${CMAKE_CURRENT_SOURCE_DIR}" "${PYTHON_CODEGEN_FILE}") set(PYTHON_FLAMEGPU_LIB_OUTPUT_CODEGEN_FILE "${PYTHON_FLAMEGPU_LIB_OUTPUT_MODULE_DIR}/${PYTHON_CODEGEN_FILE_CLEAN}") @@ -281,6 +309,24 @@ foreach(PYTHON_CODEGEN_FILE IN LISTS PYTHON_CODEGEN_SRC_FILES) unset(PYTHON_FLAMEGPU_LIB_OUTPUT_CODEGEN_FILE) endforeach() +# Copy GLM files into the python module directory and append the filename to the list of python wheel dependencies. +if(FLAMEGPU_ENABLE_GLM) + foreach(glm_INC_FILE IN LISTS glm_INC_FILES) + file(RELATIVE_PATH glm_INC_FILE_CLEAN "${glm_ROOT}" "${glm_INC_FILE}") + set(PYTHON_FLAMEGPU_LIB_OUTPUT_glm_FILE "${PYTHON_FLAMEGPU_LIB_OUTPUT_MODULE_DIR}/glm/${glm_INC_FILE_CLEAN}") + add_custom_command( + OUTPUT "${PYTHON_FLAMEGPU_LIB_OUTPUT_glm_FILE}" + DEPENDS ${glm_INC_FILE} + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${glm_INC_FILE} ${PYTHON_FLAMEGPU_LIB_OUTPUT_glm_FILE} + COMMENT "Copying ${glm_INC_FILE} to ${PYTHON_FLAMEGPU_LIB_OUTPUT_glm_FILE}" + ) + list(APPEND PYTHON_MODULE_TARGET_NAME_DEPENDS "${PYTHON_FLAMEGPU_LIB_OUTPUT_glm_FILE}") + unset(PYTHON_FLAMEGPU_LIB_OUTPUT_glm_FILE) + endforeach() + unset(glm_ROOT) + unset(glm_INC_FILES) +endif() + # Copy the visualisation dlls if required, this must occur before the wheel is built if (FLAMEGPU_VISUALISATION) if(COMMAND flamegpu_visualiser_get_runtime_depenencies) diff --git a/swig/python/__init__.py.in b/swig/python/__init__.py.in index d9c74d9b8..1c6919389 100644 --- a/swig/python/__init__.py.in +++ b/swig/python/__init__.py.in @@ -4,7 +4,7 @@ if not "FLAMEGPU_INC_DIR" in os.environ or not "FLAMEGPU2_INC_DIR" in os.environ os.environ["FLAMEGPU_INC_DIR"] = str(pathlib.Path(__file__).resolve().parent / "include") else: print("@PYTHON_MODULE_NAME@ warning: env var 'FLAMEGPU_INC_DIR' is present, RTC headers may be incorrect.", file=sys.stderr) - + # Some Windows users have dll load failed, because Python can't find nvrtc # It appears due to a combination of Python and Anaconda versions # Python 3.8+ requires DLL loads to be manually specified with os.add_dll_directory() @@ -31,7 +31,15 @@ if os.name == 'nt' and hasattr(os, 'add_dll_directory') and callable(getattr(os, # module version __version__ = '@FLAMEGPU_VERSION_PYTHON@' -del os, sys, pathlib, subprocess # Normal module stuff __all__ = ["@PYTHON_MODULE_NAME@"] -from .@PYTHON_MODULE_NAME@ import * \ No newline at end of file +from .@PYTHON_MODULE_NAME@ import * + +# GLM delayed so we can check whether it was enabled +if GLM: + if not "FLAMEGPU_GLM_INC_DIR" in os.environ or not "FLAMEGPU_GLM_INC_DIR" in os.environ: + os.environ["FLAMEGPU_GLM_INC_DIR"] = str(pathlib.Path(__file__).resolve().parent / "glm") + else: + print("@PYTHON_MODULE_NAME@ warning: env var 'FLAMEGPU_GLM_INC_DIR' is present, GLM include path may be incorrect.", file=sys.stderr) + +del os, sys, pathlib, subprocess \ No newline at end of file diff --git a/swig/python/flamegpu.i b/swig/python/flamegpu.i index 469d0f657..364995d91 100644 --- a/swig/python/flamegpu.i +++ b/swig/python/flamegpu.i @@ -1175,4 +1175,11 @@ TEMPLATE_VARIABLE_INSTANTIATE_INTS(poisson, flamegpu::HostRandom::poisson) #define SEATBELTS false #else #define SEATBELTS false +#endif + +#ifdef FLAMEGPU_USE_GLM + #undef FLAMEGPU_USE_GLM + #define GLM true +#else + #define GLM false #endif \ No newline at end of file diff --git a/swig/python/setup.py.in b/swig/python/setup.py.in index 6ef0e5798..e41959209 100644 --- a/swig/python/setup.py.in +++ b/swig/python/setup.py.in @@ -36,7 +36,7 @@ setup( 'Topic :: Scientific/Engineering', ], package_data={ - '@PYTHON_MODULE_NAME@':['$', @FLAMEGPU_CODEGEN_INCLUDE_CLEAN@@FLAMEGPU_INCLUDE_CLEAN@@FLAMEGPU_PYTHON_PACKAGE_DATA_OS_SPECIFIC@], + '@PYTHON_MODULE_NAME@':['$', @FLAMEGPU_CODEGEN_INCLUDE_CLEAN@@FLAMEGPU_INCLUDE_CLEAN@@FLAMEGPU_PYTHON_PACKAGE_DATA_OS_SPECIFIC@@GLM_INCLUDE_CLEAN@], }, install_requires=[ 'astpretty',