Wednesday, July 30, 2014

Getting Qt to play nice with CUDA (Via CMake)

For a while, I tried to get qmake to compile and run CUDA kernels as part of my Qt project. I still think this is achievable, but I got frustrated with it and tried a different approach, as suggested by a coworker and friend of mine, whom we will call Bob :)  He was a huge help with this, and I decided to pass it on and document how it was done. 

First off, we are not creating a typical Qt project. You need to have CMake installed and select "Non-Qt Project" from the Qt templates:


Do not fret, however, as you will still be able to use all the Qt GUI functionality. We are just using CMake instead of qmake to build our project. Now we have to write the CMakeLists.txt file, which will tell CMake how to link everything and build the project correctly.

You can rewrite the 3 or so lines that were generated automatically. We just need the minimum required version of CMake, and to set our project's executable name and store it in a variable:
 cmake_minimum_required(VERSION 2.8.8)  
 set(EXECUTABLE_NAME <your executable name>)  
 project(${EXECUTABLE_NAME})  

Next, we deal with the moc files Qt creates (MOC stands for Meta Object Compiler in case anyone was wondering):
 # Tell CMake to run moc when necessary:  
 set(CMAKE_AUTOMOC ON) 
  
 # As moc files are generated in the binary dir, tell CMake  
 # to always look for includes there:  
 set(CMAKE_INCLUDE_CURRENT_DIR ON)  

We then tell CMake to look for the packages we will need, namely Qt5 Widgets, OpenGL, and of course CUDA:
 find_package(Qt5Widgets REQUIRED)  
 find_package(Qt5OpenGL REQUIRED)  
 find_package(CUDA REQUIRED)  

Add all the sources so they show up in Qt Creator:
 #Qt UI stuff...  
 qt5_wrap_ui(UI_HEADERS mainwindow.ui)  

 #Non-UI headers...  
 set(HEADERS  
       mainwindow.h  
       <stuff.h>  
   )  

 #ALL the source files  
 set(SOURCES  
       main.cpp  
       mainwindow.cpp  
       <stuff.cpp>  
   )  

Note: you can set nvcc compiler flags, but I have not needed to for this example. Here is how:
 set(CUDA_NVCC_FLAGS ${CUDA_NVCC_FLAGS};-O3 -gencode arch=compute_20,code=sm_20)  

Next, compile the CUDA objects. You can actually have multiple calls to cuda_compile and build different objects with different flags. But in this example, we only have one.
 cuda_compile(CUDA_OBJECTS cudakernel.cu)  

Pass additional compiler flags (non-nvcc):
 set(EXTRA_CXX_FLAGS "--std=c++11" CACHE STRING "common C++ build flags")  
 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${EXTRA_CXX_FLAGS}")  

Finally, link everything together: your executable, additional libraries, and Qt modules all use their own CMake commands to link properly:
 #Build the rest of the executable.  
 add_executable(${EXECUTABLE_NAME} ${SOURCES} ${HEADERS} ${UI_HEADERS} ${CUDA_OBJECTS})  

 #Remember dependencies!  
 target_link_libraries(${EXECUTABLE_NAME} GL ${CUDA_LIBRARIES})  

 #Add the Qt stuff.  
 qt5_use_modules(${EXECUTABLE_NAME} Widgets OpenGL)  

And that's it! Put all of that together in the CMakeLists.txt file and you'll be good to go. Now I can have my fun with Qt Creator and CUDA kernels :D

If anyone has any questions or issues with this, let me know in the comments and I will do my best to help.