Hardware Overlays and Swap Chains

Game OS developers can use the following techniques to efficiently target the Background and HUD planes.

Overlay Overview

In graphics programming, the terms “scaler” and “overlay” have been used interchangeably to indicate presentation layers that can be composited by dedicated display hardware. In this section, we refer to Multi-plane overlays (MPOs) as simply “overlays”. If there is only one plane visible, it is usually referred to as the “primary”.

On Xbox One there are three hardware overlay planes, two of which are available for title use. A title might use these to separate out HUD and background content, and have each rendered at different resolutions or updated at different frequencies. Alternatively, the two planes could be used in other ways than separating HUD and background.

Overlay Description
Background Plane The farthest back (in z-order) of the three overlay planes - an order which cannot be changed. This plane is dedicated to the Game OS; it is never used by the System OS. This plane is called the “background plane” because most of the time games will use this plane for background game content (as opposed to HUD content).
HUD Plane The middle (in z-order) of the three overlay planes. This plane is mostly used by the Game OS but can also be used for video playback UI by the System OS. This plane is called the “HUD plane” because most of the time games will use this plane for higher resolution but sparser HUD content.
System Plane The front-most (in z-order) of the three overlay planes. This plane is dedicated to the System OS; it is never used by the Game OS.

Overlay Implementation

On Direct3D for Xbox One, multi-plane overlay is implemented as follows.

Note The destination rectangle should always be the same size as the game plane, whereas the source rectangle is relative to the swap chain buffer. There is a fixed buffer limit for all swap chains of 16 buffers - a larger number of buffers can be used for jitter absorption.

The following sample code shows a Game OS app with a 1080p HUD plane and a 720p Background plane, both being displayed at 1080p.

  // Create the descriptor for the background plane, note that it is lower res than HUD
  DXGI_SWAP_CHAIN_DESC1 swapChainDesc_BG = {0};
    swapChainDesc_BG.Width = 1280;
    swapChainDesc_BG.Height = 720;
    swapChainDesc_BG.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    swapChainDesc_BG.Stereo = false;
    swapChainDesc_BG.SampleDesc.Count = 1;	        // don't use multi-sampling
    swapChainDesc_BG.SampleDesc.Quality = 0;
    swapChainDesc_BG.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT | DXGI_USAGE_UNORDERED_ACCESS | DXGI_USAGE_SHADER_INPUT;
    swapChainDesc_BG.BufferCount = 2;		            // use two buffers to enable flip effect
    swapChainDesc_BG.Scaling = DXGI_SCALING_STRETCH;
    swapChainDesc_BG.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;

  // Create the swapchain for the background plane
  IDXGISwapChain1*             pSwapChain_BG;
  CreateSwapChainForCoreWindow(pd3dDevice,
      reinterpret_cast< IUnknown* >( pWindow ), &swapChainDesc_BG, NULL, &pSwapChain_BG);

  // Create the descriptor for the HUD plane
  DXGI_SWAP_CHAIN_DESC1 swapChainDesc_HUD = {0};
    swapChainDesc_HUD.Width = 1920;
    swapChainDesc_HUD.Height = 1280;
    swapChainDesc_HUD.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    swapChainDesc_HUD.Stereo = false;
    swapChainDesc_HUD.SampleDesc.Count = 1;	      // don't use multi-sampling
    swapChainDesc_HUD.SampleDesc.Quality = 0;
    swapChainDesc_HUD.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT | DXGI_USAGE_UNORDERED_ACCESS | DXGI_USAGE_SHADER_INPUT;
    swapChainDesc_HUD.BufferCount = 2;		          // use two buffers to enable flip effect
    swapChainDesc_HUD.Scaling = DXGI_SCALING_STRETCH;
    swapChainDesc_HUD.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;

  // Create the swapchain for the HUD plane
  IDXGISwapChain1*             pSwapChain_HUD;
  CreateSwapChainForCoreWindow(pd3dDevice,
      reinterpret_cast< IUnknown* >( pWindow ), &swapChainDesc_HUD, NULL, &pSwapChain_HUD);

  // TODO: some rendering using the appropriate swap chain back buffer as the appropriate render target
  {...}


  DXGIX_PRESENTARRAY_PARAMETERS arrayPresentParams[2];

  // Set up the present parameters for the background buffer
  // Note that we are outputting this plane at 1080p
  arrayPresentParams[1].Disable = FALSE;
  arrayPresentParams[1].UsePreviousBuffer = FALSE;
  arrayPresentParams[1].SourceRect.left = 0;
  arrayPresentParams[1].SourceRect.top = 0;
  arrayPresentParams[1].SourceRect.right = 1280;
  arrayPresentParams[1].SourceRect.bottom = 720;
  arrayPresentParams[1].DestRectUpperLeft.x = 0;
  arrayPresentParams[1].DestRectUpperLeft.y = 0;
  arrayPresentParams[1].ScaleFactorVert = 1.5f;	  // vertical scaling factor, must range from [0.25,16]
  arrayPresentParams[1].ScaleFactorHorz = 1.5f;	  // horizontal scaling factor, must range from [0.25,16]

  // Set up the present parameters for the HUD buffer
  // Note that we are outputting this plane at 1080p
  arrayPresentParams[0].Disable = FALSE;
  arrayPresentParams[0].UsePreviousBuffer = FALSE;
  arrayPresentParams[0].SourceRect.left = 0;
  arrayPresentParams[0].SourceRect.top = 0;
  arrayPresentParams[0].SourceRect.right = 1920;
  arrayPresentParams[0].SourceRect.bottom = 1080;
  arrayPresentParams[0].DestRectUpperLeft.x = 0;
  arrayPresentParams[0].DestRectUpperLeft.y = 0;
  arrayPresentParams[0].ScaleFactorVert = 1.0f;	  // vertical scaling factor, must range from [0.25,16]
  arrayPresentParams[0].ScaleFactorHorz = 1.0f;	  // horizontal scaling factor, must range from [0.25,16]

  IDXGISwapChain1* array_pSwapChains[2];
  array_pSwapChains[0] = pSwapChain_HUD;
  array_pSwapChains[1] = pSwapChain_BG;

  // TODO: When/where does the SwapChainXBOX get declared?
  DXGIXPresentArray(1,                            // Flip next on VSYNCH
                    0,                            // PresentImmediateThreshhold,
                    0,                            // flags that apply to all swap chains
                    2,
                    array_pSwapChains,
                    arrayPresentParams );  

