目录
一、DXGI流程
DXGI全称是Microsoft DirectX Graphics Infrastructure,即微软图形设备基础架构。
它的工作,就是为图形库提供底层的硬件设备的接口支持。
正是由于它的存在,也使得不同图形库之间能进行交互,因为在DXGI中,资源的接口是IDXGIResource,它可以转换成上层的图形库的接口,如D3D的纹理。
本文章,主要是介绍使用DXGI 采集桌面,比起传统的GDI方式,要强大很多。
1、官方资料
DXGI
Desktop Duplication API - Win32 apps | Microsoft Docs
2、官方示例
也可从这里下载:https://download.csdn.net/download/shuilan0066/86260425
不过这个例子很复杂,
下面这个例子简单些,从github上下载来,原地址忘了
https://download.csdn.net/download/shuilan0066/86260562
3、采集桌面流程
最关键的是 要获取 IDXGIOutputDuplication 接口,然后通过这个接口获得桌面数据
1)、获得D3D设备
typedef struct _DX_RESOURCES
{
ID3D11Device* Device;
ID3D11DeviceContext* Context;
ID3D11VertexShader* VertexShader;
ID3D11PixelShader* PixelShader;
ID3D11InputLayout* InputLayout;
ID3D11SamplerState* SamplerLinear;
} DX_RESOURCES;
DUPL_RETURN InitializeDx()
{
HRESULT hr = S_OK;
// Driver types supported
D3D_DRIVER_TYPE DriverTypes[] =
{
D3D_DRIVER_TYPE_HARDWARE,
D3D_DRIVER_TYPE_WARP,
D3D_DRIVER_TYPE_REFERENCE,
};
UINT NumDriverTypes = ARRAYSIZE(DriverTypes);
// Feature levels supported
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;
// Create device
for (UINT DriverTypeIndex = 0; DriverTypeIndex < NumDriverTypes; ++DriverTypeIndex)
{
hr = D3D11CreateDevice(nullptr, DriverTypes[DriverTypeIndex], nullptr, 0, FeatureLevels, NumFeatureLevels,
D3D11_SDK_VERSION, &m_DxRes->Device, &FeatureLevel, &m_DxRes->Context);
if (SUCCEEDED(hr))
{
// Device creation success, no need to loop anymore
break;
}
}
if (FAILED(hr))
{
return ProcessFailure(nullptr, L"Failed to create device in InitializeDx", hr);
}
return DUPL_RETURN_SUCCESS;
}
DX_RESOURCES *m_DxRes;
m_DxRes = new (std::nothrow) DX_RESOURCES;
RtlZeroMemory(m_DxRes, sizeof(DX_RESOURCES));
DUPL_RETURN Ret = InitializeDx();
if (Ret != DUPL_RETURN_SUCCESS)
{
fprintf_s(log_file, "DX_RESOURCES couldn't be initialized.");
return Ret;
}
2)、获得DXGI设备
// Get DXGI device
IDXGIDevice* DxgiDevice = nullptr;
HRESULT hr = m_DxRes->Device->QueryInterface(__uuidof(IDXGIDevice), reinterpret_cast<void**>(&DxgiDevice));
if (FAILED(hr))
{
return ProcessFailure(nullptr, L"Failed to QI for DXGI Device", hr);
}
3)、获取DXGI Adapter
Adapter可以理解为我们显卡的抽象层。当我们创建D3D设备时,其实参数中已经指定好了哪块显卡了,我填的NULL,而且指定是硬件类型的,所以它会为我们指定一块默认的(当然,我就一块显卡)。除此之外,还可以通过获取IDXGIFactory对象,然后调用EnumAdapters方法来获取某个Adapter。代码如下
// Get DXGI adapter
IDXGIAdapter* DxgiAdapter = nullptr;
hr = DxgiDevice->GetParent(__uuidof(IDXGIAdapter), reinterpret_cast<void**>(&DxgiAdapter));
DxgiDevice->Release();
DxgiDevice = nullptr;
if (FAILED(hr))
{
return ProcessFailure(m_DxRes->Device, L"Failed to get parent DXGI Adapter", hr, SystemTransitionsExpectedErrors);
}
4)、获取DXGI Output
指定好显卡之后,我们就要指定它的输出了。我们知道,有时候我们可能不止使用一块显示器,显然,显卡是支持多显示设备进行输出的,因此我们要指定好哪一个输出设备,我们需要调用IDXGIAdapter里的EnumOutputs方法,我就一块显示器,第一个参数直接填0就完事了。
// Get output
IDXGIOutput* DxgiOutput = nullptr;
hr = DxgiAdapter->EnumOutputs(0, &DxgiOutput);
DxgiAdapter->Release();
DxgiAdapter = nullptr;
if (FAILED(hr))
{
return ProcessFailure(m_DxRes->Device, L"Failed to get specified output in DUPLICATIONMANAGER", hr, EnumOutputsExpectedErrors);
}
5)、获取DXGI Output1
这时看到这个标题的读者可能又要问了,output1是什么鬼,刚才不是已经获取了output了么。当然,output1也并不是代表第一个显示器,它可以说是output的扩展,它是在DXGI 1.2之后推出的,里面包含了我们Desktop Duplication API接口。可能微软为了与之前的接口兼容,让我们先获取output,然后再通过output来获取到output1。获取DXGI output1的代码如下:
DxgiOutput->GetDesc(&m_OutputDesc);
// QI for Output 1
IDXGIOutput1* DxgiOutput1 = nullptr;
hr = DxgiOutput->QueryInterface(__uuidof(DxgiOutput1), reinterpret_cast<void**>(&DxgiOutput1));
DxgiOutput->Release();
DxgiOutput = nullptr;
if (FAILED(hr))
{
return ProcessFailure(nullptr, L"Failed to QI for DxgiOutput1 in DUPLICATIONMANAGER", hr);
}
6)、获取Output Duplication
在我们刚才获取到的output1中,就可以调用DuplicateOutput方法来获取Output Duplication,然后我们就可以通过它来采集桌面图像了
IDXGIOutputDuplication* m_DeskDupl;
// Create desktop duplication
hr = DxgiOutput1->DuplicateOutput(m_DxRes->Device, &m_DeskDupl);
DxgiOutput1->Release();
DxgiOutput1 = nullptr;
if (FAILED(hr))
{
if (hr == DXGI_ERROR_NOT_CURRENTLY_AVAILABLE)
{
MessageBoxW(nullptr, L"There is already the maximum number of applications using the Desktop Duplication API running, please close one of those applications and then try again.", L"Error", MB_OK);
return DUPL_RETURN_ERROR_UNEXPECTED;
}
return ProcessFailure(m_DxRes->Device, L"Failed to get duplicate output in DUPLICATIONMANAGER", hr, CreateDuplicationExpectedErrors);
}
7)、获取Output Desc
在采集桌面前,我们需要获得显示设备相关的信息,这样才方便我们后续去做编码和渲染等工作,如显示器的宽高,刷新率,像素格式,扫描方式,缩放格式等等。调用GetDesc方法,我们可以获得与这些信息有关的一个结构体对象,便可以通过它来读取,代码如下:
DXGI_OUTDUPL_DESC lOutputDuplDesc;
m_DeskDupl->GetDesc(&lOutputDuplDesc);
8)、获取桌面图像
到了这步,我们就可以获取桌面图像了,直接调用AcquireNextFrame即可,代码如下:
IDXGIResource* DesktopResource = nullptr;
DXGI_OUTDUPL_FRAME_INFO FrameInfo;
// Get new frame
HRESULT hr = m_DeskDupl->AcquireNextFrame(0, &FrameInfo, &DesktopResource);
if (hr == DXGI_ERROR_WAIT_TIMEOUT)
{
return DUPL_RETURN_SUCCESS;
}
if (FAILED(hr))
{
return ProcessFailure(m_DxRes->Device, L"Failed to acquire next frame in DUPLICATIONMANAGER", hr, FrameInfoExpectedErrors);
}
IDXGIResource便是我们的图像了。我们并不能直接访问里面的数据,这些数据是存在于显存中的。
对于英伟达的硬编码,肯定是在显卡中的,所以理论上我们可以直接将他扔给它来帮我们做编码了。然而,目前英伟达并不支持IDXGIResource形式的图像输入,但支持DirectX 的纹理输入(dx版本最低是9,最高是11,12在官方文档中是不支持的),因此,我们需要将其转成dx纹理,才能扔进去进行编码。
9)、创建纹理
D3D11_TEXTURE2D_DESC desc;
DXGI_OUTDUPL_DESC lOutputDuplDesc;
m_DeskDupl->GetDesc(&lOutputDuplDesc);
desc.Width = lOutputDuplDesc.ModeDesc.Width;
desc.Height = lOutputDuplDesc.ModeDesc.Height;
desc.Format = lOutputDuplDesc.ModeDesc.Format;
desc.ArraySize = 1;
desc.BindFlags = 0;
desc.MiscFlags = 0;
desc.SampleDesc.Count = 1;
desc.SampleDesc.Quality = 0;
desc.MipLevels = 1;
desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
desc.Usage = D3D11_USAGE_STAGING;
hr = m_DxRes->Device->CreateTexture2D(&desc, NULL, &m_DestImage);
if (FAILED(hr))
{
ProcessFailure(nullptr, L"Creating cpu accessable texture failed.", hr);
return DUPL_RETURN_ERROR_UNEXPECTED;
}
if (m_DestImage == nullptr)
{
ProcessFailure(nullptr, L"Creating cpu accessable texture failed.", hr);
return DUPL_RETURN_ERROR_UNEXPECTED;
}
ID3D11Texture2D* m_AcquiredDesktopImage;
// If still holding old frame, destroy it
if (m_AcquiredDesktopImage)
{
m_AcquiredDesktopImage->Release();
m_AcquiredDesktopImage = nullptr;
}
// QI for IDXGIResource
hr = DesktopResource->QueryInterface(__uuidof(ID3D11Texture2D), reinterpret_cast<void **>(&m_AcquiredDesktopImage));
DesktopResource->Release();
DesktopResource = nullptr;
if (FAILED(hr))
{
return ProcessFailure(nullptr, L"Failed to QI for ID3D11Texture2D from acquired IDXGIResource in DUPLICATIONMANAGER", hr);
}
10)、拷贝图像数据
void CopyImage(BYTE* ImageData)
{
m_DxRes->Context->CopyResource(m_DestImage, m_AcquiredDesktopImage);
D3D11_MAPPED_SUBRESOURCE resource;
UINT subresource = D3D11CalcSubresource(0, 0, 0);
m_DxRes->Context->Map(m_DestImage, subresource, D3D11_MAP_READ, 0, &resource);
BYTE* sptr = reinterpret_cast<BYTE*>(resource.pData);
//Store Image Pitch
m_ImagePitch = resource.RowPitch;
int height = GetImageHeight();
memcpy_s(ImageData, resource.RowPitch*height, sptr, resource.RowPitch*height);
m_DxRes->Context->Unmap(m_DestImage, subresource);
DoneWithFrame();
}