티스토리 뷰

저번 포스팅에서 간략하게 DXGI가 무엇인지, 무슨 역할을 수행하는지에 대해서 간략히 배웠습니다. 이와 관련하여 추가로 찾아보신 분들은 DXGI 개념을 이해하는 것이 어렵지 않을 것입니다. 하지만, 개념을 안다고 DXGI를 안다고 할 수는 없습니다. DXGI 개요를 바탕으로 DXGI API를 자유자재로 사용할 수 있어야 하기 때문입니다.

 

이번 포스팅에서는 DXGI와 관련된 각종 구성요소(함수, 구조체/인터페이스 등...)들을 살펴보면서 DXGI을 살펴보도록 하겠습니다.

 

 

 

IDXGIFactory


IDXGIFactory는 IDXGIObject를 상속받은 인터페이스(구조체로 선언됨)로 DXGI 객체들을 생성하는 메소드를 구현하며 다음과 같은 기능을 수행할 수 있습니다 :

  • 소프트웨어 어댑터 생성(IDXGIFactory::CreateSoftwareAdapter)
  • 스왑 체인 생성(IDXGIFactory::CreateSwapChain)
  • 어댑터(비디오카드) 열거(IDXGIFactory::EnumAdapters)
  • 전체화면으로 전환을 제어하는 윈도우 반환(IDXGIFactory::GetWindowAssociation)
  • Window Association 생성(IDXGIFactory::MakeWindowAssociation)

 

이러한 기능들을 수행하기 위해서 IDXGIFactory를 생성해야 하며, CreateDXGIFactory 함수를 통해서 수행할 수 있습니다. 해당 함수의 시그니처는 다음과 같습니다 :

// function signature
HRESULT CreateDXGIFactory(REFIID riid, void **ppFactory)

 

이 때 첫 번째 인자인 REFIID는 IDXGIFacotry의 GUID(globally unique identifier)를, 두 번째 인자는 IDXGIFactory* 의 주소를 입력합니다. 아래는 그 사용 예입니다 :

/* how to use */
IDXGIFactory* DXGIFactory;
CreateDXGIFactory(__uuidof(IDXGIFactory), &DXGIFactory);

 

참고
DXGIFactory에서 제공하는 메소드들에 대한 설명은 다른 구성 요소와도 연관되어 있습니다. 따라서, 추후 섹션에서 소개할 수 있는 부분들을 소개하겠습니다.

 

비고
스왑 체인을 생성하지 않고도 Direct3D 장치를 생성할 수 있다는 점에서 스왑 체인을 생성할 때 장치를 생성하는데 사용되는 공장을 찾아야할 수도 있습니다. 이 때, Direct3D 디바이스로부터 IDXGIDevice 인터페이스를 요청하고, IDXGIObject::GetParent 메소드를 호출함으로써 찾을 수 있습니다. 아래에 예시 코드를 첨부합니다 :

IDXGIDevice * pDXGIDevice = nullptr;
hr = g_pd3dDevice->QueryInterface(__uuidof(IDXGIDevice), (void **)&pDXGIDevice);

IDXGIAdapter * pDXGIAdapter = nullptr;
hr = pDXGIDevice->GetAdapter( &pDXGIAdapter );

IDXGIFactory * pIDXGIFactory = nullptr;
pDXGIAdapter->GetParent(__uuidof(IDXGIFactory), (void **)&pIDXGIFactory);

 

 

메모리 해제

CreateDXGIFactory 함수가 성공적으로 수행하면 IDXGIFactory 인터페이스에 대한 참조 카운트가 증가하게 됩니다. 따라서 메모리 누수를 방지하려면 IDXGIFactory 인터페이스의 사용을 마친 후 반드시 IDXGIFactory::Release 메소드를 호출해야 합니다.

IDXGIFactory* DXGIFactory;

/* initialize and do something using DXGIFactory ... */

/* must be released */
DXGIFactory->Release();

 

 

버전

