OptiX CMake build setup with modern CUDA

Hey

I’ve been upgrading my OptiX renderer in my multi-renderer engine from 6.5 to 7.0+. Incrementally as OptiX 9.1 requires a driver that deprecates my old 6.5 based renderer. :D

As part of the upgrade I was hoping I could upgrade my CMake scripts to use the more modern CUDAToolkit. But I’ve discovered that that is harder than it seems. Seperable PTX compilation puts restrictions on how I can create my library that I don’t fully understand (something about OBJECT), documentation about variables and effects are hard to come by, and NVRTC setup documentation is non-existent as far as I can tell, which is a must for me right now as anything from OptiX 7.0/CUDA 11.1 and onwards won’t compile without NVRTC for some reason. I’ll debug that once I’m fully on OptiX 9.1 and CUDA 12.8.
So my question is, why do the OptiX samples still use an outdated way to setup CUDA? Is there a reason beyond simply not having updated the build system as it still works?

I’ve found Detlef’s OptiX_Apps repo, which uses CUDAToolkit, but the promise of an easier compilation setup disappeared after I looked into nvcuda_compile_module.cmake. Am I reading it right and those samples basically sidestep the issue and use FindCUDAToolkit to find the library, but then still manually setup shader compilation?

If you find the CMake script using the nvcuda_compile_module.cmake, which BTW can translate to PTX and OptiX-IR intermediate representations, too involved, have a look at the GLTF_renderer example which is using native CMake features namely LANGUAGES CUDA for native CUDA kernels and a CMake “Object Library” for the OptiX device code.

Read this post and the linked one in there for explanations:

https://forums.developer.nvidia.com/t/optix-advanced-samples-on-github/48410/19

See the dedicated README.md for similar information.

In any case, the advanced examples always use the CMake-provided FindCUDAToolkit.cmake to determine the CUDA SDK installation on your system. There is some hierarchy of methods that uses to find that, but under Windows, it normally boils down to the folder referenced by the CUDA_PATH environment variable.

Brilliant. Thanks Darofearth. That gives me a place to start.

Do you know what would have to be changed to use NVRTC? In the OptiX samples we still call CUDA_WRAP_SRCS if NVRTC is enabled, but I’m wondering if that’s needed, since it’s runtime compiled.

The CMakeLists.txt inside the advanced examples are only building static OptiX device code. For that, they either create custom build rules with the nvcuda_compile_module.cmake macro resp. use the more modern method with the CMake “Object Library” to separate the OptiX device code from native CUDA kernel code. Both are using NVCC for the static part of the device code.

When you want to build OptiX device code input modules as *.ptx or better as *.optixir by using NVRTC, there isn’t really a need to configure something in CMake for those runtime translated files.

The OptiX SDK contains all necessary source code showing that runtime translation already. If you search for functions starting with the nvrtc prefix inside the source code, you’ll find the necessary NVRTC API calls.

What the OptiX SDK does inside the CMakeLists.txt is configuring some NVRTC compiler build flags into defines for that, but that is not strictly necessary and could be hardcoded inside the source code as well. If the device code comes from files, you would also need to know the file names inside the host code which could be configured inside CMakeLists.txt as well, but I wouldn’t translate static code with NVRTC then.

This post explains what to look for inside the SDK code: https://forums.developer.nvidia.com/t/nvrtc-include-pathes/224224/2

The drawback with translating OptiX device code into modules is, that the target system must have the CUDA Toolkit headers and the OptiX SDK headers installed, as well as all your headers you use inside the OptiX device code of your renderer.

On the other hand, NVRTC is compiling faster than NVCC (about 3x last time I measured that) because it doesn’t write files. If you use that to translate shader code dynamically from some runtime generated *.cu sources, that’s a good method. I implemented a whole shader engine that way in the past. When possible you can also cache the translated module to avoid recompiling it again on program start when the input source hasn’t changed.

This post show some (old SM version) NVRTC options and include path configurations: https://forums.developer.nvidia.com/t/optix-compile-error-in-ptxstring/70636/2

While the OptiX SDK headers can be downloaded automatically from github in the meantime, to support open-source projects better, the full CUDA Toolkit installation is required due to licensing.

Your program will also need the redistributable NVRTC compiler library and its standard library to be included in the shipping target. Look for the two DLLs named with “nvrtc” inside the CUDA/<version>/bin folder inside your CUDA Toolkit installation.