DirectX 스크린 캡쳐 (DXGI 캡쳐 예제)
뻘짓

DirectX 스크린 캡쳐 (DXGI 캡쳐 예제)

GDI를 사용한 화면 덤프는 끔찍하게 느리다.

그래서 그래픽카드의 도움을 받기로 했다.

#include <stdio.h>
#include <stdlib.h>
#include <d3d11.h>
#include <DXGI.h>
#include <dxgi1_2.h>
 
#pragma comment (lib, "d3d11.lib")
#pragma comment (lib, "dxgi.lib")
 
/**
    @brief 텍스쳐 생성함수
*/
ID3D11Texture2D* CreateTexture(ID3D11Device* d3d11, int width, int height, DXGI_FORMAT colorformat)
{
    D3D11_TEXTURE2D_DESC td;
    ID3D11Texture2D* OutputTexVal;
 
    if (!d3d11) return NULL;
    memset(&td, 0sizeof(td));
    td.Width = width;
    td.Height = height;
    td.MipLevels = 1;
    td.ArraySize = 1;
    td.Format = colorformat;
    td.BindFlags = 0;
    td.SampleDesc.Count = 1;
    td.Usage = D3D11_USAGE_STAGING;
    td.CPUAccessFlags = D3D11_CPU_ACCESS_READ; /*이 개체에 CPU가 접근 할 수 있도록 셋팅*/
    d3d11->CreateTexture2D(&td, NULL&OutputTexVal);
    if (!OutputTexVal) return NULL;
    return OutputTexVal;
}
 
int main()
{
    IDXGIFactory1* pFactory;
    IDXGIAdapter* pAdapter;
    IDXGIOutput* pOut;
    DXGI_OUTPUT_DESC outdesc;
    IDXGIOutput1* pOut1;
    IDXGIOutputDuplication* pDup;
    DXGI_OUTDUPL_FRAME_INFO dupframeinfo;
    IDXGIResource* res;
    ID3D11Texture2D* pTex, *pTmpTex;
    IDXGISurface1* pSurface;
    DXGI_MAPPED_RECT map;
    IDXGIDevice1* pDxgiDev;
    D3D11_TEXTURE2D_DESC texdesc;
    ID3D11Device* d3d11;
    D3D_FEATURE_LEVEL fl = D3D_FEATURE_LEVEL_9_1;
    ID3D11DeviceContext* d3d11context;
 
    CreateDXGIFactory1(__uuidof(IDXGIFactory2), (void**)&pFactory); /*팩토리 생성*/
    pFactory->EnumAdapters(0&pAdapter); /*만들어진 팩토리로 어댑터 얻기*/
    pAdapter->EnumOutputs(0&pOut); /*어댑터에 물린 디스플레이 얻기 (여기선 0번 화면) */
    pOut->GetDesc(&outdesc);
    pOut->QueryInterface(__uuidof(IDXGIOutput1), (void**)&pOut1); /*출력개체 확장 얻어오기*/
 
    /*d3d11 디바이스 및 컨텍스트 생성*/
    D3D11CreateDevice(pAdapter, D3D_DRIVER_TYPE_UNKNOWN, 0000, D3D11_SDK_VERSION, &d3d11, &fl, &d3d11context);
    d3d11->QueryInterface(__uuidof(IDXGIDevice1), (void**)&pDxgiDev); /*DXGI 인터페이스 얻어오기*/
    pOut1->DuplicateOutput(d3d11, &pDup); /*Duplicator 얻어오기*/
    pDup->AcquireNextFrame(16&dupframeinfo, &res); /*현재 화면 프레임 얻어오기 (16ms 안에 얻어오기) */
    res->QueryInterface(__uuidof(ID3D11Texture2D), (void**)&pTmpTex); /*얻어온 리소스는 텍스쳐로*/
    pTmpTex->GetDesc(&texdesc);
    pTex = CreateTexture(d3d11, texdesc.Width, texdesc.Height, texdesc.Format); /*저 텍스쳐는 CPU가 바로 사용 할 수 없으므로 새로 만듬*/
    d3d11context->CopyResource(pTex, pTmpTex); /*화면 복사*/
    pTex->QueryInterface(__uuidof(IDXGISurface1), (void**)&pSurface); /*서페이스 얻어오고*/
    pSurface->Map(&map, DXGI_MAP_READ); /*메모리 얻어오기*/
    return 0;
}
cs

pSurface->Map 이후 성공 했다면 map.pBits 포인터는 이렇게 생겼다.

B G R A 순으로 이쁘게 잘 들어가있다.

 

 

물론 한번 딱 얻어오고 끝나지 않고 지속적으로 얻어오고 싶을때도 있을것이다.