DXGI가 버전업을 통해 개선 및 변경점이 추가되면서 IDXGIFactory 인터페이스 역시 추가 기능들을 제공합니다. 이러한 기능들은 IDXGIFactory 인터페이스에 반영되는 것이 아니라 이 인터페이스를 상속받는 형태로 IDXGIFactory1, IDXGIFactory2, ..., IDXGIFactory5 와 같이 추가되어 왔습니다.

 

이는 IDXGIFactory 뿐만이 아니라 IDXGIAdapter 인터페이스 등 수많은 곳에 적용되고 있습니다. 버전 별로 최소 지원 버전 및 기능이 달라질 수 있으므로 자세한 IDXGIFactory의 버전에 따른 기능은 Microsoft DXGI 문서를 살펴보시기 바랍니다.

참고
여러 버전의 IDXGIFactory 인터페이스를 함께 사용하면 안됩니다. 즉, DXGI 1.0 버전의 IDXGIFactory와 DXGI 1.1 버전의 IDXGIFactory1 인터페이스를 어플리케이션 내에서 섞여서 사용하면 안됩니다.

 

 

 

IDXGIAdapter


IDXGIAdapter 인터페이스는 앞선 포스팅 'DXGI 개요'에서도 언급했듯이 컴퓨터의 하드웨어 및 소프트웨어 기능을 추상화한 것입니다. 다른 말로, 하나 이상의 GPU, DAC, 비디오 메모리를 포함하는 디스플레이 서브시스템을 표현합니다. IDXGIAdapter 인터페이스에서 제공하는 기능들은 다음과 같습니다 :

  • 시스템이 그래픽 구성 요소에 대한 장치 인터페이스를 지원하는 지 확인(IDXGIAdapter::CheckInterfaceSupport)
  • 어댑터(비디오 카드) 출력을 열거(IDXGIAdapter::EnumOutputs)
  • 어댑토(또는 비디오 카드)의 DXGI 1.0 설명(description)을 가져옴(IDXGIAdapter::GetDesc)

 

 

DXGI_ADAPTER_DESC 구조체

DXGI_ADAPTER_DESC 구조체는 DXGI 1.0버전에서 어댑터를 기술하는 구조체이며, 다음과 같이 구성되어 있습니다 :

typedef struct DXGI_ADAPTER_DESC {
  WCHAR  Description[128];        /* 어댑터 설명,  */
  UINT   VendorId;                /* 하드웨어 공급업체의 PCI ID */
  UINT   DeviceId;                /* 하드웨어 장치의 PCI ID */
  UINT   SubSysId;                /* 서브시스템의 PCI ID */
  UINT   Revision;                /* 어댑터 개정(revision) 번호의 PCI ID */
  SIZE_T DedicatedVideoMemory;    /* CPU와 공유되지 않은 전용 비디오 메모리의 바이트 수 */
  SIZE_T DedicatedSystemMemory;   /* CPU와 공유되지 않는 전용 시스템 메로리 바이트 수 */
  SIZE_T SharedSystemMemory;      /* 공유 시스템 메모리의 바이트 수 */
  LUID   AdapterLuid;             /* 어댑터를 식별하는 고유값(Locally Unique Identifier) */
} DXGI_ADAPTER_DESC;

 

이 구조체의 정보는 IDXGIAdapter::GetDesc 메소드를 통해서 얻을 수 있습니다. 이를 통해 어댑터의 정보를 읽어들일 수 있습니다.

 

 

어댑터 열거

IDXGIFactory의 EnumAdapter 메소드 및 DXGI_ADAPTER_DESC 구조체를 통해서 현재 컴퓨터에서 사용 중인 어댑터의 종류를 열거할 수 있습니다. 이것을 구현한 코드는 다음과 같습니다 :

