文章目录
一、概要
DXGI(DirectX Graphics Infrastructure)图像截取是一种技术,用于从图形设备接口(Graphics Device Interface)中捕获屏幕上的图像数据。它是基于 DirectX 和 DXGI 接口的截取方法。
二、DXGI 图像截取的实现步骤
1. 获取 DXGI 设备和适配器: 首先,通过调用 CreateDXGIFactory 来创建一个 DXGI 工厂对象。然后使用工厂对象的 EnumAdapters 方法来获取系统上的适配器(显示适配器)。可以根据需求选择适配器。
2. 创建 DXGI 输出(Output)对象: 使用选定的适配器调用 EnumOutputs 方法来获取适配器上的输出(显示器)。可以选择某个输出,或者使用默认输出(通常是主显示器)。
3. 创建 DXGI 输出复制(Output Duplication)对象: 调用 IDXGIOutput1 接口的 DuplicateOutput 方法来创建一个 DXGI 输出复制对象。输出复制对象用于捕获屏幕上的图像数据。
4. 获取桌面帧: 使用输出复制对象的 AcquireNextFrame 方法来获取下一个桌面帧(Desktop Frame)。这个桌面帧包含了屏幕上的图像数据。
5. 访问桌面帧数据: 可以通过输出复制对象的 MapDesktopSurface 方法将桌面帧数据映射到内存中。然后可以使用指针来访问图像数据。
6. 释放桌面帧和输出复制对象: 使用 IDXGIOutputDuplication 接口的 ReleaseFrame 方法来释放桌面帧。最后调用 Release 方法释放输出复制对象。
三、DXGI常用接口介绍
1. D3D11CreateDevice
D3D11CreateDevice是 DirectX 11 中用于创建 D3D 设备的函数
HRESULT D3D11CreateDevice(
IDXGIAdapter *pAdapter,
D3D_DRIVER_TYPE DriverType,
HMODULE Software,
UINT Flags,
const D3D_FEATURE_LEVEL *pFeatureLevels,
UINT FeatureLevels,
UINT SDKVersion,
ID3D11Device **ppDevice,
D3D_FEATURE_LEVEL *pFeatureLevel,
ID3D11DeviceContext **ppImmediateContext
);
接口参数说明
pAdapter: 指向 DXGI 适配器(IDXGIAdapter)的指针,用于指定使用的图形适配器。可以为 nullptr,表示使用默认适配器。
DriverType: 指定驱动类型,如 D3D_DRIVER_TYPE_HARDWARE、D3D_DRIVER_TYPE_WARP 或 D3D_DRIVER_TYPE_REFERENCE。
Software: 指定一个可选的软件模块的句柄,用于软件驱动。一般情况下可以传入 nullptr。
Flags: 配置标志,用于配置设备创建的行为。可以是 D3D11_CREATE_DEVICE_FLAG 枚举值的组合,如D3D11_CREATE_DEVICE_DEBUG 用于启用调试模式。
pFeatureLevels: 指向 D3D 特性级别(D3D_FEATURE_LEVEL)数组的指针,指定所需的 D3D 特性级别。可以为 nullptr,表示使用默认的特性级别。
FeatureLevels: 数组 pFeatureLevels 中的特性级别个数。
SDKVersion: DirectX SDK 版本号。通常使用 D3D11_SDK_VERSION 宏。
ppDevice: 输出参数,返回创建的 D3D 设备对象的指针。
pFeatureLevel: 输出参数,返回实际创建的 D3D 设备的特性级别。
ppImmediateContext: 输出参数,返回与设备关联的即时渲染上下文(ID3D11DeviceContext)的指针。
D3D_DRIVER_TYPE常见类型
D3D_DRIVER_TYPE 枚举类型定义了在 Direct3D 中可用的驱动程序类型。下面是 D3D_DRIVER_TYPE 中所有枚举值的含义:
1.D3D_DRIVER_TYPE_HARDWARE: 表示使用硬件加速的图形驱动程序。它指示 Direct3D 使用可用的硬件加速功能来执行图形渲染操作。通常用于具有独立显卡的系统。
2.D3D_DRIVER_TYPE_WARP: 表示使用 WARP(Windows Advanced Rasterization Platform)软件渲染器进行图形渲染。WARP 是一种在不具备独立显卡的系统上进行软件渲染的选择。它可以提供基本的图形渲染功能,但性能相对较低。
3.D3D_DRIVER_TYPE_REFERENCE: 表示使用参考软件渲染器进行图形渲染。参考渲染器是一种纯软件实现的渲染器,不依赖于硬件加速,适用于调试和开发目的。它通常用于验证和测试渲染代码,但性能较低。
4.D3D_DRIVER_TYPE_NULL: 表示使用空驱动程序类型。它不执行任何图形渲染操作,主要用于模拟或测试环境中。
5.D3D_DRIVER_TYPE_SOFTWARE: 表示使用软件驱动程序进行图形渲染。这是一种在没有硬件加速的系统上进行软件渲染的选择。它通常性能较低,仅用于特殊情况。
以上是 D3D_DRIVER_TYPE 中的所有枚举值及其含义。根据具体的系统配置和需求,你可以选择适合的驱动类型来执行 Direct3D 图形渲染操作。通常情况下,硬件加速的图形驱动程序(D3D_DRIVER_TYPE_HARDWARE)是首选的,以获得最佳性能和图形质量。
D3D_FEATURE_LEVEL 枚举类型定义了在 Direct3D 中可用的功能级别(Feature Level)。它表示硬件或驱动程序所支持的图形功能和功能集合。
D3D11CreateDevice 函数的使用示例
bool ScreenCapture::InitD3D11Device()
{
D3D_DRIVER_TYPE driverTypes[] = {
D3D_DRIVER_TYPE_HARDWARE,
D3D_DRIVER_TYPE_WARP,
D3D_DRIVER_TYPE_REFERENCE,
};
UINT numDriverTypes = std::extent<decltype(driverTypes)>::value;
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 = std::extent<decltype(featureLevels)>::value;
D3D_FEATURE_LEVEL featureLevel;
for (UINT i = 0; i < numDriverTypes; ++i)
{
HRESULT hr = D3D11CreateDevice(nullptr, driverTypes[i], nullptr, 0, featureLevels, numFeatureLevels, D3D11_SDK_VERSION, &m_pDevice, &featureLevel, &m_pDeviceContext);
if (SUCCEEDED(hr)) {
break;
}
}
if (m_pDevice == nullptr || m_pDeviceContext == nullptr) {
qDebug() << __FILE__ << __FUNCTION__ << __LINE__ << "InitD3D11Device Failed";
return false;
}
return true;
}
2.QueryInterface
在 DXGI中,QueryInterface 是一种用于查询接口支持和获取接口指针的函数
HRESULT QueryInterface(
REFIID riid,
void **ppvObject
);
接口参数说明
参数说明:
riid:一个指向所需接口的唯一标识符(IID,Interface Identifier)的引用。可以使用 __uuidof 宏获取接口的 IID。
ppvObject:一个指向指针的指针,用于接收查询到的接口指针。
返回值:
如果操作成功,函数返回 S_OK,并且 ppvObject 将指向查询到的接口指针。
如果查询的接口不可用,函数返回 E_NOINTERFACE。
其他可能的返回值包括各种错误代码,表示查询接口失败或发生其他错误。
使用 QueryInterface 函数的一般流程如下:
在合适的对象上调用 QueryInterface 函数,传递所需接口的 IID 和接收指针的地址。
检查函数的返回值,如果返回 S_OK,表示查询成功。
使用获取到的接口指针执行相应的操作。
在不再需要接口时,记得释放接口指针的引用计数。
3.GetAdapter
在DXGI中,IDXGIDevice 接口提供了 GetAdapter 方法,用于获取与设备关联的适配器(adapter)对象
HRESULT GetAdapter(
IDXGIAdapter **ppAdapter
);
参数说明:
ppAdapter:指向 IDXGIAdapter 指针的指针,用于接收获取到的适配器对象指针。
返回值:
如果操作成功,函数返回 S_OK,并且 ppAdapter 将指向获取到的适配器对象指针。
如果获取适配器失败,函数返回错误代码。
适配器(adapter)代表了一个物理或虚拟的图形适配器,它与显示设备(如显卡)相关联。通过调用 GetAdapter 方法,可以获得与当前设备相关的适配器对象,并进一步获取适配器的属性和信息。
4.EnumOutputs
在 DXGI中,EnumOutputs 是一个函数,用于枚举与图形适配器相关联的显示输出(display output)。
HRESULT EnumOutputs(
UINT Output,
IDXGIOutput **ppOutput
);
参数说明:
Output:要枚举的输出的索引,从0开始。
ppOutput:指向 IDXGIOutput 指针的指针,用于接收获取到的输出对象指针。
返回值:
如果操作成功,函数返回 S_OK,并且 ppOutput 将指向获取到的输出对象指针。
如果枚举输出失败,函数返回错误代码。
显示输出(display output)表示与适配器关联的显示设备,如显示器或投影仪。通过调用 EnumOutputs 函数,可以枚举适配器上的所有显示输出,并获取相应的输出对象。
5.DuplicateOutput
在 DXGI(DirectX Graphics Infrastructure)中,DuplicateOutput 函数用于创建一个用于复制输出的 DXGI 接口对象,即 IDXGIOutputDuplication 接口对象。通过复制输出功能,可以实现屏幕捕捉、录制或远程显示等应用场景。
HRESULT DuplicateOutput(
IUnknown *pDevice,
IDXGIOutputDuplication **ppOutputDuplication
);
参数说明:
pDevice:指向用于复制输出的 Direct3D 设备(ID3D11Device 或 ID3D12Device)的指针。
ppOutputDuplication:指向 IDXGIOutputDuplication 指针的指针,用于接收创建的输出复制对象。
返回值:
如果操作成功,函数返回 S_OK,并且 ppOutputDuplication 将指向创建的输出复制对象的指针。
如果复制输出失败,函数返回错误代码。
要使用 DuplicateOutput 函数,首先需要创建一个 Direct3D 设备对象(ID3D11Device 或 ID3D12Device),并获取要复制的输出对象(IDXGIOutput1)。然后,调用 DuplicateOutput 函数来创建输出复制对象。
代码示例
bool ScreenCapture::InitDuplication()
{
HRESULT hr = S_OK;
/* IDXGIDevice 接口是 DXGI中的一个接口,用于表示一个设备对象,该设备对象是与 Direct3D 设备相关联的。 */
IDXGIDevice* dxgiDevice = nullptr;
hr = m_pDevice->QueryInterface(__uuidof(IDXGIDevice), reinterpret_cast<void**>(&dxgiDevice));
if (FAILED(hr)) {
qDebug() << __FILE__ << __FUNCTION__ << __LINE__ << "QueryInterface Failed:" << hr;
return false;
}
if (dxgiDevice == nullptr) {
qDebug() << __FILE__ << __FUNCTION__ << __LINE__ << "dxgiDevice is nullptr";
return false;
}
IDXGIAdapter* dxgiAdapter = nullptr;
/* 获取与设备相关联的适配器对象 */
hr = dxgiDevice->GetAdapter(&dxgiAdapter);
dxgiDevice->Release();
if (FAILED(hr)) {
qDebug() << __FILE__ << __FUNCTION__ << __LINE__ << "GetAdapter Failed:" << hr;
return false;
}
if (dxgiAdapter == nullptr) {
qDebug() << __FILE__ << __FUNCTION__ << __LINE__ << "dxgiAdapter is nullptr";
return false;
}
UINT output = 0;
IDXGIOutput* dxgiOutput = nullptr;
while (true)
{
hr = dxgiAdapter->EnumOutputs(output++, &dxgiOutput);
if (hr == DXGI_ERROR_NOT_FOUND) {
return false;
} else {
if (dxgiOutput == nullptr) {
qDebug() << __FILE__ << __FUNCTION__ << __LINE__ << "dxgiOutput is nullptr";
break;
}
DXGI_OUTPUT_DESC desc;
dxgiOutput->GetDesc(&desc);
m_screenWidth = desc.DesktopCoordinates.right - desc.DesktopCoordinates.left;
m_screenHeight = desc.DesktopCoordinates.bottom - desc.DesktopCoordinates.top;
break;
}
}
dxgiAdapter->Release();
/* IDXGIOutput1 接口是 DXGI中的一个接口,它是 IDXGIOutput 接口的扩展版本。IDXGIOutput1 提供了一些额外的方法和功能,
* 用于与显示输出(display output)进行交互和控制。 */
IDXGIOutput1* dxgiOutput1 = nullptr;
hr = dxgiOutput->QueryInterface(__uuidof(IDXGIOutput1), reinterpret_cast<void**>(&dxgiOutput1));
dxgiOutput->Release();
if (FAILED(hr)) {
return false;
}
if (dxgiOutput1 == nullptr) {
qDebug() << __FILE__ << __FUNCTION__ << __LINE__ << "dxgiOutput1 is nullptr";
return false;
}
hr = dxgiOutput1->DuplicateOutput(m_pDevice, &m_pDuplication);
dxgiOutput1->Release();
if (FAILED(hr)) {
qDebug() << __FILE__ << __FUNCTION__ << __LINE__ << "DuplicateOutput Failed";
return false;
}
return true;
}
6.AcquireNextFrame
在 DXGI中,AcquireNextFrame 函数用于从 IDXGIOutputDuplication 接口获取下一帧的图像数据。
HRESULT AcquireNextFrame(
UINT TimeoutInMilliseconds,
DXGI_OUTDUPL_FRAME_INFO *pFrameInfo,
IDXGIResource **ppDesktopResource
);
参数说明:
TimeoutInMilliseconds:超时时间(以毫秒为单位),指定等待新帧的最大时间。
pFrameInfo:指向 DXGI_OUTDUPL_FRAME_INFO 结构的指针,用于接收获取到的帧信息。
ppDesktopResource:指向 IDXGIResource 指针的指针,用于接收获取到的桌面资源。
返回值:
如果操作成功,函数返回 S_OK,并且 pFrameInfo 将包含帧信息,ppDesktopResource 将指向获取到的桌面资源。
如果获取帧失败或超时,函数返回错误代码。
要使用 AcquireNextFrame 函数,首先需要创建一个 IDXGIOutputDuplication 接口对象,通过 DuplicateOutput 方法获取。然后,在循环中调用 AcquireNextFrame 函数来获取每一帧的图像数据。
代码示例
bool ScreenCapture::GetDesktopFrame(QString fileName)
{
HRESULT hr = S_OK;
DXGI_OUTDUPL_FRAME_INFO frameInfo;
IDXGIResource* resource = nullptr;
ID3D11Texture2D* acquireFrame = nullptr;
ID3D11Texture2D* texture = nullptr;
if (m_pDuplication == nullptr) {
qDebug() << __FILE__ << __FUNCTION__ << __LINE__ << "m_pDuplication is nullptr";
return false;
}
hr = m_pDuplication->AcquireNextFrame(0, &frameInfo, &resource);
if (FAILED(hr)) {
return false;
}
if (resource == nullptr) {
qDebug() << __FILE__ << __FUNCTION__ << __LINE__ << "resource is nullptr";
return false;
}
hr = resource->QueryInterface(__uuidof(ID3D11Texture2D), reinterpret_cast<void**>(&acquireFrame));
resource->Release();
if (FAILED(hr)) {
return false;
}
if (acquireFrame == nullptr) {
qDebug() << __FILE__ << __FUNCTION__ << __LINE__ << "acquireFrame is nullptr";
return false;
}
D3D11_TEXTURE2D_DESC desc;
acquireFrame->GetDesc(&desc);
desc.Usage = D3D11_USAGE_STAGING;
desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
desc.BindFlags = 0;
desc.MiscFlags = 0;
desc.MipLevels = 1;
desc.ArraySize = 1;
desc.SampleDesc.Count = 1;
m_pDevice->CreateTexture2D(&desc, NULL, &texture);
if (texture) {
/* 将图像数据从显存中拷贝到内存中 */
m_pDeviceContext->CopyResource(texture, acquireFrame);
QImage image = this->CopyDesktopImage(texture);
image.save(fileName, "PNG");
}
acquireFrame->Release();
hr = m_pDuplication->ReleaseFrame();
if (FAILED(hr)) {
return false;
}
return true;
}
四.部分代码示例
QImage ScreenCapture::CopyDesktopImage(ID3D11Texture2D* texture)
{
D3D11_TEXTURE2D_DESC desc;
texture->GetDesc(&desc);
D3D11_MAPPED_SUBRESOURCE mapped_resource;
m_pDeviceContext->Map(texture, 0, D3D11_MAP_READ, 0, &mapped_resource);
QImage image(static_cast<uchar *>(mapped_resource.pData), m_screenWidth, m_screenHeight, QImage::Format_ARGB32);
texture->Release();
texture = nullptr;
return image;
}
开始截图(截图之前,需要加载d3d11.lib和dxgi.lib,另外还需要包含头文件d3d11.h和dxgi1_2.h)
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
std::shared_ptr<ScreenCapture> screen_capture = std::make_shared<ScreenCapture>();
if (!screen_capture.get()->InitD3D11Device()) {
return -1;
}
if (!screen_capture.get()->InitDuplication()) {
return -1;
}
int counts = 0;
/* 每隔1秒获取一次图像 */
while (counts < 5) {
QString fileName = QString("%1.png").arg(QDateTime::currentDateTime().toString("yyyy-MM-dd-hh-mm-ss"));
screen_capture.get()->GetDesktopFrame(fileName);
counts++;
Sleep(1000);
}
return a.exec();
}