原文https://msdn.microsoft.com/en-us/library/windows/desktop/hh404487(v=vs.85).aspx
win8放弃了标准的win2k显示驱动模块(XDDM),也就是镜像驱动,并提供桌面拷贝API来替代它。The desktop duplication API 提供远程接口操作一组桌面截图的协作方案。应用可以使用the desktop duplication API 来一帧一帧更新桌面。因为应用收到更新的桌面截图来自与DXGI接口,所以该应用可以完全使用GPU来完成图片的更新。
更新桌面图片数据
旋转桌面图片
更新桌面指针
相关话题
1)更新桌面图片数据
DXGL提供一种包含当前桌面图片的接口,通过新的 IDXGIOutputDuplication::AcquireNextFrame 方法。格式化桌面图片永远是DXGI_FORMAT_B8G8R8A8_UNORM,不管当前显示模式是什么。通过这个接口,那些IDXGIOutputDuplication 方法返回指定类型的信息来帮助你决定哪些像素你需要去处理。
• IDXGIOutputDuplication::GetFrameDirtyRects 返回的是“脏”的区域,没有重叠的一组组矩形区域就是自从你要求提供桌面图片后操作系统更新过的区域,
IDXGIOutputDuplication::GetFrameMoveRects 返回的是移动的区域, 操作系统将有着一组一模一样的矩形区域的像素的桌面截图移动到到另一个位置。每个移动的区域包含一个目标矩形和一个源点。源点指定从操作系统复制该区域的位置和目标矩形指定操作系统移动该区域的位置。移动区域不能被拉伸,所以源和目标的尺寸一直都是相同的。
支持桌面图片通过低速的连接传到你远程客户端中。一定数量的数据被传输到指定连接对象用来接收仅仅是图片数据,指定你的客户端程序移动区域,而不是实际像素数据。为了实现这个移动,你的客户程序必须存储上一张完成好的桌面图片。
当操作系统积累了许多未处理的桌面图片,它就会用光用来准确移动区域的存储空间,在这种情况下,操作系统开始将积累已存在更新覆盖掉刚刚更新的图片。结果导致操作系统实际不是按帧更新的而是按像素点。但是这种情况不会导致客户端的显示问题,因为你收到的是一整张的桌面图片而不仅仅是更新的像素点。
为了重建正确的桌面图片,你的客户端第一次必须处理所有的移动区域,然后再处理所有的脏的区域。两种区域的列表应该完全的清空。这个示例取自 Desktop Duplication Sample 展示了怎么样处理两种区域在同一帧中。
//
// Get next frame and write it into Data
//
HRESULT DUPLICATIONMANAGER::GetFrame(_Out_ FRAME_DATA* Data)
{
HRESULT hr = S_OK;
IDXGIResource* DesktopResource = NULL;
DXGI_OUTDUPL_FRAME_INFO FrameInfo;
//Get new frame
hr = DeskDupl->AcquireNextFrame(500, &FrameInfo, &DesktopResource);
if (FAILED(hr))
{
if ((hr != DXGI_ERROR_ACCESS_LOST) && (hr != DXGI_ERROR_WAIT_TIMEOUT))
{
DisplayErr(L"Failed to acquire next frame in DUPLICATIONMANAGER", L"Error", hr);
}
return hr;
}
// If still holding old frame, destroy it
if (AcquiredDesktopImage)
{
AcquiredDesktopImage->Release();
AcquiredDesktopImage = NULL;
}
// QI for IDXGIResource
hr = DesktopResource->QueryInterface(__uuidof(ID3D11Texture2D), reinterpret_cast<void **>(&AcquiredDesktopImage));
DesktopResource->Release();
DesktopResource = NULL;
if (FAILED(hr))
{
DisplayErr(L"Failed to QI for ID3D11Texture2D from acquired IDXGIResource in DUPLICATIONMANAGER", L"Error", hr);
return hr;
}
// Get metadata
if (FrameInfo.TotalMetadataBufferSize)
{
// Old buffer too small
if (FrameInfo.TotalMetadataBufferSize > MetaDataSize)
{
if (MetaDataBuffer)
{
delete [] MetaDataBuffer;
MetaDataBuffer = NULL;
}
MetaDataBuffer = new (std::nothrow) BYTE[FrameInfo.TotalMetadataBufferSize];
if (!MetaDataBuffer)
{
DisplayErr(L"Failed to allocate memory for metadata in DUPLICATIONMANAGER", L"Error", E_OUTOFMEMORY);
MetaDataSize = 0;
Data->MoveCount = 0;
Data->DirtyCount = 0;
return E_OUTOFMEMORY;
}
MetaDataSize = FrameInfo.TotalMetadataBufferSize;
}
UINT BufSize = FrameInfo.TotalMetadataBufferSize;
// Get move rectangles
hr = DeskDupl->GetFrameMoveRects(BufSize, reinterpret_cast<DXGI_OUTDUPL_MOVE_RECT*>(MetaDataBuffer), &BufSize);
if (FAILED(hr))
{
if (hr != DXGI_ERROR_ACCESS_LOST)
{
DisplayErr(L"Failed to get frame move rects in DUPLICATIONMANAGER", L"Error", hr);
}
Data->MoveCount = 0;
Data->DirtyCount = 0;
return hr;
}
Data->MoveCount = BufSize / sizeof(DXGI_OUTDUPL_MOVE_RECT);
BYTE* DirtyRects = MetaDataBuffer + BufSize;
BufSize = FrameInfo.TotalMetadataBufferSize - BufSize;
// Get dirty rectangles
hr = DeskDupl->GetFrameDirtyRects(BufSize, reinterpret_cast<RECT*>(DirtyRects), &BufSize);
if (FAILED(hr))
{
if (hr != DXGI_ERROR_ACCESS_LOST)
{
DisplayErr(L"Failed to get frame dirty rects in DUPLICATIONMANAGER", L"Error", hr);
}
Data->MoveCount = 0;
Data->DirtyCount = 0;
return hr;
}
Data->DirtyCount = BufSize / sizeof(RECT);
Data->MetaData = MetaDataBuffer;
}
Data->Frame = AcquiredDesktopImage;
Data->FrameInfo = FrameInfo;
return hr;
}
//
// Release frame
//
HRESULT DUPLICATIONMANAGER::DoneWithFrame()
{
HRESULT hr = S_OK;
hr = DeskDupl->ReleaseFrame();
if (FAILED(hr))
{
DisplayErr(L"Failed to release frame in DUPLICATIONMANAGER", L"Error", hr);
return hr;
}
if (AcquiredDesktopImage)
{
AcquiredDesktopImage->Release();
AcquiredDesktopImage = NULL;
}
return hr;
}
2)旋转桌面图片
您必须向桌面复制客户端应用程序添加显式代码以支持旋转模式。在旋转模式下,从IDXGIOutputDuplication :: AcquireNextFrame接收的表面始终处于未旋转方向,桌面图像在这个表面内旋转。 例如,如果桌面在90度旋转时设置为768x1024,则AcquireNextFrame返回一个1024x768表面,桌面图像在其中旋转。 这里有一些旋转示例。
桌面复制客户端应用程序中的代码必须在显示桌面映像之前正确旋转桌面映像。
注意在多监视器方案中,您可以单独旋转每个监视器的桌面图像。
3)更新桌面光标
你需要用 desktop duplication API 来指定你是否需要把鼠标指定加到桌面图片。IDXGIOutputDuplication::AcquireNextFrame 可以提供有或者无鼠标光标的桌面图片。如果桌面被画上鼠标光标,那么光标的位置数据要被指派到AcquireNextFrame(在PointerPosition成员中DXGI_OUTDUPL_FRAME_INFO 的pFrameInfo 参数)报告分离的光标使其不可见。如果图形适配器将鼠标指针叠加在桌面图像的顶部,AcquireNextFrame报告单独的指针是可见的。因此,您的客户端应用程序必须将鼠标指针形状绘制到桌面图像上,以准确表示当前用户在其显示器上将看到的内容。
要绘制桌面的鼠标指针,请使用AcquireNextFrame的pFrameInfo参数中的DXGI_OUTDUPL_FRAME_INFO的PointerPosition成员来确定在桌面图像上的鼠标指针左上角的位置。 绘制第一帧时,必须使用IDXGIOutputDuplication :: GetFramePointerShape方法来获取有关鼠标指针形状的信息。 每次调用AcquireNextFrame以获得下一帧也提供该帧的当前指针位置。 另一方面,只有当形状改变时,您才需要再次使用GetFramePointerShape。 所以,保留最后一个指针图像的副本,并使用它来绘制桌面上,除非鼠标指针的形状更改。
注意:与指针形状图像一起,GetFramePointerShape提供热点位置的大小。 热点仅供参考。 指针图像的绘制位置与热点无关。
//
// Retrieves mouse info and write it into PtrInfo
//
HRESULT DUPLICATIONMANAGER::GetMouse(_Out_ PTR_INFO* PtrInfo, _In_ DXGI_OUTDUPL_FRAME_INFO* FrameInfo, INT OffsetX, INT OffsetY)
{
HRESULT hr = S_OK;
// A non-zero mouse update timestamp indicates that there is a mouse position update and optionally a shape change
if (FrameInfo->LastMouseUpdateTime.QuadPart == 0)
{
return hr;
}
bool UpdatePosition = true;
// Make sure we don't update pointer position wrongly
// If pointer is invisible, make sure we did not get an update from another output that the last time that said pointer
// was visible, if so, don't set it to invisible or update.
if (!FrameInfo->PointerPosition.Visible && (PtrInfo->WhoUpdatedPositionLast != OutputNumber))
{
UpdatePosition = false;
}
// If two outputs both say they have a visible, only update if new update has newer timestamp
if (FrameInfo->PointerPosition.Visible && PtrInfo->Visible && (PtrInfo->WhoUpdatedPositionLast != OutputNumber) && (PtrInfo->LastTimeStamp.QuadPart > FrameInfo->LastMouseUpdateTime.QuadPart))
{
UpdatePosition = false;
}
// Update position
if (UpdatePosition)
{
PtrInfo->Position.x = FrameInfo->PointerPosition.Position.x + OutputDesc.DesktopCoordinates.left - OffsetX;
PtrInfo->Position.y = FrameInfo->PointerPosition.Position.y + OutputDesc.DesktopCoordinates.top - OffsetY;
PtrInfo->WhoUpdatedPositionLast = OutputNumber;
PtrInfo->LastTimeStamp = FrameInfo->LastMouseUpdateTime;
PtrInfo->Visible = FrameInfo->PointerPosition.Visible != 0;
}
// No new shape
if (FrameInfo->PointerShapeBufferSize == 0)
{
return hr;
}
// Old buffer too small
if (FrameInfo->PointerShapeBufferSize > PtrInfo->BufferSize)
{
if (PtrInfo->PtrShapeBuffer)
{
delete [] PtrInfo->PtrShapeBuffer;
PtrInfo->PtrShapeBuffer = NULL;
}
PtrInfo->PtrShapeBuffer = new (std::nothrow) BYTE[FrameInfo->PointerShapeBufferSize];
if (!PtrInfo->PtrShapeBuffer)
{
DisplayErr(L"Failed to allocate memory for pointer shape in DUPLICATIONMANAGER", L"Error", E_OUTOFMEMORY);
PtrInfo->BufferSize = 0;
return E_OUTOFMEMORY;
}
// Update buffer size
PtrInfo->BufferSize = FrameInfo->PointerShapeBufferSize;
}
UINT BufferSizeRequired;
// Get shape
hr = DeskDupl->GetFramePointerShape(FrameInfo->PointerShapeBufferSize, reinterpret_cast<VOID*>(PtrInfo->PtrShapeBuffer), &BufferSizeRequired, &(PtrInfo->ShapeInfo));
if (FAILED(hr))
{
if (hr != DXGI_ERROR_ACCESS_LOST)
{
DisplayErr(L"Failed to get frame pointer shape in DUPLICATIONMANAGER", L"Error", hr);
}
delete [] PtrInfo->PtrShapeBuffer;
PtrInfo->PtrShapeBuffer = NULL;
PtrInfo->BufferSize = 0;
return hr;
}
return hr;
}
4)相关话题
DXGI 1.2 Improvements