Working Around Constructors in HLSL (or lack thereof)

As of today, HLSL doesn’t officially support constructors for user-created classes. In the meantime, with the current tools at our disposal, maybe there is a way to work around this limitation and emulate constructor functionality ourselves?

It turns out we can!

This post describes how to implement constructor-like* functionality in HLSL. The following implementation relies on Microsoft’s DirectX Shader Compiler (DXC) and works for default constructors and constructors with a variable number of arguments.

Note: constructor-like is essential here. Not all functionality is supported, but enough to make it meaningful and worth your while.

Constructor Support in HLSL

Currently, constructors are only available for native HLSL types such as vector and matrix types:

bool2    u = bool2(true, false);
float3   v = float3(1, 2, 3);
float4x4 m = float3x3(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1);

For example, the following is not supported (yet):

class MyClass 
{
public:
    // Default Constructor
    MyClass()
    {
        x = 0;
        b = false;
        i = 0;
    }

    // Constructor with a single parameter
    MyClass(const float inX) : MyClass()
    {
        x = inX;
    }

    // Constructor with multiple parameters
    MyClass(const float inX, const bool inB, const int inI)
    {
        x = inX;
        b = inB;
        i = inI;
    }

private:
    // Member variables
    float x;
    bool  b;
    int   i; 
};

MyClass a = MyClass();              // Default Constructor
MyClass b = MyClass(3.0f);          // Constructor with a single parameter
MyClass c = MyClass(3.0f, true, 7); // Constructor with multiple parameters

At compilation, several errors will appear due to the lack of functionality.

So, typically, workarounds come in different shapes or forms:

// Initializes the whole class to 0
MyClass a = (MyClass)0;

// Initializes an instance of the class via another function, 
// with single parameter
MyClass CreateMyClass(const float x) 
{ 
    MyClass a; 
    a.x = x; 
    return a; 
}
MyClass b = CreateMyClass(3.0f);

// Initializes an instance of the class via another function, 
// with multiple parameters
MyClass CreateMyClass(float inX, bool inB, int inI) 
{ 
    MyClass a; 
    a.x = inX; 
    a.b = inB; 
    a.i = inI; 
    return a; 
}
MyClass b = CreateMyClass(3.0f, true, 7);

While this might be fine for a simple case, it really adds up in bigger codebases. Also, wouldn’t it be great if these functions could live together with the class they initialize like a typical constructor does? HLSL supports public classes implemented via structs, where member functions are possible. For sure we can do better.

Implementing HLSL Constructors

It turns out that constructors can be emulated in DXC using variadic macros, from LLVM, and with a bit of elbow grease. Let’s adjust our previous example:

#define MyClass(...) static MyClass ctor(__VA_ARGS__)
class MyClass
{
    // Default Constructor
    MyClass() 
    { 
        return (MyClass)0; 
    }

    // Constructor with a single parameter
    MyClass(const float x) 
    { 
        MyClass a = MyClass();
        a.x = x;
        return a;
    }

    // Constructor with multiple parameters
    MyClass(const float x, const bool b, const int i)
    {
        MyClass a;
        a.x = x;
        a.b = b;
        a.i = i;
        return a;
    }

    // Member variables
    float x;
    bool  b;
    int   i; 
};
#define MyClass(...) MyClass::ctor(__VA_ARGS__)

Note: public and private are reserved keywords in HLSL. Currently, classes are structs.

How Does it Work?

First, at Line 1, the macro handles the typical signature one would expect from a constructor. The variadic ... and __VA_ARGS__ enables the definition of constructors-like member functions with a variable number of parameters. At compilation, this replaces all following calls of MyClass(…) with static MyClass ctor(...).

Then, line #33 redefines the MyClass(...) static member function(s), so they can be used later in the code. We are now able to call MyClass(...) directly, which creates and initializes an instance of that class:

MyClass a = MyClass();              // Default Constructor
MyClass b = MyClass(3.0f);          // Constructor with a single parameter
MyClass c = MyClass(3.0f, true, 7); // Constructor with multiple parameters

Success!

Further Simplification

If one wants to eliminate the generic zero-ing default constructor, further simplification is possible with this macro:

#define DEFAULT_CONSTRUCTOR(Type) static Type ctor() { return (Type)0; }

This optional macro further helps remove code deduplication, especially as one implements many classes throughout a big HLSL codebase.

#define MyClass(...) static MyClass ctor(__VA_ARGS__)
class MyClass
{
    // Default Constructor
    DEFAULT_CONSTRUCTOR(MyClass);

