Compiling shaders vs creating shaders

Last time I talked about shader compilation at launch time, but I wasn't being precise about shader compilation and shader creation.

Shader compilation is the act of taking HLSL source code (i.e. text) and producing a bytecode representation. The main API to do this is D3DCompile. This bytecode can be used immediately or persisted to disk to be reloaded later, possibly on a different computer.

Shader creation is the act of taking shader bytecode and creating an in-memory representation that can be used to run the shader on a particular GPU. The graphics driver is typically involved in this step. The main API to do this in Direct3D 11 is ID3D11Device::CreateVertexShader (or pixel shader, etc.). On Direct3D 12, you will set a D3D12_SHADER_BYTECODE structure (which is really a pointer and size) in a D3D12_GRAPHICS_PIPELINE_DESC or D3D12_COMPUTE_PIPELINE_DESC structure when creating a pipeline. Note that both a pipeline and a shader object can only be used with the graphics device that creates them, because they are specialized for that device.

The device independence you get from compiling without creating is important, because it means you can compile your shaders at development time, and not have to spend time and CPU doing this when the user wants to run your game.

The MSDN page on Compiling Shaders give you a good overview of what your options are for compiling.

  • Use fxc.exe from the command-line.
  • Use Visual Studio (which will call fxc under the covers).

An option not discussed above is simply writing your own program to do this compilation yourself. It's more work, but you get a lot more flexibility in specifying definitions, targets, and handling the resulting shader bytecode however you need to.

Another thing to consider is whether you want to load the shaders from disk (the .cso approach) or have it directly as a constant in your application (the included header approach). Loading from disk can be an expensive thing to do, so you may want to pack multiple shaders together in a single file in a way that you can quickly extract the ones you want (and ideally put these close together to minimize I/O).

In summary:

  • Compile your shaders offline whenever you can.
  • If you can't compile all your shaders offline, compile the most critical ones (with longest compile time or the ones on your startup path).
  • If you don't know which shaders to compile, instrument your game to compile on-the-fly and save your compiled shaders. You can then use these saved shaders are your starting point to bypass compilation.
  • Have a strategy to amortize the cost of reading shaders off disk.

Enjoy!