기록공간

[DirectX 12] 기본지식 - DXGI(DirectX Graphics Infrastructure) 본문

DirectX/기초

[DirectX 12] 기본지식 - DXGI(DirectX Graphics Infrastructure)

입코딩 2020. 2. 5. 15:41
반응형

DXGI(DirectX Graphics Infrastructure)는 Direct3D와 함께 쓰이는 API로 DirectX 그래픽 런타임에 독립적인 저수준(Low-Level)의 작업을 관리한다. 또한 DirectX 그래픽을 위한 기본적이고 공통적인 프레임워크를 제공한다. DXGI는 유연성을 위해 새로운 그래픽 라이브러리가 나오더라도 변하지 않을 수 있도록 구성되어 있다.

 

예를 들어 2차원 랜더링 API에도 3차원 렌더링 API처럼 스왑 체인과 페이지 전환이 필요하다. 이 때문에, 스왑 체인을 대표하는 인터페이스인 IDXGISwapChain은 실제로 DXGI API의 일부이다.

 

DXGI는 그 밖에도 여러가지 공통적인 그래픽 기능성을 처리한다. 이를테면 화면 모드 전환, 디스플레이 어탭터나 모니터, 지원되는 디스플레이 모드 (해상도, 주사율 등) 같은 그래픽 시스템 정보의 열거 등의 기능은 DXGI가 제공한다. 또한, 지원되는 표현 형식들도 DXGI에 정의되어 있다. (DXGI_FORMAT)


Direct3D 초기화 과정에서 쓰이는 몇 가지 DXGI 개념들과 인터페이스들을 간략하게 살펴보자. DXGI의 핵심 인터페이스 중 하나로 IDXGIFactory 인터페이스가 있다. 이 인터페이스는 주로 IDXGISwapChain(스왑체인) 인터페이스 생성과 디스플레이 어댑터 열거에 쓰인다.

 

디스플레이 어댑터(Display Adapter)는 그래픽 기능성을 구현한다. 일반적으로, 디스플레이 어댑터는 물리적인 하드웨어 장치(이를테면, 그래픽 카드)이다. 그러나, 하드웨어 그래픽 기능성을 흉내내는 소프트웨어 디스플레이 어댑터도 존재한다. 또한 하나의 시스템에 여러 개의 어댑터가 있을 수 있다(이를테면 한 시스템에 여러 개의 GPU가 장착되어 있는 경우). 

 

디스플레이 어댑터를 대표하는 인터페이스는 IDXGIAdapter이다. 다음 코드는 시스템에 있는 모든 어댑터를 열거하는 방법을 보여준다.

void D3DApp::LogAdapters()
{
   // 어뎁터에 접근할 index 변수
   UINT i = 0;
   // Adapter를 담아둘 포인터 변수
   IDXGIAdapter* adapter = nullptr;
   // Adapter들을 저장할 vector 컨테이너
   std::vector<IDXGIAdapter*> adapterList;
   
   // IDXGIFactory 객체를 통해 어뎁터들을 열거한다. i를 index로 사용한다.
   // 만약 index가 시스템에 존재하는 어뎁터의 갯수와 같거나 더 크다면 while문을 종료한다.
   while(mdxgiFactory->EnumAdapters(i, &adapter) != DXGI_ERROR_NOT_FOUND)
   {
       // 어뎁터 정보 구조체
       DXGI_ADAPTER_DESC desc;
       adapter->GetDesc(&desc);
       
       // 어뎁터의 이름을 출력한다. (GPU 정보)
       std::wstring text = L"***Adapter: ";
       test += desc.Description;
       text += L"\n";

       OutputDebugString(text.c_str());
       
       // 어뎁터 정보를 컨테이너에 담는다.
       adapterList.push_back(adapter);
       
       ++i;
   }
    
   // adapter의 모든 출력을 열거하고 release한다.
   for(size_t i = 0; i < adapterList.size(); ++i)
   {
       LogAdapterOutputs(adapterList[i]);
       ReleaseCom(adapterList[i]);
   }
}

출력 결과 예

 

***Adapter: NVIDIA GeForce GTX 760

***Adapter: Microsoft Basic Render Driver

("Microsoft Basic Render Driver"는 Window 8 이상에 포함된 소프트웨어 디스플레이 어뎁터이다.)


한 시스템에 모니터가 여러 개 연결되어 있을 수 있다. 모니터는 디스플레이 출력의 한 예이다. 디스플레이 출력은 IDXGIOutput 인터페이스가 대표한다. 각 어댑터에는 출력들의 목록이 연관되어 있다.

 