#include <dxgi.h>
...

    IDXGIAdapter* Adapter = nullptr;
    for (size_t i = 0; ; ++i)
    {
        /* 더이상 발견되는 어댑터가 없으면 루프를 탈출합니다 */
        if (DXGISystem().GetDXGIFactory()->EnumAdapters(i, &Adapter) == DXGI_ERROR_NOT_FOUND)
            return;

        DXGI_ADAPTER_DESC AdapterDesc;
        Adapter->GetDesc(&AdapterDesc);

        /* 출력 예상 형식 : "* Adatper[0] : Microsoft Basic Render Driver" */
        wstring Text;
        Text += L"* Adapter[";
        Text += to_wstring(i);
        Text += L"] : ";
        Text += AdapterDesc.Description;

        wcout << Text << "\n";

        /* 사용 이후 메모리 누수 방지를 위해 꼭 해제해주어야 합니다 ! */
        Adapter->Release();
    }

 

 

위 코드를 실행하였을 때 여러분들이 가지고 있는 디스플레이 어댑터들이 출력으로 나오게 됩니다. 제 경우, 내장 그래픽 + 외장 그래픽 + 마이크로 소프트 렌더 전용 드라이버 총 3가지가 출력되었습니다.

참고
디스플레이 서브시스템은 대개 비디오 카드로 여겨지지만, 일부 시스템에서는 디스플레이 서브시스템이 마더보드의 일부일 수 있습니다.

디스플레이 서브시스템을 열거하려면, IDXGIFactory::EnumAdapters 메소드를 사용하세요.

특정 장치에 대한 어댑터 인터페이스를 얻고자 한다면, IDXGIDevice::GetAdapter 메소드를 사용하세요.

소프트웨어 어댑터를 생성하려면, IDXGIFactory::CreateSoftwareAdapter 메소드를 사용하세요.

 

 

 

IDXGIOutput


IDXGIOutput 인터페이스는 어댑터 출력(모니터와 같이) 을 나타냅니다. 인터페이스에서 제공하는 기능은 다음과 같습니다 :

  • 요청한 디스플레이 모드와 가장 근접하게 일치하는 디스플레이 모드 탐색(IDXGIOutput::FindClosestMatchingMode)
  • 요청 형식 및 기타 입력 옵션과 일치하는 디스플레이 모드 리스트 반환(IDXGIOutput::GetDisplayModeList)
  • 현재 디스플레이 화면의 복사본 반환(IDXGIOutput::GetDisplaySurfaceData)
  • 디스플레이 모드 변경(IDXGIOutput::SetDisplaySurface)
  • 최근 렌더링된 프레임 통계 반환(IDXGIOutput::GetFrameStatistics)
  • 감마 제어 설정 반환(IDXGIOutput::GetGammaControl)
  • 감마 컨트롤 설정(IDXGIOutput::SetGammaControl)
  • 감마 제어 능력 에 대한 설명(IDXGIOutput::GetGammaControlCapabilities)
  • 출력 소유권 해제(IDXGIOutput::ReleaseOwnership)
  • 출력 소유권 획득(IDXGIOutput::TakeOwnership)
  • 다음 vertical blank 발생 전까지 스레드 중지(IDXGIOutput::WaitForVBlank)
  • 출력에 대한 설명(IDXGIOutput::GetDesc)

 

IDXGIOutput은 IDXGIAdapter::EnumOutputs 메소드를 통해서 구할 수 있습니다.

 

 

DXGI_OUTPUT_DESC 구조체

DXGI_OUTPUT_DESC 구조체는 어댑터(비디오 카드)와 장치 사이의 출력 또는 물리적 연결을 설명하며, 다음과 같이 구성되어 있습니다 :

typedef struct DXGI_OUTPUT_DESC {
  WCHAR              DeviceName[32];         /* 출력 장치 이름 */
  RECT               DesktopCoordinates;     /* 데스크탑 좌표 내 출력 범위. 데스크탑 좌표는 DPI에 의존함 */
  BOOL               AttachedToDesktop;      /* 데스크탑과의 연결 여부 */
  DXGI_MODE_ROTATION Rotation;               /* 출력에 의해 이미지가 회전하는 방식에 대한 열거형 */
  HMONITOR           Monitor;                /* 디스플레이 모니터에 대한 핸들 */
} DXGI_OUTPUT_DESC;

 

