Utilisation de DXCore pour énumérer des adaptateurs

DXCore est une API d’énumération d’adaptateurs pour les périphériques DirectX, de sorte que certaines de ses fonctionnalités se chevauchent avec celles de DXGI.

DXCore permet l’exposition de nouveaux types de périphériques en mode utilisateur, tels que MCDM (Microsoft Compute Driver Model), à utiliser avec Direct3D 12, DirectML et Windows Machine Learning. Contrairement à DXGI, DXCore ne fournit aucune information sur les technologies ou propriétés liées à l’affichage.

Dans les prochaines sections, nous examinerons les principales fonctionnalités de DXCore avec quelques exemples de code (écrits en C++/WinRT). Les exemples de code ci-dessous sont extraits de l’énumération complète du code source que vous pouvez trouver dans la rubrique Application DXCore minimale.

Créer une fabrique d’adaptateurs

Vous commencez l’énumération des adaptateurs DXCore en créant un objet de fabrique d’adaptateurs, représenté par l’interface IDXCoreAdapterFactory. Pour créer une fabrique, incluez le fichier en-tête dxcore.h et appelez la fonction libre DXCoreCreateAdapterFactory.

#include <dxcore.h>
...
winrt::com_ptr<IDXCoreAdapterFactory> adapterFactory;
winrt::check_hresult(::DXCoreCreateAdapterFactory(adapterFactory.put()));

Récupérer une liste d’adaptateurs

Contrairement à DXGI, une fabrique d’adaptateurs DXCore nouvellement créée ne crée pas automatiquement un instantané de l’état des adaptateurs du système. DXCore crée cet instantané lorsque vous récupérez explicitement un objet de liste d’adaptateurs, représenté par l’interface IDXCoreAdapterList.

winrt::com_ptr<IDXCoreAdapterList> d3D12CoreComputeAdapters;
GUID attributes[]{ DXCORE_ADAPTER_ATTRIBUTE_D3D12_CORE_COMPUTE };
winrt::check_hresult(
    adapterFactory->CreateAdapterList(_countof(attributes),
        attributes,
        d3D12CoreComputeAdapters.put()));

Sélectionner un adaptateur approprié dans la liste

Cette section montre comment, à partir d’un objet de liste d’adaptateurs, vous pouvez trouver le premier adaptateur matériel dans la liste.

La méthode IDXCoreAdapterList::GetAdapterCount vous indique le nombre d’éléments dans la liste, et IDXCoreAdapterList::GetAdapter récupère un adaptateur spécifique par index.

Vous pouvez ensuite interroger les propriétés de cet adaptateur en suivant ces étapes.

  • Tout d’abord, pour confirmer qu’il est valide de récupérer la valeur d’une propriété donnée pour cet adaptateur sur cette version du système d’exploitation, appelez IDXCoreAdapter::IsPropertySupported. Passez une valeur de l’énumération DXCoreAdapterProperty pour identifier la propriété que vous interrogez.
  • Optionnellement, confirmez la taille de la valeur de la propriété en appelant IDXCoreAdapter::GetPropertySize. Pour une propriété telle que DXCoreAdapterProperty::IsHardware, qui est un simple booléen, cette étape n’est pas nécessaire.
  • Enfin, récupérez la valeur de la propriété en appelant IDXCoreAdapter::GetProperty.
winrt::com_ptr<IDXCoreAdapter> preferredAdapter;

const uint32_t count{ d3D12CoreComputeAdapters->GetAdapterCount() };

for (uint32_t i = 0; i < count; ++i)
{
    winrt::com_ptr<IDXCoreAdapter> candidateAdapter;
    winrt::check_hresult(
        d3D12CoreComputeAdapters->GetAdapter(i, candidateAdapter.put()));

    bool isHardware{ false };
    winrt::check_hresult(candidateAdapter->GetProperty(
        DXCoreAdapterProperty::IsHardware,
        &isHardware));

    if (isHardware)
    {
        // Choose the first hardware adapter, and stop looping.
        preferredAdapter = candidateAdapter;
        break;
    }

    // Otherwise, ensure that (as long as there are *any* adapters) we'll
    // at least choose one.
    if (!preferredAdapter)
    {
        preferredAdapter = candidateAdapter;
    }
}

Sélectionner l’adaptateur préféré en triant une liste d’adaptateurs

Vous pouvez trier une liste d’adaptateurs DXCore en appelant la méthode IDXCoreAdapterList::Sort.

L’énumération DXCoreAdapterPreference définit les valeurs représentant les critères de tri. Passez un tableau de ces valeurs à Sort, puis lisez le premier adaptateur de la liste triée.

Pour déterminer si un type de tri sera compris par Sort, appelez d’abord IDXCoreAdapterList::IsAdapterPreferenceSupported.

