WDDM 기능 지원 및 사용 쿼리

이 문서에서는 WDDM(Windows Display Driver Model) 기능의 지원 및 사용을 쿼리하는 방법을 설명합니다. 다음을 설명합니다.

  • 사용자 모드 및 커널 모드 표시 드라이버(UMD 및 KMD)가 OS를 쿼리하여 시스템에서 WDDM 기능이 지원되고 사용하도록 설정되어 있는지 여부를 확인하는 방법입니다.
  • OS에서 드라이버가 특정 WDDM 기능을 지원하는지 여부를 결정하는 방법입니다.

이 기능 쿼리 메커니즘은 Windows 11 버전 24H2(WDDM 3.2)부터 도입되었습니다.

WDDM 기능 개요

WDDM은 기능 컬렉션으로 볼 수 있습니다. 여기서 기능은 특정 기능을 다루는 WDDM API/DDI의 컬렉션입니다.

기능은 범주 ID 및 범주 내의 기능 자체에 대한 하위 ID로 구성된 기능 ID로 식별됩니다.

OS에 알려진 각 기능에는 해당 기능이 시스템에서 지원 및/또는 사용하도록 설정되어 있는지 여부를 확인하기 위해 연결된 상태 정보가 있습니다. 일부 기능은 드라이버 기능수 있습니다. 드라이버 기능을 사용하려면 드라이버에서 어느 정도의 지원이 필요합니다. Dxgkrnl 은 기능 구성을 결정하는 핸드셰이크 메커니즘을 제공합니다. 레지스트리 키는 어댑터별로 기능별 구성을 재정의할 수 있습니다.

드라이버 기능에는 기능과 관련된 드라이버의 DDI를 제공하는 기능 인터페이스 가 있을 수도 있습니다. 개별 기능 인터페이스를 지원하여 이전에 업데이트된 WDDM 버전 관리 변경으로만 확장할 수 있었던 OS와 KMD 간의 기본 인터페이스를 업데이트하는 데 더 이상 의존할 필요가 없습니다. 이 방법은 특별한 지원을 정의할 필요 없이 이전 OS 또는 Windows Moment 릴리스를 통해 기능을 보다 유연하게 백포팅하는 방법을 제공합니다.

각 기능에는 필수 구성 요소로 지원되어야 하는 종속성 목록이 있을 수 있습니다. 이러한 종속성이 필요한 향후 기능은 설명서에 필요한 종속성을 나타냅니다.

기능은 버전이 지정되며 지원되는 각 버전에 대해 서로 다른 인터페이스 또는 구성을 가질 수 있습니다.

WDDM은 특정 기능 상태를 쿼리하는 API 집합을 도입했습니다. API는 다음과 같습니다.

  • DXGK_FEATURE_INTERFACE

    • KMD는 이 인터페이스를 쿼리하고 사용하여 DxgkDdiStartDevice가 호출된 후 시스템에서 특정 기능이 지원되고 사용하도록 설정되어 있는지 여부를 확인할 수 있습니다.
    • 이 메커니즘은 다음과 같은 기존 DDI를 대체합니다.
  • DXGKDDI_FEATURE_INTERFACE

    • 이 메커니즘을 사용하면 OS가 KMD에서 기능 인터페이스를 쿼리할 수 있습니다.
  • DxgkIsFeatureEnabled2

    • 이 함수는 시스템 제공 displib 라이브러리의 진입점입니다.
    • KMD는 Dxgkrnl을 초기화하지 않고 DriverEntry 루틴에서 DxgkIsFeatureEnabled2를 호출하여 시스템에 특정 기능이 활성화되어 있는지 여부를 확인할 수 있습니다.
  • D3DKMTIsFeatureEnabled

    • 이 사용자 모드 API를 사용하면 사용자 모드 모듈이 특정 기능을 사용할 수 있는지 여부를 확인할 수 있습니다.

디스플레이 미니포트 드라이버가 로드되면 WDDM 포트 드라이버는 드라이버 지원에 의존하는 모든 기능을 쿼리합니다.

드라이버는 로드될 때 지원되는 기능에 대해 WDDM 포트 드라이버를 쿼리할 수 있습니다.

WDDM 기능 정의

기능은 DXGK_FEATURE_ID 값으로 표현되는 기능 ID 식별됩니다. DXGK_FEATURE_ID 값의 형식은 다음과 같습니다.

  • 기능의 범주 ID는 기능의 범주를 식별하는 DXGK_FEATURE_CATEGORY 값입니다. 상위 4비트 DXGK_FEATURE_ID 저장됩니다.
  • 기능 범주 내의 실제 기능을 식별하는 기능의 하위 ID 입니다. 하위 ID는 하위 28비트 DXGK_FEATURE_ID 저장됩니다.

DXGK_FEATURE_CATEGORY DXGK_FEATURE_CATEGORY_DRIVER 경우 기능의 하위 ID는 실제 기능을 식별하는 DXGK_DRIVER_FEATURE 값입니다.

전역 및 어댑터 기능

개별 기능은 전역 기능 또는 어댑터 관련 기능에 해당합니다. 기능 설명서는 이 기능이 글로벌 기능인지 여부를 나타냅니다. 해당 어댑터와 관련된 기능 구성을 쿼리하거나 전역 데이터베이스를 사용하려면 hAdapter 매개 변수가 필요할 수 있으므로 기능을 사용할 수 있는지 여부를 쿼리할 때 이 정보를 알아야 합니다.

다음 기능은 현재 전역 기능으로 정의되어 있습니다.

  • DXGK_FEATURE_GPUVAIOMMU

가상화

