桌面复制 API
Windows 8禁用标准 Windows 2000 显示驱动程序模型 (XDDM) 镜像驱动程序,并改为提供桌面重复 API。 桌面重复 API 提供对协作方案的桌面映像的远程访问。 应用可以使用桌面重复 API 来访问桌面的逐帧更新。 由于应用在 DXGI 图面中接收桌面映像的更新,因此应用可以使用 GPU 的全部功能来处理图像更新。
更新桌面映像数据
DXGI 通过新的 IDXGIOutputDuplication::AcquireNextFrame 方法提供包含当前桌面图像的图面。 无论当前显示模式是什么,桌面图像的格式始终 DXGI_FORMAT_B8G8R8A8_UNORM 。 除了此图面外,这些 IDXGIOutputDuplication 方法还返回指示的信息类型,这些信息可帮助你确定需要处理图面中的像素:
- IDXGIOutputDuplication::GetFrameDirtyRects 返回脏区域,这些区域是非重叠矩形,指示自你处理上一个桌面映像以来操作系统更新的桌面映像区域。
- IDXGIOutputDuplication::GetFrameMoveRects 返回移动区域,这些区域是桌面映像中操作系统移动到同一映像中另一个位置的像素矩形。 每个移动区域由一个目标矩形和一个源点组成。 源点指定操作系统从中复制区域的位置,目标矩形指定操作系统将该区域移动到的位置。 移动区域始终是非拉伸区域,因此源的大小始终与目标相同。
假设桌面映像是通过慢速连接传输到远程客户端应用的。 通过连接发送的数据量通过仅接收有关客户端应用必须如何移动像素区域而不是实际像素数据的数据来减少。 若要处理移动,客户端应用必须已存储完整的最后一个映像。
当操作系统累积未处理的桌面映像更新时,它可能会耗尽空间来准确存储更新区域。 在这种情况下,操作系统将更新与现有更新区域合并以涵盖所有新更新,从而开始累积更新。 因此,操作系统涵盖它尚未在该帧中实际更新的像素。 但这种情况不会在客户端应用上产生视觉问题,因为你会收到整个桌面图像,而不仅仅是更新的像素。
若要重新构造正确的桌面映像,客户端应用必须首先处理所有移动区域,然后处理所有脏区域。 这些脏和移动区域列表之一都可以完全为空。 桌面重复示例中的示例代码演示如何在单个帧中处理脏和移动区域:
//
// 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;
}
旋转桌面映像
必须将显式代码添加到桌面重复客户端应用,以支持轮换模式。 在旋转模式下,从 IDXGIOutputDuplication::AcquireNextFrame 接收的表面始终处于未旋转的方向,桌面图像在图面内旋转。 例如,如果桌面在旋转 90 度时设置为 768x1024, 则 AcquireNextFrame 将返回一个 1024x768 的图面,其中桌面图像在其中旋转。 下面是一些旋转示例。
从显示控制面板设置的显示模式 | GDI 或 DXGI 返回的显示模式 | 从 AcquireNextFrame 返回的 Surface |
---|---|---|
1024x768 横向 | 1024x768 0 度旋转 | 1024x768[newline] |
1024x768 纵向 | 768x1024 90 度旋转 | 1024x768[新线] |
1024x768 横向 (翻转) | 1024x768 180 度旋转 | 1024x768[新线] |
1024x768 纵向 (翻转) | 768x1024 270 度旋转 | 1024x768[新线] |
在显示桌面映像之前,桌面重复客户端应用中的代码必须相应地旋转桌面映像。
注意
在多监视器方案中,可以单独轮换每个监视器的桌面映像。
更新桌面指针
需要使用桌面重复 API 来确定客户端应用是否必须将鼠标指针形状绘制到桌面图像上。 鼠标指针已绘制到 IDXGIOutputDuplication::AcquireNextFrame 提供的桌面图像上,或者鼠标指针与桌面图像分开。 如果将鼠标指针绘制到桌面图像上,则 AcquireNextFrame 报告的指针位置数据 (pFrameInfo 参数指向的DXGI_OUTDUPL_FRAME_INFO的 PointerPosition 成员) 指示单独的指针不可见。 如果图形适配器覆盖桌面图像顶部的鼠标指针, 则 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;
}
相关主题