Note

Although multiple display planes allow titles to render UI separately from game visuals, when combined with a specific color quantization setting, an ostensibly transparent foreground plane will actually result in a 6% transparent white, resulting in the background plane’s colors washing out.

Titles using multiple display planes should avoid using the DXGIX_SWAP_CHAIN_FLAG_QUANTIZATION_RGB_FULL on the foreground (for example, UI) plane to avoid making the background plane appear too white.

Note

Trying to create a swap chain for both High Dynamic Range (HDR) and 4K output is not supported, but will not produce an error code. The output will be distorted though.

Performance Metrics

If you are having trouble hitting your target frame-rate at your target resolution on the GPU, consider dynamically resizing your resolution based on GPU load. The DXGIXPresentArray method allows the scaler resolution settings to be synchronously changed frame-to-frame. Remember that you can have your HUD on one overlay plane and your 3D content on another overlay plane with a different resolution and blended using per-pixel hardware. If your situation allows, you can also render one plane at a slower update rate by taking advantage of the UsePreviousBuffer field of DXGIX_PRESENTARRAY_PARAMETERS. The scaler quality is an improvment over the Xbox 360.

To get some metrics on performance, consider using the DXGIXGetFrameStatistics method to obtain timing and other pertinent information about frames that have been already displayed in real-time. This information is useful because it allows the app to vary the content of frames rendered in the future to match the latency between calls to DXGIXPresentArray and when the presented frame is actually displayed out for viewing. In addition, it is useful for the title to find out if it is queuing more frames than the rate at which they can be displayed, or if it is not generating frames quickly enough.

In the DXGIX_FRAME_STATISTICS structure, the NumberFrames member specifies the number of frames to obtain statistics for. pFrameStatistics is a pointer to an array of DXGIX_FRAME_STATISTICS structures. The array should contain at least NumberFrames elements. The statistics are returned in temporal order: element 0 will contain statistics for the most recent frame queued or presented, element 1 will contain statistics for the frame prior to that, and so on. The history buffer in the runtime currently holds only enough space for the past 17 queued and displayed frames.

For frames which are queued but not displayed, only CPUTimePresentCalled, CPUTimeAddedToQueue and QueueLengthAddedToQueue will contain actual, valid data. The elements that correspond to queued but not displayed frames will have VSyncCount set to zero. For example, if currently four frames are queued and 13 have been displayed, if NumberFrames is 16, the first four elements will have VSyncCount set to zero, and the last 12 elements will have VSyncCount set to the actual non-zero data that is returned.

See also

DirectX