GPU-PV의 경우 OS는 호스트와 게스트 간에 기능 지원 및 활성화를 자동으로 협상합니다. 드라이버는 이러한 쿼리에 대한 특별한 지원을 구현할 필요가 없습니다.

종속성

각 기능은 종속성 목록을 지정할 수 있습니다. 이러한 종속성은 기능 자체의 정의에 연결되며 OS에 의해 컴파일 시간에 하드 코딩됩니다.

특정 기능을 사용하도록 설정하려면 모든 종속성도 사용하도록 설정해야 합니다.

현재 다음 기능에는 종속성이 있습니다.

  • DXGK_FEATURE_USER_MODE_SUBMISSION
  • DXGK_FEATURE_HWSCH
  • DXGK_FEATURE_NATIVE_FENCE

기능의 설명서는 기능에도 사용하도록 설정해야 하는 종속성이 있는지 여부를 지정합니다.

KMD에서 기능 지원 쿼리

OS는 핸드셰이크 메커니즘을 사용하여 OS와 드라이버가 모두 기능을 지원하는지 여부를 확인합니다. 이 메커니즘을 사용하면 기능이 원본(OS/Dxgkrnl, KMD, UMD, 런타임 등)에서 시작되는지 여부를 초기 쿼리할 수 있으며 OS 및 드라이버가 기능 지원을 협상할 수 있는 적절한 메커니즘을 계속 사용할 수 있습니다.

KMD는 포트 드라이버가 기능 지원을 쿼리하기 위해 DXGKDDI_FEATURE_INTERFACE 인터페이스를 구현해야 합니다. 인터페이스 GUID가 GUID_WDDM_INTERFACE_FEATURE.

드라이버가 DXGKDDI_FEATURE_INTERFACE 구현하는 경우 DxgkCbQueryFeatureSupport를 호출하여 포트 드라이버에서 기능을 미리 사용하도록 설정할 필요가 없습니다. 대신 DXGKDDI_FEATURE_INTERFACE 인터페이스를 사용하여 요청 시 기능 지원을 쿼리할 수 있습니다.

쿼리 기능 사용

이 섹션에서는 구성 요소가 시스템에서 기능을 사용할 수 있는지 여부를 검사 방법을 설명합니다. DXGK_ISFEATUREENABLED_RESULT 구조는 기능 쿼리의 결과를 정의합니다.

사용자 모드 쿼리

사용자 모드 클라이언트는 D3DKMTIsFeatureEnabled를 호출하여 특정 WDDM 기능을 사용할 수 있는지 여부를 쿼리합니다.

커널 모드 쿼리

기능 지원을 쿼리하기 위한 콜백을 얻으려면 KMD에서 DxgkServicesFeature 인터페이스를 쿼리해야 합니다. 이 인터페이스를 가져오기 위해 KMD는 다음 코드 조각과 같이 ServiceType을 사용하여 DxgkrnlDxgkCbQueryServices 콜백을 DxgkServicesFeatureDXGK_SERVICES 값으로 호출합니다. KMD는 DxgkDdiStartDevice 호출에서 콜백 포인터를 가져오면 DxgkCbQueryServices를 호출수 있습니다.

DXGK_FEATURE_INTERFACE FeatureInterface = {};
FeatureInterface.Size = sizeof(pDevExt->FeatureInterface);
FeatureInterface.Version = DXGK_FEATURE_INTERFACE_VERSION_1;
Status = DxgkInterface.DxgkCbQueryServices(DxgkInterface.DeviceHandle, DxgkServicesFeature, (PINTERFACE)&FeatureInterface);

Dxgkrnl이 초기화되기 전에 기능 확인

DxgkIsFeatureEnabled2 는 디스플레이 포트 드라이버 라이브러리(displib.h)에 정의되어 있습니다. 따라서 KMD는 DxgkRnl이 초기화되기 전에 DxgkIsFeatureEnabled2를 호출하여 기능의 존재에 대한 검사 수 있습니다.

이 호출은 DriverEntry에서 사용되므로 전역 기능의 하위 집합만 이를 통해 쿼리할 수 있습니다. 이 하위 집합에는 현재 다음이 포함됩니다.

  • DXGK_FEATURE_GPUVAIOMMU

레지스트리 재정의

드라이버 개발 및 테스트 중에 레지스트리의 기능 구성을 재정의할 수 있습니다. 이 기능은 기본 기능 구성이 지원되지 않음을 나타낼 수 있는 경우 개발 환경에서 특정 기능을 사용하도록 강제 적용하는 데 유용합니다.

드라이버는 드라이버를 설치하는 동안 INF에서 이러한 레지스트리 키를 정의해서는 안됩니다. 이러한 키는 테스트 및 개발 목적으로만 사용되며 특정 기능을 광범위하게 재정의하지 않습니다.

드라이버의 기능 구성은 어댑터의 PNP 소프트웨어 키인 XXXX\Features\YYYY 키 아래에 저장됩니다. 여기서 XXXX는 디바이스가 설치될 때 PnP에서 할당한 디바이스 식별자이고 YYYY는 기능 ID나타냅니다. 예제는 HKLM\SYSTEM\CurrentControlSet\Control\Class\{4d36e968-e325-11ce-bfc1-08002be10318}\0000\Features\4입니다.

다음 섹션에서는 기능에 대해 지정할 수 있는 재정의에 대해 설명합니다.

레지스트리 키 이름: 사용

속성 타입 설명
사용 DWORD 0(지원되지 않음) 또는 1(지원됨). 기본값은 기능 종속입니다. 기능에 대한 OS 지원을 재정의합니다.