    ...

Wrapping-Up & Additional Thoughts

This post demonstrates an implementation of constructor-like functionality in HLSL that works with out-of-the-box DXC.

Woohoo!

Now, as you probably noticed it lacks some functionality to fully cover what constructors enable. But it’s close!

In the event where you have your own HLSL parser, you might be able to work around this whole problem altogether. You could, for example, as a precompilation step, parse your entire codebase and create, build, and call constructors with unique signatures to prevent name collisions. In my case, I wanted to build something that would work out of the box with vanilla DXC. This is what the previous examples solve.

Also, it would be nice if recursive macros were supported. One could use recursive macros to generalize the two #define above and possibly eliminate the various return calls. Unfortunately, recursive macros are not available.

Either way, I’ve been experimenting with this approach for a few months now, and I have found it quite helpful. I find it cleans up usage of user-created classes and brings us one step closer. I hope you find it helpful too!

Until we get native support for constructors in HLSL, please post in the comments if you manage to improve or simplify this approach further or stumble on a more straightforward way. Thanks!

PS: Thanks to Jon Greenberg for reviewing this small blog post.

Blending Normal Maps?

What is the best way to blend two normal maps together? Why can’t I just add two normal maps together in Photoshop? I heard that to combine two normals together, you need to add the positive components and subtract the negative components, then renormalize. Looks right to me… Why shouldn’t I be using Overlay (or a series of Photoshop blend modes) to blend normal maps together? I want to add detail to surfaces. How does one combine normal maps in real-time so that the detail normal map follows the topology described by the base normal map?

If this is something you’ve heard before, something you’ve asked yourself, check out this article, written together with Stephen Hill (@self_shadow) on the topic of blending normal maps.

Continue reading “Blending Normal Maps?”

A Taste of Live Code Editing With Visual Studio’s Tracepoints

Needless to say that from one software project to another, compile times vary greatly. When debugging we often spend a significant amount of time changing some lines of code, recompiling, waiting and then relaunching the software. While it is true that one has to change their debugging “style” from one project to another (i.e. having a thorough understanding of the problem before “poking around” code is definitely a plus when dealing with big code bases where edit-and-continue is not possible), waiting for the compiler and linker when debugging is never fun. It’s also very unproductive.

In parallel, some of the tools we use everyday allow us to greatly improve our debugging efficiency, though sometimes we are completely oblivious to the power that is available to us. Regardless of how fast or slow compile and link times are on your current project, any tools that can mitigate the recompile cycle when debugging are welcome. One such tool is Visual Studio’s tracepoint: a breakpoint with a custom action associated to it.

Continue reading “A Taste of Live Code Editing With Visual Studio’s Tracepoints”

Tools of The Trade: RockScroll & MetalScroll

Every once in a while you stumble onto a tool that makes your life (as a software developer) so much easier. What’s even better is when the tool is so great that you can’t  (or really hard to) work without it anymore! Over the years, many have fallen into this category. I recall when I was first introduced to Whole Tomato Software’s Visual Assist X and Perforce (coming from the pre-changelist era SCMs): it was hard to even consider working on a project without the latter (especially side projects at home – luckily these are affordable/available for home/side projects). So many people have made so many great add-ons for current IDEs and external tools, finding some that suit your code poet style will definitely make your life easier.

This new addition to my list of must-haves is called RockScroll:

Developed by Rocky Downs from Microsoft and publicly released by Scott Hanselman, Rockscroll is an add-on for Microsoft’s Visual Studio which replaces the scroll bar with a zoomed-out/graphic view of the file you’re currently editing. Acting as a graphic replacement to the default scroll bar, the current section of code you’re editing is highlighted, allowing you to quickly identify where you’re positioned in that file. This is especially useful for big files in which you keep scrolling from section to section. As you work, you will recognize the various sections from the zoomed-out image/bar (i.e. sections of comments, big blocks of code, etc), and instinctively jump to them as you progress with your modifications. Additionally, the bar will update itself and show supplementary information as required. For example, if you double-click on a word or variable, it will highlight all the other sections of code that have that same word/variable. Also, RockScroll will show differences in color based on saved/unsaved code.

RockScroll is free and available here.

In parallel, Mihnea Balta has released an open-source and improved version: MetalScroll. Here are some of the improvements, taken from the Google Code page where this nifty variation resides:

  • double-clicking the scrollbar brings up an options dialog where the color scheme and various other things can be configured.
  • the width of the scrollbar is configurable.
  • the widget at the top of the scrollbar which splits the editor into two panes is still usable when the add-in is active.
  • you must hold down ALT when double-clicking a word to highlight all its occurrences in the file. RockScroll highlights words on regular double-click, which can be annoying when you actually meant to use drag&drop text editing, for example when dragging a variable to the watches window. People who prefer the old behavior can disable this in the options dialog.
  • pressing ESC clears the highlight markers.
  • lines containing highlighted words are marked with 5×5 pixel blocks on the right edge of the scrollbar, to make them easier to find (similar to breakpoints and bookmarks).
  • multiline comments are recognized.
  • hidden text regions and word wrapping are supported.
  • it works in split windows.
  • middle-clicking the scrollbar shows a preview of the code around the clicked line
  • it’s open source, so people who want to change stuff or add features can do so themselves.

Whether you settle for the original RockScroll or the improved MetalScroll, you will never want to code without these ever again. Luckily, both are free, so you should definitely check them out, and see which one you prefer.

Happy coding, and Happy Holidays 🙂