デスクトップ重複 API

Windows 8は、標準の Windows 2000 ディスプレイ ドライバー モデル (XDDM) ミラー ドライバーを無効にし、代わりにデスクトップ重複 API を提供します。 デスクトップ複製 API は、コラボレーション シナリオ用のデスクトップ イメージへのリモート アクセスを提供します。 アプリでは、デスクトップ重複 API を使用して、フレームごとの更新プログラムにデスクトップにアクセスできます。 アプリは DXGI サーフェイスでデスクトップ イメージの更新プログラムを受け取るため、アプリは GPU のフルパワーを使用してイメージの更新を処理できます。

デスクトップ イメージ データの更新

DXGI は、新しい IDXGIOutputDuplication::AcquireNextFrame メソッドを使用して、現在のデスクトップ イメージを含むサーフェスを提供します。 デスクトップ イメージの形式は、現在の表示モードに関係なく常に DXGI_FORMAT_B8G8R8A8_UNORM されます。 このサーフェスに加えて、これらの IDXGIOutputDuplication メソッドは、処理する必要があるサーフェス内のピクセルを決定するのに役立つ、指定された種類の情報を返します。

  • IDXGIOutputDuplication::GetFrameDirtyRects は、ダーティ領域を返します。これは、前のデスクトップ イメージを処理した後にオペレーティング システムが更新したデスクトップ イメージの領域を示す重複しない四角形です。
  • IDXGIOutputDuplication::GetFrameMoveRects は移動領域を返します。これは、オペレーティング システムが同じイメージ内の別の場所に移動したデスクトップ イメージ内のピクセルの四角形です。 各移動領域は、移動先の四角形とソース ポイントで構成されます。 ソース ポイントは、オペレーティング システムがリージョンをコピーした場所を指定し、コピー先の四角形はオペレーティング システムがその領域を移動した場所を指定します。 移動領域は常にストレッチされていない領域であるため、ソースは常に移動先と同じサイズになります。

デスクトップ イメージがリモート クライアント アプリへの低速接続を介して送信されたとします。 接続経由で送信されるデータの量は、クライアント アプリが実際のピクセル データではなくピクセルの領域を移動する方法に関するデータのみを受け取ることによって削減されます。 移動を処理するには、クライアント アプリに最後のイメージ全体が格納されている必要があります。

オペレーティング システムは未処理のデスクトップ イメージの更新プログラムを蓄積しますが、更新リージョンを正確に格納するための領域が不足する可能性があります。 この状況では、オペレーティング システムは、すべての新しい更新プログラムをカバーするために既存の更新リージョンと結合することで、更新プログラムの蓄積を開始します。 その結果、オペレーティング システムは、そのフレームでまだ実際に更新されていないピクセルをカバーします。 ただし、この状況では、更新されたピクセルだけでなくデスクトップ イメージ全体を受け取るため、クライアント アプリで視覚的な問題は発生しません。

正しいデスクトップ イメージを再構築するには、クライアント アプリで最初にすべての移動リージョンを処理してから、すべてのダーティリージョンを処理する必要があります。 これらのダーティ領域と移動領域の一覧は、いずれも完全に空にすることができます。 デスクトップ重複サンプルのコード例は、ダーティと移動領域の両方を 1 つのフレームで処理する方法を示しています。

//
// 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
1024 x 768 ランドスケープ 1024x768 0 度回転 1024x768[改行] 回転されていないリモート デスクトップ
1024 x 768 縦 768x1024 90度回転 1024x768[改行] 90 度のリモート デスクトップ回転
1024 x 768 横 (反転) 1024x768 180度回転 1024x768[改行] 回転 180 度リモート デスクトップ
1024 x 768 縦 (反転) 768x1024 270 度回転 1024x768[改行] 回転 270 度リモート デスクトップ

 

デスクトップ複製クライアント アプリのコードは、デスクトップ イメージを表示する前に、デスクトップ イメージを適切に回転させる必要があります。

注意

マルチモニターのシナリオでは、モニターごとにデスクトップ イメージを個別に回転させることができます。

 

デスクトップ ポインターの更新

デスクトップ複製 API を使用して、クライアント アプリでマウス ポインターの図形をデスクトップ イメージに描画する必要があるかどうかを判断する必要があります。 マウス ポインターは、 IDXGIOutputDuplication::AcquireNextFrame が提供するデスクトップ イメージに既に描画されているか、マウス ポインターがデスクトップ イメージから分離されています。 マウス ポインターがデスクトップ イメージに描画される場合、AcquireNextFrame によって報告されるポインター位置データ (pFrameInfo パラメーターが指すDXGI_OUTDUPL_FRAME_INFOのPointerPosition メンバー内) は、別のポインターが表示されていないことを示します。 グラフィックス アダプターがデスクトップ イメージの上にマウス ポインターをオーバーレイすると、 AcquireNextFrame は別のポインターが表示されていることを報告します。 そのため、クライアント アプリは、現在のユーザーがモニターに表示する内容を正確に表すために、マウス ポインターの図形をデスクトップ イメージに描画する必要があります。

デスクトップのマウス ポインターを描画するには、AcquireNextFramepFrameInfo パラメーターから 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 の機能強化