Fundamentals 11 min read

Comparing GDI and DXGI Screenshot Methods in Qt with Performance Analysis

This article introduces GDI and DXGI screen capture techniques, provides Qt implementations with detailed code examples, compares their performance and visual results, and presents a CPU/GPU usage benchmark, concluding that DXGI offers higher GPU efficiency while both methods have specific trade‑offs.

TAL Education Technology
TAL Education Technology
TAL Education Technology
Comparing GDI and DXGI Screenshot Methods in Qt with Performance Analysis

This article introduces two Windows screenshot methods—GDI and DXGI—and demonstrates how to implement them in Qt, followed by a performance and visual quality comparison.

GDI

GDI captures the screen by obtaining a device context (DC) of the target window and using BitBlt (or StretchBlt) to copy the bitmap. It works across Windows versions and does not require specific OS support.

QImage GrabWindowHelper::grabWindow(WId winId, bool needMouse){
    QSize windowSize;
    int x = 0;    
    int y = 0;    
    HWND hwnd = reinterpret_cast
(winId);
    if (!hwnd) {
        return QImage();
    }
    //以下部分在获取这个句柄应用的size    
    RECT r;    
    GetClientRect(hwnd, &r);
    windowSize = QSize(r.right - r.left, r.bottom - r.top);
    int width = windowSize.width();    
    int height = windowSize.height(); 
    HDC display_dc = GetDC(nullptr);
    HDC bitmap_dc = CreateCompatibleDC(display_dc);
    HBITMAP bitmap = CreateCompatibleBitmap(display_dc, width, height);
    HGDIOBJ null_bitmap = SelectObject(bitmap_dc, bitmap);
    BOOL imgFlag = FALSE;HDC window_dc = GetDC(hwnd);  //获取句柄DC
    imgFlag = BitBlt(bitmap_dc, 0, 0, width, height, window_dc, x, y, SRCCOPY | CAPTUREBLT); //使用Bitblt方法
    ReleaseDC(hwnd, window_dc);
    if (needMouse) {  //需要截取鼠标的话单独处理        
        CURSORINFO curinfo;        
        curinfo.cbSize = sizeof(curinfo);        
        GetCursorInfo(&curinfo);        
        POINT screenPt = curinfo.ptScreenPos;        
        ScreenToClient(hwnd, &screenPt);        
        DrawIcon(bitmap_dc, screenPt.x, screenPt.y, curinfo.hCursor);
    }
    SelectObject(bitmap_dc, null_bitmap);
    DeleteDC(bitmap_dc);
    QImage image;
    if (imgFlag) {
        image = QtWin::imageFromHBITMAP(bitmap);
    }
    DeleteObject(bitmap);
    ReleaseDC(nullptr, display_dc);
    return image;
}

The code above uses BitBlt; an alternative StretchBlt can perform scaling. Qt also provides a built‑in function:

QPixmap QScreen::grabWindow(WId window, int x = 0, int y = 0, int width = -1, int height = -1)

For broader compatibility the article shows how to load PrintWindow dynamically via QLibrary and replace the BitBlt call with PrintWindow when available.

typedef BOOL (__stdcall *PtrPrintWindow )(HWND ,HDC ,UINT );
PtrPrintWindow GrabWindowHelper::printWindow() {
    static bool hasTestPrintWindowFunction = false;  
    static PtrPrintWindow printWindowFunnction = nullptr;  
    if (hasTestPrintWindowFunction) {
        return printWindowFunnction;
    }
    hasTestPrintWindowFunction = true;
    printWindowFunnction = reinterpret_cast
(QLibrary::resolve("user32.dll", "PrintWindow"));
    return printWindowFunnction;
}

Using PrintWindow simply replaces the BitBlt line with:

imgFlag = printWindow()(hwnd, bitmap_dc, PW_CLIENTONLY | PW_RENDERFULLCONTENT);

DXGI

