데스크톱 중복 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[newline] 회전 90도 원격 데스크톱
1024x768 가로(대칭 이동) 1024x768 180도 회전 1024x768[newline] 회전 180도 원격 데스크톱
1024x768 세로(대칭 이동) 768x1024 270도 회전 1024x768[newline] 원격 데스크톱 270도 회전

 

데스크톱 중복 클라이언트 앱의 코드는 데스크톱 이미지를 표시하기 전에 데스크톱 이미지를 적절하게 회전해야 합니다.

참고

다중 모니터 시나리오에서는 각 모니터에 대해 데스크톱 이미지를 독립적으로 회전할 수 있습니다.

 

데스크톱 포인터 업데이트

데스크톱 중복 API를 사용하여 클라이언트 앱이 마우스 포인터 셰이프를 데스크톱 이미지에 그려야 하는지 확인해야 합니다. 마우스 포인터가 IDXGIOutputDuplication::AcquireNextFrame 이 제공하는 데스크톱 이미지에 이미 그려져 있거나 마우스 포인터가 데스크톱 이미지와 분리되어 있습니다. 마우스 포인터가 바탕 화면 이미지에 그려지면 AcquireNextFrame에서 보고하는 포인터 위치 데이터(pFrameInfo 매개 변수가 가리키는 DXGI_OUTDUPL_FRAME_INFOPointerPosition 멤버)는 별도의 포인터가 표시되지 않음을 나타냅니다. 그래픽 어댑터가 데스크톱 이미지 위에 마우스 포인터를 오버레이하는 경우 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 개선 사항