Introducing a New API for Checking Feature Support in Direct3D 12

Peter

Background

Direct3D can perform a lot of different operations. We use “capability” or “cap” to describe what the library can do. Direct3D 12 has a single API, ID3D12Device::CheckFeatureSupport, which tells us whether a cap is supported and its level of support in the runtime environment. Here’s the function signature:

HRESULT CheckFeatureSupport(
    D3D12_FEATURE Feature,
    void *pFeatureSupportData,
    UINT FeatureSupportDataSize
);

To make use of this API, you must provide a D3D12_FEATURE enum to identify the feature group you’re querying. You must also provide a pointer to one of the D3D12_FEATURE_DATA_* data structures specific to that feature and its size.

You may find this API hard to use for the following reasons:

  • Unless you memorize the cap locations, you must first look up which feature group contains the caps. Making things worse, the documentation is indexed by features, not caps. It feels like using the value to find the key in a dictionary.
  • The call routine is complicated. After determining the feature, you have to create an empty feature data struct and initialize its input fields and arrays (if any). You then call the API and check if the return code is S_OK. Finally, you read the cap information from the feature data struct.
  • If two caps belong to two different feature groups, you’ll need to repeat the whole process again.
  • The query process for some features, for example FEATURE_LEVELS is not intuitive.

Solution: The new “CD3DX12FeatureSupport” class

To improve your experience with feature checking, we have added a wrapper API with a simpler call routine and more friendly interface. Actually, it’s not a single API but an entire class. Allow me to introduce the new “CD3DX12FeatureSupport” class, located in d3dx12.h and available from the DirectX-Headers repo on GitHub.

It’s hard to explain why this class is better the original API. Let’s first walk through two examples.

Case 1: Multiple queries from different feature groups

In the first example, we’d like to check three different caps: “ROVs Supported”, “Wave Ops”, and Raytracing Tier. With the old API, we must figure out which features include these three caps. After looking up from the documentation, we found that they come from three different feature groups: D3D12_OPTIONS, D3D12_OPTIONS1, and D3D12_OPTIONS5. This is bad news since we’ll need to create three separate “D3D12_FEATURE_DATA” structs. What’s worse, it’s very easy to mistake one feature for another because all three have similar names. The final code looks like this:

// Old API

D3D12_FEATURE_DATA_D3D12_OPTIONS dOptions;
device->CheckFeatureSupport(
    D3D12_FEATURE_D3D12_OPTIONS,
    &dOptions,
    sizeof(D3D12_FEATURE_DATA_D3D12_OPTIONS)
);
BOOL ROVsSupported = dOptions.ROVsSupported;

D3D12_FEATURE_DATA_D3D12_OPTIONS1 dOptions1;
device->CheckFeatureSupport(
    D3D12_FEATURE_D3D12_OPTIONS1,
    &dOptions1,
    sizeof(D3D12_FEATURE_DATA_D3D12_OPTIONS1)
);
BOOL WaveOps = dOptions1.WaveOps;

D3D12_FEATURE_DATA_D3D12_OPTIONS5 dOptions5;
device->CheckFeatureSupport(
    D3D12_FEATURE_D3D12_OPTIONS5,
    &dOptions5,
    sizeof(D3D12_FEATURE_DATA_D3D12_OPTIONS5)
);
D3D12_RAYTRACING_TIER RaytracingTier = dOptions5.RaytracingTier;

Also notice that the API calls above are not safe. If this code is running on a very old version of D3D12 runtime, some of these checks may fail and the data fields may be invalid. You should enclose the “CheckFeatureSupport” calls in a “FAILED()” macro to determine whether the check succeeds.

Now let’s see how it’s done with the new API:

// New API

CD3DX12FeatureSupport features;
features.Init(device);
BOOL ROVsSupported = features.ROVsSupported();
BOOL WaveOps = features.WaveOps();
D3D12_RAYTRACING_TIER RaytracingTier = features.RaytracingTier();