DXGI (DirectX Graphics Infrastructure) is a newer Windows graphics interface that interacts directly with the GPU, offering higher efficiency and lower CPU usage, but requires Windows 8 or later and cannot capture desktop windows directly.

BOOL Init(){
    int adaptIndex = 0, outputIndex = 0;
    QList
list;
    bool flag = getScreens(list); //获取屏幕列表
    if (!flag || list.size() == 0) {
        return false;
    }
    adaptIndex = list.at(0).adaptorIndex;
    outputIndex = list.at(0).outputIndex;
    HRESULT hr = S_OK;
    if (m_bInit) {
        return FALSE;
    }
    D3D_DRIVER_TYPE DriverTypes[] = { D3D_DRIVER_TYPE_HARDWARE, D3D_DRIVER_TYPE_WARP, D3D_DRIVER_TYPE_REFERENCE };
    UINT NumDriverTypes = ARRAYSIZE(DriverTypes);
    D3D_FEATURE_LEVEL FeatureLevels[] = { D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1, D3D_FEATURE_LEVEL_10_0, D3D_FEATURE_LEVEL_9_1 };
    UINT NumFeatureLevels = ARRAYSIZE(FeatureLevels);
    D3D_FEATURE_LEVEL FeatureLevel;
    for (UINT DriverTypeIndex = 0; DriverTypeIndex < NumDriverTypes; ++DriverTypeIndex) {
        hr = D3D11CreateDevice(NULL, DriverTypes[DriverTypeIndex], NULL, 0, FeatureLevels, NumFeatureLevels, D3D11_SDK_VERSION, &m_hDevice, &FeatureLevel, &m_hContext);
        if (SUCCEEDED(hr)) {
            break;
        }
    }
    if (FAILED(hr)) {
        return FALSE;
    }
    IDXGIDevice *hDxgiDevice = NULL;
    hr = m_hDevice->QueryInterface(__uuidof(IDXGIDevice), reinterpret_cast
(&hDxgiDevice));
    if (FAILED(hr)){
        return FALSE;
    }
    IDXGIFactory* pFactory;
    hr = CreateDXGIFactory(__uuidof(IDXGIFactory), (void**)(&pFactory));
    IDXGIAdapter* hDxgiAdapter = nullptr;
    hr = pFactory->EnumAdapters(0, &hDxgiAdapter);
    RESET_OBJECT(hDxgiDevice);
    if (FAILED(hr)) {
        return FALSE;
    }
    int nOutput = outputIndex;
    IDXGIOutput *hDxgiOutput = NULL;
    hr = hDxgiAdapter->EnumOutputs(nOutput, &hDxgiOutput);
    RESET_OBJECT(hDxgiAdapter);
    if (FAILED(hr)) {
        return FALSE;
    }
    hDxgiOutput->GetDesc(&m_dxgiOutDesc);
    IDXGIOutput1 *hDxgiOutput1 = NULL;
    hr = hDxgiOutput->QueryInterface(__uuidof(hDxgiOutput1), reinterpret_cast
(&hDxgiOutput1));
    RESET_OBJECT(hDxgiOutput);
    if (FAILED(hr)) {
        return FALSE;
    }
    hr = hDxgiOutput1->DuplicateOutput(m_hDevice, &m_hDeskDupl);
    RESET_OBJECT(hDxgiOutput1);
    if (FAILED(hr)) {
        return FALSE;
    }
    m_bInit = TRUE;
    return TRUE;
}

Note: the initialization thread must not have processed UI before calling this function.

After initialization, frames are captured with:

