Tessellation

Describes the advantages and disadvantages of Onchip and Offchip tessellation.

Actions involved in tessellation

Tessellation on Xbox One performs the following actions, regardless of whether tessellation is performed on-chip or off-chip:

  1. The GPU divides all the patches generated by each individual draw into threadgroups.
  2. For each threadgroup, the GPU allocates some amount of “local data store” (LDS) memory in a “compute unit” (CU). All the necessary VS and HS threads will be scheduled on that CU.
  3. For each threadgroup, the GPU will run as many VS threads as input control points, times the number of patches per threadgroup:
    VS threads = (input control points) x (patches per threadgroup)  
    

    Threads are always packed at 64 threads per wave per shader stage.

  4. For each threadgroup, the GPU will also run as many HS threads as output control points times the number of patches per threadgroup:
    HS threads = (output control points) x (patches per threadgroup)  
    

    Threads are always packed at 64 threads per wave per shader stage.

  5. All the HS threads must run simultaneously as a threadgroup, because synchronization barriers may be needed. This is similar to how compute shader threadgroups work, except that in this case the synchronization is implicit and not specified in HLSL.
  6. The GPU’s tessellator then generates DS threads to run, depending on the tessellation factors calculated by the HS. (Also packed at 64 threads per wave.)

Onchip tessellation mode

By default, the D3D11 driver always configures the GPU for onchip tessellation mode. In onchip tessellation mode, all of the data for input and output control points and per-patch constant is stored in LDS, including the constant factors. Because LDS is memory internal to the GPU, this means that no additional memory bandwidth is generated, and access to the data is guaranteed to be low-latency.

However, because this data is needed by the DS threads generated by tessellating the patches in a threadgroup, all such DS threads will need to run in the same CU that ran the VS and HS threads for the same threadgroup. This poses a severe limitation to the GPU’s ability to load-balance work among the 12 available CUs, especially when the tessellation factors are high.

With high tessellation factors, a single threadgroup might generate many waves of DS threads, and the LDS memory used will be blocked for any other use, including other threadgroups which might otherwise be able to run in the CU. (LDS is also used for threadgroup-local data in compute shaders, for PS interpolant values, and for onchip GS mode.)

Offchip tessellation mode

Offchip tessellation is an option that enables the use of non-LDS memory with hull and domain shaders.

The GPU’s offchip tessellation mode is enabled by specifying the flag D3D11X_TESSELLATION_OFFCHIP. This mode uses the same amount of LDS as the onchip mode, but the HS also writes all output control points, tessellation factors, and per-patch constants to a memory buffer.

A heuristic is then used to run some DS waves in the same CU in onchip mode, to read the data from LDS, and to run other DS waves in other CUs, using offchip mode and reading the data from memory. This allows the GPU to release the threadgroup’s LDS memory before all DS waves are finished, or even launched, and it also allows the GPU to load-balance DS waves better across all CUs.

The advantage of doing tessellation off-chip is that LDS memory is freed-up for other graphics purposes. Whether a performance improvement is actually achieved by doing tessellation off-chip is very much dependent on the title code; a performance improvement might not be the result.

Heuristic used to run DS waves in onchip or offchip mode

The heuristic used to run some DS waves in onchip mode and some in offchip mode is run per threadgroup as follows:

Enabling offchip tessellation

Hull and domain shaders to be used with offchip tessellation should be compiled with the following #define statement:

__XBOX_ENABLE_HSOFFCHIP  

Setting this value causes the hull or domain shaders to be precompiled for offchip tessellation, and avoids runtime recompilation. The shaders must match, both offchip or both not.

Setting tessellation parameters

Calling HSSetTessellationParameters

To use offchip tessellation, call ID3D11DeviceContextX::HSSetTessellationParameters:

D3DINLINE void D3DAPI ID3D11DeviceContextX::HSSetTessellationParameters(
          _In_opt_ const D3D11X_TESSELLATION_PARAMETERS* pTessellationParameters);  

Calling ID3D11DeviceContextX::HSSetTessellationParameters will change the behavior of any future Draws that use tessellation. If pTessellationParameters is NULL, the driver will revert to its default behavior. If pTessellationParameters is non-NULL, it must point to a D3D11X_TESSELLATION_PARAMETERS structure that is instantiated and populated by the title. This optional structure has the following layout:

typedef struct D3D11X_TESSELLATION_PARAMETERS
{
   UINT  Size;
   UINT  Flags;
   UINT  PatchesPerThreadgroup;
   float TfThreshold;
   UINT  DsWaveThreshold;
} D3D11X_TESSELLATION_PARAMETERS;  

The fields of this structure are as follows:

Field Description
Size The size of this structure, such as sizeof(D3D11X_TESSELLATION_PARAMETERS).
Flags Flags to specify offchip tessellation and to enable each of the below values found in this structure by specifying “override”. A bitwise-OR’d combination of D3D11X_TESSELLATION_FLAGS enumeration constants.
PatchesPerThreadgroup How many patches will be processed simultaneously in a compute unit in the GPU. Currently, the driver calculates a value for this that maximizes the number of patches that can be processed simultaneously.
TfThreshold Tessellation Factor Threshold: This is the minimum average tessellation factor in a threadgroup that will cause the hull shader to dynamically switch modes from onchip to offchip. This field is only valid when Flags contains the flag D3D11X_TESSELLATION_OFFCHIP. Currently, the driver calculates a tessellation factor that will generate approximately 256 DS threads (4 waves).
DsWaveThreshold Domain Shader Wave Threshold: The number of DS waves that will be launched by the tessellation unit using the onchip method, before switching DS processing to offchip mode, freeing LDS memory and starting to launch DS waves to other CUs. Currently, the driver hardcodes this to 4 waves. This field is only valid when Flags contains the flag D3D11X_TESSELLATION_OFFCHIP.

Overriding the driver’s calculations for PatchesPerThreadgroup, TfThreshold, and DsWaveThreshold

The main fields in the optional D3D11X_TESSELLATION_PARAMETERS structure are PatchesPerThreadgroup, TfThreshold, and DsWaveThreshold. These parameter values are normally calculated by the driver, rather than using this structure. The driver calculates these values, taking into consideration factors including control point counts and sizes, with the overall goal of maximizing patch throughput.

Instead of using the driver’s calculated values for these parameters, the application can use the fields of the optional D3D11X_TESSELLATION_PARAMETERS structure in a call to ID3D11DeviceContextX::HSSetTessellationParameters, to explicitly specify the desired values, overriding the driver’s calculations. These fields can also be read after a call to ID3D11DeviceContextX::HSGetLastUsedTessellationParameters, to find out which values the driver calculated.

Even when you override the driver’s calculated parameter values, the driver might need to modify the values you specify in the D3D11X_TESSELLATION_PARAMETERS structure, to internally account for internal limitations of the GPU. To discover such modifications, call ID3D11DeviceContextX::HSGetLastUsedTessellationParameters.

The PatchesPerThreadgroup field specifies how many patches to put in each threadgroup. That’s the value calculated in the first step above.

Retrieving the values that the driver calculates

The method ID3D11DeviceContextX::HSGetLastUsedTessellationParameters can be used to retrieve from the driver the values that the driver calculated, which can be the values it adjusted for the immediately preceding Draw call. Before calling HSGetLastUsedTessellationParameters, the caller must initialize the Size field to the size of the structure.

D3DINLINE void D3DAPI ID3D11DeviceContextX::HSGetLastUsedTessellationParameters(
                    _Inout_ D3D11X_TESSELLATION_PARAMETERS* pTessellationParameters);  

See also

DirectX