Immediately you’ll find the whole process a lot easier. There’s no need to set up multiple instances to check different features, nor do you need to reinitialize between cap query calls. Plus, you don’t need to find out which feature groups contain the caps you’d like to query about. With a single instance of CD3DX12FeatureSupport and one single initialization, you can query any cap directly by calling a function of the same name.

What if an old version of D3D12 runtime doesn’t recognize a feature group and the feature check failed? The cap function will return a default “unsupported” value, which is usually 0 or false. It makes sense since the D3D12 runtime cannot support a cap or feature unless it knows about it.

Case 2: Querying the more complex FEATURE_LEVELS

In the second example we deal with FEATURE_LEVELS, which used to have a complicated check routine. With the old API, you need to pass in an array of “D3D_FEATURE_LEVEL” enums, along with the size of that array. The API then picks the highest supported level in the current runtime from the array.

// Old API

D3D12_FEATURE_DATA_FEATURE_LEVELS dLevels;
D3D_FEATURE_LEVEL aLevels [] =
{
    D3D_FEATURE_LEVEL_12_2,
    D3D_FEATURE_LEVEL_12_1,
    D3D_FEATURE_LEVEL_12_0,
    ...
};
dLevels.NumFeatureLevels = sizeof(aLevels) / sizeof(D3D_FEATURE_LEVEL);
dLevels.pFeatureLevelsRequested = aLevels;
device->CheckFeatureSupport(
    D3D12_FEATURE_FEATURE_LEVELS,
    &dLevels,
    sizeof(D3D12_FEATURE_DATA_FEATURE_LEVELS)
);
D3D_FEATURE_LEVEL MaxFeatureLevel = dLevels.MaxSupportedFeatureLevel;

That works but sounds counter-intuitive. We know the feature level enums are listed in increasing order where a newer level has a larger enum. We also know that the feature levels are backwards compatible. So why don’t we just obtain the feature level of the runtime and compare with what we want? The new API allows you to do exactly that:

// New API

CD3DX12FeatureSupport features;
features.Init(device);
D3D_FEATURE_LEVEL MaxFeatureLevel = features.MaxSupportedFeatureLevel();

Summary:

The above examples illustrated the key characteristics of the new CD3DX12FeatureSupport class:

  • Single entry point. The new API class can query any cap from any feature group. No need to use separate feature data structs for different caps.
  • Simpler call routine. After a single initialization, you may query any cap by calling a member function of the same name and read the result from the returned values.
  • Multiple use. You can query multiple caps from one instance of CD3DX12FeatureSupport without setting up a new instance or re-initializing.
  • Failure protection. If a cap query failed because the runtime doesn’t recognize its feature enum (probably due to an old D3D12 runtime), the function returns a default value, usually 0 or false, instead failing. There’s no need to capture error codes or exceptions from the query APIs.

Usage and Resources

We’ve demonstrated the general steps for using the new API:

  1. Include the “d3dx12.h” header. (See the DirectX-Headers GitHub repo)
  2. Create a CD3DX12FeatureSupport instance through the default constructor.
  3. Call “Init()” on the instance to bind it to a device. If the initialization succeeds, the function returns “S_OK”. Otherwise, it returns the error code from the last fatal initialization failure. We recommend you enclose this function call with the “FAILED” macro.
  4. For each cap you’d like to query, call the member function using its name. If a feature check requires inputs, you can pass them in as function arguments.

There are some special cases. Some caps are renamed to be more distinguishable in the new API class (for example, “Supported” from “D3D12_FEATURE_DATA_EXISTING_HEAPS” becomes “ExistingHeapsSupported()”). Other cap query functions may have different signatures due to their complicated in/out patterns.

Before the official documentation comes out, we have some resources to help you get started with this new API. Since the CD3DX12FeatureSupport class is part of the open-source DirectX-Headers repo available on GitHub, you have access to all its definition and implementation. This can give you a clear idea on how the new API is implemented and what it’s doing behind the scenes. We’ve also provided a program in the same repo (test/feature_check_test.cpp) that contains examples of how to query each cap with the new APIs. It also comes with correctness checks; you can compile and run it to make sure the new API returns the same results as the old API.

We hope this new API provides a better experience for checking feature support in Direct3D!

0 comments

Leave a comment