Comparing GDI and DXGI Screen Capture Methods in Qt: Implementation, Performance, and Code Samples
This article introduces GDI and DXGI screen‑capture techniques, shows how to implement them in Qt with complete code examples, compares their performance and visual results, and discusses practical considerations for teacher‑side applications such as multi‑screen and live‑streaming environments.
This article introduces two Windows screen‑capture methods—GDI and DXGI—and demonstrates how to implement them in Qt, providing performance and visual comparisons relevant to teacher‑side applications like multi‑screen teaching and half‑body live streaming.
GDI
GDI (Graphics Device Interface) captures the screen by obtaining a device context (DC) and using BitBlt to copy the bitmap. It works across Windows versions and is simple but may miss windows using 3D acceleration.
QImage GrabWindowHelper::grabWindow(WId winId, bool needMouse)
{
QSize windowSize;
int x = 0;
int y = 0;
HWND hwnd = reinterpret_cast
(winId);
if (!hwnd) {
return QImage();
}
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);
imgFlag = BitBlt(bitmap_dc, 0, 0, width, height, window_dc, x, y, SRCCOPY | CAPTUREBLT);
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 same approach can be used with StretchBlt for scaling, and Qt provides a convenience wrapper QScreen::grabWindow that internally uses BitBlt.
For better compatibility with windows that use 3D acceleration, the PrintWindow API can be loaded dynamically:
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;
}Replacing the BitBlt call with printWindow() yields more complete captures, as shown in the comparison images.
DXGI
DXGI (DirectX Graphics Infrastructure) is a modern, high‑performance interface introduced in Windows 8 that interacts directly with the GPU, offering low CPU usage and fast frame acquisition.
BOOL Init()
{
int adaptIndex = 0, outputIndex = 0;
QList<ScreenOutput> 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 that the initialization thread must not have processed UI messages, otherwise initialization fails.
BOOL QueryFrame(QRect &rect, 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);
}Performance comparison images show that DXGI consumes far less CPU and leverages GPU efficiently, making it the preferred method for high‑frequency, high‑resolution captures in live‑streaming scenarios.
In conclusion, while GDI remains simple and widely compatible, DXGI provides superior performance and lower CPU load for modern, high‑resolution screen‑capture needs, especially in teacher‑side live‑streaming applications.
Xueersi Online School Tech Team
The Xueersi Online School Tech Team, dedicated to innovating and promoting internet education technology.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.