이름에도 불구하고 이 항목은 기능의 OS 쪽 지원 만 재정의합니다. 기능을 항상 사용하도록 설정하지는 않습니다. 즉, IsFeatureEnabled(ID)에 대한 호출이 Enabled=TRUE를 반환한다고 보장하지는 않습니다. 드라이버 기능에는 적절한 드라이버 쪽 협상이 여전히 필요합니다.

레지스트리 키 이름: MinVersion

속성 타입 설명
MinVersion DWORD 기능 종속 이 기능에 대해 지원되는 최소 버전을 기본 최소 버전보다 더 제한적으로 제한합니다.

이 값은 OS가 지원되는 기본 최소 버전보다 낮은 버전을 지원하도록 강제하는 데 사용할 수 없습니다(이 기본 최소값은 구현 지원으로 인해 선택됨). 대신 이 키는 지원되는 최소 버전을 기본값보다 높은 값으로 제한할 수 있습니다.

이 구성은 특정 버전의 기능 구현에 있을 수 있는 버그를 해결하는 데 유용합니다.

MinVersion이 지정된 경우 MaxVersion도 지정해야 합니다.

레지스트리 키 이름: MaxVersion

속성 타입 설명
MaxVersion DWORD 기능 종속 이 기능에 대해 지원되는 최대 버전을 기본 최대 버전보다 더 제한적으로 제한합니다.

이 값은 OS가 지원되는 기본 최대 버전보다 높은 버전을 지원하도록 강제하는 데 사용할 수 없습니다(이 기본 최대값은 구현 지원으로 인해 선택됨). 대신 이 키는 지원되는 최대 버전을 기본값보다 낮은 값으로 제한할 수 있습니다.

이 구성은 특정 버전의 기능 구현에 있을 수 있는 버그를 해결하는 데 특히 유용합니다.

MaxVersion을 지정하는 경우 MinVersion도 지정해야 합니다.

레지스트리 키 이름: AllowExperimental

속성 타입 설명
AllowExperimental DWORD 0(실험적 지원은 허용되지 않음) 또는 1(지원됨). 기본값은 분기 정의입니다. 빌드에서 기본적으로 허용하지 않는 경우에도 OS에서 이 기능의 실험적 버전을 로드하도록 허용합니다.

OS는 일반적으로 실험적 지원을 정의합니다. 기본적으로 실험적 지원은 기능별로 정의되며, 개발 빌드에서 사용할 수 있는 전역 재정의가 있습니다(예: 내부 개발자 빌드는 항상 모든 기능에 대한 실험적 지원을 허용하지만 시험판 빌드는 특정 기능에 대해서만 지원을 허용할 수 있음).

이 값을 사용하면 특정 기능 ID에 대해 OS 정의를 재정의할 수 있습니다. 릴리스 빌드에서 이 기능을 지원하는 OS에서 실험적 드라이버 지원을 제공하는 데도 사용할 수 있지만 소매 환경에서는 기능을 사용하지 않도록 설정할 수 있습니다.

디버거 확장: dxgkdx

dxgkdx 커널 디버거 확장은 다양한 기능의 상태를 쿼리할 수 있는 명령을 구현 !feature 합니다.

현재 지원 명령(예제 출력 포함)은 다음과 같습니다.

!feature list

Lists features with descriptor information

2: kd> !dxgkdx.feature list

  Id  FeatureName                                       Supported  Version  VirtMode     Global  Driver
   0  HWSCH                                             Yes        1-1      Negotiate    -       X
   1  HWFLIPQUEUE                                       Yes        1-1      Negotiate    -       X
   2  LDA_GPUPV                                         Yes        1-1      Negotiate    -       X
   3  KMD_SIGNAL_CPU_EVENT                              Yes        1-1      Negotiate    -       X
   4  USER_MODE_SUBMISSION                              Yes        1-1      Negotiate    -       X
   5  SHARE_BACKING_STORE_WITH_KMD                      Yes        1-1      HostOnly     -       X
  32  PAGE_BASED_MEMORY_MANAGER                         No         1-1      Negotiate    -       X
  33  KERNEL_MODE_TESTING                               Yes        1-1      Negotiate    -       X
  34  64K_PT_DEMOTION_FIX                               Yes        1-1      DeferToHost  -       -
  35  GPUPV_PRESENT_HWQUEUE                             Yes        1-1      DeferToHost  -       -
  36  GPUVAIOMMU                                        Yes        1-1      None         X       -
  37  NATIVE_FENCE                                      Yes        1-1      Negotiate    -       X

!feature config

Lists the current configuration information for each feature. In most cases, this will be unspecified/default values if not overriden.

2: kd> !dxgkdx.feature config

  Id  FeatureName                                       Enabled Version  AllowExperimental
   0  HWSCH                                             --       --       -
   1  HWFLIPQUEUE                                       --       --       -
   2  LDA_GPUPV                                         --       --       -
   3  KMD_SIGNAL_CPU_EVENT                              --       --       -
   4  USER_MODE_SUBMISSION                              --       --       -
   5  SHARE_BACKING_STORE_WITH_KMD                      --       --       -
  32  PAGE_BASED_MEMORY_MANAGER                         --       --       -
  33  KERNEL_MODE_TESTING                               --       --       -
  34  64K_PT_DEMOTION_FIX                               --       --       -
  35  GPUPV_PRESENT_HWQUEUE                             --       --       -
  36  GPUVAIOMMU                                        --       --       -
  37  NATIVE_FENCE                                      --       --       -

!feature state

Lists the current state of each feature. Features that have bnot been queried will have an unknown state