화면을 얻어오는 매서드는 IDXGIOutputDuplication (여기서는 pDup) 의 AcquireNextFrame() 인데, 재호출 하기 전에 얻어온 리소스를 해제 해야한다.

pSurface->Map() 으로 매핑했던 리소스를 pSurface->Unmap() 으로 풀어준 후 pDup->ReleaseFrame() 으로 해제한 후엔 다시 pDup->AcquireNextFrame() 을 호출하여 화면을 갱신할 수 있다.

 

그러면 이 내용을 적용하여 main 함수만 다시 적절히 수정 해보자.

int main()
{
    IDXGIFactory1* pFactory;
    IDXGIAdapter* pAdapter;
    IDXGIOutput* pOut;
    DXGI_OUTPUT_DESC outdesc;
    IDXGIOutput1* pOut1;
    IDXGIOutputDuplication* pDup;
    DXGI_OUTDUPL_FRAME_INFO dupframeinfo;
    IDXGIResource* res;
    ID3D11Texture2D* pTex, * pTmpTex;
    IDXGISurface1* pSurface;
    DXGI_MAPPED_RECT map;
    IDXGIDevice1* pDxgiDev;
    D3D11_TEXTURE2D_DESC texdesc;
    ID3D11Device* d3d11;
    D3D_FEATURE_LEVEL fl = D3D_FEATURE_LEVEL_9_1;
    ID3D11DeviceContext* d3d11context;
 
    CreateDXGIFactory1(__uuidof(IDXGIFactory2), (void**)&pFactory); /*팩토리 생성*/
    pFactory->EnumAdapters(0&pAdapter); /*만들어진 팩토리로 어댑터 얻기*/
    pAdapter->EnumOutputs(0&pOut); /*어댑터에 물린 디스플레이 얻기 (여기선 0번 화면)*/
    pOut->GetDesc(&outdesc);
    pOut->QueryInterface(__uuidof(IDXGIOutput1), (void**)&pOut1); /*출력개체 확장 얻어오기*/
 
    /*d3d11 디바이스 및 컨텍스트 생성*/
    D3D11CreateDevice(pAdapter, D3D_DRIVER_TYPE_UNKNOWN, 0000, D3D11_SDK_VERSION, &d3d11, &fl, &d3d11context);
    d3d11->QueryInterface(__uuidof(IDXGIDevice1), (void**)&pDxgiDev); /*DXGI 인터페이스 얻어오기*/
    pOut1->DuplicateOutput(d3d11, &pDup); /*Duplicator 얻어오기*/
    pDup->AcquireNextFrame(16&dupframeinfo, &res); /*현재 화면 프레임 얻어오기 (16ms 안에 얻어오기)*/
    res->QueryInterface(__uuidof(ID3D11Texture2D), (void**)&pTmpTex); /*얻어온 리소스는 텍스쳐로*/
    pTmpTex->GetDesc(&texdesc); /*화면 크기 등의 정보를 얻기 위함*/
    pDup->ReleaseFrame(); /*일단 다시 해제*/
    pTex = CreateTexture(d3d11, texdesc.Width, texdesc.Height, texdesc.Format); /*저 텍스쳐는 CPU가 바로 사용 할 수 없으므로 새로 만듬*/
 
    int i = 0;
    while (1) {
        pDup->AcquireNextFrame(16&dupframeinfo, &res); /*현재 화면 프레임 얻어오기 (16ms 안에 얻어오기)*/
        res->QueryInterface(__uuidof(ID3D11Texture2D), (void**)&pTmpTex); /*얻어온 리소스는 텍스쳐로*/
        d3d11context->CopyResource(pTex, pTmpTex); /*화면 복사*/
        pTex->QueryInterface(__uuidof(IDXGISurface1), (void**)&pSurface); /*서페이스 얻어오고*/
        pSurface->Map(&map, DXGI_MAP_READ); /*메모리 얻어오기*/
 
        /*뭔가 하고싶은거 있으면 이 사이에 하면댐. 일단 여기서는 화면 좌상단의 1픽셀 데이터만 표시 해봄.*/
        printf("\rLOOP#%d : %02x %02x %02x %02x", i++, map.pBits[0], map.pBits[1], map.pBits[2], map.pBits[3]);
 
 
        pSurface->Unmap(); /* 쓴거 해제*/
        pDup->ReleaseFrame(); /*쓴거 해제*/
    }
 
    pTex->Release();
    return 0;
}
cs

 

실행 결과 : 

화면 좌상단 픽셀이 실시간으로 업데이트 된다. 좌상단에 메모장 갖다 박으면 ff ff ff ff 뜬다.