winrt::com_ptr<IDXCoreAdapter> TryFindHardwareHighPerformanceGraphicsAdapter()
{
    // You begin DXCore adapter enumeration by creating an adapter factory.
    winrt::com_ptr<IDXCoreAdapterFactory> adapterFactory;
    winrt::check_hresult(::DXCoreCreateAdapterFactory(adapterFactory.put()));

    // From the factory, retrieve a list of all the Direct3D 12 Graphics adapters.
    winrt::com_ptr<IDXCoreAdapterList> d3D12GraphicsAdapters;
    GUID attributes[]{ DXCORE_ADAPTER_ATTRIBUTE_D3D12_GRAPHICS };
    winrt::check_hresult(
        adapterFactory->CreateAdapterList(_countof(attributes),
            attributes,
            d3D12GraphicsAdapters.put()));

    DXCoreAdapterPreference sortPreferences[]{
        DXCoreAdapterPreference::Hardware, DXCoreAdapterPreference::HighPerformance };

    // Ask the OS to sort for the highest performance hardware adapter.
    winrt::check_hresult(d3D12GraphicsAdapters->Sort(_countof(sortPreferences), sortPreferences));

    winrt::com_ptr<IDXCoreAdapter> preferredAdapter;

    if (d3D12GraphicsAdapters->GetAdapterCount() > 0)
    {
        winrt::check_hresult(d3D12GraphicsAdapters->GetAdapter(0, preferredAdapter.put()));
    }

    return preferredAdapter;
}

Interroger et définir l’état d’un adaptateur (propriétés)

Vous pouvez récupérer et définir l’état d’un élément d’état spécifié d’un adaptateur en appelant les méthodes IDXCoreAdapter::QueryState et IDXCoreAdapter::SetState.

void SetDesiredMemoryReservation(winrt::com_ptr<IDXCoreAdapter> const& adapter, uint64_t reservation)
{
    DXCoreAdapterMemoryBudgetNodeSegmentGroup nodeSegmentGroup{};
    nodeSegmentGroup.nodeIndex = 0;
    nodeSegmentGroup.segmentGroup = DXCoreSegmentGroup::Local;

    DXCoreAdapterMemoryBudget memoryBudget{};
    winrt::check_hresult(adapter->QueryState(
        DXCoreAdapterState::AdapterMemoryBudget,
        &nodeSegmentGroup,
        &memoryBudget));

    // Clamp the reservation to what's available.
    reservation = std::min<uint64_t>(reservation, memoryBudget.availableForReservation);

    winrt::check_hresult(adapter->SetState(
        DXCoreAdapterState::AdapterMemoryBudget,
        &nodeSegmentGroup,
        &reservation));
}

En pratique, avant d’appeler QueryState et SetState, vous devez appeler IsQueryStateSupported pour confirmer que l’interrogation de l’état est disponible pour cet adaptateur et ce système d’exploitation.

Actualité de la liste d’adaptateurs

Si une liste d’adaptateurs devient obsolète en raison des conditions changeantes du système, elle sera marquée comme telle. Vous pouvez déterminer la fraîcheur d’une liste d’adaptateurs en sondant sa méthode IDXCoreAdapterList::IsStale.

Toutefois, plus simplement, vous pouvez vous abonner aux notifications pour des conditions telles que l’obsolescence. Pour ce faire, passez DXCoreNotificationType::AdapterListStale à IDXCoreAdapterFactory::RegisterEventNotification, et stockez en toute sécurité le cookie renvoyé pour une utilisation ultérieure.

uint32_t m_eventCookie = 0;
...
winrt::check_hresult(factory->RegisterEventNotification(
    m_adapters.get(),
    DXCoreNotificationType::AdapterListStale,
    OnAdapterListStale,
    this,
    &m_eventCookie));
...
static void WINAPI OnAdapterListStale(
    DXCoreNotificationType notificationType,
    IUnknown* staleObject,
    void* context)
{
    ...
}

Vous pouvez ensuite générer un nouvel objet de liste d’adaptateurs actuel à partir de l’objet de fabrique que vous possédez déjà. Gérer ces conditions est essentiel pour pouvoir répondre de manière fluide aux événements tels que l’arrivée et la suppression d’adaptateurs (qu’il s’agisse d’un GPU ou d’un adaptateur de calcul spécialisé), et pour réaffecter les charges de travail en conséquence.

Avant de détruire l’objet de liste d’adaptateurs, vous devez utiliser la valeur du cookie pour désinscrire cet objet des notifications en appelant IDXCoreAdapterFactory::UnregisterEventNotification. Si vous ne vous désinscrivez pas, une exception fatale est levée lorsque la situation est détectée.

HRESULT hr = factory->UnregisterEventNotification(m_eventCookie);

Informations sur l’affichage

Remarque

DXCore ne fournit aucune information sur l’affichage. Si nécessaire, vous devez utiliser la classe DisplayMonitor du Runtime Windows pour récupérer cette information. Le LUID d’un adaptateur fournit un identifiant commun que vous pouvez utiliser pour mapper un adaptateur DXCore aux informations DisplayMonitor.DisplayAdapterId. Pour obtenir le LUID d’un adaptateur, passez DXCoreAdapterProperty::InstanceLuid à la méthode IDXCoreAdapter::GetProperty.