2: kd> !dxgkdx.feature state

  Id  FeatureName                                       Enabled  Version  Driver  Config
   0  HWSCH                                             No       0        No      No    
   1  HWFLIPQUEUE                                       No       0        No      No    
   2  LDA_GPUPV                                         No       0        No      No    
   3  KMD_SIGNAL_CPU_EVENT                              Yes      1        Yes     Yes   
   4  USER_MODE_SUBMISSION                              No       0        No      No    
   5  SHARE_BACKING_STORE_WITH_KMD                      Unknown  --       --      --    
  32  PAGE_BASED_MEMORY_MANAGER                         No       0        No      No    
  33  KERNEL_MODE_TESTING                               No       0        No      No    
  34  64K_PT_DEMOTION_FIX                               Unknown  --       --      --    
  35  GPUPV_PRESENT_HWQUEUE                             Unknown  --       --      --    
  36  GPUVAIOMMU                                        Unknown  --       --      --    
  37  NATIVE_FENCE                                      No       0        No      No    

샘플 구현

지원의 용이성을 위해 Barebones 샘플 구현은 다음과 같습니다. 드라이버는 이 코드를 자체 구현의 시작점으로 사용하고 필요에 따라 추가 기능(예: 기능 재정의 방법 연결)으로 확장할 수 있습니다.


#include "precomp.h"

#pragma code_seg("PAGE")

#define VERSION_RANGE(Min, Max) Min, Max
#define DEFINE_FEATURE_INTERFACE(Name, Version, InterfaceStruct) InterfaceStruct Name##_Interface_##Version =
#define DEFINE_FEATURE_INTERFACE_TABLE(Name) const DRIVER_FEATURE_INTERFACE_TABLE_ENTRY Name##_InterfaceTable[] =
#define FEATURE_INTERFACE_ENTRY(Name, Version) { &Name##_Interface_##Version, sizeof(Name##_Interface_##Version) }
#define NO_FEATURE_INTERFACE { nullptr, 0 }
#define FEATURE_INTERFACE(Name, Version) { &Name##_Interface_##Version, sizeof(Name##_Interface_##Version) }
#define FEATURE_INTERFACE_TABLE(Name) { Name##_InterfaceTable, ARRAYSIZE(Name##_InterfaceTable) }
#define NO_FEATURE_INTERFACE_TABLE { nullptr, 0 }

struct DRIVER_FEATURE_INTERFACE_TABLE_ENTRY
{
    const void* Interface;
    SIZE_T InterfaceSize;
};

struct DRIVER_FEATURE_INTERFACE_TABLE
{
    const DRIVER_FEATURE_INTERFACE_TABLE_ENTRY* Entries;
    SIZE_T Count;
};

//
// Interfaces
//

DEFINE_FEATURE_INTERFACE(FEATURE_SAMPLE, 4, DXGKDDIINT_FEATURE_SAMPLE_4)
{
    DdiFeatureSample_AddValue,
};

DEFINE_FEATURE_INTERFACE(FEATURE_SAMPLE, 5, DXGKDDIINT_FEATURE_SAMPLE_5)
{
    DdiFeatureSample_AddValue,
    DdiFeatureSample_SubtractValue,
};

DEFINE_FEATURE_INTERFACE_TABLE(FEATURE_SAMPLE)
{
    NO_FEATURE_INTERFACE,                 // Version 3
    FEATURE_INTERFACE(FEATURE_SAMPLE, 4), // Version 4
    FEATURE_INTERFACE(FEATURE_SAMPLE, 5), // Version 5
};

static const DRIVER_FEATURE_INTERFACE_TABLE g_FeatureInterfaceTables[] =
{
    NO_FEATURE_INTERFACE_TABLE,              // DXGK_FEATURE_HWSCH
    NO_FEATURE_INTERFACE_TABLE,              // DXGK_FEATURE_HWFLIPQUEUE
    NO_FEATURE_INTERFACE_TABLE,              // DXGK_FEATURE_LDA_GPUPV
    NO_FEATURE_INTERFACE_TABLE,              // DXGK_FEATURE_KMD_SIGNAL_CPU_EVENT
    NO_FEATURE_INTERFACE_TABLE,              // DXGK_FEATURE_USER_MODE_SUBMISSION
    NO_FEATURE_INTERFACE_TABLE,              // DXGK_FEATURE_SHARE_BACKING_STORE_WITH_KMD
    NO_FEATURE_INTERFACE_TABLE,              // Reserved
    NO_FEATURE_INTERFACE_TABLE,              // Reserved
    NO_FEATURE_INTERFACE_TABLE,              // Reserved
    NO_FEATURE_INTERFACE_TABLE,              // Reserved
    NO_FEATURE_INTERFACE_TABLE,              // Reserved 
    NO_FEATURE_INTERFACE_TABLE,              // Reserved
    NO_FEATURE_INTERFACE_TABLE,              // Reserved
    NO_FEATURE_INTERFACE_TABLE,              // Reserved
    NO_FEATURE_INTERFACE_TABLE,              // Reserved
    NO_FEATURE_INTERFACE_TABLE,              // Reserved
    NO_FEATURE_INTERFACE_TABLE,              // Reserved
    NO_FEATURE_INTERFACE_TABLE,              // Reserved
    NO_FEATURE_INTERFACE_TABLE,              // Reserved
    NO_FEATURE_INTERFACE_TABLE,              // Reserved
    NO_FEATURE_INTERFACE_TABLE,              // Reserved
    NO_FEATURE_INTERFACE_TABLE,              // Reserved
    NO_FEATURE_INTERFACE_TABLE,              // Reserved
    NO_FEATURE_INTERFACE_TABLE,              // Reserved
    NO_FEATURE_INTERFACE_TABLE,              // Reserved
    NO_FEATURE_INTERFACE_TABLE,              // Reserved
    NO_FEATURE_INTERFACE_TABLE,              // Reserved
    NO_FEATURE_INTERFACE_TABLE,              // Reserved
    NO_FEATURE_INTERFACE_TABLE,              // Reserved
    NO_FEATURE_INTERFACE_TABLE,              // Reserved
    NO_FEATURE_INTERFACE_TABLE,              // Reserved
    FEATURE_INTERFACE_TABLE(FEATURE_SAMPLE), // DXGK_FEATURE_SAMPLE
    NO_FEATURE_INTERFACE_TABLE,              // DXGK_FEATURE_PAGE_BASED_MEMORY_MANAGER
    NO_FEATURE_INTERFACE_TABLE,              // DXGK_FEATURE_KERNEL_MODE_TESTING
    NO_FEATURE_INTERFACE_TABLE,              // DXGK_FEATURE_64K_PT_DEMOTION_FIX
    NO_FEATURE_INTERFACE_TABLE,              // DXGK_FEATURE_GPUPV_PRESENT_HWQUEUE
    NO_FEATURE_INTERFACE_TABLE,              // DXGK_FEATURE_NATIVE_FENCE
};
static_assert(ARRAYSIZE(g_FeatureInterfaceTables) == DXGK_FEATURE_MAX, "New feature must define an interface table");

