Home > Back-end >  Why IDXGIAdapter cannot cast to IDXGIFactory? Aren't they interfaces of the same component obje
Why IDXGIAdapter cannot cast to IDXGIFactory? Aren't they interfaces of the same component obje

Time:02-12

I understand that DirectX does not follow the COM standard. However, they look heck of a lot similar, hence my confusion.

For simplicity, I use the word 'COM' very loosely, and I omit all HRESULT handlings, so please bear with me.

Correct code:

Consider the following code.

ComPtr<IDXGIDevice3>            g_pDXGIDevice = nullptr;
ComPtr<ID3D11Device>            g_pD3DDevice = nullptr;
ComPtr<ID3D11DeviceContext>     g_pD3DDeviceContext = nullptr;

D3D11CreateDevice(..., &g_pD3DDevice, ..., &g_pD3DDeviceContext); 

A 'COM' object is instantiated. Since it is not a pure COM object, the component is instantiated through the D3D11 function D3D11CreateDevice() instead of the generic COM function coCreateInstance().

Upon success, the function returns 2 COM pointers: g_pD3DDevice and g_pD3DDeviceContext. This enables the client to interact and use methods with the COM server without the burden of the implementation details.

g_pD3DDevice.As(&g_pDXGIDevice);

Next, we QueryInterface the DXGI device from the device interface through the As method of the ComPtr class.

The return is a success since the DXGI-device interface and the device interface are provided by the same COM object.

Next, we create the swap chain with:

ComPtr<IDXGIAdapter> m_adapter = nullptr;
ComPtr<IDXGIFactory> m_factory = nullptr;

g_pDXGIDevice->GetAdapter(&m_adapter);
m_adapter->GetParent(IID_PPV_ARGS(&m_factory));
m_factory->CreateSwapChain(...);

This comes from the official MSD doc and works.

Incorrect code:

Now, before creating the swap chain, let's do the following.

m_adapter.As(&m_factory);

This returns E_NOINTERFACE. Here's my confusion.

  • Why can't we QueryInterface from IDXGIAdapter to get IDXGIFactory?
  • Aren't they interfaces of the same DLL, that is, dxgi.dll?
  • Are they interfaces pertaining to the same component object?

From MSD documentation, IDXGIAdapter and IDXGIFactory are derived from the same base-class interface IDXGIObject.

Can we safely assume that they are interfaces of the same COM object, and thus, it is possible to navigate within the object interfaces through QueryInterface?

If not, how do we know from the doc if two interfaces pertain to the same object? It seems that the doc is not clear about that.

To consolidate my arguments, in debug mode, we can clearly see that both IDXGIAdapter and IDXGIFactory come from the same DLL (see screenshot below).

Screenshot of m_adapter and m_factory as items to watch in debug mode

So why does m_adapter.As(&m_factory); return an error?

CodePudding user response:

A good place to start is to read through Microsoft Docs: Programming DirectX with COM.

In many cases the DirectX COM components allow you to do just what you say: If the C interface class inherits from another one, you should be able to QueryInterface up or down the chain with the same object instance. IDXGIAdapter is derived from IDXGIObject, so you can QI from the adapter instance to the object interface. IDXGIFactory is also derived from IDXGIObject, so you can QI to the object interface as well. Just because they both implement the same base interface, however, does not mean you an go from an instance of adapter to an instance of the factory. I.e., they are not the same 'object'. This mirrors C rules for inheritance: You can freely cast an instance of a class to it's publicly inherited parent, but can't freely cast back down the hierarchy.

There are some cases in DXGI where you can "navigate back" via IUnknown::QueryInterface and IDXGIObject::GetParent. For example, the following code is a good, consistent way to "get back" to the DXGI factory from a Direct3D 11 device:

ComPtr<IDXGIFactory1> dxgiFactory;
{
    ComPtr<IDXGIDevice> dxgiDevice;
    if (SUCCEEDED(device.As(&dxgiDevice)))
    {
        ComPtr<IDXGIAdapter> adapter;
        if (SUCCEEDED(dxgiDevice->GetAdapter(&adapter)))
        {
            hr = adapter->GetParent(IID_PPV_ARGS(&dxgiFactory));
            if ( SUCCEEDED(hr) )
            {
                ...
            }
        }
    }
}

See Anatomy of Direct3D 11 Create Device.

While this works in simple cases like the one above, general type shifting is often problematic and/or buggy. For example, with Direct3D 11 there were problems if you use the DXGICreateFactory method instead of DXGICreateFactory1, or if you mixed both in the same process.

As such, for DirectX 12 these DXGI back-track conversions (such as the code above) are explicitly not supported. You are expected to create the DXGI factory directly and then create the Direct3D 12 device from the enumerated adapter and -not- try to go the other way.

See Anatomy of Direct3D 12 Create Device.

Really the only time you are expected to use those DXGI interfaces beyond enumeration is for "surface sharing" scenarios. See Microsoft Docs: DX11 and DX12. Everything else is really there only for internal implementation purposes, and is therefore subject to change or other undocumented behaviors.

  • Related