桌面复制 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] nonrotated remote desktop
1024x768 纵向 768x1024 90 度旋转 1024x768[newline] rotated 90 degrees remote desktop
1024x768 横向 (翻转) 1024x768 180 度旋转 1024x768[newline] rotated 180 degrees remote desktop
1024x768 纵向 (翻转) 768x1024 270 度旋转 1024x768[newline] rotated 270 degrees remote desktop

 

桌面重复客户端应用中的代码必须在显示桌面图像之前正确旋转桌面图像。

注意

在多监视器方案中,可以独立旋转每个监视器的桌面映像。

 

更新桌面指针

需要使用桌面重复 API 来确定客户端应用是否必须将鼠标指针形状绘制到桌面图像上。 鼠标指针已绘制到 IDXGIOutputDuplication::AcquireNextFrame 提供的桌面映像上,或者鼠标指针与桌面图像分开。 如果鼠标指针绘制到桌面图像上,则由 AcquireNextFrame (在 pFrameInfo 参数) 指向DXGI_OUTDUPL_FRAME_INFOPointerPosition 成员中报告的指针位置数据指示单独的指针不可见。 如果图形适配器覆盖桌面图像顶部的鼠标指针, AcquireNextFrame 将报告单独的指针可见。 因此,客户端应用必须将鼠标指针形状绘制到桌面图像上,以准确表示当前用户在其监视器上看到的内容。

若要绘制桌面的鼠标指针,请使用 AcquireNextFrame的 pFrameInfo 参数DXGI_OUTDUPL_FRAME_INFOPointerPosition 成员来确定在桌面图像上找到鼠标指针左上角的位置。 绘制第一帧时,必须使用 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;
}

DXGI 1.2 改进