CUDA 10.2, VS2019, and CMake

Introduction

It's a pain to start a new CUDA project in Visual Studio. I've had to edit my .vcproj files to handle CUDA dependencies and IDE settings. Usually, people will just copy the samples and hack them into shape. There is also a huge hole in documentation for this, from both Microsoft and Nvidia. This article isn't about programming in CUDA, but about setting up the programming development on your machine. I'll try to explain some of my solutions and some things I've picked up from trying to create new CUDA VS solutions and projects.

Visual Studio Solutions

This first part is about using the standard VS solutions and projects. Unfortunately it's notoriously buggy since CUDA and VS don't play very well together sometimes.

Hidden CUDA Template

If you open up VS2019, create a new project, and search for "CUDA", you'll see nothing. However, if you have a lot of template suggestions like me, you'll miss that there is a CUDA 10.2 Runtime template at the very bottom.

Visual Studio CUDA Template

The important part is that this template will create these two new CUDA configuration properties tabs.

Configuration Properties

Now if you hit play, it will run the default kernel.cu file.

Template Complications

CUDA and Visual Studio haven't been well integrated in the past. If this template works for you, then perfect. However, if you can't find the template, you're not alone. j2inet wrote a short post about copying files from the NVIDIA GPU Computing Toolkit into the Visual Studio build customizations folder.

Something else I've done in the past is manually edit the vcxproj files. The template inserts two CUDA specific items into the vcxproj file.

<ImportGroup Label="ExtensionSettings">
    <Import Project="$(VCTargetsPath)\BuildCustomizations\CUDA 10.2.props" />
</ImportGroup>
...
<!-- all the build information -->
...
<ImportGroup Label="ExtensionTargets">
    <Import Project="$(VCTargetsPath)\BuildCustomizations\CUDA 10.2.targets" />
</ImportGroup>
If you can't find the template file, then try adding these lines. This assumes that you followed j2inet's guide and copied the files into the build customizations folder.

All of the standard CUDA header files should be included using this template and these targets. Notably, the cuda_helper.h is not included since that is a part of the CUDA samples. I highly recommend using the cuda helper files that are included in the CUDA samples. You can go into your project properties --> linker --> additional dependencies and include cufft.lib or any other library you want that's detailed on the CUDA documentaiton.

CMake

Unfortunately there's another complication with using CMake because the standard for CUDA updated and several functions became deprecated. Some new projects will still use the old functions, especially if you need to generate embedded PTX files or other very fine grained controls like with Nvidia OptiX.

CMake Version 3.8+

Nvidia themselves wrote a blog about this here. Essentially, this is full support for CUDA where the rest of your CMake code stays the same, and you only have to add project (Project_Name LANGUAGES CUDA CXX) to your project definition. If you want to add libraries like CUFFT, then you have to make sure you look in the right place. Here's one of my CMake files of a GPU reverberator.

project (GPU_Reverberator LANGUAGES CUDA CXX)
# Add external libraries
if (WIN32)   
    set ( PROJECT_LINK_LIBS libsndfile-1.lib portaudio_x64.lib)
    link_directories(../libsndfile ../portaudio)
    include_directories(../libsndfile ../portaudio)
else (WIN32)
    set(PROJECT_LINK_LIBS sndfile.so)
    link_directories(/usr/lib /usr/lib/x86_64-linux-gnu /usr/local/lib)
    include_directories(/usr/include /usr/local/include)
endif (WIN32)

# Add include directories
include_directories(cuda_helpers ${CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES})

# Use file(GLOB ...) for wildcard additions:
file(GLOB SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/*)

# Add source to this project's executable.
add_executable(gpu_reverberator ${SOURCES})

# Find cufft
find_library(CUDA_CUFFT_LIBRARY cufft PATHS ${CMAKE_CUDA_IMPLICIT_LINK_DIRECTORIES})

# Add to linker
target_link_libraries(gpu_reverberator 
    ${PROJECT_LINK_LIBS}
    ${CUDA_CUFFT_LIBRARY}
)
CMake CUDA language support gives you the variable CMAKE_CUDA_IMPLICIT_LINK_DIRECTORIES. This find_library command searches for the string cufft in that specified path. There is another nicely written blog post here regarding CMake and CUDA.

Older CMake Versions

This full language support for CUDA only happened in version 3.8. Older versions will use find_package(CUDA REQUIRED). You still set include directories and libraries the same way, but you add source files to your compiler using cuda_add_executable(). You can also directly set nvcc flags. The OptiX 7 tutorial code is a good example of how much you can do using CMake and CUDA. OptiX requires you to compile your CUDA code into a PTX, embed that PTX into a string, and put that string into a c file.

This is my CMake file for my Acoustic Raytracing project that utilizes the embedded PTX, from Ingo Wald's tutorial.


find_package(CUDA REQUIRED)
set(CUDA_NVCC_FLAGS ${CUDA_NVCC_FLAGS} -gencode=arch=compute_61,code=sm_61)
include_directories(${OptiX_INCLUDE} ${PROJECT_SOURCE_DIR}/common/cuda_helpers ${SNDFILE_INCLUDE_DIR} ${PORTAUDIO_INCLUDE_DIR})
link_directories(${SNDFILE_INCLUDE_DIR} ${PORTAUDIO_INCLUDE_DIR})
cuda_compile_and_embed(embedded_ptx_code devicePrograms.cu)

cuda_add_executable(Acoustic_Raytracing.out
    audio.cpp
    audio.h
    kernels.cu
    kernels.cuh
    ${embedded_ptx_code}
    optix7.h
    CUDABuffer.h
    debug.cuh
    kernels.cuh
    LaunchParams.h
    main.cpp
    prd.h
    OptixSetup.h
    OptixSetup.cpp
    Bui/Microphone.h
    Bui/Microphone.cpp
    Bui/SoundItem.h
    Bui/SoundItem.cpp
    Bui/SoundSource.h
    Bui/SoundSource.cpp
    Bui/convolve.cu
    Bui/convolve.cuh
    )
target_link_libraries(Acoustic_Raytracing.out
    gdt
    ${optix_LIBRARY}
    ${CUDA_LIBRARIES}
    ${CUDA_CUDA_LIBRARY}
    ${SNDFILE_LIB}
    ${PORTAUDIO_LIB}
    ${CUDA_cufft_LIBRARY}
    )
This CMake file is in a subdirectory, and I set the portaudio and soundfile variables in the parent CMake file. You can ignore the cuda_compile_and_embed function if you're not using OptiX. The find_package command also creates the CUDA library linker variables such as CUDA_cufft_LIBRARY.

Conclusion

Setting up CUDA can be tricky if you don't know what you're doing. This short post was about the different ways I've setup a CUDA development environment without flat out copying the CUDA samples, which is what I've done in the past. It's also very nice to start from a very minimal example and start off from there.