참고
DXGI_MODE_ROTATION 열거형의 요소는 다음과 같습니다 :

  • DXGI_MODE_ROTATION_UNSPECIFIED : 지정되지 않은 회전.
  • DXGI_MODE_ROTATION_IDENTITY : 회전을 지정되지 않음.
  • DXGI_MODE_ROTATION_ROTATE90 : 90도 회전을 지정함.
  • DXGI_MODE_ROTATION_ROTATE180 : 180도 회전을 지정함.
  • DXGI_MODE_ROTATION_ROTATE270 : 270도 회전을 지정함.

 

이 구조체의 정보는 IDXGIOutput::GetDesc 메소드를 통해서 얻을 수 있습니다. 이를 통해 출력의 정보를 읽어들일 수 있습니다.

 

 

출력 열거

마찬가지로 EnumOutputsDXGI_OUTPUT_DESC 구조체를 활용하여 어댑터의 출력 정보를 읽어들일 수 있습니다. 이를 구현한 코드는 다음과 같습니다 :

#include <dxgi.h>
void EnumerateAdapterOutputs(IDXGIAdapter* Adapter)
{
    for (size_t i = 0; ; ++i)
    {
        IDXGIOutput* Output = nullptr;
        /* 장치 출력을 발견할 수 없으면 루프를 탈출합니다 */
        if (Adapter->EnumOutputs(i, &Output) == DXGI_ERROR_NOT_FOUND)
            break;

        /* 출력 형식 구성 */
        DXGI_OUTPUT_DESC OutputDesc;
        Output->GetDesc(&OutputDesc);

        wstring Text;
        Text += L"* Output[";
        Text += to_wstring(i);
        Text += L"] : ";
        Text += OutputDesc.DeviceName;

        wcout << Text << "\n";
        /* 사용 후 메모리 해제 */
        Output->Release();
    }
}

 

 

DXGI_MODE_DESC 구조체

DXGI_MODE_DESC는 디스플레이 모드는 설명하는 구조체이며, 다음과 같이 구성됩니다 :

typedef struct DXGI_MODE_DESC {
  UINT                     Width;               /* 가로 해상도 */
  UINT                     Height;              /* 세로 해상도 */
  DXGI_RATIONAL            RefreshRate;         /* 갱신 빈도(hz단위) */
  DXGI_FORMAT              Format;              /* 디스플레이 형식 열거(DXGI_FORMAT_R32G32B32A32_UINT) */
  DXGI_MODE_SCANLINE_ORDER ScanlineOrdering;    /* 스캔 방식(순차 주사(프로그레시브), 비월 주사(인터레이스) */
  DXGI_MODE_SCALING        Scaling;             /* 영상 스케일링 모드 */
} DXGI_MODE_DESC;

 

참고
DXGI_MODE_SCANLINE_ORDER 열거형의 요소는 다음과 같습니다 :

  • DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED : 스캔라인 순서가 지정되지 않음.
  • DXGI_MODE_SCANLINE_ORDER_PROGRESSIVE : 이미지가 건너뛰지 않고 첫번째 스캔라인부터 마지막까지 순차적으로 생성.
  • DXGI_MODE_SCANLINE_ORDER_UPPER_FIELD_FIRST : 이미지가 상단 필드부터 시작하여 생성.
  • DXGI_MODE_SCANLINE_ORDER_LOWER_FIELD_FIRST : 이미지가 하단 필드부터 시작하여 생성

DXGI_MODE_SCALING 열거형의 요소는 다음과 같습니다 :

  • DXGI_MODE_SCALING_UNSPECIFIED : 스케일링이 지정되지 않음.
  • DXGI_MODE_SCALING_CENTERED : 스케일링을 지정하지 않으며 이미지가 디스플레이 중앙에 위치함.
  • DXGI_MODE_SCALING_STRECTED : 확장 스케일링을 지정.

 

 

디스플레이 모드 구하기

IDXGIOutput::GetDisplayModeList 메소드를 통해서 디스플레이 모드에 대한 정보를 얻을 수 있습니다. 이 함수의 시그니처는 다음과 같습니다 :