#define VERSION_RANGE(Min, Max) Min, Max

//
// TODO: This table may be defined independently for each supported hardware or architecture,
// or may be completely overriden dynamically at runtime during DRIVER_ADAPTER::InitializeFeatureConfiguration
//
static const DRIVER_FEATURE_DESC g_FeatureDefaults[] =
{
//                                      SupportedOnConfig
//    VersionRange           Supported  |       Experimental  
//    |                      |          |       |
    { VERSION_RANGE(0, 0), { FALSE,     FALSE,  FALSE,   }, }, // DXGK_FEATURE_HWSCH
    { VERSION_RANGE(0, 0), { FALSE,     FALSE,  FALSE,   }, }, // DXGK_FEATURE_HWFLIPQUEUE
    { VERSION_RANGE(0, 0), { FALSE,     FALSE,  FALSE,   }, }, // DXGK_FEATURE_LDA_GPUPV
    { VERSION_RANGE(0, 0), { FALSE,     FALSE,  FALSE,   }, }, // DXGK_FEATURE_KMD_SIGNAL_CPU_EVENT
    { VERSION_RANGE(0, 0), { FALSE,     FALSE,  FALSE,   }, }, // DXGK_FEATURE_USER_MODE_SUBMISSION
    { VERSION_RANGE(0, 0), { FALSE,     FALSE,  FALSE,   }, }, // DXGK_FEATURE_SHARE_BACKING_STORE_WITH_KMD
    { VERSION_RANGE(0, 0), { FALSE,     FALSE,  FALSE,   }, }, // Reserved
    { VERSION_RANGE(0, 0), { FALSE,     FALSE,  FALSE,   }, }, // Reserved
    { VERSION_RANGE(0, 0), { FALSE,     FALSE,  FALSE,   }, }, // Reserved
    { VERSION_RANGE(0, 0), { FALSE,     FALSE,  FALSE,   }, }, // Reserved
    { VERSION_RANGE(0, 0), { FALSE,     FALSE,  FALSE,   }, }, // Reserved 
    { VERSION_RANGE(0, 0), { FALSE,     FALSE,  FALSE,   }, }, // Reserved
    { VERSION_RANGE(0, 0), { FALSE,     FALSE,  FALSE,   }, }, // Reserved
    { VERSION_RANGE(0, 0), { FALSE,     FALSE,  FALSE,   }, }, // Reserved
    { VERSION_RANGE(0, 0), { FALSE,     FALSE,  FALSE,   }, }, // Reserved
    { VERSION_RANGE(0, 0), { FALSE,     FALSE,  FALSE,   }, }, // Reserved
    { VERSION_RANGE(0, 0), { FALSE,     FALSE,  FALSE,   }, }, // Reserved
    { VERSION_RANGE(0, 0), { FALSE,     FALSE,  FALSE,   }, }, // Reserved
    { VERSION_RANGE(0, 0), { FALSE,     FALSE,  FALSE,   }, }, // Reserved
    { VERSION_RANGE(0, 0), { FALSE,     FALSE,  FALSE,   }, }, // Reserved
    { VERSION_RANGE(0, 0), { FALSE,     FALSE,  FALSE,   }, }, // Reserved
    { VERSION_RANGE(0, 0), { FALSE,     FALSE,  FALSE,   }, }, // Reserved
    { VERSION_RANGE(0, 0), { FALSE,     FALSE,  FALSE,   }, }, // Reserved
    { VERSION_RANGE(0, 0), { FALSE,     FALSE,  FALSE,   }, }, // Reserved
    { VERSION_RANGE(0, 0), { FALSE,     FALSE,  FALSE,   }, }, // Reserved
    { VERSION_RANGE(0, 0), { FALSE,     FALSE,  FALSE,   }, }, // Reserved
    { VERSION_RANGE(0, 0), { FALSE,     FALSE,  FALSE,   }, }, // Reserved
    { VERSION_RANGE(0, 0), { FALSE,     FALSE,  FALSE,   }, }, // Reserved
    { VERSION_RANGE(0, 0), { FALSE,     FALSE,  FALSE,   }, }, // Reserved
    { VERSION_RANGE(0, 0), { FALSE,     FALSE,  FALSE,   }, }, // Reserved
    { VERSION_RANGE(0, 0), { FALSE,     FALSE,  FALSE,   }, }, // Reserved
    { VERSION_RANGE(3, 5), { TRUE,      TRUE,   FALSE,   }, }, // DXGK_FEATURE_TEST_FEATURE_SAMPLE
    { VERSION_RANGE(0, 0), { FALSE,     FALSE,  FALSE,   }, }, // DXGK_FEATURE_PAGE_BASED_MEMORY_MANAGER
    { VERSION_RANGE(0, 0), { FALSE,     FALSE,  FALSE,   }, }, // DXGK_FEATURE_KERNEL_MODE_TESTING
    { VERSION_RANGE(0, 0), { FALSE,     FALSE,  FALSE,   }, }, // DXGK_FEATURE_64K_PT_DEMOTION_FIX
    { VERSION_RANGE(0, 0), { FALSE,     FALSE,  FALSE,   }, }, // DXGK_FEATURE_GPUPV_PRESENT_HWQUEUE
    { VERSION_RANGE(0, 0), { FALSE,     FALSE,  FALSE,   }, }, // DXGK_FEATURE_NATIVE_FENCE
};
static_assert(ARRAYSIZE(g_FeatureDefaults) == DXGK_FEATURE_MAX, "New feature requires a descriptor");

