Dynamic Resolution Rendering Update for Microsoft DirectX* 12

example of dynamic resolution rendering

Figure 1. In this figure we’ve captured a single image that we rendered using our DirectX* 12 DRR implementation being used to maintain a fixed refresh rate. This implementation uses Microsoft DirectX 12 Placed Resources. While we show an image here, DRR is best demonstrated in real-time, and a link to sample code and a working demo is included.8

Introduction

In 2011, Intel’s Doug Binks published an article on Dynamic Resolution Rendering (DRR).1 In 2012 he added an update that included temporal anti-aliasing and other enhancements.2 This work has been a canonical reference and very little about this technique, if anything, has changed over the past seven years. Many game engines on both console and PC already employ DRR techniques.

One of the primary issues inhibiting adoption of DRR is the modification to post-processing pipelines that it requires. With the introduction of Microsoft DirectX 12 and Placed Resources we are introducing an updated algorithm implementation that removes the need for most, if not all, post-processing pipeline modifications at the cost of increasing your memory requirements with an additional dynamic resolution render target buffer. This will be explained in the text below. We will abbreviate Microsoft DirectX 12 as DirectX 12 in the rest of the article.

Previous Algorithm

The DRR implementation described in the 2011 article included an algorithm that kept a fixed displayed render target resolution – but, internally, dynamically varied the viewport resolution that is driving the engine shading. The viewport resolution can be changed to maintain a desired framerate as the rendering workload varies.

A scaled viewport within a larger render target requires post-processing effects to be aware of the size discrepancy between the scaled viewport and render target, and shader code must appropriately handle the right and bottom viewport edges. In a general sense, this shader code is mimicking a hardware sampler's functionality at render target borders, but at the virtual boundaries of the viewport within the render target. Refer to the original articles for more details.

Microsoft DirectX 12 Alternative

DirectX 12 introduced the concept of Placed Resources.3 Placed Resources allow the developer to allocate a single heap to provide memory for multiple resource creations. One or more resources can share the same heap and even the same location in the heap. Therefore, an intuitive use case is to alias different resource types to the same memory location.

However, instead of using Placed Resources for aliasing, we propose recreating them to a new resolution when the DRR algorithm determines the resolution should be modified. Because the developer can provide an allocated heap, these resources have an extremely lightweight release and creation stage.

To put it differently, these resources can be recreated at a new resolution with a negligible performance cost. We’ve measured it at .0075ms (CPU time) on an Intel® NUC Kit NUC6i7KYK. This allows us to resize the physical render target instead of scaling the viewport within the render target. Consequently, there is no need for additional post-processing shader code to handle viewport edges.

Creating a placed resource render target

To leverage this functionality, create a heap large enough for your render target at its maximum desired resolution. Create your render target using D3D12Device::CreatePlacedResource:4, 5

// Determine how much memory is required for our resource type
3D12_RESOURCE_DESC desc = …
D3D12_RESOURCE_ALLOCATION_INFO allocInfo = 
			Device->GetResourceAllocationInfo(0, 1, &desc);

// Create the heap
ID3D12Heap *g_pHeap;

D3D12_HEAP_DESC desc = {};
{
  // To avoid wasting memory SizeInBytes should be 
  // multiples of the effective alignment [Microsoft 2018a]
  desc.SizeInBytes = allocInfo.SizeInBytes;
  desc.Properties = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT);;
  desc.Flags = D3D12_HEAP_FLAG_ALLOW_ONLY_RT_DS_TEXTURES;

  Device->CreateHeap(&desc, MY_IID_PPV_ARGS(&g_pHeap));
}
// Create the render target from the heap
Device->CreatePlacedResource(g_pHeap, offset, …, MY_IID_PPV_ARGS(&g_pResource));

Note that when we create a placed resource, we specify the specific offset, or place, of the resource within the heap. This makes the operation maximally efficient compared to having the driver runtime find a location for the resource.

example of dynamic resolution rendering

Figure 2. Here the DRR scale is set to be 70% of the final framebuffer, with the goal of maintaining a frame rate of 45Hz. Our sample implementation has a number of parameters that can be adjusted. The detailed descriptions for these values are described in the read me associated with the sample code.

Resizing the render target

When the DRR algorithm determines that the resolution should be modified, it's a simple matter of recreating (i.e., releasing and creating) the render target to the new size:

// Release the resource
g_pResource->Release();

// Create with the updated size
ResourceDesc.Width = NewWidth;
ResourceDesc.Height = NewHeight;
Device->CreatePlacedResource(g_pHeap, offset, &ResourceDesc,…,
						IID_PPV_ARGS(&g_pResource));

Double-buffering the render target

