Xbox One Shader Compiler

Xbox Advanced Technology Group

Updated April 22nd 2013

In this topic

Tools overview

Shader compilation process

Shader binary file

GPU code optimization

Future Xbox One HLSL extensions

Dynamic linkage

UAV slot merging

Conclusion

Tools overview

High Level Shader Language (HLSL) code can be compiled using either Effects compiler—the offline shader compiler—or the D3DCompile API at run time in-game or in the tools. The same back-end compiler DLL is used in either case, which ensures that the compiled code is the same regardless of whether it was compiled offline or at run time.

Both the offline and run-time Xbox One shader compilers are provided in 64-bit flavor only, and there are no plans to provide 32-bit versions of these tools. This means that your toolchain must be able to run in a 64-bit environment, and, if you’re planning to use the D3DCompile API instead of calling Effects compiler to compile the shaders, it also means that your toolchain executable needs to be compiled for x64. Note that for purposes of offline compilation, using the D3DCompile API is faster than using Effects compiler because the compiler DLL needs to be loaded just once.

Although the desktop and Xbox One versions of Effects compiler produce compatible shader binaries, there are no Xbox One-specific benefits when desktop tools are used. If you can’t link both the desktop and Xbox One shader compilers with your toolchain, consider using the Xbox One toolset for targeting your development computer as well, especially if you’ve been using the old version of the compiler from the June 2010 DirectX SDK. The Xbox One shader compiler contains some important bug fixes.

In order to use the D3DCompile API with the Xbox One compiler on your development computer, you should statically link with d3dcompiler.lib, and you need to make sure that the Xbox One version of D3DCompiler_46.dll is loaded and that SC_DLL.dll is available to load. Otherwise, your toolchain might pick the desktop version of D3DCompiler_46.dll or fail to initialize. The Xbox One version of the compiler DLL is located at $(DurangoXDK)/bin. When searching for the shader compiler DLL, the system follows the regular DLL search rules for desktop applications.

Shader compilation process

The compilation process itself involves two stages:

  1. Compilation of HLSL into D3D bytecode, which is a hardware-independent assembler language and can’t be directly executed by the GPU.

  2. Compilation of the D3D bytecode into hardware-specific shader instructions for specific hardware stages.

The reason for having a two-stage process in Direct3D 11 (D3D 11) was that Microsoft would implement the first step, and the GPU drivers would implement the second step. This makes sense for a generic API like D3D because (a) compiling D3D bytecode is easier than compiling HLSL, and (b) Microsoft can’t support all combinations of hardware out there.

  1. Both desktop and Xbox One shader compilers use the same header file name and function names, so if they both need to be linked with the same tool, you will have to write custom function stubs to avoid ambiguity.

For a fixed platform like Xbox One, this two-stage arrangement may add unnecessary overhead because we can go directly from HLSL to the GPU instructions. However, Xbox One does use a two-stage approach.

Shader compilation can be done offline by Effects compiler or by using the D3DCompile API on the development computer (with the Xbox One shader compiler DLL), or at run time by using the D3DCompile API on Xbox One. As is true with most other processes, there are run-time and memory effects to take into consideration when deciding between the run-time and offline approaches.

If you absolutely need to generate your HLSL shaders at run time, or to run-time compile them from the source files, for example for real-time editing, offline compilation might not be a convenient option. In this case, the D3DCompile API can be used to compile shaders at run time. This is the most flexible approach, but it bears the highest run-time and memory costs. This is because both stages of the shader-compilation process need to happen at run time and you need to have the original HLSL loaded in memory. Handling compilation errors at run time could be problematic as well.

For most games, it makes perfect sense to compile all the shaders offline. This will minimize both time and memory costs at run time. Xbox One Effects compiler and the D3DCompile API can perform both compilation stages offline, resulting in a shader blob that the GPU can use immediately. By specifying /noprecompile (only available on Xbox One shader compiler) on the command line, Xbox One Effects compiler becomes restricted to Stage 1, and Stage 2 has to be performed at run time by the driver, slightly increasing run-time costs. This mode is useful, however, in case of a suspected discrepancy between offline and run-time compilations.

You can turn off shader pre-compilation on a per-stage basis by defining one or more of the following #define values for shader compilation.

Name of the shader compiler #define Compiler action
__XBOX_DISABLE_PRECOMPILE_LS=1 Disable pre-compilation of local shaders
__XBOX_DISABLE_PRECOMPILE_HS=1 Disable pre-compilation of hull shaders
__XBOX_DISABLE_PRECOMPILE_ES=1 Disable pre-compilation of export shaders
__XBOX_DISABLE_PRECOMPILE_GS=1 Disable pre-compilation of geometry shaders
__XBOX_DISABLE_PRECOMPILE_VS=1 Disable pre-compilation of vertex shaders
__XBOX_DISABLE_PRECOMPILE_PS=1 Disable pre-compilation of pixel shaders
__XBOX_DISABLE_PRECOMPILE_CS=1 Disable pre-compilation of compute shaders