const DRIVER_FEATURE_DESC*
DRIVER_ADAPTER::GetFeatureDesc(
    DXGK_FEATURE_ID FeatureId
    ) const
{
    PAGED_CODE();

    if(FeatureId >= DXGK_FEATURE_MAX)
    {
        return nullptr;
    }

    return &m_FeatureDescs[FeatureId];
}

void
DRIVER_ADAPTER::InitializeFeatureConfiguration(
    )
{
    //
    // Choose correct default table to use here, or override manually below
    //
    static_assert(sizeof(DRIVER_ADAPTER::m_FeatureDescs) == sizeof(g_FeatureDefaults));
    memcpy(m_FeatureDescs, g_FeatureDefaults, sizeof(g_FeatureDefaults));
    
    //
    // Example overrides
    //

    //
    // While this is a sample feature and not tied to any architectural support, this is
    // an example of how a feature can be marked as supported by the driver in the table
    // above, and then dynamically enabled on this configuration here.
    //
    // The same can be done for hardware features, such as hardware scheduling
    //
    if(IsSampleFeatureSupportedOnThisGPU())
    {
        m_FeatureDescs[DXGK_FEATURE_TEST_FEATURE_SAMPLE].SupportedOnConfig = TRUE;
    }
}

NTSTATUS
DdiQueryFeatureSupport(
    IN_CONST_HANDLE                    hAdapter,
    INOUT_PDXGKARG_QUERYFEATURESUPPORT pArgs
    )
{
    PAGED_CODE();

    //
    // Start by assuming the feature is unsupported
    //
    pArgs->SupportedByDriver = FALSE;
    pArgs->SupportedOnCurrentConfig = FALSE;
    pArgs->MinSupportedVersion = 0;
    pArgs->MaxSupportedVersion = 0;

    DRIVER_ADAPTER* pAdapter = GetAdapterFromHandle(hAdapter);

    const DRIVER_FEATURE_DESC* pFeatureDesc = pAdapter->GetFeatureDesc(pArgs->FeatureId);

    if(pFeatureDesc == nullptr)
    {
        //
        // Unknown feature
        //
        return STATUS_INVALID_PARAMETER;
    }

    if(pFeatureDesc->Supported)
    {
        if(pFeatureDesc->Experimental == FALSE ||
           pArgs->AllowExperimental)
        {
            pArgs->SupportedByDriver = TRUE;
            pArgs->SupportedOnCurrentConfig = pFeatureDesc->SupportedOnConfig;

            pArgs->MinSupportedVersion = pFeatureDesc->MinSupportedVersion;
            pArgs->MaxSupportedVersion = pFeatureDesc->MaxSupportedVersion;
        }
    }

    return STATUS_SUCCESS;
}