HRESULT IDXGIOutput::GetDisplayModeList(
    DXGI_FORMAT     EnumFormat,         /* 색상 형식 */
    UINT            Flags,              /* DXGI_ENUM_MODES 열거형 중 하나 */
    UINT*            pNumModes,         /* pDesc가 NULL이면 형식, 플래그와 잂치하는 디스플레이 모드 수 반환,
                                         * 이외의 경우, pDesc에서 반환되는 디스플레이 모드 수를 반환 */
    DXGI_MODE_DESC*    pDesc            /* 디스플레이 모드 정보 */
);

 

참고
이 포스팅에서는 하위 호환성 및 학습 목적으로 GetDisplayModeList 메소드를 사용하였지만, 공식 문서에서 Direct3D 11.1부터는 IDXGIOutput1::GetDisplayModeList1을 사용하는 것을 권장하고 있으니 참고하시기 바랍니다.

 

DXGI_FORMAT 열거형의 경우, 요소가 너무 많아서 직접 포스팅하기에는 과하다고 판단하여 올리지 않았습니다. 혹시 어떤 디스플레이 형식이 있는지 궁금하신 분들은 여기에서 살펴보시길 바랍니다.

 

아래는 위 메소드를 통해 DXGI_FORMAT::DXGI_FORMAT_B8G8R8A8_UNORM 형식을 지원하는 디스플레이 모드들에 대한 열거를 수행하는 코드입니다 :

void EnumerateOutputDisplayModes(IDXGIOutput* Output, DXGI_FORMAT Format)
{
    UINT count = 0;
    UINT flags = 0;
    /* null로 인자를 주면 디스플레이 모드 개수를 알아낼 수 있습니다 */
    Output->GetDisplayModeList(Format, flags, &count, nullptr);

    /* 디스플레이 모드 개수만큼 벡터를 생성 */
    vector<DXGI_MODE_DESC> DisplayModes(count);
    Output->GetDisplayModeList(Format, flags, &count, &DisplayModes[0]);

    DXGI_OUTPUT_DESC OutputDesc;
    Output->GetDesc(&OutputDesc);
    wcout << L"* " << OutputDesc.DeviceName << L" : " << to_wstring(count) << L" Display Modes \n";
    for (const auto& it : DisplayModes)
    {
        cout << "** Width, Height : " << it.Width << "," << it.Height << "\n";
        cout << "** RefreshRate : " << it.RefreshRate.Numerator 
            << "/" << it.RefreshRate.Denominator << "\n\n";
    }
}

int main()
{
    /* get some idxgiadapter */

    IDXGIOutput* Output;
    Adapter->EnumOutputs(0, &Output);
    DXGIUtility::EnumerateOutputDisplayModes(Output, DXGI_FORMAT::DXGI_FORMAT_B8G8R8A8_UNORM);
}

 

아래는 위 함수를 실행시켰을 때 결과입니다. 제 경우, 43가지 디스플레이 모드가 있었습니다.

 

 

 

레퍼런스


참고 자료
Microsoft | 개발자 센터 - CreateDXGIFactory function
Microsoft | 개발자 센터 - IDXGIFactory interface
Microsoft | 개발자 센터 - DXGI_ADAPTER_DESC structure
Microsoft | 개발자 센터 - IDXGIAdapter interface
Microsoft | 개발자 센터 - DXGI_OUTPUT_DESC structure
Microsoft | 개발자 센터 - IDXGIOutput interface
Microsoft | 개발자 센터 - DXGI_MODE_DESC structure
Microsoft | 개발자 센터 - DXGI_FORMAT enumeration
stackoverflow | What are the difference between IDXGIFactory and other IDXGIFactory version?
stackoverflow | IDXGIFactory versions

'개발 > DirectX' 카테고리의 다른 글

DXGI 개요(DirectX Graphics Infrastructure Overview)  (1) 2019.12.21
XMVECTOR와 XMFLOAT  (1) 2019.11.26
댓글