You can use the preceding values by entering them on the command line; for example, /D__XBOX_DISABLE_PRECOMPILE_LS=1. For more information regarding the mapping of D3D to hardware shader stages, please see the XFest 2012 talk GPU Shader Instructions on Xbox One located on XGD.

Shader binary file

The shader binary file generated by Xbox One Effects compiler contains D3D level information and Xbox One GPU level information.

The D3D part of the binary contains D3D shader bytecode, debug information, and reflection information. The Xbox One part of the binary contains GPU shader binary code and the hardware settings that have to be set on the GPU before the shader is run.

Several GPU shaders can be embedded in the GPU section of the binary shader file. This is because the GPU requires several distinct versions of D3D Domain and Vertex Shaders. Also, the D3D Geometry Shader’s shader binary will contain a GPU version of the Geometry Shader and a GPU Vertex Shader that performs the implicitly generated vertex pass-through operation. Other types of D3D shaders need to be compiled and stored only once.

Currently, the D3DCompile API can be used for run-time compilation of all necessary flavors of GPU shaders.

Storing all necessary data in the GPU shader binary file means that no compilation work is required at run time, but it also means that, depending on the D3D pipeline configuration, some variants of GPU shaders will go unused. For example, if a D3D Vertex Shader is used only in a Vertex Shader + Pixel Shader configuration, there is no need to generate two out of three variants of the GPU shader for it (the two unneeded variants would be Local Shader and Export Shader). Although this has a relatively small cost in terms of on-disc storage, the shader compiler will support specifying which GPU shaders to generate, to reduce the size of a shader binary.

The GPU shader binary files are compressed by the Xbox One Effects compiler by default, but of course you could also use your own resource management system to compress them further. If you don’t require debugging and run-time reflection, you can use Qstrip_debug Qstrip_reflect (available on both the desktop and Xbox One shader compilers) to strip debug and reflection

  1. D3D Domain Shader needs to be compiled as GPU Export and Vertex Shader, and D3D Vertex Shader needs to be compiled as GPU Local, Export, and Vertex Shaders. For more information, see the XFest 2012 presentation entitled “GPU Shader Instructions on Xbox One.”

information from the shader file, potentially reducing the file size dramatically. This data can also be removed programmatically by using the D3DStripShader function.

Alternatively, to minimize the binary file size while retaining ability to debug, use /Fd to store the shader debug information into a separate file.

Also, the shader binary files support adding private data to Effects compiler by using the /setprivate command-line key (available on both the desktop and Xbox One shader compilers) or by calling the D3DSetBlobPart function. Private data is opaque to D3D, and it can be used to store any arbitrary user data. This data can be obtained programmatically by using the D3DGetBlobPart function called with D3D_BLOB_PRIVATE_DATA. The private data can be stripped out by using the /Qstrip_priv command-line key to Effects compiler. That functionality is available on both the desktop and Xbox One shader compilers.

Effects compiler supports a command-line option /dumpbin to load a binary instead of HLSL and display the contents. Although this option is available on both the desktop and Xbox One shader compilers, the output of the Xbox One shader compiler will include disassembly of GPU shader instructions and the GPU shader setup data.

GPU code optimization

The Xbox One shader compiler’s back end has no relation to the Xbox 360 compiler and has been specifically designed for the Xbox One GPU.

All graphics programmers want maximum performance from their shaders, so let’s briefly outline shader optimization goals for the Xbox One GPU.

An Xbox One GPU’s single-instruction, multiple-data stream (SIMD) has 256 vector general-purpose registers (VGPRs). Each SIMD can accept multiple shader vectors, and having several shader vectors in flight at the same time helps hide long latencies when the shader accesses memory. The number of shader vectors in flight is referred to as occupancy. Up to 10 shader vectors can be accepted by a single SIMD, so up to 40 can be accepted by a single Shader Core, but the actual number will depend on how many resources each shader vector requires. For example, if a shader vector requires 52 VGPRs, only 16 shader vectors can be in-flight on a single Shader Core. Low occupancy (low number of in-flight shader vectors) might impede hiding memory instruction latency.

  1. For more information, see the white paper entitled “Xbox One GPU Shader Cores.” CHECK APPROPRIATE LINK
  2. Floor ( 256 / 52 ) * 4 = 4 * 4 = 16

It is imperative, therefore, to reduce the number of VGPRs used in the GPU shader to maximize occupancy. However, reducing the number of used registers may mean that some data might need to be repeatedly loaded from memory or recalculated. This, in turn, means an increase in the number of shader instructions, which may slow down the shader.