NTSTATUS
DdiQueryFeatureInterface(
    IN_CONST_HANDLE                      hAdapter,
    INOUT_PDXGKARG_QUERYFEATUREINTERFACE pArgs
    )
{
    PAGED_CODE();

    DRIVER_ADAPTER* pAdapter = GetAdapterFromHandle(hAdapter);

    UINT16 InterfaceSize = pArgs->InterfaceSize;
    pArgs->InterfaceSize = 0;

    const DRIVER_FEATURE_DESC* pFeatureDesc = pAdapter->GetFeatureDesc(pArgs->FeatureId);

    if(pFeatureDesc == nullptr)
    {
        //
        // Unknown feature
        //
        return STATUS_INVALID_PARAMETER;
    }

    if(pFeatureDesc->Supported == FALSE)
    {
        //
        // Cannot query a feature interface for an unsupported feature.
        //
        return STATUS_UNSUCCESSFUL;
    }

    if(pArgs->Version < pFeatureDesc->MinSupportedVersion ||
       pArgs->Version > pFeatureDesc->MaxSupportedVersion)
    {
        //
        // Invalid feature version.
        //
        return STATUS_UNSUCCESSFUL;
    }

    const DRIVER_FEATURE_INTERFACE_TABLE* pInterfaceTable = &g_FeatureInterfaceTables[pArgs->FeatureId];
    if(pInterfaceTable->Entries == nullptr)
    {
        //
        // This feature does not have any interfaces. It's unclear why the driver is asking for it,
        // but the size should be zero and we will not return any data for it.
        //
        return STATUS_SUCCESS;
    }

    if((SIZE_T)(pArgs->Version - pFeatureDesc->MinSupportedVersion) >= pInterfaceTable->Count)
    {
        //
        // The interface table should have an entry for every supported version. This is
        // a bug in the OS, and the feature interface table must be updated for this feature!
        //
        NT_ASSERT(FALSE);

        //
        // Invalid feature version.
        //
        return STATUS_UNSUCCESSFUL;
    }

    UINT32 InterfaceTableIndex = pArgs->Version - pFeatureDesc->MinSupportedVersion;
    const DRIVER_FEATURE_INTERFACE_TABLE_ENTRY* pInterfaceEntry = &pInterfaceTable->Entries[InterfaceTableIndex];
    if(pInterfaceEntry->Interface == nullptr)
    {
        //
        // This feature does not have any interfaces. It's unclear why the OS is asking for one.
        //
        return STATUS_INVALID_PARAMETER;
    }

    if(InterfaceSize < pInterfaceEntry->InterfaceSize)
    {
        //
        // The driver-provided buffer is too small to store the interface for this feature and version
        //
        return STATUS_BUFFER_TOO_SMALL;
    }

    //
    // We have an interface!
    //
    RtlCopyMemory(pArgs->Interface, pInterfaceEntry->Interface, pInterfaceEntry->InterfaceSize);
    if(InterfaceSize != pInterfaceEntry->InterfaceSize)
    {
        //
        // Zero out remainder of interface in case the provided buffer was larger than
        // the actual interface. This may be done in cases where multiple interface versions
        // are supported simultaneously (e.g. in a unioned structure). Only the requested
        // interface should be valid.
        //
        RtlZeroMemory((BYTE*)pArgs->Interface + pInterfaceEntry->InterfaceSize, InterfaceSize - pInterfaceEntry->InterfaceSize);
    }

    //
    // Write back the interface size
    //
    pArgs->InterfaceSize = (UINT16)pInterfaceEntry->InterfaceSize;

    return STATUS_SUCCESS;
}

static void DdiReferenceFeatureInterfaceNop(PVOID pMiniportDeviceContext)
{
    PAGED_CODE();
}

//
// DRIVER_INITIALIZATION_DATA::DxgkDdiQueryInterface
//
NTSTATUS
DdiQueryInterface(
    IN_CONST_PVOID          pMiniportDeviceContext,
    IN_PQUERY_INTERFACE     pQueryInterface
    )
{
    DDI_FUNCTION();

    PAGED_CODE();

    if(pQueryInterface->Version == DXGK_FEATURE_INTERFACE_VERSION_1)
    {
        PDXGKDDI_FEATURE_INTERFACE Interface = (PDXGKDDI_FEATURE_INTERFACE)pQueryInterface->Interface;

        Interface->Version = DXGK_FEATURE_INTERFACE_VERSION_1;
        Interface->Context = pMiniportDeviceContext;
        Interface->Size = sizeof(DXGKDDI_FEATURE_INTERFACE);

        //
        // Returned interface shouldn't be larger than size provided for Interface
        //
        if (Interface->Size > pQueryInterface->Size)
        {
            return STATUS_BUFFER_TOO_SMALL;
        }

        Interface->InterfaceReference = DdiReferenceFeatureInterfaceNop;
        Interface->InterfaceDereference = DdiReferenceFeatureInterfaceNop;

        Interface->QueryFeatureSupport = DdiQueryFeatureSupport;
        Interface->QueryFeatureInterface = DdiQueryFeatureInterface;

        return STATUS_SUCCESS;
    }
    else
    {
        return STATUS_INVALID_PARAMETER;
    }
}

//
// These two functions act as hooks for when the OS doesn't support the feature functionality.
// If DxgkInterface.DxgkCbQueryServices(DxgkServicesFeature) returns a failure, it may mean
// we're running on an older OS, and we can fake the interface implementation using these
// functions instead.
//
// See DdiStartDevice sample code for how this is used
//
NTSTATUS
LegacyIsFeatureEnabled(
    IN_CONST_PVOID                     hDevice,
    INOUT_PDXGKARGCB_ISFEATUREENABLED2 pArgs
    )
{
    PAGED_CODE();

    DRIVER_ADAPTER* pAdapter = GetAdapterFromHandle(hAdapter);

    pArgs->Result = {};

    if (pAdapter->m_WddmVersion >= DXGKDDI_WDDMv2_9 &&
        pAdapter->m_DxgkInterface.Version >= DXGKDDI_INTERFACE_VERSION_WDDM2_9)
    {
        //
        // QueryFeatureSupport should be available.
        //
        DXGKARGCB_QUERYFEATURESUPPORT Args = {};
        Args.DeviceHandle = pAdapter->m_DxgkInterface.DeviceHandle;
        Args.FeatureId = pArgs->FeatureId;

        //
        // Example experimental status
        //

        /*
        switch(pArgs->FeatureId)
        {
            case DXGK_FEATURE_HWFLIPQUEUE:
            {
                Args.DriverSupportState = DXGK_FEATURE_SUPPORT_EXPERIMENTAL;
                break;
            }

            default:
            {
                Args.DriverSupportState = DXGK_FEATURE_SUPPORT_STABLE;
                break;
            }
        }
        */

        NTSTATUS Status = pAdapter->m_DxgkInterface.DxgkCbQueryFeatureSupport(&Args);
        if(NT_SUCCESS(Status))
        {
            if(Args.Enabled)
            {
                pArgs->Result.Enabled = Args.Enabled;
                pArgs->Result.Version = 1;
                pArgs->Result.SupportedByDriver = TRUE;
                pArgs->Result.SupportedOnCurrentConfig = TRUE;
            }
        }

        return Status;
    }
    else
    {
        return STATUS_NOT_SUPPORTED;
    }
}

