I've been working on implementing various data structures (octrees, queues, and sorting algorithms) in HLSL to execute in parallel on a GPU. I quickly discovered that I was doing a lot of copy and paste because HLSL doesn't support fancy features like function pointers and lambdas.

Most recently I implemented a queue-like structure and I wanted the ability to sort this structure with an arbitrary sorting function. That is, depending on the purpose of the particular queue I want to use the sorting functions I already implemented, but with a custom domain-specific way to determine the order.

I've discovered this can be accomplished with preprocessor macros, although it is a bit ugly looking. Note that since HLSL is extremely similar to C this same technique can be used there as well, as seen here.

#define CustomSort(name, sortFunctionName) 				\
void Sort##name(int index) {							\
	/* code... */					\
	bool swap = sortFunctionName(index, index+1));		\
	/* code... */								\

And then it can be used like this

bool CompareShortestDistances(index1, index2) {
	return (CalcDistance(data[index1])<CalcDistance(data[index2]));

// Generate a new sorting function called SortCalcDistance which uses
// CompareShortestDistances internally.
CustomSort(CalcDistance, CompareShortestDistances)

void SomeOtherFunction(int threadId) {
    int index=threadId;
	SortCalcDistance(index); //Function generated by CustomSort macro

(There are a lot of missing bits here since I didn't include full code for sorting, but I'm just trying to demonstrate the core concept.)

So now some important points:

  1. Multiline preprocessor macros require a slash at the end of each line, which is why the macro definition looks a bit messy.
  2. Preprocessor macros don't really recognize string syntax with quotes, so the "CalcDistance" is passed raw to the macro to generate the new function name. In other words, this doesn't work:
    CustomSort("CalcDistance", CompareShortestDistances)
    (Also note there is no semicolon at the end of the statement since this is a preprocessor macro)
  3. The function being passed to the macro, "CompareShortestDistances" in this case, needs to have the signature expected within the macro or else there will be a compiler error (well, if your lucky). In this scenario it expects a function that takes 2 integers and returns a boolean.
  4. If you are using namespaces, the macro-generated function will not be in the same namespace scope as where the macro is actually defined (since macros exist outside the concept of namespaces). So for my application even though I was defining my macro inside a namespace I had to add references to that namespace within the macro function. HLSL doesn't allow referencing a namespace inside the same namespace.

So kind of hacky, but it seems to work pretty well and now I have just one chunk of code to modify (like implement a better sorting algorithm) that and all other modules that use the macro will automatically receive those improvements.