You can see that optimizing for Xbox One GPU poses a very difficult problem. It’s a balancing act between the number of VGPRs used, the number of instructions, and the placement of memory synchronization instructions in the GPU shader.

To generate the most-efficient code, the Xbox One shader compiler performs multiple passes on the GPU shader code, always optimizing globally across the entire shader. When there is a tie, it will default to optimizing for lower number of VGPRs to maximize occupancy.

The Stage 1 compilation optimization level can be specified as a parameter to the compiler (using the /O[0-3] command-line parameter to the desktop and Xbox One Effects compilers and D3D11_SHADER_OPTIMIZATION_LEVEL[0-3] in Flags1 to the D3DCompile API). Currently there are no exposed compiler options to drive Stage 2 of the shader optimization, but as we learn more about the hardware, we will expose them as necessary.

To guide shader optimizations, you can use Xbox One-specific options. The following options are accepted as #define directives to the compiler.

Note: These options are experimental at the moment, and using them can lead to a broken shader.

Name of the shader compiler #define Compiler action
__XBOX_REGALLOC_SGPR_LIMIT=x Control scalar register allocation limit; 0 means no limit
__XBOX_REGALLOC_VGPR_LIMIT=x Control vector register allocation limit; 0 means no limit

The meaning and effect of these settings will change over time as the shader compiler is refined. Whenever you install a new release of the XDK, you should either remove or retune these settings.

Future Xbox One HLSL extensions

It is worth noting that there are currently no plans to allow writing GPU assembler on Xbox One. Shaders can only be authored in HLSL, and manual assembler code will not be allowed.

However, there are a few HLSL extensions that make sense on Xbox One. As the compiler implements HLSL extensions, we will update this section of the paper. Some examples of HLSL extensions that may be implemented include:

Dynamic linkage

D3D11 adds ID3D11ClassLinkage to support dynamic shader linking. This is a new feature that might see wider adoption as D3D11 becomes more mainstream. Its main goal is to reduce combinatorial explosion of the number of shaders by allowing selection of functions to use at run time. It’s fundamentally a run-time API and it requires run-time compilation by the driver, which naturally precludes GPU shader pre-compilation. At the moment we don’t recommend using it on Xbox One, but we would like to hear your feedback about whether you think it’s a useful feature.

UAV slot merging

Both the desktop compiler and the Xbox One Effects compiler support merging the unordered access view (UAV) register slots between multiple compute shaders. Such merging makes all compute shaders use a single set of UAV bindings. This could be used to reduce the number of CSSetUnorderedAccessViews API calls on some parts of rendering. UAV slot merging works based on the name of a UAV in HLSL, and the shader compiler tries to ignore the register() modifier (when the register() modifier can’t be ignored, it might cause compile errors). As an example, let’s look at merging UAV slot declarations from the following three shaders:

| RWStructuredBuffer< uint > g_buf1; | RWStructuredBuffer< float > g_buf2 : register( u10 ); | RWStructuredBuffer< float > g_buf1; | | ————————————- | ———————————–| ————–|

To merge the UAV slots from these three shaders, you would use the following commands:

Step 1 – collecting information about what needs to be merged

fxc.exe /T cs_5_0 A.fx /Fo Atmp.o
fxc.exe /T cs_5_0 B.fx /Fo Btmp.o /mergeUAVs /shtemplate Atmp.o
fxc.exe /T cs_5_0 C.fx /Fo C.o /mergeUAVs /shtemplate Btmp.o

Step 2 – generating shaders that have merged UAV binding declarations

fxc.exe /T cs_5_0 B.fx /Fo B.o /matchUAVs /shtemplate C.o<br />
fxc.exe /T cs_5_0 A.fx /Fo A.o /matchUAVs /shtemplate C.o

After merging, Effects compiler will produce the following bindings for the shader in the first and third column:

// Name                                 Type  Format         Dim Slot Elements
// ------------------------------ ---------- ------- ----------- ---- --------
// g_buf1                                UAV  struct         r/w    0        1
// g_buf2                                UAV  struct         r/w    1        1 [unused]

And for the shader in the second column, Effects compiler will produce these bindings:

// Name                                 Type  Format         Dim Slot Elements
// ------------------------------ ---------- ------- ----------- ---- --------
// g_buf1                                UAV  struct         r/w    0        1 [unused]
// g_buf2                                UAV  struct         r/w    1        1

Note: that the differences in type (int and float) and register specifications were ignored. It’s best to avoid having explicit register specifications and UAV definitions that have the same name but are of different types to avoid errors at compile time and run time.

Conclusion

The next-generation Xbox One shader compiler is designed from scratch to efficiently compile shaders that are several times longer than on Xbox 360. It is efficient in terms of both time and memory, producing optimal GPU shader instructions and exposing Xbox One-specific extensions.