BOOL QueryFrame(QRect ▭, void *pImgData, INT &nImgSize){
    if (!m_bInit || !AttatchToThread()) {
        return FALSE;
    }
    DXGI_OUTDUPL_FRAME_INFO FrameInfo;
    IDXGIResource *hDesktopResource = NULL;
    HRESULT hr = m_hDeskDupl->AcquireNextFrame(20, &FrameInfo, &hDesktopResource);
    if (FAILED(hr)) {
        hDesktopResource = nullptr;
        return TRUE;
    }
    ID3D11Texture2D *hAcquiredDesktopImage = NULL;
    hr = hDesktopResource->QueryInterface(__uuidof(ID3D11Texture2D), reinterpret_cast
(&hAcquiredDesktopImage));
    RESET_OBJECT(hDesktopResource);
    if (FAILED(hr)) {
        return FALSE;
    }
    D3D11_TEXTURE2D_DESC frameDescriptor;
    hAcquiredDesktopImage->GetDesc(&frameDescriptor);
    ID3D11Texture2D *hNewDesktopImage = NULL;
    frameDescriptor.Usage = D3D11_USAGE_STAGING;
    frameDescriptor.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
    frameDescriptor.BindFlags = 0;
    frameDescriptor.MiscFlags = 0;
    frameDescriptor.MipLevels = 1;
    frameDescriptor.ArraySize = 1;
    frameDescriptor.SampleDesc.Count = 1;
    hr = m_hDevice->CreateTexture2D(&frameDescriptor, NULL, &hNewDesktopImage);
    if (FAILED(hr)) {
        RESET_OBJECT(hAcquiredDesktopImage);
        m_hDeskDupl->ReleaseFrame();
        return FALSE;
    }
    m_hContext->CopyResource(hNewDesktopImage, hAcquiredDesktopImage);
    RESET_OBJECT(hAcquiredDesktopImage);
    m_hDeskDupl->ReleaseFrame();
    static IDXGISurface *hStagingSurf = NULL;
    if (hStagingSurf == NULL) {
        hr = hNewDesktopImage->QueryInterface(__uuidof(IDXGISurface), (void **)(&hStagingSurf));
        RESET_OBJECT(hNewDesktopImage);
        if (FAILED(hr)) {
            return FALSE;
        }
    }
    DXGI_MAPPED_RECT mappedRect;
    hr = hStagingSurf->Map(&mappedRect, DXGI_MAP_READ);
    if (SUCCEEDED(hr)) {
        QRect desttopRect(m_dxgiOutDesc.DesktopCoordinates.left, m_dxgiOutDesc.DesktopCoordinates.top,
                           m_dxgiOutDesc.DesktopCoordinates.right - m_dxgiOutDesc.DesktopCoordinates.left,
                           m_dxgiOutDesc.DesktopCoordinates.bottom - m_dxgiOutDesc.DesktopCoordinates.top);
        copyImageByRect((char*)mappedRect.pBits, desttopRect.size(), (char*)pImgData, nImgSize, rect);
        hStagingSurf->Unmap();
    }
    RESET_OBJECT(hStagingSurf);
    return SUCCEEDED(hr);
}

The article then presents a table comparing CPU%, GPU% and memory usage for GDI vs. DXGI at various frame rates, showing that DXGI consistently uses less GPU time and similar memory, confirming its higher efficiency.

Frame

GDI/DXGI (CPU%)

GDI/DXGI (GPU%)

GDI/DXGI (Memory MB)

12

3.9/0

0/0.3

39.6/39.8

20

4.0/0

0/0.3

39.5/39.5

30

4.2/0

0/0.3

39.7/39.8

100

18.1/0.6

0/0.3

48.9/39.9

In conclusion, while both methods can capture the screen, DXGI provides higher GPU efficiency and is preferable for high‑performance scenarios, whereas GDI remains compatible with older Windows versions.

The article ends with a brief note on emerging screenshot technologies such as Magnification and Window Graphics Capturer, encouraging continuous learning.

performanceC++QtDXGIGDIscreen-capture
TAL Education Technology
Written by

TAL Education Technology

TAL Education is a technology-driven education company committed to the mission of 'making education better through love and technology'. The TAL technology team has always been dedicated to educational technology research and innovation. This is the external platform of the TAL technology team, sharing weekly curated technical articles and recruitment information.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.