In most cases, your render target will be in use by the GPU when your CPU algorithm determines it should be resized. One option is to use fences and wait until the GPU is finished with the render target. However, this would most likely result in an unacceptable stall in frame time.

The option we present is to double-buffer the render target, so there is always one that is not in use by the GPU. The cost is an expanded memory footprint that needs to be considered when using Placed Resources. The double-buffering additions to the code are minor and are described below.

Creating a double-buffered placed resource render target

// Create the heap with enough memory for both resources
ID3D12Heap *g_pHeap;

D3D12_HEAP_DESC desc = {};
g_HeapOffset[0] = 0;
g_HeapOffset[1] = allocInfo.SizeInBytes;

{
  // To avoid wasting memory SizeInBytes should be 
  // multiples of the effective alignment [Microsoft 2018a]
  desc.SizeInBytes = allocInfo.SizeInBytes * 2;
  desc.Properties = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT);;
  desc.Flags = D3D12_HEAP_FLAG_ALLOW_ONLY_RT_DS_TEXTURES;

  Device->CreateHeap(&desc, MY_IID_PPV_ARGS(&g_pHeap));
}

// Create the first render target from the heap
Device->CreatePlacedResource(g_pHeap, g_HeapOffset[0], …, MY_IID_PPV_ARGS(&g_pResource[0]));

// Create the second render target from the heap
Device->CreatePlacedResource(g_pHeap, g_HeapOffset[1], …, MY_IID_PPV_ARGS(&g_pResource[1]));

// Resource 0 will be the default one used
// Resource 1 will be ready if resolution must change
g_FreeResource = 1;

Switching buffers and resizing the render target

// Release the free resource
g_pResource[g_FreeResource]->Release();

// Create with the updated size
ResourceDesc.Width = NewWidth;
ResourceDesc.Height = NewHeight;
Device->CreatePlacedResource(g_pHeap, g_HeapOffset[g_FreeResource], &ResourceDesc,…,
						IID_PPV_ARGS(&g_pResource[g_FreeResource]));

// Update our index to the next free one
g_FreeResource = g_FreeResource ^ 1;

Scaling to display resolution

As described above, by using Placed Resources, we no longer need to scale the viewport, because we resize the physical render target. However, this render target still needs to be scaled to the final display resolution. The implementation provided shows how to handle this step which can be found in the routine PreparePresentLDR in the file GraphicsCore.cpp.

DRR 70% Frametime in ms

Figure 3. Results of a fly-through of the DirectX 12 MiniEngine rendering of the Crytek Sponza scene, with a fixed DRR render target size of 70% of the final 1080p framebuffer.

Performance

We measured the performance of our DRR implementation in the DirectX 12 MiniEngine from the DirectX 12 Samples6 using a fly-through of the Cryengine* Sponza Sample Scene with a 1080p final render target and various fixed dynamic render target sizes.

If the DRR render target is kept at a 70% fixed size for the entire fly-through, the average speed-up is around 1.4 times, as shown in Figure 3. We use 70% of the final render target size as a dynamic render target resolution because, subjectively, this seems to be a lower bound on the resolution where we notice no visual degradation in the image quality.

Conclusion

Using DirectX 12 Placed Resources can be a convenient way to add DRR functionality to your existing rendering pipeline. The usage described above requires few, if any, changes to your post-processing pipeline, but it does come with an additional cost to your memory footprint.

Our implementation can be found in a fork of the Microsoft DirectX 12 MiniEngine.7 This sample code also includes an implementation that integrates with checkerboard rendering (CBR), originally described in Checkerboard Rendering for Real-Time Upscaling on Intel Integrated Graphics and presented at ACM* SIGGRAPH 2018.9 The SIGGRAPH 2018 talk gives additional details on the performance results when CBR and DRR are combined, as well as a discussion of the integration challenges.

References

  1. Doug Binks, Dynamic Resolution Rendering Article. July 13, 2011
  2. Doug Binks, Dynamic Resolution Rendering Updated. May 14, 2012
  3. [Microsoft 2018a] Resource Heaps
  4. ID3D12Device::CreateHeap
  5. ID3D12Device::CreatePlacedResource
  6. Microsoft DirectX 12 Samples.
  7. Github Repository for Dynamic Resolution Rendering and Checkerboard Rendering implementations.
  8. Trapper McFerron and Adam Lake, Checkerboard Rendering for Real-Time Upscaling on Intel Integrated Graphics.
  9. Trapper McFerron and Adam Lake, Dynamic Resolution Rendering Techniques on Intel Integrated Graphics. Intel Sponsored Session, SIGGRAPH 2018.
For more complete information about compiler optimizations, see our Optimization Notice.