Compiling OpenCL Kernels with Xcode
The talk I’m working on for NSConference 2010 involves a decent amount of OpenCL kernel code. Originally, I thought I would develop the kernel code the same way I develop Core Image kernel code: build the kernel in Quartz Composer and then copy paste the code into Xcode when I got it working.
Unfortunately, I’ve not had much luck with Quartz Composer in way of meaningful compiler errors. Sometimes it returns good errors, sometimes inscrutable ones.
However, I’ve found debugging OpenCL kernels to be much easier thanks to the invention of a function I like to call “printf.” If you run your kernel on the CPU, then you can print out values just like in C. With debugging solved, I just needed to figure out how to find compiler errors without having to run my code.
Unlike Core Image, OpenCL actually requires you to compile your kernel code explicitly before using it. The API’s required to do this will return nice compiler error messages and everything. The obvious thing to do would be wrap up these API’s in a command line tool and invoke the tool from Xcode, which is what I did.
The Code
You can download the Xcode project for clc, the OpenCL Compiler here. It requires Mac OS X 10.6 Snow Leopard and the associated Xcode tools. To install, just build the project and copy the resulting binary into your path somewhere. I used ~/bin.
Most of the code is self explanatory, with the possible exception of the output. Unlike most compilers, clc doesn’t output object code because the host and runtime CPU/GPU’s could easily be different. It could simply copy the source code as is for the output, but that was a little too obvious for my tastes. Instead clc compresses the source code and stuffs it into a binary plist. It has the slight benefit of potentially being smaller on disk, and making your source code negligibly less accessible to prying eyes, if you’re into that sort of thing.
There are definite possibilities for improvement. For example, there could be an option to encrypt the source code to be more “secure.” Or it could store the binary code generated by the host machine in the output on the chance that the host and runtime machines have the same hardware.
Setting up Xcode
To use clc, you’ll need to set up a build rule in your app’s target settings to run all OpenCL source files through it. Selecting the menu item Project > Edit Active Target, and then selecting the Rules tab, should land you here:
Add another rule, set it to process OpenCL source files using a Custom script. For the custom script enter:
~/bin/clc ${INPUT_FILE_PATH} -o ${TARGET_BUILD_DIR}/${PRODUCT_NAME}.app/Contents/Resources/${INPUT_FILE_BASE}.clar
Finally, you’ll need to tell Xcode where you’re putting the output file, which is:
${TARGET_BUILD_DIR}/${PRODUCT_NAME}.app/Contents/Resources/${INPUT_FILE_BASE}.clar
By default, Xcode won’t put OpenCL source files (.cl) into the target, meaning they won’t get compiled. For each of your OpenCL source files
you’ll need to explicitly add them to your target. There are several ways to do this, but the easiest is probably selecting all of them, and doing a Get Info (File > Get Info). Switch to the Targets tab and check the target they should be compiled into.
Runtime Code
At runtime, it’s pretty easy to retrieve the original OpenCL source code using the + (NSString *) openCLSourceWithData:(NSData *)data error:(NSError **)error; method on ONOpenCLArchive. However, there are a couple of convenience methods on NSBundle that makes retrieving the kernel easier. For example:
#import "ONOpenCLArchive.h" NSString *kernelSource = [[NSBundle mainBundle] openCLResource:@"MyKernel"];
That’s pretty much all there is to it.
Conclusion
Although not ground breaking, this little tool has certainly been helpful to me by finding compiler errors at compile time instead of runtime. Hopefully it will be useful to other people as well.