Time is short. You need to ship your title, but you’re willing to tweak performance just a bit more. Our graphics experts have put together a quick list of tuning tips. A combination of these techniques should enable you to make the final performance tweaks that put your title on the road to success.
We strongly recommend using PresentThreshold with Presents. This allows you to have Vsync-locked Swaps when you’re hitting your target frame-rate and to have immediate Swaps when you’re running behind your target frame-rate. You can control what portion of the display you’re okay with tearing happening. This is essentially the same functionality as D3DRS_PRESENTIMMEDIATETHRESHOLD as provided on Xbox 360. You will need to switch from calling the IDXGISwapChain::Present Method to the new DXGIXPresentArray method to enable this functionality.
If you use Command Lists, try out D3D11_CREATE_DEFERRED_CONTEXT_TITLE_MANAGED_COMMAND_LIST_OBJECT_LIFETIMES—see D3D11_CREATE_DEFERRED_CONTEXT_FLAG Enumeration. This flag reduces Command List record times and Command List Release overhead. The August 2013 XDKs added more overhead to implement compliant Direct3D refcounting semantics on objects by default. This flag allows you to avoid that overhead if you’re managing Direct3D object lifetimes yourself and were running fine prior to the Direct3D refcounting updates.
If you use Command Lists and call ExecuteCommandList multiple times in a row, you will win significant CPU performance by bracketing the sequence of executes with a single BeginCommandListExecution/EndCommandListExecution pair, which is available only with the Monolithic Direct3D. This saves significant CPU performance: Direct3D doesn’t have to reset all of the API state on every ExecuteCommandList; rather, it does so only once at EndCommandListExecution time. Note that other Draw methods cannot be called while in a BeginCommandListExecution/EndCommandListExecution bracket.
Are you looking to TriJuice your textures? There is full support for all of the sampler capabilities of the hardware, including what Xbox 360 called D3DSAMP_TRILINEARTHRESHOLD. Use CreateSamplerStateX and set the PerfMip field appropriately to make trilinear sampling cost the same as bilinear, when sampling close to a mip. The higher the number, the more it performs bilinear fetches instead of trilinear.
The easiest way to determine how to fill in the many fields of D3D11X_SAMPLER_DESC:
What’s your render color space? Developers can choose between rendering in sRGB or REC709 space, and if you’re using sRGB, whether full or studio swing is required.
For an easy small GPU win, specify D3D11_CREATE_DEVICE_FAST_KICKOFFS with Monolithic Direct3D—see D3D11_CREATE_DEVICE_FLAG Enumeration. This removes GPU overhead with every internal kick-off.
Did you know that surfaces don’t have to fit entirely in ESRAM? You can have surfaces spill off the end of ESRAM, or you can have portions of your ESRAM frame-buffer that get little overdraw still live in main memory. See the ESRAM sample for an example of how to do this.
If you’re 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. 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. The scaler quality is even better than on Xbox 360, and Xbox 360 had a great scaler.
__XBOX_WAVESIM_ITERATION and __XBOX_ATTEMPT_WAVE_WIDENING can make big differences in generated shader code performance. Consider trying these out at least on important shaders such as for Post-effects. For details, see Shaders and the FXC Shader Compiler.
If PIX indicates a lot of context rolls due shader changes, check out the __XBOX_PRESERVE_ALL_INPUTS flag, which can be used to have the compiler attempt to preserve all inputs to the pixel shader. This can be used to force pixel shaders to have the same input signature and thereby avoid provoking driver context rolls. More details can be found in Shaders and the FXC Shader Compiler.
If you use Command Lists and profile with the INSTRUMENTED version of the Monolithic Direct3D libraries, be sure to double-check performance occasionally with the retail version of the libraries. PIX currently adds measurable record-time overhead to Command Lists with INSTRUMENTED builds.
Try performance with the D3D11_CREATE_DEVICE_WRITE_COMBINED_COMMAND_BUFFERS and D3D11_CREATE_DEVICE_WRITE_COMBINED_DYNAMIC_BUFFERS flags for Monolithic Direct3D. The improved cache effects may be a win for your title.
You can eliminate massive CPU overhead by tuning your XMemAlloc allocator, or the default system XMemAllocDefault allocator, never to call the kernel to allocate, or free, pages when your game is in its steady state. This is especially important for any titles that use Command Lists; they do graphics memory allocations every time a Command List is recorded.
If you’re using the default system XMemAllocDefault allocator, you can query statistics by calling XMemGetAllocationStatistics. Make sure that the dwAllocationCount and dwFreeCount fields of the XMEM_HEAP_STATISTICS Structure never increment during game play. If they do increment, you can call XMemSetAllocationHysteresis to tune the size hysteresis so that this doesn’t happen. XMemPreallocateFreeSpace can optionally be called to ensure that the heap has fully committed pages up to its hysteresis limit. Call XMemSetAllocationHysteresis to the value appropriate to your content, and call XMemPreallocateFreeSpace at level load time. That way, you can ensure that the heap will never again go back to the kernel to ask for memory.
If you have multiple Dispatch calls in a row, you may be able save significant GPU time by manually controlling hazard tracking. By default, the driver inserts GPU flushes between every Dispatch call to avoid hazards with UAVs—one Dispatch might write to a UAV and the next Dispatch immediately reads from the same UAV. Such a case requires a partial flush to be issued to the hardware to ensure proper coherency with the read-after-write. It’s too much overhead for the driver to properly track the true dependencies; by default, it simply and conservatively always issues such a flush after every Dispatch. But if you know that you don’t have any such hazard problem with your Dispatch—that is, you know that the results of one Dispatch aren’t fed directly into the next one—you can stop the driver from doing this by calling CSEnableAutomaticGpuFlush(FALSE). This inhibits all the driver’s automatic partial flushes and allows you complete control. Doing so can save significant GPU time that is lost to partial pipeline idling, especially when there are a large number of small dispatches. You can manually call GpuSendPipelinedEvent(D3D11X_GPU_PIPELINED_EVENT_CS_PARTIAL_FLUSH) if you need to insert synchronization, or you can call CSEnableAutomaticGpuFlush(TRUE) to resume the driver’s default behavior.
If you run a CPU profile on the graphics driver—either UMD*.DLL or CIUMD_HYDRA.DLL—and see CGraphicsContext::SlowRebindBecauseResourceSetTwiceInSameStage anywhere, then that’s good news: typically, easy CPU time that you can reclaim. When Map is called on a dynamic resource that is currently bound into the context—for example, by VSSetConstantBuffers—the driver needs to tell the hardware, at that time, the new location of the resource in memory. The driver uses a fast path when the resource is bound only once into any given stage. However, if a resource is bound more than once into any single stage, this slow path is called. It’s extremely slow because it has to do an exhaustive search through all stages and all slots to find all occurrences of that resource. So, if you see this function in your list, put a breakpoint on it (for example, umd-i.dll!CGraphicsContext::SlowRebindBecauseResourceSetTwiceInSameStage) and remove your duplicate binds.
Consider using EQAA for antialiasing with a small performance and memory overhead. See the Multisampling sample for an example implementation. The driver supports 1× EQAA.
If you’re running with 2× MSAA, consider asking ATG for sample code that shows how to support 4× MSAA with the first two fragments of every pixel in ESRAM and the last two fragments in main memory. The last two fragments are accessed infrequently due to compression, so the GPU overhead is typically quite low.