//
// Sample code for DdiStartDevice
//
NTSTATUS
DdiStartDevice(
    IN_CONST_PVOID          pMiniportDeviceContext,
    IN_PDXGK_START_INFO     pDxgkStartInfo,
    IN_PDXGKRNL_INTERFACE   pDxgkInterface,
    OUT_PULONG              pNumberOfVideoPresentSources,
    OUT_PULONG              pNumberOfChildren
    )
{
    DRIVER_ADAPTER* pAdapter = GetAdapterFromHandle(hAdapter);

    ...

    //
    // Check fi the OS supports the feature interface.
    //
    pAdapter->m_FeatureInterface.Size = sizeof(pAdapter->m_FeatureInterface);
    pAdapter->m_FeatureInterface.Version = DXGK_FEATURE_INTERFACE_VERSION_1;

    Status = pAdapter->m_DxgkInterface.DxgkCbQueryServices(pAdapter->m_DxgkInterface.DeviceHandle, DxgkServicesFeature, (PINTERFACE)&pAdapter->m_FeatureInterface);

    if(!NT_SUCCESS(Status))
    {
        //
        // OS interface unavailable. This forwards calls to the Legacy functions defined above
        // when not available, which hard codes support for the handful of existing features
        // at the time (optionally going through DxgkCbQueryFeatureSupport).
        //
        // Doing this is optional, but may keep the driver code cleaner.
        //

        pAdapter->m_FeatureInterface.Context = pAdapter;
        pAdapter->m_FeatureInterface.InterfaceReference = nullptr;
        pAdapter->m_FeatureInterface.InterfaceDereference = nullptr;

        //
        // Use legacy function above.
        //
        pAdapter->m_FeatureInterface.IsFeatureEnabled = LegacyIsFeatureEnabled;

        //
        // QueryFeatureInterface is only used by the OS to query an interface for a feature,
        // but the OS doesn't support this. Any attempt to call this function implies
        // the driver is calling it themselves, which makes no sense.
        //
        pAdapter->m_FeatureInterface.QueryFeatureInterface = nullptr;

        Status = STATUS_SUCCESS;
    }
    
    Status = pAdapter->InitializeFeatureConfiguration();

    if(!NT_SUCCESS(Status))
    {
        goto cleanup;
    }

    ...
}

DRIVER_FEATURE_RESULT
DRIVER_ADAPTER::IsFeatureEnabled(
    DXGK_FEATURE_ID FeatureId
    )
{
    PAGED_CODE();

    DRIVER_FEATURE_RESULT Result = {};

    DXGKARGCB_ISFEATUREENABLED2 Args = {};
    Args.FeatureId = FeatureId;

    //
    // Will either call the OS, or the LegacyIsFeatureEnabled function above
    // depending on whether this is supported on the OS. 
    //
    if(NT_SUCCESS(FeatureInterface.IsFeatureEnabled(DxgkInterface.DeviceHandle, &Args)))
    {
        Result.Enabled = Args.Result.Enabled;
        Result.Version = Args.Result.Version;
    }

    return Result;
}

다음 코드는 FEATURE_SAMPLE 기능에 대한 인터페이스를 구현합니다.

//
// This file implements the interfaces for the FEATURE_SAMPLE feature
//

#include "precomp.h"

//
// The OS supports 3 versions of the feature: 3, 4, and 5.
//
// - v3 has no interface
// - v4 has an interface that defines an "Add" function
// - v5 has an interface that defines both "Add" and "Subtract" functions
//
NTSTATUS
APIENTRY CALLBACK
DdiFeatureSample_AddValue(
    IN_CONST_HANDLE hAdapter,
    INOUT_PDXGKARG_FEATURE_SAMPLE_ADDVALUE pArgs
    )
{
    PAGED_CODE();

    DRIVER_ADAPTER* pAdapter = GetAdapterFromHandle(hAdapter);

    if(pAdapter->m_FeatureState.SampleFeatureVersion < 4)
    {
        //
        // Unexpected. This function should only be called for v4 and above of this feature
        //
        return STATUS_INVALID_PARAMETER;
    }

    DXGKARGCB_FEATURE_SAMPLE_GETVALUE GetValueArgs = {};

    NTSTATUS Status = pAdapter->m_FeatureState.SampleFeatureInterface.GetValue(pAdapter->m_DxgkInterface.DeviceHandle, &GetValueArgs);

    if(!NT_SUCCESS(Status))
    {
        return Status;
    }

    pArgs->OutputValue = pArgs->InputValue + GetValueArgs.Value;

    return STATUS_SUCCESS;
}

NTSTATUS
APIENTRY CALLBACK
DdiFeatureSample_SubtractValue(
    IN_CONST_HANDLE hAdapter,
    INOUT_PDXGKARG_FEATURE_SAMPLE_SUBTRACTVALUE pArgs
    )
{
    PAGED_CODE();

    DRIVER_ADAPTER* pAdapter = GetAdapterFromHandle(hAdapter);

    if(pAdapter->m_FeatureState.SampleFeatureVersion < 5)
    {
        //
        // Unexpected. This function should only be called for v5 and above of this feature
        //
        return STATUS_INVALID_PARAMETER;
    }

    DXGKARGCB_FEATURE_SAMPLE_GETVALUE GetValueArgs = {};

    NTSTATUS Status = pAdapter->m_FeatureState.SampleFeatureInterface.GetValue(pAdapter->m_DxgkInterface.DeviceHandle, &GetValueArgs);
    
    if(!NT_SUCCESS(Status))
    {
        return Status;
    }

    pArgs->OutputValue = pArgs->InputValue - GetValueArgs.Value;

    return STATUS_SUCCESS;
}