Énumérer les adaptateurs pour les charges de travail multimédia

Bien que la méthode IDXCoreAdapterFactory1::CreateAdapterListByWorkload vous permette de fournir des filtres spécifiques pour la charge de travail, le runtime et le type de matériel, nous vous recommandons de limiter les filtres à ce qui est essentiel. La raison principale en est d’aider à garantir la compatibilité avec une large gamme de configurations d’utilisateurs finaux. Vous pouvez compter sur DXCore pour fournir à votre application l’adaptateur le plus approprié en première entrée de la liste d’adaptateurs, et vous pouvez ignorer le reste, si nécessaire. Dans cet exemple, nous allons créer une liste d’adaptateurs pour une charge de travail multimédia (c’est-à-dire le traitement vidéo) où les accélérateurs multimédia seront prioritaires par DXCore.

void EnumerateAcceleratorsForMedia(ComPtr<IDXCoreAdapterList> &accelsForMedia)
{
    ComPtr<IDXCoreAdapterFactory1> adapterFactory;
    winrt::check_hresult(DXCoreCreateAdapterFactory(&adapterFactory));
    
    winrt::check_hresult(adapterFactory->CreateAdapterListByWorkload(
        DXCoreWorkload::Media,
        DXCoreRuntimeFilterFlags::None,
        DXCoreHardwareTypeFilterFlags::None,
        &accelsForMedia));
 
    // Ordering for returned adapter list:
    // - Media only
    // - Media and ML
    // - Media with Compute shaders
    // - Media with full GPU
}

Énumérer les NPU pour les charges de travail ML

Dans certains cas, il est approprié de réduire encore la liste des adaptateurs pour IDXCoreAdapterFactory1::CreateAdapterListByWorkload, par exemple dans le cas où vous ne voulez que le traitement efficace des charges de travail d’apprentissage automatique sur un NPU. Cet exemple fournira une liste d’adaptateurs composée de NPU capables de traiter les charges de travail ML via des méta-commandes DirectX, en excluant les GPU et les accélérateurs de calcul.

void EnumerateNPUsForML(ComPtr<IDXCoreAdapterList> &npus)
{
    ComPtr<IDXCoreAdapterFactory1> adapterFactory;
    winrt::check_hresult(DXCoreCreateAdapterFactory(&adapterFactory));
    
    winrt::check_hresult(adapterFactory->CreateAdapterListByWorkload(
        DXCoreWorkload::MachineLearning,
        DXCoreRuntimeFilterFlags::None,
        DXCoreHardwareTypeFilterFlags::NPU,
        &npus));
 
    // Ordering for returned adapter list:
    // - NPUs with metacommands
    // - NPUs with metacommands and compute shaders
}

Énumérer les adaptateurs avec/sans pilotes en mode utilisateur DirectX

DXCore prend en charge l’énumération des périphériques MCDM/WDDM qui ne fournissent pas de pilote en mode utilisateur Direct 3D (mais s’appuient plutôt sur des interfaces privées ou d’autres bibliothèques pour l’interaction). Ces périphériques ne peuvent pas être énumérés via la méthode IDXCoreAdapterFactory1::CreateAdapterListByWorkload, car cette méthode est conçue pour prendre en charge les cas d’utilisation impliquant la pile DirectX. Cependant, cette classe étroite d’adaptateurs sera énumérable via la méthode IDXCoreAdapterFactory::CreateAdapterList en utilisant les nouvelles attributs de type matériel. Cet exemple énumère chaque adaptateur sur le système, indépendamment de la prise en charge du runtime Direct 3D, et l’ajoute à une carte avec le GUID de l’attribut de type matériel comme clé.

void EnumerateAllAdapters(std::map<GUID, ComPtr<IDXCoreAdapterList>> &adapterListByType)
{
    ComPtr<IDXCoreAdapterFactory> adapterFactory;
    winrt::check_hresult(DXCoreCreateAdapterFactory(&adapterFactory));
    
    const GUID attributes[] = {
        DXCORE_HARDWARE_TYPE_ATTRIBUTE_GPU,
        DXCORE_HARDWARE_TYPE_ATTRIBUTE_COMPUTE_ACCELERATOR,
        DXCORE_HARDWARE_TYPE_ATTRIBUTE_NPU,
        DXCORE_HARDWARE_TYPE_ATTRIBUTE_MEDIA_ACCELERATOR,
    };
 
    for(uint32_t i = 0; i < ARRAYSIZE(attributes); ++i)
    {
        ComPtr<IDXCoreAdapterList> adapterList = nullptr;
        winrt::check_hresult(adapterFactory->CreateAdapterList(1, attributes[i], &adapterList));
 
        if(adapterList->GetAdapterCount() > 0)
        {
            adapterListByType.insert({attributes[i], adapterList});
        }
    }
}

Voir aussi