예를 들어 어떤 시스템에 그래픽 카드가 두 개, 모니터가 세개 연결되어 있으며, 세 모니터 중 둘은 하나는 그래픽 카드에 물려 있고 나머지 하나는 다른 한 그래픽 카드에 물려 있다고 하자. 그러면 한 어댑터에는 출력이 둘인 목록이 연관되며 다른 한 어댑터에는 출력이 하나인 출력 목록이 연관된다. 

 

 

다음은 주어진 한 어댑터에 연관된 모든 출력을 열거하는 코드이다. 

void D3DApp::LogAdapterOutputs(IDXGIAdapter* adapter)
{
    UINT i = 0;
    IXGIOutput* output = nullptr;
    while(adapter->EnumOutputs(i, &output) != DXGI_ERROR_NOT_FOUND)
    {
        DXGI_OUPUT_DESC desc;
        output->GetDesc(&desc);
        
        std::wstring text = L"***Output: ";
        text += desc.DeviceName;
        test += L"\n";
        OutputDebugString(text.c_str());
        
        LogOutputDisplayModes(output, DXGI_FORMAT_B8G8R8A8_UNORM);
        
        ReleaseCom(output);
        
        ++i;
    }
}

하나의 모니터에는 여러 디스플레이 모드를 지원한다. DXGI_MODE_DESC 구조체에는 하나의 디스플레이 모드를 서술하는 여러 멤버들이 있다.

typedef struct DXGI_MODE_DESC
{
    UINT Width;  // 가로 해상도
    UINT Height; // 세로 해상도
    DXGI_RATIONAL RefreshRate; // 주사율 구조체
    DXGI_FORMAT Format; // 디스플레이 형식
    DXGI_MODE_SCANLINE_ORDER ScanlineOrdering; 
    // 스캔 방식 : 순차 주소(프로그레시브) 또는 비월 주소(인터레이스) 
    DXGI_MODESCALING Scaling;
    // 영상을 모니터 크기에 맞게 늘리거나 줄이는 방식
} DXGI_MODE_DESC;

다음 코드는 주어진 출력과 디스플레이 형식을 지원하는 모든 디스플레이 모드를 담은 목록을 얻는 방법을 보여준다.

void D3DApp::LogOutputDisplayModes(IDXGIOutput* output, DXGI_FORMAT format)
{
    UINT count = 0;
    UINT flags = 0;
    
    // nullptr을 인수로 해서 호출하면 목록의 크기(모드 개수)를 얻게 된다.
    output->GetDisplayModeList(format, flags, &count, nullptr);
    
    std::vector<DXGI_MODE_DESC> modeList(count);
    // mode 정보들을 컨테이너에 넣는다.
    output->GetDisplayModeList(format, flags, &count, &modeList[0]);
    
    // 모드의 정보들을 출력한다.
    for(auto& x : modeList)
    {
        UINT n = x.RefreshRate.Numerator;
        UINT d = x.RefreshRate.Denominator;
        std::wstring text = 
        L"Width = " + std::to_wstring(x.Width) + L" " +
        L"Height = " + std::to_wstring(x.Height) + L" " + 
        L"Refresh = " + std::to_wstring(n) + L"\n" + std::to_wstring(d) + 
        L"\n";
        
        ::OutputDebugString(text.c_str());
    }
}

출력 결과 예

 

***Output: \\.\DISPLAY2

...

Width = 1920 Height = 1080 Refresh = 59950 / 1000

Width = 1920 Height = 1200 Refresh = 59950 / 1000


이러한 디스플레이 모드 열거는 전체 화면 모드로 갈 때 특히나 중요하다. 전체 화면 성능을 극대화하려면, 지정된 디스플레이 모드가 반드시 모니터가 지원하는 디스플레이 모드와 정확히 일치해야 한다. 모니터가 지원하는 디스플레이 모드들을 열거해서 그 중 하나를 지정하면 그러한 일치가 보장된다.

 

DXGI가 하는 기능들 몇가지를 살펴보았다. Direct3D가 3차원 세계를 렌더링하는 저수준의 API를 제공하는 것에서 알 수 있듯이, DXGI는 렌더링 하는데 있어 하드웨어를 효율적으로 돌릴 수 있도록 여러 저수준 작업들을 제공해준다. 그렇기 때문에 DXGI는 Direct3D를 사용하는데에 있어서 빠질 수 없는 개념이다. 

 

반응형
Comments