PIX and ID3D12ManualWriteTrackingResource 

Austin Kinross


If you’re using GPU_UPLOAD heaps and you are experiencing capture-time performance issues when you launch your application via PIX (e.g. your game’s framerate drops) then read on to learn how ID3D12ManualWriteTrackingResource might help you.



When taking a GPU Capture, PIX (and other graphics debugging tools) often use WRITE_WATCH to automatically detect modifications to CPU-writeable (a.k.a. “mappable”) resources such as UPLOAD or GPU_UPLOAD resources. This was a reliable solution, but it poses a problem for GPU_UPLOAD heaps because current versions of Windows* don’t allow WRITE_WATCH resources/heaps to live in discrete video memory.  (*as-of March 2023)

This means that GPU_UPLOAD resources/heaps are forced into system memory when WRITE_WATCH is turned on, which may cause a performance loss in your game when it’s running under PIX capture.  

This issue affects all retail versions of Windows today, but it is fixed in the latest Windows 11 Insider build. Note that this issue does not affect PIX playback performance or the correctness of performance data in PIX. 

Applications can avoid these issues by using ID3D12ManualWriteTrackingResource. 


Introducing ID3D12ManualWriteTrackingResource

The PIX and D3D12 teams have collaborated to define a manual write tracking API: ID3D12ManualWriteTrackingResource, defined in d3d12sdklayers.h. This API allows applications or middleware components to turn off WRITE_WATCH tracking for a particular mappable resource or heap, on the condition that the application or component manually notifies PIX every time the resource/heap is modified via the CPU. 


How to use ID3D12ManualWriteTrackingResource

Firstly you must check if manual write tracking is supported by calling ID3D12Device::CheckFeatureSupport: 

    bool IsManualWriteTrackingSupported(ID3D12Device* device) 
        D3D12_FEATURE_DATA_D3D12_OPTIONS17 options17; 
        if (SUCCEEDED(device->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS17, &options17, sizeof(options17)))) 
            return !!options17.ManualWriteTrackingResourceSupported; 

        return false; 

The D3D12 runtime will always set ManualWriteTrackingResourceSupported to FALSE. However, PIX and (other graphics debugging tools) will intercept your API call and override ManualWriteTrackingResourceSupported to TRUE when appropriate. 

If ManualWriteTrackingResourceSupported is TRUE, then you can set D3D12_HEAP_FLAG_TOOLS_USE_MANUAL_WRITE_TRACKING as a heap flag when you create a mappable heap or committed resource: 

typedef enum D3D12_HEAP_FLAGS 
        D3D12_HEAP_FLAG_NONE = 0, 
        D3D12_HEAP_FLAG_SHARED = 0x1, 
        D3D12_HEAP_FLAG_DENY_BUFFERS = 0x4, 
        D3D12_HEAP_FLAG_ALLOW_DISPLAY = 0x8, 
        D3D12_HEAP_FLAG_DENY_RT_DS_TEXTURES = 0x40, 
        D3D12_HEAP_FLAG_ALLOW_WRITE_WATCH = 0x200, 
        D3D12_HEAP_FLAG_CREATE_NOT_ZEROED = 0x1000, 
    } D3D12_HEAP_FLAGS; 

This flag tells PIX (or other graphics debugging tools) to turn off WRITE_WATCH tracking on the heap or committed resource. The D3D12 runtime will reject any attempt to set this flag when ManualWriteTrackingResourceSupported is FALSE. 

After resource/heap creation, if you map your resource on the CPU then you must tell PIX any time you modify the mapped bytes on the CPU timeline by calling ID3D12ManualWriteTrackingResource::TrackWrite from d3d12sdklayers.h: 

    ID3D12ManualWriteTrackingResource : public IUnknown 
        virtual void STDMETHODCALLTYPE TrackWrite(  
            UINT Subresource, 
            _In_opt_  const D3D12_RANGE *pWrittenRange) = 0; 

You can get this interface by QueryInterface()’ing for it off your ID3D12Resource*. 

You must call ID3D12ManualWriteTrackingResource::TrackWrite after you modify your mapped bytes, but before you call ID3D12CommandQueue::ExecuteCommandLists() to execute any command list whose GPU work may depend on your modifications.