桌面重复 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 接收的图面始终处于未旋转的方向,桌面图像在图面中旋转。 例如,如果桌面设置为 768x1024,旋转 90 度,AcquireNextFrame 返回一个 1024x768 表面,桌面图像在其中旋转。 下面是一些旋转示例。
显示控制面板中的显示模式集 | GDI 或 DXGI 返回的显示模式 | 从 AcquireNextFrame 返回的 Surface |
---|---|---|
1024x768 横向 | 1024x768 0 度旋转 | 1024x768[newline] ![]() |
1024x768 纵向 | 768x1024 90 度旋转 | 1024x768[newline] ![]() |
1024x768 横向 (翻转) | 1024x768 180 度旋转 | 1024x768[newline] ![]() |
1024x768 纵向(翻转) | 768x1024 270 度旋转 | 1024x768[newline] ![]() |
在显示桌面图像之前,桌面重复客户端应用中的代码必须适当地旋转桌面图像。
注意
在多监视器方案中,可以独立旋转每个监视器的桌面映像。
更新桌面指针
需要使用桌面重复 API 来确定客户端应用是否必须将鼠标指针形状绘制到桌面图像上。 鼠标指针已绘制到 IDXGIOutputDuplication::AcquireNextFrame 提供的桌面映像上,或者鼠标指针与桌面图像分开。 如果鼠标指针绘制到桌面图像上,则 AcquireNextFrame 报告的指针位置数据(在 PointerPosition 成员 DXGI_OUTDUPL_FRAME_INFOpFrameInfo 参数指向)指示单独的指针不可见。 如果图形适配器覆盖桌面图像顶部的鼠标指针,AcquireNextFrame 报告单独的指针可见。 因此,客户端应用必须将鼠标指针形状绘制到桌面图像上,以准确表示当前用户在其监视器上看到的内容。
若要绘制桌面的鼠标指针,请使用 pFrameInfoAcquireNextFrame 的 PointerPositionDXGI_OUTDUPL_FRAME_INFO 成员来确定在桌面图像上定位鼠标指针左上角的位置。 绘制第一帧时,必须使用 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;
}
相关主题